Огляд
У багатьох розвинених додатках на Ruby on Rails часто з'являються кілька класів, які починають заплутуватися в кодовій базі. Ці ранні моделі починалися просто, можливо, навіть були присутні з самого першого коміту, але з часом інші частини кодової бази починають "висяти" на них.
Деякі називають їх богами об'єктів (omniscient або all-knowing objects), оскільки вони посилаються на велику кількість інших об'єктів і часто мають непов’язані методи. Це добре відомий антипатерн або "запах коду" (code smell).
Робота з моделями-спагеті може здаватися надмірно складною. Це зайняло роки, щоб вони сформувалися, і розплутати цей клубок може бути схоже на розв'язування гордіївого вузла, без чітко визначеного місця початку. У Gusto у нас є дві основні моделі-спагеті: компанія та працівник. Коли я вперше почав розплутувати модель компанії, я не знав, з чого почати. Інші члени моєї команди були стурбовані, чи взагалі ми зможемо вирішити проблему. Ось клас, що містить 11 років досвіду, 408 методів, 195 асоціацій і 51 колонку. Інженери з інших команд бояться його чіпати: "Якщо ти його змінюєш, це може зламати нашу критично важливу платіжну систему. Він настільки заплутаний."
Тож, з трохи сміливості, наївності та досвіду, я взявся за справу і повільно почав обробляти цей код. На щастя, ми знаємо з книги Анджели Дакворт, що стійкість є секретом досягнення видатних результатів.
Це історія мого шляху. Я б хотів вважати, що маю формулу, яку ви можете використовувати для очищення своїх власних моделей-спагеті. Тертя від довготривалого проекту Rails могли сформувати вашу модель подібно до нашої. Але я можу поділитися лише тим, що спрацювало для мене та як я підійшов до вирішення проблеми. Не бійтеся спробувати техніки в іншому порядку і поділіться своїм власним досвідом.
Ймовірно, ваша модель-спагеті використовується всюди. Я був стурбований тим, що можу зламати систему, тому я поділюсь тактиками, які я використовував для безпечного видалення та рефакторингу коду.
Мета: Переміщення доменних концепцій у доменні модулі. Більшість того, що знаходиться в моделі Company, є доменними концепціями, які повинні бути в різних модулях або пакетах. У нашому проекті ми розділили нашу кодову базу на пакети з публічними та приватними API (див. https://github.com/rubyatscale/packs/ для більш детальної інформації). Тому я робив рефакторинг для переміщення методів з компанії або її concern у доменні пакети. Наша довгострокова мета — створити струнку модель компанії, яка міститиме лише інформацію про ідентичність.
Мета: Чисті межі. Публічні API повертають прості об'єкти Ruby. Приватні API можуть повертати моделі ActiveRecord. Код, що викликає інші домени, повинен використовувати публічні API. Замість того, щоб передавати об'єкт компанії, ми передаємо company_id.
Ваш план
Мета 1: Не допустити погіршення моделі
Чесно кажучи, я не почав з цього, але коли я помітив, що інші розробники все ще додають нові елементи до моделі компанії, ми вирішили надати механізм зворотного зв'язку для розробників, щоб зупинити це.
Тактики
- Додати тести, щоб запобігти додаванню нових методів і асоціацій [Частина 2 скоро]
Мета 2: Перевірка бази даних
Я почав з аудиту моделі. Я швидко переглянув кожен метод, намагаючись зрозуміти, з чого почати. Після цього я подивився на стовпці бази даних. Я помітив, що деякі з них не змінювалися роками. Я вирішив почистити нашу таблицю бази даних перед тим, як братися за інші зміни. Ознайомившись зі стовпцями, я прибрав непотрібні методи та почав вбудовувати код.
Тактики
- Безпечно видаляти мертві стовпці [Частина 3 скоро]
Мета 3: Переміщення доменної логіки в доменні модулі
Я пройшовся по моделі спагеті та перемістив доменну логіку в доменні пакети. Після переміщення деяких методів, асоціацій і callback-ів, я виявив, що найпростіше працювати з:
- методами
- ActiveSupport::Concerns (*)
- рідко використовуваними асоціаціями
- методами lifecycle callback.
(*) Оскільки ActiveSupport::Concerns є вбудованими моделями, вони можуть містити методи, асоціації та lifecycle callback-и.
Щоб вирішити питання з ActiveSupport::Concerns, я використовував ті самі тактики, що і для методів, асоціацій та lifecycle callback-ів.
Тактики
- Робота з методами [Частина 4, скоро]
- Безпечно видаляти мертві методи
- Безпечно вбудовувати обгортки методів
- Безпечно виділяти нові публічні API
- Безпечно виділяти нові публічні API за допомогою об'єктів значень
- Чому? Це дає швидкі перемоги, і ви починаєте набирати обертів
- Робота з ActiveSupport::Concerns
- Безпечно видаляти активні concerns (див. інші техніки) [Частина 5, скоро]
- Розслідування
- Робота з Active Model
- Безпечно видаляти scope [Частина 6, скоро]
- Безпечно видаляти асоціації [Частина 7, скоро]
- Чому? Візуально ці елементи займають одну лінію у файлі і є найбільш складними для переміщення, тому зберігайте їх для останнього.
- ActiveAdmin
- Створення іншої моделі для методів ActiveAdmin ransack (скоро)
Відслідковування застарілих елементів
З часом я втомився відслідковувати, коли я вносив попередження про застарілість. Я використовував таблицю для відстеження всіх своїх попереджень про застарілість. Кожного разу, коли я додавав нове попередження про застарілість, я додавав новий рядок у таблицю.
|------------|------------------|--------------------|--------------------|
| Дата додавання | Клас | Додано користувачем | Видалено користувачем |
|------------|------------------|--------------------|--------------------|
| 2023/6/14 | company | | |
| 2023/6/15 | company | | |
| 2023/7/1 | company | | |
| 2023/7/15 | bank_accountable | | |
|------------|------------------|--------------------|--------------------|
Кожного разу, коли в мене був кілька хвилин між зустрічами, я переглядав цю таблицю, щоб перевірити, чи готовий якийсь елемент до видалення. Потім я перевіряв DataDog, щоб впевнитися, що цей метод не використовувався останні кілька тижнів.
Перекладено з: Unraveling a Spaghetti Model