Clientul întreabă: "Dacă adăugarea acestei funcții într-o aplicație nouă ar dura o săptămână, de ce aici sunt necesare trei săptămâni?"
Aceasta este o întrebare foarte bună.
Și, de multe ori, răspunsul nu sună așa: "pentru că programatorii lucrează mai încet".
Problema poate fi mult mai profundă - în arhitectura sistemului, dependențele sale, modul de stocare a datelor, lipsa testelor, deciziile istorice și schimbările adăugate de-a lungul anilor.
De aceea costul dezvoltării software-ului nu este constant.
Aceeași funcție poate costa o sumă complet diferită în două sisteme diferite.
Codul nu este evaluat doar după numărul de funcții
La prima vedere, sarcina poate părea banală.
"Să adăugăm posibilitatea de a exporta datele în Excel."
Sau: "Să adăugăm un nou rol de utilizator."
Sau: "Să conectăm sistemul cu CRM-ul nostru."
Problema este că o funcție nu există niciodată complet separat de restul sistemului.
O funcționalitate nouă poate necesita schimbări în:
- baza de date,
- API,
- backend,
- frontend,
- sistemul de permisiuni,
- autentificare,
- raportare,
- integrări,
- teste,
- mecanisme de cache,
- documentație,
- procesul de implementare.
Cu cât sistemul este mai interconectat, cu atât mai multe elemente trebuie analizate înainte de schimbare.
Cel mai mare cost poate apărea înainte de scrierea primei linii de cod
Într-un sistem matur, programatorul nu ar trebui pur și simplu să înceapă să scrie.
Mai întâi trebuie să răspundă la întrebările:
- Unde ar trebui adăugată această funcție?
- Cu ce module va comunica?
- Ce date folosește?
- Mecanismele existente de permisiuni o acoperă?
- Va afecta schimbarea alte procese?
- Ce teste trebuie actualizate?
- Arhitectura actuală permite, în general, realizarea corectă a acesteia?
Toate acestea fac parte din costul realizării funcției.
De aceea, într-un sistem vechi, o parte semnificativă a muncii poate consta nu în programare în sine, ci în identificarea dependențelor și a limitărilor soluției existente.
Technical debt acționează ca niște dobânzi
Un mod bun de a privi technical debt-ul este tocmai costul schimbărilor ulterioare.
Dacă o anumită soluție a fost cândva făcută rapid, poate fi perfect justificată.
Problema apare atunci când soluția temporară devine o parte permanentă a sistemului.
Apare o nouă funcție.
Apoi încă una.
Apare o excepție.
Apoi încă o excepție.
La asta se adaugă o integrare, un workaround, un proces manual și o regulă suplimentară.
După câțiva ani, nimeni nu-și mai amintește de ce sistemul funcționează exact așa.
Dar fiecare schimbare nouă trebuie să țină cont de toate aceste decizii istorice.
Martin Fowler descrie technical debt-ul ca efortul suplimentar suportat la modificarea sistemului, ca urmare a problemelor legate de calitatea sa internă.
Așadar, se poate spune: technical debt-ul nu trebuie neapărat să oprească imediat dezvoltarea. La început, el face ca fiecare schimbare ulterioară să devină mai scumpă.
Semnalul unu: "în treacăt, mai trebuie reparate încă cinci lucruri"
Acesta este unul dintre cele mai caracteristice simptome.
Clientul comandă o singură funcție.
În timpul analizei se constată că, pentru a o implementa, trebuie:
- corectată structura tabelului,
- modificat modul de autorizare,
- actualizată biblioteca,
- reparat API-ul vechi,
- rescris un fragment de frontend.
Dintr-odată, o funcție mică nu mai este o funcție mică. Nu pentru că cerința este complicată. Ci pentru că sistemul nu mai are granițe arhitecturale adecvate.
Semnalul doi: o schimbare necesită testarea întregului sistem
Dacă o modificare mică necesită un regres complet manual, organizația plătește pentru lipsa automatizării.
Odată cu creșterea sistemului, numărul de combinații posibile crește.
Fără un set adecvat de teste, devine din ce în ce mai greu să ai certitudinea că noua funcție nu a deteriorat-o pe cea veche.
Acest lucru, la rândul lui, provoacă prudență.
Implementările sunt mai rare.
Schimbările sunt mai mari.
Riscul crește.
Iar implementările mai mari sunt mai greu de diagnosticat în caz de probleme.
Se formează un cerc vicios.
Semnalul trei: "modulul acesta mai bine să nu fie atins"
Această frază ar trebui să aprindă un semnal de avertizare.
Dacă un anumit modul a devenit o zonă pe care echipa o evită, deoarece comportamentul său este imprevizibil, sistemul are o problemă importantă de mentenanță.
Și mai rău este dacă doar o singură persoană îi cunoaște funcționarea. Atunci compania nu are doar technical debt. Are și knowledge risk.
Plecarea unui singur angajat poate însemna pierderea cunoștințelor necesare pentru dezvoltarea în siguranță a sistemului.
Semnalul patru: fiecare funcție necesită excepții
Un sistem bine proiectat ar trebui să aibă reguli previzibile.
Dacă fiecare funcție nouă necesită adăugarea unei excepții speciale, a unei condiții suplimentare sau a unui traseu individual, arhitectura probabil începe să limiteze dezvoltarea.
Acest lucru duce adesea la cod care nu mai poate fi prevăzut ușor.
Iar lipsa de previzibilitate înseamnă un cost mai mare de analiză, testare și întreținere.
Trebuie rescris totul?
Nu.
Și aici ajungem la o distincție foarte importantă.
Technical debt-ul nu înseamnă automat necesitatea unui rewrite.
Soluțiile posibile includ:
Refactorizare
Adică îmbunătățirea structurii codului existent fără a schimba comportamentul său de business.
Este o direcție bună atunci când sistemul încă are o arhitectură rezonabilă, dar anumite fragmente sunt dificil de întreținut.
Modernizarea componentelor selectate
Nu este necesară înlocuirea întregii aplicații.
Se poate începe cu modulul, integrarea sau stratul cel mai problematic.
Migrare treptată
Elementele noi pot funcționa alături de sistemul vechi, iar ariile următoare sunt migrate succesiv.
Această abordare permite reducerea riscului unei migrări unice. În literatura privind modernizarea sistemelor legacy se folosește adesea tocmai separarea treptată a funcționalităților și înlocuirea unor părți succesive ale sistemului.
Rewrite
Construirea unui sistem nou are sens atunci când arhitectura actuală este atât de restrictivă încât modernizarea ulterioară nu mai oferă un randament justificat.
Dar un rewrite ar trebui să fie o decizie rezultată din analiză, nu o reacție la frustrarea echipei.
Când nu merită încă să investești în modernizare?
Datoria tehnică în sine nu este un motiv pentru a opri dezvoltarea. Fiecare sistem are un anumit nivel de datorie tehnică. Uneori, achitarea ei nu are sens economic.
Dacă aplicația:
- funcționează stabil,
- este sigură,
- are un număr mic de modificări,
- gestionează un proces care nu se va dezvolta semnificativ,
- nu generează probleme operaționale,
poate fi rațional să fie lăsată în starea actuală.
Nu este vorba ca fiecare sistem să fie tehnologic perfect.
Este vorba ca nivelul datoriei să fie o decizie conștientă.
Când costul datoriei devine o problemă de business?
Atunci când începe să influențeze rezultatele companiei.
De exemplu:
O funcționalitate nouă trebuia lansată pe piață într-o lună, dar are nevoie de trei.
Integrarea cu un nou partener se prelungește, pentru că API-ul sistemului vechi nu permite gestionarea ușoară a datelor noi.
Persoana cheie din echipă trebuie să participe de fiecare dată la lucru, pentru că doar ea cunoaște modulul vechi.
Fiecare implementare mai mare necesită ore întregi de regresie.
Concurentul lansează mai repede funcții noi, pentru că platforma lui permite experimente mai rapide.
În acel moment, technical debt încetează să mai fie o problemă a departamentului IT.
Devine o problemă de business.
Cum măsori dacă situația se înrăutățește?
Nu este nevoie să creezi un sistem KPI complicat.
Merită să urmărești câțiva indicatori simpli:
Lead time - cât timp trece de la începerea lucrului la o modificare până la implementarea ei.
Frecvența implementărilor - cât de des poate echipa livra în siguranță modificări.
Rata eșecurilor la schimbare - cât de des implementările provoacă probleme.
Timpul de restabilire a funcționării - cât de repede se poate reveni la o funcționare stabilă după o avarie.
Timpul de realizare a unei funcționalități - dacă sarcini similare necesită tot mai mult efort.
În plus, merită analizat numărul de operațiuni manuale, acoperirea cu teste, actualitatea dependențelor și timpul necesar pentru a integra un nou programator în proiect.
Astfel de date permit să vezi dacă problema este într-adevăr una tehnică sau dacă provine din proces, cerințe ori din modul de organizare a muncii.
Cea mai proastă soluție este „încă un mic patch rapid”
Dacă echipa știe că arhitectura necesită schimbări, dar de fiecare dată amână subiectul, sistemul poate intra într-o spirală.
„Hai să facem acum un workaround.”
„Refactorizarea o facem mai târziu.”
„Deocamdată este suficient.”
„La următorul release.”
Problema este că următorul release aduce cerințe noi.
Iar fiecare workaround suplimentar crește costul următoarei modificări.
De aceea, decizia de a plăti technical debt ar trebui să facă parte din strategia de dezvoltare a produsului, nu să fie o reacție întâmplătoare la o criză.
O aplicație bună nu este aceea care nu îmbătrânește niciodată
Fiecare sistem se va schimba.
Tehnologiile se vor schimba.
Clienții vor avea nevoi noi.
Vor apărea integrări noi.
Se va schimba modul de lucru al companiei.
De aceea, scopul nu ar trebui să fie crearea unei aplicații care să nu trebuiască niciodată modernizată. Scopul ar trebui să fie crearea unei astfel de arhitecturi, în care modernizarea este posibilă fără a opri businessul. Aceasta este o diferență enormă.
Pentru că cel mai bun sistem nu este cel care arată cel mai modern în ziua lansării. Este acel sistem care și după câțiva ani permite companiei să reacționeze rapid la schimbări.
Iar dacă fiecare funcție nouă costă tot mai mult, nu înseamnă întotdeauna că funcția este dificilă.
Poate că sistemul în sine a devenit deja dificil.
