Przejdź do treści

Pieśni o Bezpieczeństwie Sieci
blog Michała "ryśka" Woźniaka

Czemu podoba mi się pomysł Zarządzania Zależnościami w Oparciu o Kontrakt (CBDM)

Jakiś tydzień temu @tomasino opublikował opis swojego pomysłu dotyczącego zarządzania zależnościami w oparciu o kontrakt (w oryginale: Contract-Based Dependency Management, CBDM), i skłamałbym, gdybym powiedział, że mi się on nie podoba.

Jest to po prostu lepszy model zarządzania zależnościami niż SemVer czy jakikolwiek inny system wersjonowania oprogramowania. Ale nie tylko! CBDM:

  • dostarcza twórcom oprogramowania dodatkowej dobrej motywacji do utrzymywania rozbudowanych zestawów testów;
  • daje twórcom oprogramowania dobry powód, by pomagać w utrzymaniu rozbudowanych zestawów testów projektom, na których się ich własny projekt opiera;
  • pozwala uzyskać jasną i jednoznaczną odpowiedź na pytanie, czy dana funkcjonalność albo dany aspekt zachowania danej zależności jest oficjalnie wpierany przez twórców tej zależności;
  • dostarcza jasną i jednoznaczną informację, jeśli jakaś funkcjonalność czy aspekt zachowania danej zależności uległ zmianie;
  • jeśli aktualizacja zależności w jakiś sposób negatywnie wpłynie na projekt od niej zależny, CBDM pozwala jednoznacznie ustalić, kto nawalił.

Czym jest Zarządzania Zależnościami w Oparciu o Kontrakt (CBDM)?

Zasadniczo pomysł sprowadza się do polegania na wynikach testów jednostkowych, zamiast na numerach wersji, podczas decydowania czy nowa wersja zależności jest kompatybilna z zależnym od niej oprogramowaniem. Testy te powinny oczywiście testować te funkcjonalności i aspekty zachowania danej zależności, na których polega zależny od niej projekt.

Innymi słowy, zamiast patrzeć na numery wersji, patrzmy na testy jednostkowe dla danej zależności (oraz wyniki ich uruchomienia).

Tomasino nurkuje w ten temat głębiej w swoim wpisie, naprawdę warto go przeczytać.

Co jest nie tak z numerami wersji?

Numery wersji bardzo często nie są godną zaufania metodą ustalania, czy coś się aby nie zepsuje po aktualizacji. Sam SemVer to przecież nic innego niż próba spowodowania, by były bardziej godne zaufania w tym kontekście.

Problem jednak polega na tym, że niemożliwe jest wyrażenie wszystkich wymiarów możliwych zmian w danym projekcie oprogramowania za pomocą zestawu zaledwie kliku liczb. Mało tego, pewnie zmiany mogą być uznane za mało istotne lub w ogóle nieistotne, kosmetyczne, przez twórców danego pakietu oprogramowania, a jednocześnie mogą być wystarczająco duże, by spowodować poważne komplikacje dla zależnych od niego projektów, wykorzystujących jakąś specyficzną jego cechę.

Stąd się biorą specyfikacje, a wraz z nimi niekończące się debaty o tym, czy konkretna zmiana łamie specyfikację, czy też nie.

CBDM w praktyce

Załóżmy, że rozwijam jakiś projekt, nazwijmy go AProject. Używa on (i zależy od) biblioteki, powiedzmy: LibBee. Twórcy LibBee są bohaterami, na których nie zasługujemy, i utrzymują dla LibBee bardzo rozbudowany system testów.

Jako twórca AProjekt definiuję zależność od LibBee nie jako:

LibBee ver x.y.z

…ale jako:

LibBee, (lista konkretnych testów z całego zestawu testów LibBee, które muszą być niezmienione, i mieć wynik pozytywny)

(Zostawmy na razie temat tego, jak dokładnie taka lista testów zależności miałaby wyglądać.)

Ta lista testów nie zawiera wszystkich testów LibBee – w istocie, nie powinna zawiera ich wszystkich, jako że to by oznaczało, że w zasadzie przypinamy na sztywno becną wersję LibBee (zakładając pełne pokrycie kodu tej biblioteki testami; wrócimy do tego). Co jednak ważne, powinna ona zawierać testy sprawdzające wszystkie funkcjonalności i aspekty zachowania LibBee, na których mój AProject polega.

Ta lista testów tworzy kontrakt. Jeżeli kontrakt ten jest spełniony przez dowolną nowszą (czy starszą) wersję LibBee, to oznacza, że mogę bezproblemowo zaktualizować moją zależność w AProject. Po aktualizacji wszystko powinno nadal działać, jak należy.

A jeśli aktualizacja LibBee mimo wszystko spowoduje problemy w AProject?

Napisałem “powinno”, bo oczywiście ludzie popełniają blędy. Jeśli aktualizacja LibBee spowoduje problemy w AProject mimo tego, że kontrakt jest spełniony (to znaczny, mimo tego, że testy wymienione w kontrakcie nie uległy zmianie, oraz że dają wynik pozytywny), to istnieje tylko jedna opcja: AProject polegał na jakiejś funkcjonalności lub jakims aspekcie zachowania LibBee, który nie był ujęty w kontrakcie.

To oznacza, że jasnym jest, kto odpowiada za ten nieprzewidzialny problem: ja. Ja, jako twórca AProject, nie upewniłem się, że kontrakt faktycznie testuje wszystko, na czym polega mój projekt. W ten sposób unikamy długiej, męczącej dyskusji pomiędzy mną a twórcami LibBee, dotyczącej tego, kto nawalił. Uzupełniam więc kontrakt o dane brakująch testów i skupiam się na naprawieniu problemu.

Tym samym AProject uzyskał lepszy, pełniejszy kontrakt zależności, a mój czas (i cenny czas developerów LibBee) nie został zmarnowany na wzajemnych oskarżeniach.

Tyle wygrać!

A jeśli dany test nie istnieje w zależności?

Jeśli korzystam w AProject z jakiejś funkcjonalności LibBee, na którą po prostu nie ma napisanego testu, mam doskonały powód, by go dopisać, i wysłać twórcom LibBee jako mój wkład w rozwój biblioteki, z której korzystam.

Jeśli mój dopisany test zostanie zaakceptowany przez twórców LibBee i włączony do zestawu testów tej biblioteki, daje mi to jasny sygnał, że funkcjonalność, z której korzystam, jest oficjalnie wspierana. Mogę zatem dodać informację o tym teście do mojego kontraktu zależności w AProject. LibBee uzyskała nowy test i ma lepsze pokrycie kodu testami, kompletnie za darmo. Mój projekt ma pełniejszy kontrakt zależności, co oznacza, że mogę się nie martwić o aktualizacje LibBee.

Tyle wygrać!

A jeśli mój test zostanie odrzucony?

Jeśli twórcy LibBee odrzucą mój test, jest to bardzo jasny sygnał, że AProject polega na jakiejś funkcjonalności albo jakimś aspekcie zachowania, które nie są oficjalnie wspierane.

Z jednej strony, mogę wtedy zdecydować, że trudno, ryzykuję korzystanie z nieoficjalnie wspieranej funkcjonalności. Dodam wtedy mój test do kontraktu, ale sam test umieszczę bezpośrednio w AProject. Pozwoli mi to weryfikować nowe wersje LibBee zanim zaktualizuję tę zależność. Z drugiej strony, mogę zdecydować, że to zbyt ryzykowne, i przepisać AProject tak, by nie polegał na niewspieranej funkcjonalności.

W każdym razie wiem, w co się pakuję, a twórcy LibBee wiedzą, że nie będę ich winił jeśli zmienią ten konkretny aspekt działania ich biblioteki – zostałem przecież ostrzeżony, i mam swój test jako dowód.

Więc znów: tyle wygrać!

Czas obalić numery wersji?

Nie, w żadnym razie. Numery wersji są nadal przydatne, choćby po to, by wiedzieć, że dana zależność została zaktualizowana. W zasadzie warto nadal z nich korzystać również w celu zarządania zależnościami, podając wspierane numery wersji zależności nawet jeśli mamy już kontrakt, w celu płynnego przejścia z zarządzania zależnościami w oparciu o numery wersji do CBDM.

Numery wersji sprawdzają się nieźle na poziomie ludzkim, te zgodne z SemVer niosą ze sobą dość dobrze zdefiniowany zestaw informacji. Po prostu nie są w stanie wyrazić wszystkiego, na czym by nam zależało w dobrym systemie zarządzania zależnościami. Zgodzi się z tym każdy, kto zarządzał większym projektem z dużą liczbą zależności.

Gdzie jest haczyk?

Zawsze jest jakiś haczyk!

Trzy kluczowe rzeczy są dość trudne do dobrego zdefiniowania:

  1. W jaki sposób “identyfikuje się test”?
  2. Jak sprawdzić, że “test nie uległ zmianie”?
  3. Jak “dodaje się test do kontraktu”?

Odpowiedzi na pytania 1. i 2. prawie na pewno zależą od jeżyka programowania, a pewnie i od systemu, w którym piszemy testy. I prawie na pewno odpowiedzi na te pytania zdefiniują w dużej mierze odpowiedź na pytanie ostatnie.

Z gruba ciosany pomysł:

  1. Test identyfikowany jest jego nazwą (praktycznie każdy system testów jednostkowych pozwala “nazywać” testy, często nawet tego wymagając).
  2. Jeśli kod źródłowy testu zmienił się w jakikolwiek sposób, test uznaje się za zmieniony. Prawdopodobnie miałoby sens odpalanie jakiegoś lintera, tak, by zmiany w znakach niedrukowalnych nie powodowały uznania testów za zmienione.
  3. Jeśli identyfikujemy testy na podstawie ich nazwy, używanie nazwy testu w kontrakcie wydaje się najsensowniejsze.

Pomysł CBDM, moim zdaniem, ma mnóstwo sensu. Rozwój oprogramowania staje się coraz bardziej oparty na testach (i bardzo dobrze!), czemu przy okazji nie użyć ich do rozwiązania problemu piekła zależności?