Для кожного програміста поняття «чистого коду» різний. У цій статті я спробую донести свої думки з цього приводу, і, можливо, в чомусь навіть змінити старі підходи.
Отже, якими критеріями визначається чистота коду?
іменування
Іноді це дуже складний і «філософський» питання. Основні правила:
- ім'я змінної повинно бути максимально коротким і максимально чітко відповідати на питання «що зберігається в цій змінній» і «яким чином змінна буде використовуватися». При цьому зрозумілість має більший пріоритет, ніж стислість. Те ж саме стосується і функцій;
- використовуємо camelCase для назв змінних і функцій, які складаються з декількох слів;
- тільки англійську мову. Часто буває, що першу версію проекту писали розробники з однієї країни, а другу версію - з іншого. І франкомовний розробник вже не зрозуміє, що хотів сказати російськомовний, назвавши змінні var moiTovary. var cena. var ssylka;
- короткі імена типу «var a, let b» має сенс використовувати тільки для змінних місцевого значення, в невеликому фрагменті коду.
Основні критерії, якими я керуюся при написанні функцій:
- створення нової функції замість дублювання коду. Якщо в 2-х місцях є однаковий код на 2-3 або більше рядків, то варто винести цю частину коду в окрему функцію.
форматування
Форматування коду дуже важливо як для зручності підтримки проекту, так і для швидкості і зручності орієнтування в структурі проекту, змінних і функцій. Дотримання стилю форматування, прийнятого в компанії, дозволяє швидше включатися в роботу розробникам, яких підключили до проекту в процесі розробки, адже при розробці вони керуються тими ж принципами, що і розробники, які вели проект з нуля.
Приклад структури проекту:
Дотримання єдиної структури проекту прискорює пошук потрібних файлів. Не менш важливим є і форматування класів. основні його принципи:
Один файл - один клас (структура).
Детальніше по пунктам:
Більш повний список Pragma Marks:
Код виглядає ще більш структурованим, якщо функції і змінні форматируются за допомогою PragmaMarks в заданій послідовності, а не розкидані випадковим чином. Особисто я додав такий список в Xcode як сніпетів, для зручності.
Хороша практика, коли в компанії є документ з назвою на кшталт «iOS Code Styles Guidline», з яким новий співробітник ознайомлюється перед початком роботи і далі форматує код в очікуваному стилі.
рефакторинг
Рефакторинг - це контрольований процес поліпшення вашого коду без написання нового функціоналу. Завдання рефакторінга - зробити код чистим.
Написання коду можна поділити на 2 пункти: зробити, щоб код працював і провести рефакторинг коду. Що може бруднити наш код? Такі речі, як:
архітектурні паттерни
Використання архітектурних патернів дозволяє збалансовано розділити обов'язки між сутностями. Поняття «патерн» можна пояснити як стиль, шаблон, покликаний вирішити конкретну задачу. Архітектурний патерн задає структуру з додатком в цілому, задає поділ обов'язків між класами, кожен з яких грає тільки одну з ролей. Архітектурний патерн (також можна назвати - дизайн-патерн) визначає не тільки роль класів і об'єктів в додатках, а й те, як об'єкти спілкуються між собою.
Головним дизайн-патерном в Apple (MacOS iOS) є MVC (Model-View-Controller). Згодом з'явилося ще кілька, в результаті сьогодні список основних архітектурних патернів має такий вигляд:
- MVC (Model-View-Controller);
- MVP (Model-Passive View-Presenter);
- MVVM (Model-View-ViewModel);
- Viper (CleanSwift).
Це найбільш часто використовуваний патерн. Він класифікує об'єкти відповідно до їхньої ролі на проекті, що допомагає чистому поділу коду. Правильна реалізація патерну MVC означає, що об'єкт потрапляє тільки в одну з 3-х груп.
View - об'єкти, що відповідають за візуальне уявлення, за те, що користувач бачить. В iOS - це все класи, які мають префікс UI (і їх спадкоємці).
Соntroller - посередник між View i Model. Координує всю роботу: реагує на дії користувача, виконані на view, і оновлює view, використовуючи model.
В ідеалі view повністю відокремлена від моделі і нічого не знає про модель. Це дозволить використовувати view для відображення інших даних.
Використовуючи MVC, ми отримуємо такі переваги:
- краще розуміння класів;
- повторне використання класів, в тому числі і в інших проектах (в основному це стосується Model і View);
- тестування класів окремо один від одного;
- в порівнянні з іншими патернами даний патерн є найпростішим і зрозумілим у використанні, а також менш витратним в розрізі часу.
Недоліком MVC є те, що з часом, коли проект поступово збільшується і змінюється, Controller розростається і в деяких проектах може досягати позначки в 1000 рядків коду і більш, і чим далі, тим більше це ускладнює підтримку проекту: багфіксінг або додавання нового функціоналу. З цієї причини деякі розробники «розшифровують» абревіатуру MVC як «Massive» ViewController :) Поки ми робимо щось одне, ми боїмося зламати щось інше. В результаті, працюючи з таким контролером, розробник не можемо правильно оцінити поставлену задачу, не вкладається в зазначений час, втрачає довіру і репутацію у замовника. Тут можна було б звинувачувати клієнта, тому що:
- клієнт не хоче розуміти, наскільки важко реалізувати таку велику кількість функціоналу на одному екрані;
- клієнт не розуміє, що не можна постійно змінювати вимоги в ТЗ;
- клієнту багаторазово намагалися пояснити, що спочатку треба зосередитися на UX, а не на UI-дизайні;
- якби клієнт на старті проекту вказав на цей функціонал, то це зайняло б значно менше часу ...
- ваш варіант :)
Але все це не скасовує того, що ситуація раз по раз буде повторюватися.
Як можна «розвантажити» ViewController?
MVP-патерн «еволюціонував» з MVC і складається з таких трьох компонентів:
- Presenter (незалежний посередник UIKit);
- Passive View (UIView і / або UIViewController);
- Model.
Цей патерн визначає View як отримує UI-події від користувача і тоді викликає відповідний Presenter, якщо це потрібно. Presenter же відповідає за оновлення View з новими даними, отриманими з моделі.
Переваги: краще розділення коду, добре тестується.
Недоліки: порівняно з MVC має значно більше коду, розробка та підтримка займають більше часу.
Цей патерн зручний в проектах, де використовуються такі фреймворки, як ReactiveCocoa i RxSwift, в яких є концепція «зв'язування даних» - зв'язування даних з візуальними елементами в двосторонньому порядку. В цьому випадку, використання патерну MVC є дуже незручним, оскільки прив'язка даних до подання (View) - це порушення принципів MVC.
View (ViewController) і Model мають «посередника» - View Model. View Model - це незалежне від UIKit уявлення View. View Model викликає зміни в Model і самостійно оновлюється з уже оновленим Model, і, так як зв'язування відбувається через View, то View оновлюється теж.
Недоліком є те, що «замість 1000 рядків в ViewController може вийти тисячі рядків в ViewModel». Також одна з проблем використання фреймворків для «реактивного програмування» - досить просто все поламати і може піти дуже багато часу на багфіксінг. Комусь може здатися, що RxSwift, наприклад, спрощує написання коду, але досить заглянути в стек викликів одного «rx-» методу, щоб оцінити це «спрощення». Можна сюди ж додати проблеми з документацією і постійні проблеми з автокомплітом в xCode.
Viper (CleanSwift)
Interaptor містить бізнес-логіку, пов'язану з даними (Entities).
Presenter містить бізнес-логіку, пов'язану з UI (але UIKit-незалежну), викликає методи в Interaptor.
Entity - прості об'єкти даних, вони не є шаром доступу до даних, так як це відповідальність Interaptor.
Router несе відповідальність за переходи між VIPER-модулями.
Навіть при такому поверхневому огляді очевидно, що краще поділ обов'язків виходить за рахунок великої кількості класів з невеликою кількістю обов'язків.