KISS, DRY, S.O.L.I.D, YAGNI — навіщо дотримуватись принципів програмування?

  • 12 апреля, 15:21
  • 14318
  • 0

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

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

Оскільки мови програмування різні, а принципи універсальні для всіх, в статті не буде прикладів самого коду. Всі приклади будуть схематичними.

До речі, опрацювання та обговорення принципів програмування є обов'язковим в програмі SPD-University, навчального проєкту компанії SPD-Ukraine.

KISS — Keep it simple, stupid

Основна ідея принципу KISS — чим простіше, тим краще, щоб кожен розробник зміг зрозуміти, що відбувається у класах та методах. Звідси випливає два тезиси:

  1. Keep it short and simple — коротко і просто.

  2. Keep it simple and straightforward — просто і зрозуміло.

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

Крім того, принцип KISS використовується не лише в програмуванні. Будь-який user experience націлений на простоту —  без зайвих дій та перешкод для користувача. Така ж ситуація, наприклад, з автомобілями, телефонами та в цілому в дизайні — простота та зрозумілість.

DRY — Don't repeat yourself

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

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

За принципом DRY ви повинні не просто писати один і той же код один раз, а уникати повторення знання.

Якщо в якомусь місці ви бачите, що такий код вже був написаний, це момент, коли його треба відрефакторити. Тобто варто подивитись, що тут спільного. Повністю спільне можна винести в reusable клас, компонент або метод.

Основна задача — зменшити кількість знань, що повторюється. Не повністю прибрати його, а саме зменшити його кількість. Частина коду має бути реалізована лише в одному місці. Також не варто забувати про SRP (Single Responsibility Principle), коли кожний модуль має робити щось одне. Про це поговоримо трохи пізніше.

Що ж робити, якщо при спробі не повторювати код, ви бачите, що порушуєте принцип DRY? Можливо, краще повторити код у двох місцях, але при цьому Single  Responsibility принцип не порушити. Або ж можна розділити код на дві або три частини та використати ці частини окремо. Все залежить від ситуації.

S.O.L.I.D.

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

S — Single Responsibility Principle

O — Open-Closed Principle

L — Liskov Substitution Principle

I — Interface Segregation Principle

D — Dependency Inversion Principle

SRP (Single Responsibility Principle)

В SRP принцип “відповідальність” (responsibility) Роберт Мартін, консультант та автор в області розробки ПЗ, визначив як “причину для зміни певного класу чи модулю, який може бути переписаним чи зміненим”.

Наприклад, у нас є якийсь Report Class. Користувач Actor звертається до цього класу, щоб скомпілювати репорт та роздрукувати його (відправити на принтер або зробити PDF). Тобто наш Report Class і компілює, і друкує.

З одного боку можна подумати, що все (і компіляція, і друк) належать до класу Report. Але що станеться, якщо сьогодні нам треба зробити PDF і відправити на принтер, а завтра нас попросять створити ще й CSV та XML-документ? 

У такому разі ми маємо відкрити цей один Report Class та змінити логіку функціональності Print — щось додати, щось розширити. Тобто наш клас стане частково більшим. Але змінюючи один клас, ми теоретично можемо зламати те, що в ньому знаходиться, а саме Compile. І після змін ми маємо перевірити функціональність Compile і впевнитись, що вона працює. У нас була ціль змінити Print, але ми вплинули на дві частини функціоналу. 

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

І в такому випадку зміни функціональності Print не вплинуть на функціональність Compile. 

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

OCP (Open-Closed Principle)

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

Джерело

Змінювати поведінку класу (з Cut на Paint) не правильно. Його можна лише розширяти додатковим методом (функціональністю).

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

Розглянемо на прикладі. Скажімо, нам треба мати змогу друкувати репорти різного типу — повний, короткий та розширений. Перше, що спадає на думку, додати ReportType. Це призведе до того, що в нас буде декілька IF всередині нашого Report, тобто ми змінили його роботу. А якщо нам треба буде додати ще 5 нових типів репортів? Такий підхід порушить Open-Closed принцип.

Краще не відштовхуватись від типу репорту, а додати окрему Report Model, де будуть друкуватись лише певні поля. Таким чином Print відповідає за одну функціональність, і принцип Single Responsibility не порушується. Ми можемо скільки завгодно розширяти функціональність цього методу, але при цьому ми його не модифікуємо.

Варто задумуватись про те, що буде з вашою програмою в майбутньому, коли ви будете щось змінювати. І при цьому не забувати про принципи Single Responsibility та Open-Closed. Саме тому розробники більше часу витрачають на обдумування рішення, ніж на саму реалізацію.

LSP (Liskov Substitution Principle)

Нічого не зрозуміло :) Зараз пояснимо простіше.

У нас є якийсь клас, в якому є метод, наприклад, Culculate, який приймає певні значення. Якщо ми будемо від цього методу наслідуватись, то в новому класі має бути такий же метод Calculate, який буде приймати ті ж параметри.

Але якби все було так просто, то всі б писали такий код і ні в кого б не було проблем.

Джерело

Тут і проблема. Якщо ми наслідувались від якогось класу, то маємо так само і в тому ж вигляді реалізовувати ті ж методи, які є в Parent класі. І при цьому вони обов'язково мають працювати.

Приклад порушення принципу LSP

У цьому випадку SubReportModel не має приймати якісь інші параметри та те ж саме з типами цих параметрів.

ISP (Interface Segregation Principle)

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

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

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

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

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

DIP (Dependency Inversion Principle)

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

Розглянемо на прикладі нашого PrintRawReport. У PrintRawReport є метод PrintRaw, який додає якусь свою логіку. Тобто наш Report приймає якусь модель, залежно від якої принтить те, що нам треба. Тобто метод PrintRaw має формувати нашу модель PrintRawReport за певними правилами та викликали Report. 

Що тут не так? А те, що наш PrintRawReport повністю залежить від саме цього PrintReport — саме цей інстанс репорту він має використовувати. Якщо ми передаємо інстанс PrintPDFReport, то він не скаже “це щось не те, я з таким не працюю”. Тому при проєктуванні залежностей краще одразу використовувати Dependecy Inversion. 

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

І наш клас PrintRawReport може одночасно приймати всі ці типи репортів. Тому що основна задача PrintRawReport — це друкувати особливий тип репорту. А те, як саме це друкувати, не його задача. Він навіть не повинен цього знати. Таким чином тепер наш PrintRawReport напряму не залежить від якогось класу, він залежить від інтерфейсу.

YAGNI — You aren't gonna need it

Ми багато говорити про те, що варто думати, як ваша програма буде працювати в майбутньому, як вона буде змінюватись, написати код, який легко розширювати і т.д. Але варто не забувати й про принцип YAGNI. 

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

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

Потрібно думати про те, що буде з вашим кодом в майбутньому, але при самій реалізації варто виконувати те, що має бути реалізовано саме сьогодні. Не реалізовувати те, що не потрібно. Чим менше коду, тим менше багів :)

І пам'ятайте, найкращий код — це той, який не був написаний.


0 комментариев
Сортировка:
Добавить комментарий

IT Новости

Смотреть все