Чотири правила простішого проектування програмного забезпечення для iOS

Наприкінці 1990-х, розробляючи програму Extreme Programming, відомий розробник програмного забезпечення Кент Бек придумав перелік правил для простого програмного проектування.

За словами Кента Бека, хороший дизайн програмного забезпечення:

  • Виконує всі тести
  • Не містить дублювання
  • Висловлює наміри програміста
  • Мінімізує кількість класів і методів

У цій статті ми поговоримо про те, як ці правила можна застосувати до світу розробки iOS, надавши практичні приклади iOS та обговоривши, як ми можемо отримати від них користь.

Виконує всі тести

Дизайн програмного забезпечення допомагає нам створити систему, яка працює за призначенням. Але як ми можемо перевірити, що система буде діяти так, як спочатку планувала її конструкція? Відповідь створюється тестами, які підтверджують її.

На жаль, у більшості випадків уникають тестів у Всесвіті для розробки iOS… Але для того, щоб створити добре розроблене програмне забезпечення, ми завжди повинні писати код Swift з урахуванням доказів.

Давайте обговоримо два принципи, які можуть спростити написання тесту та дизайн системи. І вони є єдиним принципом відповідальності та введенням залежності.

Принцип єдиної відповідальності (SRP)

SRP зазначає, що клас повинен мати одну, і лише одну причину для зміни. СРП - це один із найпростіших принципів і один із найскладніших для отримання права. Змішування обов'язків - це те, що ми робимо природно.

Наведемо приклад деякого коду, який справді важко перевірити, а після цього перефактуруйте його за допомогою SRP. Потім обговоріть, як це зробило код тестуваним.

Припустимо, що в даний час нам потрібно представити PaymentViewController від нашого поточного контролера перегляду, PaymentViewController повинен налаштувати його подання залежно від ціни на наш платіжний продукт. У нашому випадку ціна змінюється залежно від деяких подій зовнішніх користувачів.

Код для цієї реалізації наразі виглядає наступним чином:

Як ми можемо перевірити цей код? Що ми повинні перевірити спочатку? Чи правильно розрахована знижка на ціну? Як ми можемо знущатися з платежних подій, щоб перевірити знижку?

Написання тестів для цього класу було б складним, ми повинні знайти кращий спосіб його написання. Ну, по-перше, давайте вирішимо велику проблему. Нам потрібно розплутати свої залежності.

Ми бачимо, що у нас є логіка для завантаження нашого продукту. У нас є події оплати, завдяки яким користувач може отримати знижку. У нас є знижки, розрахунок знижок, і список продовжується.

Тож спробуємо просто перевести їх у код Swift.

Ми створили PaymentManager, який управляє нашою логікою, пов’язаною з платежами, і окремий PriceCalculator, який легко перевірити. Також завантажувач даних, який відповідає за взаємодію мережі або бази даних для завантаження наших продуктів.

Ми також зазначили, що нам потрібен клас, відповідальний за управління знижками. Назвемо це CouponManager, і нехай він добре керує купонами зі знижкою користувачів.

Наш контролер перегляду платежів може виглядати наступним чином:

Зараз ми можемо писати такі тести, як

  • testCalculatingFinalPriceWithoutCoupon
  • testCalculatingFinalPriceWithCoupon
  • testCouponExists

та багато інших! Створюючи окремі об’єкти, тепер ми уникаємо зайвого дублювання, а також створили код, на який легко писати тести.

Ін'єкційна залежність

Другий принцип - це введення залежності. І з наведених вище прикладів ми побачили, що ми вже використовували ін'єкцію залежності від ініціаторів об'єктів.

Є дві основні переваги введення наших залежностей, як вище. Це дає зрозуміти, на які залежності покладаються наші типи, і це дозволяє нам вставляти макетні об’єкти, коли ми хочемо перевірити замість реальних.

Хорошою технікою є створення протоколів для наших об'єктів і надання конкретної реалізації реальним та макетним об’єктом, як описано нижче:

Тепер ми можемо легко визначити, який клас ми хочемо ввести як залежність.

Щільне з'єднання ускладнює складання тестів. Таким чином, аналогічно, чим більше тестів ми пишемо, тим більше ми використовуємо такі принципи, як DIP та такі інструменти, як введення залежності, інтерфейси та абстракція, щоб мінімізувати з'єднання.

Зростання коду більш тестируемим не тільки усуває наш страх порушити його (оскільки ми напишемо тест, який підтримає нас), але й сприяє написанню більш чистого коду.

Ця частина статті стосувалася більше того, як написати код, який буде перевіреним, ніж написання фактичного одиничного тесту. Якщо ви хочете дізнатися більше про написання одиничного тесту, ви можете ознайомитися з цією статтею, де я створюю гру життя, використовуючи тестові розробки.

Не містить дублювання

Копіювання - головний ворог добре розробленої системи. Він представляє додаткову роботу, додатковий ризик, додає зайвої складності.

У цьому розділі ми обговоримо, як ми можемо використовувати шаблон дизайну шаблонів для видалення загального дублювання в iOS. Щоб полегшити розуміння, ми збираємося реалізувати реалізацію чату в реальному житті.

Припустимо, що у нас в нашому додатку є стандартний розділ чату. Виникає нова вимога, і тепер ми хочемо реалізувати новий тип чату - чат в прямому ефірі. Чат, який повинен містити повідомлення з максимальною кількістю 20 символів, і цей чат зникне, коли ми відхилимо перегляд чату.

Цей чат матиме ті ж перегляди, що й наш поточний чат, але матиме кілька різних правил:

  1. Мережевий запит для надсилання повідомлень чату буде різним.

2. Повідомлення в чаті повинні бути короткими, не більше 20 символів для повідомлення.

3. Повідомлення чату не повинні зберігатися в нашій локальній базі даних.

Припустимо, що ми використовуємо архітектуру MVP і в даний час ми обробляємо логіку відправки повідомлень чату в нашому презентаторі. Спробуємо додати нові правила для нашого нового типу чату під назвою live-chat.

Наївна реалізація виглядає наступним чином:

Але що станеться, якщо в майбутньому у нас буде набагато більше типів чату?
Якщо ми продовжимо додавати, якщо інше перевіряє стан нашого чату в кожній функції, код стане безладним для читання та підтримки. Крім того, навряд чи можна перевірити, і державна перевірка буде дублюватися у всьому обсязі презентації.

Тут використовується шаблон шаблону. Шаблон шаблону використовується, коли нам потрібно кілька реалізацій алгоритму. Шаблон визначається, а потім будується з подальшими варіаціями. Використовуйте цей метод, коли більшості підкласів потрібно реалізувати ту саму поведінку.

Ми можемо створити протокол для Presenter Chat і розділити методи, які будуть реалізовані по-різному конкретними об'єктами у фазах чат-презентації.

Тепер ми можемо зробити наш ведучий відповідним IChatPresenter

Наш Ведучий тепер обробляє надсилання повідомлень, викликаючи загальні функції всередині себе та делегує функції, які можна реалізувати по-різному.

Тепер ми можемо надати Створення об'єктів, що відповідають фазам презентації, та налаштувати ці функції, виходячи з їх потреб.

Якщо ми використовуємо введення залежності в наш контролер подання, ми можемо використовувати повторно один і той же контролер подання у двох різних випадках.

Використовуючи шаблони дизайну, ми можемо реально спростити наш iOS-код. Якщо ви хочете дізнатися більше про це, наступна стаття надає додаткове пояснення.

Виразний

Більшість витрат на програмний проект припадає на довготривале обслуговування. Написання простого для читання та обслуговування коду є необхідним для розробників програмного забезпечення.

Ми можемо запропонувати більш виразний код, використовуючи хороший тест Назви, Використовуючи тест SRP та Написання.

Іменування

Номер одне, що робить код більш виразним - і це називання. Важливо писати імена, які:

  • Розкрийте намір
  • Уникайте дезінформації
  • Легкий пошук

Що стосується іменування класів та функцій, хороший трюк - використовувати іменник або іменникову фразу для класів та дієслів користувача чи назви словосполучень для методів.

Також при використанні різних шаблонів дизайну іноді добре додавати назви шаблонів, таких як Command або Visitor, до імені класу. Тож читач одразу дізнається, яка модель використовується там, без потреби читати весь код, щоб дізнатися про це.

Використання SRP

Інша річ, яка робить виразним код, - це використання принципу єдиної відповідальності, який згадувався вище. Ви можете виразити себе, зберігаючи свої функції та класи невеликими та з єдиною метою. Невеликі класи та функції, як правило, легко називати, легко писати та легко розуміти. Функція повинна виконувати лише одну мету.

Письмовий тест

Написання тестів також приносить велику чіткість, особливо при роботі зі застарілим кодом. Добре написані одиничні тести також є виразними. Основна мета тестів - діяти як документація на прикладі. Хтось, читаючи наші тести, повинен мати можливість швидко зрозуміти, що таке клас.

Мінімізуйте кількість класів і методів

Функції класу повинні залишатися короткими, функція завжди повинна виконувати лише одне. Якщо функція має занадто багато рядків, це може бути так, що вона виконує дії, які можна розділити на дві або більше окремих функцій.

Хорошим підходом є підрахунок фізичних ліній і намагання націлити на максимум чотири-шість рядків функцій, у більшості випадків все, що перевищує цю кількість рядків, може стати важким для читання та обслуговування.

Хорошою ідеєю в iOS є відбивання конфігураційних викликів, які ми зазвичай робимо на функціях viewDidLoad або viewDidAppear.

Таким чином, кожна з функцій була б малою та підтримуваною замість однієї функції перегляду безладдяDidLoad. Те саме має стосуватися і делегата програми. Ми повинні уникати метання кожного методу ondidFinishLaunchingWithOptions та окремих функцій конфігурації або навіть кращих класів конфігурації.

За допомогою функцій трохи простіше виміряти, тривалий чи короткий ми зберігаємо, ми можемо більшість разів просто розраховувати на підрахунок фізичних ліній. З класами ми використовуємо іншу міру. Ми розраховуємо обов'язки. Якщо в класі є лише п’ять методів, це не означає, що клас малий, можливо, він має занадто багато обов'язків лише з цими методами.

Відома проблема в iOS - великий розмір UIViewControllers. Це правда, що за допомогою дизайну контролера яблучного перегляду важко утримати ці об'єкти єдиною метою, але ми повинні постаратися.

Є багато способів зробити так, щоб UIViewControllers був малим, моє перевагу - це використовувати архітектуру, яка має кращі розділення проблем на кшталт VIPER або MVP, але це не означає, що ми також не можемо зробити це краще в Apple MVC.

Намагаючись відокремити якомога більше проблем, ми можемо досягти досить пристойного коду з будь-якою архітектурою. Ідея полягає у створенні одноцільових класів, які можуть служити помічниками для контролерів перегляду та зробити код більш читабельним та перевіреним.

Деякі речі, яких можна просто уникнути без виправдання у контролерах перегляду:

  • Замість того, щоб писати мережевий код безпосередньо, у NetworkManager повинен бути клас, який відповідає за мережеві дзвінки
  • Замість маніпулювання даними в контролерах перегляду ми можемо просто створити DataManager клас, який відповідає за це.
  • Замість того, щоб грати з рядками UserDefaults в UIViewController, ми можемо створити фасад над цим.

У висновку

Я вважаю, що ми повинні складати програмне забезпечення з компонентів, точно названих, простих, малих, відповідальних за одне і багаторазового використання.

У цій статті ми обговорили чотири правила простого дизайну від Кента Бека та навели практичні приклади того, як ми можемо їх реалізувати в середовищі розробки iOS.

Якщо вам сподобалася ця стаття, обов'язково плескайте, щоб показати свою підтримку. Дотримуйтесь мене, щоб переглянути ще багато статей, які можуть перенести ваші навички розробника iOS на новий рівень.

Якщо у вас є запитання чи коментарі, не соромтесь залишити тут записку або надіслати електронною поштою arlindaliu.dev@gmail.com.