ABONAMENTE VIDEO REDACȚIA
RO
EN
×
▼ LISTĂ EDIȚII ▼
Numărul 30
Abonament PDF

Riscurile operării cu branch-uri

Anghel Conțiu
Design Lead
@Endava
PROGRAMARE


Folosirea instrumentelor de control al versiunii a devenit un obicei în modul nostru de lucru, iar una dintre proprietățile lor, aceea de a folosi branch-uri, poate fi fructificată după cum consideră fiecare echipa că aduce valoare. S-au scris multe articole despre strategiile de branching și în timp ce unele dintre ele au devenit foarte populare, aproape toate au o problemă comună care trebuie abordată. Aceasta este problema procesului merging între branch-uri. Există un motiv pentru care se discută nu doar în termeni de strategie de branching, ci in termeni de strategie de branching și merging.

Fuziunea se poate transforma ușor în coșmar atunci când strategia abordată este greșită. Putem să luăm ca exemplu una dintre cele mai populare strategii de fuziune, strategia Vincent Driessen prezentată în Figura 1. Această strategie poate să nu fie cea mai potrivită în anumite condiții întâlnite destul de des.

Figura 1: modelul Vincent Driessen

Ne concentrăm pe cele două branch-uri care sunt intens folosite: “feature” și “develop”.

Branch-ul “feature” este creat din branch-ul “develop” și este folosit de programator pentru a implementa o nouă funcționalitate.

Când termină implementarea, va aduce modificările de pe branch-ul “feature” în branch-ul “develop” astfel încât acestea vor deveni disponibile pentru restul echipei.

De urmărit următoarea situație:

Acesta pare a fi un workflow sănătos, însă ascunde câteva riscuri care în final vor duce la un efort adițional atât pe partea de dezvoltare, cât și pe cea de testare. Iată câteva dintre aceste riscuri:

Riscul retestării funcționalității

Dan își petrece o zi testând toate particularitățile implementării Mariei și în final își dă acordul. Totul funcționează bine. Ceea ce el încă nu știe este faptul că Andrei lucrează cu două dintre fișierele modificate de Maria. Andrei își efectuează un merge al branch-ului său trei zile mai târziu în branch-ul develop și e bucuros că nu întâmpină conflicte. Dan începe testarea dar observă că funcționalitatea Mariei nu se mai comportă bine. Dan creează un tichet pentru Maria, Maria rezolvă problema folosind de această dată și modificările aduse de Andrei, iar Dan urmează să testeze din nou atât funcționalitatea Mariei, cât și pe cea a lui Dan.

Așadar Maria a pierdut timp pentru reparația respectivă, iar Dan a pierdut timp deoarece a trebuit să testeze cel puțin de două ori ambele funcționalități.

Riscul fuzionării

Se întâmplă des ca dezvoltarea funcționalităților și scrierea de teste unitare pentru ele să dureze câteva zile. Pentru a micșora riscul apariției de conflicte la fuziune programatorii încearcă să își aducă branch-ul “develop” cât mai des (probabil în fiecare zi) în branch-ul pe care ei dezvoltă separat funcționalitatea. Faptul că se implementează simultan mai multe funcționalități diferite pe branch-uri diferite este cert, iar riscul de conflicte la fuziune este oricum ridicat, iar cu cât programatorii petrec mai mult timp pe branch-ul lor specific, cu atât riscul crește.

Riscul pentru integrarea continuă

Programatorii ar trebui să scrie teste unitare, teste între componente și teste de integrare. Atunci când aceste teste sunt dezvoltate împreună cu funcționalitatea, în izolare, pe branch-ul de implementare a funcționalității, și în același timp o altă funcționalitate este dezvoltată împreună cu testele corespunzătoare pe un alt branch, la momentul efectuării operației de merge șansele ca testele scrise să pice sunt ridicate. Testele nu au fost scrise luând în considerare comportamentul dezvoltat în paralel pe celelalte branch-uri. Ele vor trebui revizuite și reparate, ceea ce aduce un efort în plus.

Riscul pentru rularea script-urilor pe baza de date

Multe echipe încearcă să își automatizeze procesul de instalare al produsului. La nivel de baze de date rulează în mod automat script-uri care aduc starea bazei de date la versiunea corectă. Din cauza faptului că programatorii vor crea aceste script-uri iîn izolare, pe branch-uri diferite, există riscul de a strica ordinea în care ele trebuie rulate.

Dacă privim cu atenție rădăcina acestor probleme, observăm izolarea în care lucrează programatorii pe branch-uri diferite. O alternativă pentru acest mod de lucru este folosirea tehnicii de branching prin abstractizare.

Branching prin abstractizare

Este definită de Martin Fowler ca fiind “o tehnică de a face o schimbare pe scară largă unui sistem software într-un mod gradual care permite lansarea sistemului în mod regulat în timp ce schimbarea e încă în desfășurare”.

Tehnica implică adăugarea unui nivel de abstractizare deasupra funcționalității la care se lucrează astfel încât codul client va comunica doar cu acest nivel de abstractizare, nefiind conștient dacă folosește versiunea originală sau nouă a implementării.

Oricare ar fi modul în care este folosită tehnica, există câteva puncte comune:

În timp ce programatorul scrie teste pentru funcționalitatea lui, el este sigur ca testele sale folosesc ultima versiune a codului deoarece colegii lui își adaugă modificările pe același branch. În acest fel nu vor exista operații de merge ulterioare care să strice testele, deoarece alte branch-uri nu există.

Este importantă plasarea corectă a nivelului de abstractizare și modul în care obiectele dintr-o versiune sau alta ale aceleiași funcționalități sunt instanțiate. Din acest punct de vedere există mult loc pentru creativitate deoarece această decizie depinde și de contextul problemei. Pot exista mai multe variante, dintre care vor fi prezentate două.

Branching prin abstractizarea componentei

Această tehnică se aplică acolo unde este necesară modificarea sau rescrierea unei componente mari.

Un nivel de abstractizare trebuie adăugat astfel încât codul client să nu mai depindă de componentă ci de abstractizare. Această acțiune ar putea implica unele modificări (refactoring) la nivel de componentă și teste adiționale ar putea fi scrise. Modificările necesare în vederea extragerii nivelului de abstractizare ar putea fi costisitor, de aceea acesta este un punct important pentru a lua o decizie asupra strategiei de branching.

Noua implementare a componentei va fi făcută pas cu pas, astfel funcționalitățile vor fi adăugate în funcție de priorități. Când un set de funcționalități e finalizat, clientul codului poate schimba legăturile cu instanțele folosite și poate migra la noua componentă. Este important de reținut că aplicația trebuie să poată fi construită și rulată în oricare moment. În final nu vor mai exista dependențe către vechea implementare.

Figura 2: Branching prin abstractizarea componentei. ClientCode este într-un process de migrare pentru a folosi NewComponent.

Branching prin abstractizare la nivel de punct de folosire

Să luăm în considerare cazul în care abstractizarea la nivel de componentă nu este cea mai bună soluție. Motivele ar putea consta în efort prea mare de modificări pentru a extrage nivelul de abstractizare la nivel de componenta. Pentru astfel de situații putem lăsa codul așa cum este și să alegem o altă tehnică.

Figura 3 face referință la codul care folosește clasa (grupul de clase) ce trebuie actualizată. Descrierea claselor din figura:

  1. Versiunea veche și versiunea nouă a implementării clasei care folosește codul ce trebuie actualizat:

    • Punctul de abstractizare este la nivel de clasă care folosește codul ce trebuie actualizat.
    • Inițial vom avea versiunea originală a clasei client și o copie a ei care va deveni noua versiune (da, nu trebuie să copiem o clasă, aceasta va fi o situație temporară până când noua versiune devine stabilă; vom avea apoi posibilitatea de a folosi versiunea originală dacă vor fi probleme în timpul folosirii noii versiuni; versiunea originală în final va fi ștearsă).
    • Vom avea o interfață nouă pe care atât clasa client originală, cât și noua versiune a acestei clase client o vor implementa („Interface” în Figura 3). Interfața va conține probabil metodele publice din implementarea originală a clasei client.
    • Noua versiune a clasei client va fi modificată treptat pentru a aduce noile funcționalități.
    • Versiunea originală va fi ștearsă la final.
  2. „The Factory”

    • Va avea responsabilitatea de a instanția versiunea originală sau versiunea nouă a clasei client; Responsabilitatea verificării dacă una sau cealaltă dintre versiuni trebuie instanțiată se face în interiorul componentei „factory”.
    • Poate deveni mai inteligentă în măsura în care este nevoie; spre exemplu, poate alege între cele două versiuni la pornirea aplicației sau în timpul rulării acesteia, poate să fie conștientă de mai multe informații din mediul în care există și să ia măsuri în funcție de acestea, etc. .
    • Se va folosi interfața clasei client astfel încât implementarea originală sau cea nouă va fi instanțiată și injectată acolo unde este nevoie de ea;
    • Va constitui garanția faptului că acolo unde e nevoie de o implementare sau de alta, modul de alegere între ele va fi unul consistent, și nu vor fi condiții diferite în locuri diferite.
  3. Spațiul de activitate al clasei client („the scope”)
    • Având componenta „factory” ca responsabilă pentru instanțierea clasei client, avem oportunitatea să stabilim spațiul de activitate al instanței;
    • În general noua versiune ar trebui să aibă același spațiu de activitate ca și cea originală, iar aceasta decizie poate fi specificată la nivelul „factory”.
  4. Rularea aplicației în medii diferite
    • Componenta „factory” poate deveni conștientă de mediul în care activează și va instanția versiunea clasei client adecvată mediului respectiv;
    • Posibilitatea de a putea instanția o implementare sau alta constituie și un mecanism de rezervă în cazul în care noua versiune nu funcționează la nivelul așteptărilor. Dezactivarea ei și activarea instanței originale se efectuează ușor si rapid.

Figura 3: Ramificarea prin absractizare la nivel de punct de folosire. Versiunea originală și cea nouă a clasei care folosește codul ce trebuie actualizat.

Sintetizăm afirmațiile de mai sus privitoare la aspectele asupra cărora se vor concentra membrii echipei:

Programatorii:

Persoanele care testează:

De îndată ce nivelul de încredere în noua funcționalitate devine suficient mecanismul de schimbare între instanțe și instanța originală pot fi șterse. Important este de luat în considerare faptul că sistemul trebuie să poată fi construit și rulat cu succes în oricare moment.

LANSAREA NUMĂRULUI 87

Prezentări articole și
Panel: Project management

Marți, 24 Septembrie, ora 18:00
Impact Hub, București

Înregistrează-te

Facebook Meetup

Conferință

Sponsori

  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • UIPatj
  • MetroSystems
  • Globant
  • Colors in projects