Архітектурна піраміда програми

Програмування - досить молода область знань, проте, в ній вже існують базові принципи «хорошого коду», що розглядаються більшістю розробників як аксіоми. Всі чули про SOLID, KISS, YAGNI та інші три- або чотирьох- літерні абревіатури, що роблять ваш код чистішим. Ці принципи впливають на архітектуру вашого додатку, але крім них існують архітектурні стилі, методології, фреймворки і багато чого ще.

Розбираючись з усім цим окремо, мене зацікавило питання - як вони взаємопов'язані? Намагаючись вибудувати ієрархію і надихнувшись відомою пірамідою Маслоу, я побудував свою піраміду «архітектури програми».

Про те, що з цього вийшло - читайте під катом.

Про піраміду

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

Рівні піраміди

Розгляньмо кожен з рівнів піраміди, послідовно піднімаючись від її основи до вершини. У статті принципів докладно описано багато разів у книгах і статтях. Тому я не буду детально їх описувати, даючи лише коротку цитату і посилання. Замість цього я спробую пояснити - чому рівні розташовані саме так і як все це можна використовувати.

Безумовні обмеження

Підставою піраміди служать об'єктивні (фізичні) обмеження для додатку. Це може бути що завгодно: розмір команди, бюджет, термін здачі, законодавство країни і навіть поточний рівень розвитку технологій. Основна ознака такого роду обмежень - ви не можете на них вплинути.

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

Бізнес вимоги

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

Бізнес вимоги вносять додаткове (а часто й основні) обмеження на архітектуру додатка. Але вони не можуть суперечити здоровому глузду безумовним обмеженням. Вони лише додатково звужують нашу свободу вибору і тому розташовуються вище безумовних вимог.

Складність: KISS, YAGNI

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

Принципами, спрямованими на боротьбу зі складністю в розробці додатків, є KISS (Keep it simple, stupid) і YAGNI (You aren't gonna need it).

Принцип KISS закликає спрощувати:

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

А YAGNI не проектувати наперед понад міру:

Вам це не знадобиться

Обидва вони дуже абстрактні і годяться для будь-якого додатку, що робить їх основоположними.

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

Зв'язок: DRY, SRP, ISP, high cohesion

Старий жарт про слона, якого потрібно їсти по частинах повною мірою годиться для будь-якої складної задачі. У тому числі, і для розробки великих додатків. За коректний поділ слона завдання на невеликі, ізольовані і точно сформульовані підзадачі відповідають два принципи: DRY (Don't repeat yourself) и SRP (The Single Responsibility Principle).

Принцип DRY говорить:

Кожна частина знання повинна мати єдине, непротиворечивое і авторитетне представлення в рамках системи

А принцип SRP:

кожен об'єкт повинен мати одну відповідальність

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

Принцип ISP (Interface segregation principle) стверджує, що:

Клієнти не повинні залежати від методів, які вони не використовують.

Несподівано, з цим принципом у мене виникли найбільші проблеми. Він чимось схожий на YAGNI - «клієнту можуть і не знадобляться ці методи інтерфейсу». З іншого боку у нього дуже багато і від SRP - «один інтерфейс для одного завдання». Його можна було б віднести до узагальнення «Складність» і поставити на один рівень з YAGNI і KISS, але ці два принципи більш абстрактні.

SRP і ISP є частиною іншого відомого принципу - SOLID і по початку мене хвилювало, що в моїй піраміді ці принципи виявилися відокремлені від інших. Однак зрештою я вирішив, що не всі йогурти однаково корисні принципи рівні між собою. Це не означає, що можна знехтувати іншими принципами SOLID. Просто SRP і ISP, на мій погляд, трохи більш узагальнені ніж інші.

Сильна зв'язаність або зачеплення (high cohesion) - це метрика, що показує, наскільки добре код згрупований за функціоналом. Виконання принципів DRY і SRP веде до коду з сильною зв'язністю, в якому частини, що виконують одне і те ж завдання розташовані «близько» один до одного, а різні - ізольовані. Виконання ISP теж веде до сильної зв'язності, хоча це і не так очевидно. Розділяючи один інтерфейс на дрібніші частини ви групуєте їх за функціоналом, залишаючи в кожному інтерфейсі тільки найбільш пов'язані між собою методи.

GRASP: high cohesion, loose / low coupling

Вище сильна пов'язаність названа «метрикою», хоча її повною мірою можна назвати принципом. High cohesion, поряд з Low coupling є частинами GRASP (General responsibility assignment software patterns), який у цій статті не розглядається.

Залежності: IoC, DIP, loose coupling

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

IoC (Inversion of control) передбачає наявність певного фреймворку, який буде передавати управління компонентам нашої програми в потрібний момент. При цьому компоненти можуть нічого не знати один про одного.

DIP (Dependency inversion principle) говорить:

Додатки верхніх рівнів не повинні залежати від додатків нижніх рівнів. Обидва типи додатків повинні залежати від абстракцій. Абстракції не повинні залежати від деталей. Деталі повинні залежати від абстракцій.

Слабка зв'язність (loose coupling) - це не принцип, а метрика, що показує, наскільки компоненти системи незалежні один від одного. Слабко пов'язані компоненти не залежать від зовнішніх змін і легко можуть бути використані повторно. IoC і DIP є засобами для досягнення слабкої зв'язності компонентів у системі.

Суфікс: OCP, LSP

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

Принципами, що належать до розширення функціоналу, є OCP (Open/closed principle) і LSP (Liskov substitution principle).

OCP визначає, що

програмні сутності (класи, додатки, функції тощо) повинні бути відкриті для розширення, але закриті для зміни

А LSP:

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

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

Методології: TDD, DDD, BDD

Крім принципів, існує ще досить великий набір методологій, наприклад BDD (Behavior-driven development) і TDD (Test-driven development). У загальному випадку методологія, на відміну від принципу, визначає деякий процес, що застосовується до розробки додатку.

Методології дуже різноманітні, вирішують різні завдання і часто, при розробці програми, використовується їх комбінація. Але яку б з них ви не вибрали, у вас виникнуть серйозні проблеми, якщо спробувати застосувати їх до коду, що порушує попередні принципи. Наприклад: чи легко буде застосувати TDD і писати тести для коду, який порушує DIP і містить посилання на конкретні реалізації?

Архітектурні стилі та фреймворки

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

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

Бібліотеки та інструменти

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

Чисто геометрично цей рівень найменший за розміром, але за іронією саме він часто викликає найбільше суперечок, обговорень і статей.

Навіщо все це потрібно?

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

Якщо зміни вносяться в існуючу систему, то він приблизно такий:

1. Чи реалізовані мої зміни з урахуванням відведеного мені бюджету, часу та інших об'єктивних обмежень?

2. Чи не суперечать вони бізнес-вимогам?

3. Чи достатньо просто те, що я збираюся зробити? Чи є простіші способи зробити це?

4. Як мені розділити моє завдання на компоненти (підзадачі) так, щоб кожен з компонентів виконував тільки одну дію? Чи не дублюю я вже існуючий функціонал?

5. Як мої компоненти будуть пов'язані з іншою програмою? Як зменшити кількість зв'язків? Чи буду я мати можливість використовувати мої компоненти повторно або замінити один компонент на інший в майбутньому?

6. Чи зможу я розширити функціонал моїх компонентів, не змінюючи їх? Чи заклав я можливості розширення в ті компоненти, ймовірність зміни яких в майбутньому особливо велика?

7. Чи не суперечать мої зміни обраної мною методології?

8. Чи співвідносяться мої зміни з кращими практиками використовуваного мною фреймворку? Чи не порушують вони загальний архітектурний стиль мого коду?

9. Чи можуть використовувані мною бібліотеки вирішити поставлене підзавдання?

Кожен пункт списку співвідноситься з певним рівнем піраміди. При цьому вибір, зроблений на кожному рівні не повинен суперечити вибору, зробленому на попередніх рівнях. Це особливо важливо при проектуванні системи з нуля, коли «невизначеність» в плані майбутньої архітектури набагато вище і ширше можливий вибір.

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

У цілому розуміння ієрархії фокусує вас на більш базових принципах (нижніх сходах). Крім того вивчення зв'язків між елементами піраміди дозволяє краще зрозуміти картину «в цілому», узагальнити і систематизувати свої знання. Для більш глибокого розуміння будь-якого нового принципу його буде корисно спробувати включити в цю систему, зрозуміти - на якому рівні він повинен розташовуватися.

Ув'язнення

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

COM_SPPAGEBUILDER_NO_ITEMS_FOUND