--- title: >- Czemu podoba mi się pomysł Zarządzania Zależnościami w Oparciu o Kontrakt (CBDM) date: 2021-10-13 02:05 modified: 2021-10-13 02:05 lang: en authors: rysiek tags: - programming - tests status: published pinned: false --- Jakiś tydzień temu [`@tomasino`](https://tilde.zone/@tomasino) opublikował [opis swojego pomysłu dotyczącego *zarządzania zależnościami w oparciu o kontrakt*](https://labs.tomasino.org/contract-based-dependency-management/) (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](https://semver.org/) 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?