У багатьох великих додатках на Ruby on Rails часто зустрічаються класи, які переплітаються з рештою коду. Ці початкові моделі спочатку малі й прості, можливо, вони були створені ще при першому коміті, але з часом решта коду починає переплітатися з ними.
Деякі називають їх богоподібними об’єктами (god objects) (omniscient or all-knowing objects), оскільки вони мають посилання на велику кількість інших об’єктів і часто містять непов’язані методи. Це добре відома антипатерн чи кодова неприємність [1996 Reference].
У подорожі Gusto до створення модульованого моноліту ми використовуємо Packwerk для переміщення доменних концептів у пакети. Наприклад, ми створили пакет payments
і перемістили всі моделі, контролери, представлення, сервіси та специфікації, які потрібні для цього домену. Маючи весь код у пакетах, ми створили публічний API для доступу до інтерфейсу цього домену з іншого коду. Залишилися лише наші “спагетті” моделі. Їх називають спагетті моделями, тому що вони переплітаються з усім кодом домену (через асоціації) і містять код домену (через методи), який має бути деінде. У цій серії блогів я поділюся тим, як я впорався з нашою найбільшою спагетті моделлю.
Подолання спагетті моделей може бути вражаючим
Це зайняло роки, щоб вони сформувалися, і розплутувати цей безлад може здатися вузлом Гордия з відсутнім чітким початком. У Gusto ми маємо дві основні спагетті моделі: компанія та працівник. Коли я вперше почав розплутувати спагетті модель компанії, я не знав, з чого почати. Інші члени моєї команди хвилювались, чи зможемо ми взагалі вирішити цю проблему. Ось клас, який має 11 років досвіду, 408 методів, 195 асоціацій і 51 стовпець. Інженери з інших команд бояться до нього торкатися: “Якщо ти змінюєш його, це може зламати нашу критичну для бізнесу зарплату. Воно так переплутане.”
Тож з невеликою зухвалістю, наївністю і досвідом я взяв свій різець і повільно почав працювати. Ми знаємо з книги Анджели Дакворт, що витривалість — це секрет досягнення видатних результатів. На щастя, я продовжував поступово працювати над нашою спагетті моделлю.
Кількість методів моделі компанії з часом
Це історія моєї подорожі
Ці техніки добре спрацювали для нас. Я прагнув безпечного видалення та рефакторингу коду без порушення роботи системи. Спробуйте ці техніки в іншому порядку та поділіться своєю подорожжю зі мною.
Мета: перемістити доменні концепти в доменні модулі. У нашій системі модель Company (Компанія) представляє бізнес, який хоче використовувати наші продукти. Ідеально, щоб наша модель Company містила лише код, що стосується ідентичності компанії (назва бізнесу, податковий номер, юридична торгова назва, id). Однак більшість того, що було в моделі Company, — це доменні концепти, які належать до інших модулів чи пакетів. У нашому проекті ми розділили кодову базу на пакети з приватними та публічними API (див. Ruby At Scale для додаткової інформації). Мої рефакторинги перемістили методи з компанії або її обов’язків до доменних пакетів. Наша довгострокова мета — це струнка модель компанії, яка містить лише ідентифікаційну інформацію.
Мета: чіткі кордони. У нашій ідеальній системі публічні API повинні повертати прості об’єкти Ruby, щоб клієнтський код не міг використовувати асоціації та інші зручні методи Ruby on Rails. Приватні API можуть повертати моделі ActiveRecord (Active Record). Код, що викликає інші домени, має використовувати публічні API.
Замість того, щоб передавати модель компанії (Company) Rails, ми передаємо company_id або об'єкт компанії.
Кількість асоціацій моделі компанії з часом
Мета 1: Запобігти погіршенню моделі
Наш перший крок — написати тести, щоб запобігти додаванню нових методів, асоціацій і стовпців іншими розробниками. Деталі див. в статті Add Tests To Stop the Growing Spaghetti Model.
Чесно кажучи, я не починав з цього, але коли я помітив, що інші розробники все ще додають до моделі компанії, ми написали кілька тестів. Тести надають автоматичний механізм зворотного зв’язку для розробників.
Мета 2: Огляд бази даних
Я почав з аудиту моделі. Я швидко переглянув кожен метод, намагаючись зрозуміти, з чого почати. Після цього я звернув увагу на стовпці бази даних. Я помітив, що деякі з них не змінювались протягом кількох років. Я вирішив очистити таблицю бази даних, перш ніж робити будь-які інші зміни. Ознайомившись з колонками, я почав видаляти непотрібні методи і почав інтегрувати код. Деталі див. в статті Safely Removing Dead Columns.
Мета 3: Перемістити логіку домену в доменні пакети
Зменшення розміру спагетті моделі — це видалення всього коду, що не має там бути. Типова модель ActiveRecord має:
- стовпці
- методи
- асоціації (hasmany, hasone, belongs_to)
- колбеки життєвого циклу (aftercreate, beforesave, …)
- валідації
- ActiveSupport::Concerns
- міксини
- делеговані методи
- scopes
Моя робота полягала в тому, щоб прибрати весь непотрібний код.
З чого почати?
Я пройшов через спагетті модель і перемістив логіку домену в доменні пакети. Після того, як я перемістив кілька методів, асоціацій і колбеків, я зрозумів, що деякі рефакторинги є простішими, ніж інші. Від найпростіших до найскладніших:
- методи — це найпростіше
- ActiveSupport::Concerns (*)
- рідко використовувані асоціації
- методи колбеків життєвого циклу
- стовпці
- часто використовувані асоціації — це найскладніше, і, можливо, ми ніколи не зможемо видалити всі з них
(*) Оскільки ActiveSupport::Concerns служать для вставки коду, вони можуть містити методи, асоціації та колбеки життєвого циклу. Для вирішення проблем з ActiveSupport::Concerns я використав ті самі тактики, що й для методів, асоціацій і колбеків.
Тактики
Спагетті модель — це набір методів, асоціацій, scopes, колбеків і делегованих методів. Для кожного з них я написав окрему статтю про те, як я з ними впорався:
A. Робота з методами [Частина 3, скоро]
- Безпечно видаляти непотрібні методи
- Безпечно інтегрувати обгорткові методи
- Безпечно витягувати нові публічні API
- Безпечно витягувати нові публічні API з об'єктами значень (value objects)
B. Робота з Active Model [Частина 4, скоро]
- Безпечно видаляти scopes
- Безпечно видаляти асоціації
C. Робота з ActiveSupport::Concerns [Частина 5, скоро]
- Безпечно видаляти активні concerns (див. інші техніки)
- Розслідування
D. ActiveAdmin [Частина 6, скоро]
- Створення іншої моделі для методів ActiveAdmin ransack (скоро)
Відстеження депрекацій
У кожній з безпечних технік, перерахованих у розділі Тактики, я використовував ActiveSupport::Deprecation.warn, щоб перевірити, чи більше не використовуються методи, асоціації або колбеки життєвого циклу в продуктивному середовищі. Я чекав, поки не відчую, що можна безпечно видаляти код із системи.
Щоб допомогти відстежувати мої злиті попередження депрекацій, я використовував просту таблицю в електронній таблиці.
|------------|------------------|--------------------|--------------------|
| Дата додавання | Клас | Додано | Видалено |
|------------|------------------|--------------------|--------------------|
| 2023/06/14 | company | | |
| 2023/06/15 | company | | |
| 2023/07/01 | company | | |
| 2023/07/15 | bank_accountable | | |
|------------|------------------|--------------------|--------------------|
Кожного разу, коли я мав кілька хвилин між зустрічами, я дивився на цю таблицю, щоб перевірити, чи готові які-небудь застарілі методи або асоціації до видалення. Потім я перевіряв у DataDog, чи не використовувався метод останні кілька тижнів. І тоді я видаляв код назавжди.
Висновок
Слідуючи популярним і загальновизнаним патернам Ruby on Rails, і за відсутності інвестицій в навмисну роботу з архітектурою, довговічні кодові бази сприяють виникненню спагетті моделей. У цьому блозі я надаю стратегію для вирішення проблеми спагетті моделей з подальшими дописами, які детальніше розглядають конкретні тактики.
З часом ваша кодова база стане менш хаотичною і буде працювати стабільно.
Оригінально опубліковано на https://sedano.org 27 липня 2023 року.
Перекладено з: Unraveling a Spaghetti Model