Будь ви простим програмістом, материм лідом, архітектором або навіть ПМ-ом, ви напевно в своїй нелегкій роботі стикалися з проблемою вибору при додаванні в систему нової можливості. Одне рішення набагато простіше реалізувати в стислі терміни і встигнути до чергового дуже важливого релізу, проте воно буде більш витратне в супроводі, менш розширюване або менш надійне. Інше рішення може не володіти всіма цими недоліками, проте володіти іншим, в деяких випадках більш важливим недоліком - на його реалізацію буде потрібно значно більше часу.
При цьому найскладнішим при виборі того чи іншого рішення є «комунікація» свого вибору безпосередньому керівнику, щоб він зміг прийняти зважене рішення. А оскільки з точки зору більшості керівників «зважування» закінчується відразу ж після того, як він почує терміни реалізації, то «комунікація» закінчуються приблизно через 37 секунд після її початку (зазвичай саме стільки часу потрібно керівнику, щоб дізнатися відповідь на дуже просте питання, що виражається одним словом: «Коли?»)
Не дивно, що багато простих програмістів, матері ліди і архітектори, а іноді навіть ПМ-и, які розуміють, що їм самим доведеться розсьорбувати проблеми «короткозорих» рішень, з таким підходом не згодні. І абсолютно не дивно, що з подібною проблемою стикалися й інші відомі і не дуже люди, які придумали типові «патерни», що описують подібну ситуацію. Одним з таких набоїв є метафора технічного боргу, вперше описана Вардом Каннінгемом (Ward Cunningham) (*) без малого двадцять років тому.
Технічний борг у класичному розумінні
У класичному розумінні, тобто в тому вигляді, в якому ця метафора була описана Вардом Каннінгемом, під технічним боргом розуміється усвідомлене компромісне рішення, коли замовник і ключові розробники чітко розуміють всі переваги від швидкого, нехай і не ідеального технічного рішення, за яке доведеться розплатитися пізніше. І хоча з точки зору багатьох розробників ситуація, коли погане рішення може бути хорошим, може здатися божевільною, насправді, це цілком можливо: якщо короткострокове рішення дозволить компанії отримати видимі переваги, випустити продукт раніше конкурентів, задовольнити ключового замовника або якимось іншим чином отримати переваги перед конкурентами, тоді таке рішення абсолютно виправдане. Іноді це може бути єдиним способом, щоб довгострокова перспектива взагалі існувала.
Можна провести паралель між технічним боргом і боргом фінансовим. Фінансовий борг означає, що ви отримуєте додаткові кошти зараз, проте кожен місяць (або кожні пів року) вам доведеться виплачувати фіксовану процентну ставку, а в кінці терміну повернути весь борг кредитору. Аналогічна ситуація відбувається і в разі прийняття неоптимального технічного рішення: ви отримали готову систему або нову можливість вже сьогодні, проте при додаванні нової можливості вам доведеться платити «відсотки», або погасити ваш борг шляхом рефакторингу системи або частини системи.
У цієї метафори є одна дуже важлива особливість: коли мова йде про неоптимальне рішення, мова йде про використання .NET Remoting замість WCF, використання Sybase, замість SQL Server, використання DataSet-ів замість Entity Framework (* *), проте ніхто не говорить про «забивання милиць», брудні хаки, поганий код, зв'язкову архітектуру тощо. Якраз навпаки, неоптимальність стратегічного рішення не означає, що до нього потрібно відноситься абияк, поширити вплив цього рішення на всю систему або просто напросто # $ нокодити. Якраз, навпаки, в більшості випадків це означає, що це рішення має бути заховане у вигляді деталі реалізації, одного або принаймні, якомога меншого числа модулів, щоб його можна було змінити пізніше.
Саме такий підхід до побудови додатків є найбільш оптимальним з архітектурної точки зору. Якщо приймаючи архітектурне рішення ви не відчуваєте компромісу між короткостроковими і довгостроковими вигодами, то в будь-якому випадку не варто вирубувати його в камені. Цілком можливо, що ви прийняли неправильне рішення неусвідомлено, а через своє нерозуміння предметної області, або через майбутню зміну вимог замовником. Значно простіше знизити вартість майбутніх змін шляхом інкапусуляції важливих рішень в найменше число модулів, ніж намагатися продумати архітектуру до найдрібніших подробиць, в надії врахувати всі можливі і неможливі сценарії. Для певного кола завдань ідеальна продумана архітектура можлива, а іноді просто необхідна, але в більшості випадків - це не так; рано чи пізно прийде момент, коли ваше бачення системи або бачення системи замовником зміниться, що вимагатиме внесення в неї істотних змін.
Однак навіть при розумному накопиченні технічного боргу існує велика ймовірність, що команда (або замовники) підуть за старим принципом - працює - не чіпай, і ніколи не повернуться до цього рішення знову, щоб розплатитися за свій технічний борг. Крім того, існує безліч випадків, коли технічний борг накопичується поступово і неусвідомлено, і якщо розробники не будуть про це замислюватися, то прийдуть до старої як світ ситуації, коли вартість додавання нової можливості стає неймовірно дорогою, всі працюють, втомлюються, зляться один на одного, не отримавши при цьому ніяких переваг перед конкурентами. Це призводить нас до другого джерела технічного боргу - брудного коду (* * *)
Брудний код, як джерело технічного боргу
Технічний борг у класичному розумінні є навмисним і в основному стосується стратегічних рішень, тому і відповідальність за нього лежить на замовниках материх лідах, архітекторах і навіть ПМ-ах, але ось все, що пов'язано з брудним кодом стосується здебільшого простих розробників. Насправді, різниця між брудним кодом і неоптимальним стратегічним рішенням, по суті, не така вже й велика: при додаванні нової можливості в систему вам доводиться розплачуватися за недалекоглядність у минулому.
Під час кодування, як і під час прийняття будь-яких інших рішень, розробник повинен розглядати короткострокові і довгострокові вигоди. Давайте розглянемо юніт-тести. Якщо запитати адепта TDD про необхідність юніт-тестів, то він скаже: «г @ # но-питання, юніт-тести повинні бути для кожного класу, модуля або функції, і, звичайно ж, вони повинні писатися до написання коду». Однак якщо послухати Кента Бека (* * * *), автора TDD, то його ставлення до юніт-тестів більш прагматичне. Приймаючи рішення про використання TDD або серйозне покриття коду юніт-тестами так само потрібно брати до уваги короткострокові і довгострокові вигоди. Безумовно, юніт-тести дуже корисні, але вони корисні, перш за все, в довгостроковій перспективі, а що якщо ви усвідомлюєте, що існує висока ймовірність того, що цих довгострокових перспектив не буде зовсім? Ви можете розробляти прототип або щось типу proof of concepts, і намагаєтеся з'ясувати, чи буде взагалі це рішення працювати чи ні. З аналогічною ситуацією неекономічності юніт тестів можна зіткнутися в багатьох реальних додатках, коли при додаванні нової можливості вартість написання юніт-тесту може в кілька разів перевищувати вартість самої реалізації.
Однак це швидше виняток з правил, ніж типова ситуація. Зазвичай у процесі розвитку програми накопичується як вантаж великих стратегічних помилок або нерозумінь вимог, так і маса дрібних тактичних помилок, типу довгих функцій з неочевидними обов'язками і складними побічними ефектами; розпливчастих класів, з нечіткими кордонами та обов'язками; відсутність ідіом іменування та документації тощо... Все це призводить до класичного синдрому розбитих вікон, коли ні в кого не виникає ідеї чинити по іншому, а в кого і виникає, то вона швидко пропадає через те, що плисти проти подібної течії дуже і дуже складно.
І якщо команда не віддає свої борги шляхом переосмислення існуючих рішень і їх подальшого рефакторингу, якщо вона не намагається тримати якість коду на високому рівні, то рано чи пізно це підвищить вартість розвитку і супроводу коду настільки, що всі існуючі кошти будуть йти на оплату відсотків по технічному боргу. Що в свою чергу призведе до низької продуктивності розробників (ще б пак, адже вони тільки й роблять, що борються з вітряками), і взаємної незадоволеності команди розробників і замовника.
Висновки
Вимірювання продуктивності програміста або команди програмістів справа складна, і саме через це виникають багато труднощів при виборі того чи іншого технічного рішення: керівники або замовники просто не розуміють, які наслідки чекають їх при виборі одного і відмові від іншого рішення. Використання метафори технічного боргу не є панацеєю і навряд чи забезпечить вирішальну перевагу при виборі того чи іншого підходу, однак, як мінімум, дозволить зрозуміти ключовим зацікавленим людям проблему в термінах, доступних простому смертному, і покаже проблему нехай і в абстрактних, але все ж у фінансових одиницях.
Крім того, навіть якщо вам доводиться йти на компроміс і влазити в боргову яму усвідомлено або не усвідомлено, намагайтеся проектувати ваші системи так, щоб вплив неоднозначних рішень був мінімальним, а якість коду реалізації - на високому рівні.
-------------------------------------------
(*) Вард Каннінгем - це відомий дядько, який зробив неймовірний внесок у розвиток комп'ютерної спільноти; він «Папа» wiki, а також один з авторів «Патьодів» і «екстремального програмування». Інформацію з приводу першого вкладу можна знайти у Вікіпедії, а з приводу другого - у статті "Шаблони проектування. Історія успіху ".
(* *) Звичайно ж, мова йде тільки про ті випадки, коли кожне з наведених рішень здається більш оптимальним, але вартість його застосування саме зараз здається невиправдано високою.
(* * *) Деякі автори, включаючи Боба Мартіна не вважають, брудний код (messy code) технічним боргом, проте подібний код збільшує вартість додавання нової можливості, так що мені здається, що його теж можна розглядати, як один з видів технічного боргу.
(* * * *) Тут мова йде про подкаст Software Engineering Radio Episode 167: The History of JUnit and the Future of Testing with Kent Beck, в якому Кент Бек якраз і висловив цю заповітну думку.