Сьогодні поговоримо про важливі відмінності між функціональними та нефункціональними вимогами і про те, чому нефункціональні вимоги мають хвилювати кожного технічного спеціаліста — від архітектора до розробника.
Функціональні та нефункціональні вимоги
Мабуть, кожен складний програмний продукт — це результат реалізації функціональних і нефункціональних вимог. Нефункціональні вимоги іноді ще називають міжфункціональними (cross-functional) або неповедінковими (non-behavioral).
Щоб відчути різницю між ними, уявімо, що ми розробники, які збираються спроєктувати й реалізувати Instagram з нуля. Ось як можуть виглядати деякі високорівневі функціональні вимоги:
- користувачі можуть завантажувати фото;
- користувачі можуть підписуватися на інших користувачів;
- користувач може надсилати приватні повідомлення іншому користувачу;
- користувачі бачать стрічку новин (news feed) із фото інших користувачів.
А ось кілька можливих нефункціональних вимог:
- стрічка новин формується в середньому за 500 мс, а для 99% запитів (p99) — не довше ніж за 1 с;
- фото, які завантажують користувачі, ніколи не губляться (довговічність даних, durability);
- доступність (availability) системи — 99,99% часу.
Як видно з прикладу Instagram, функціональні вимоги описують, що система має робити, а нефункціональні — як вона має при цьому поводитися.
Інші важливі відмінності між функціональними та нефункціональними вимогами:
- Система може працювати, навіть якщо реалізовано лише функціональні вимоги. Простим продуктам чи PoC (proof of concept) нефункціональні вимоги часто взагалі не важливі. Натомість нефункціональні вимоги неможливо реалізувати без функціональних.
- Про функціональні вимоги легко домовитися, на відміну від нефункціональних. Наприклад, легко погодитися, що користувач Instagram може завантажити фото (функціональна вимога). Але як швидко домовитися про відсоток доступності (нефункціональна вимога)? 99,99%? А чому не 99,95%?
- Функціональні вимоги зазвичай документують як сценарії використання (use cases) або user stories, а нефункціональні — як атрибути якості (quality attributes).
- За збір (elicitation) і документування функціональних вимог відповідають бізнес-аналітики. За нефункціональні — зазвичай архітектори програмного забезпечення, а частково також техліди й кожен розробник.
- І найважливіша відмінність із технічного погляду: архітектуру визначають насамперед нефункціональні вимоги. Функціональні впливають на неї значно менше — їх можна реалізувати майже в будь-якій архітектурі.
Функціональні вимоги та архітектура
Згадаймо, мабуть, найпростішу можливу архітектуру застосунку — трирівневу (3-tier architecture):
Річ у тім, що навіть найпростіша трирівнева архітектура здатна реалізувати будь-яку кількість дуже складних функціональних вимог: усі функціональні вимоги Instagram можна реалізувати на трирівневій архітектурі. У базі даних потенційно будуть сотні таблиць, а кодова база може налічувати сотні тисяч рядків коду. Звісно, з появою нових функцій супровідність (maintainability) такої монолітної системи може погіршуватися.
Проте трирівнева архітектура успішно реалізує функціональні вимоги, якщо вона правильно структурована, а якість коду підтримується на належному рівні. Реалізація нових функцій здебільшого зводитиметься до створення нових таблиць у базі даних, розширення доменної моделі, написання нових бізнес-правил та іншого допоміжного коду.
Головний висновок: коли йдеться лише про реалізацію функціональних вимог, архітектура програмного забезпечення і system design не мають великого значення.
Просто, чи не так?
Нефункціональні вимоги та архітектура
Коли ж ідеться про нефункціональні вимоги, архітектура може бути дуже важливою. Деякі нефункціональні вимоги неможливо реалізувати без суттєвих змін в архітектурі чи дизайні системи.
Як уже згадувалося, трирівнева архітектура може успішно реалізувати всі функціональні вимоги замовника. Уявімо, що трирівневу систему вже реалізовано і вона працює в продакшні:
Але тепер з’являється нова нефункціональна вимога, яку раніше не враховували: усі GET-ендпоінти мають відповідати в середньому за 500 мс, а для 99% запитів (p99) — не довше ніж за 1 с при 5000 одночасних користувачів.
Звісно, розробники можуть спробувати досягти цього, оптимізувавши запити до бази даних, додавши кешування в пам’яті (in-memory caching), зменшивши кількість алокацій у керованій купі (managed heap) тощо. Проте у великій монолітній системі таких кроків — і навіть горизонтального масштабування — може виявитися недостатньо.
Щоб виконати таку вимогу, може знадобитися перепроєктувати систему (а для системи в продакшні це може стати справжнім кошмаром). Наприклад, упровадити патерн CQRS на рівні архітектури — з окремо масштабованими сервісами читання, окремим екземпляром кешу та іншими складними речами:
Якщо такі нефункціональні вимоги, як продуктивність (performance), доступність (availability) чи надійність (reliability), повністю проґавити на етапах збору вимог і проєктування, систему, можливо, доведеться перебудовувати архітектурно. До того ж, навіть якщо всі нефункціональні вимоги зібрано якомога раніше, дуже важливо звертати увагу на цифри. Наприклад, системи з доступністю 99,9% і 99,95% можуть потребувати двох різних архітектур, а 99,95% обійдуться ще й у значно більші рахунки від хмарного провайдера.
Для орієнтиру — скільки простою допускає кожен рівень доступності:
| Доступність | Простій за рік | За місяць | За день |
|---|---|---|---|
| 99,9% | ≈ 8 год 46 хв | ≈ 43 хв 50 с | ≈ 1 хв 26 с |
| 99,95% | ≈ 4 год 23 хв | ≈ 21 хв 55 с | ≈ 43 с |
| 99,99% | ≈ 52 хв 36 с | ≈ 4 хв 23 с | ≈ 9 с |
Тож варто ставити собі запитання:
- Чи справді системі потрібна доступність 99,95%? Чим обґрунтоване це число? Чому 99,9% недостатньо?
- Чи справді кожен GET-ендпоінт має відповідати за 500 мс? Чим обґрунтоване це число? Можливо, така вимога стосується лише одного ендпоінта?
- …
Вчасно поставлені запитання — ці та інші — можуть заощадити команді багато зусиль, а клієнтам — гроші.
Чому розробникам варто думати про нефункціональні вимоги
Нефункціональні вимоги — турбота не лише архітекторів, а й техлідів і розробників.
Коли розробник бере в роботу story, нефункціональні вимоги там можуть узагалі не згадуватися.
Наприклад, story може стосуватися нового API-ендпоінта з новою бізнес-логікою і новими таблицями в базі даних. Якщо нефункціональну вимогу — скажімо, максимальний час відповіді API 300 мс — у story не згадано і ніхто її заздалегідь не уточнив, розробник може використати неефективні алгоритми, неоптимальну схему бази даних, обійтися без кешування тощо, аби лише виконати функціональні вимоги і якнайшвидше передати story в QA.
Згодом з’явиться ризик переробляти реалізацію, коли QA-інженер або замовник побачить реальний час відповіді. Тож одне запитання від розробника чи техліда, поставлене заздалегідь, — «Яким має бути середній і максимальний час відповіді цього API?» — допоможе уникнути подвійної роботи.
Ключові висновки
- Функціональні вимоги можна реалізувати на простій архітектурі. Нові функціональні вимоги зазвичай не потребують змін в архітектурі системи.
- Нефункціональні вимоги можуть суттєво впливати на архітектуру системи. Як архітектор, подбайте, щоб важливі нефункціональні вимоги було зібрано якомога раніше — до проєктування та реалізації системи.
- Нефункціональні вимоги можуть суттєво впливати на дизайн окремого вебсервісу чи мікросервісу, на алгоритми, структури даних тощо. Як техлід або розробник, уточнюйте нефункціональні вимоги — продуктивність, масштабованість (scalability), пропускну здатність (throughput), інтероперабельність (interoperability) — до того, як братися за реалізацію story. Так ви уникнете можливих переробок у майбутньому.
Це переклад моєї англомовної статті «Why Missing Non-Functional Requirements Can Be a Disaster», опублікованої на Medium. Таблицю простоїв додано в українській версії.