Ludzie dzielą się na na takich, którzy zabezpieczają swoje samochody na wszelkie możliwe sposoby, i na takich, którzy po prostu kupują autocasco. Okazuje się, że ten podział odnosi się także do informatyki.

Na chwilę oderwijmy się od ziemi i przenieśmy w przestrzeń kosmiczną. Na znajdujące się tam satelity działa bardzo wiele sił i zjawisk, a jednym z nich jest promieniowanie. Rozpędzone do prędkości kilkunastu tysięcy kilometrów na godzinę cząstki sieją spustoszenie w układach elektronicznych wszystkiego, co wysyłamy na orbitę. Jak wiemy, komputery nie używają pisma (nawet obrazkowego), a do przeprowadzania wszystkich operacji i przechowywania danych wykorzystują jedynie zera i jedynki. Dowolna informacja, jak na przykład liczba 23 (w systemie dziesiętnym), może być przedstawiona za pomocą odpowiedniej kombinacji zer i jedynek (w systemie dwójkowym) – w tym przypadku 10111. Jak widzimy, do zapisania ten informacji potrzebujemy pięciu komórek pamięci. Komórka pamięci, mimo że nie widać jej gołym okiem, jest obiektem fizycznym i ma swój rozmiar. Na tyle duży, że może w nią trafić zabłąkana cząsteczka alfa. Jeżeli tak się stanie, następuje tzw. bit-flip, czyli zamiana 0 na 1 bądź odwrotnie. Czyli 10111, po trafieniu cząsteczka zamienia się nagle na 10011 – czyli 19 w systemie dwójkowym. Jeżeli liczba ta mówi np. o wymaganym czasie działania silników, mamy poważny problem. Problem, który musimy rozwiązać. A możemy to zrobić na dwa sposoby. Sposób pierwszy to za wszelką cenę nie dopuścić do wystąpienia błędu, na przykład stosując zaawansowane (a przy okazji oczywiście drogie i ciężkie) osłony otaczające wrażliwe układy. To tzw. podejście fail-safe. Druga droga (safe-to-fail) polega z kolei na zaprojektowaniu takiego rozwiązania, w którym konsekwencje spowodowane błędem będą nieistotne. Skoro wiemy, że podczas wykonywania obliczeń nawigacyjnych może wkraść się błąd, najprostszym sposobem otrzymania prawidłowego wyniku jest… statystyka. Jeżeli dane działanie wykonamy kilka razy, możemy liczyć, że najczęściej otrzymywany wynik jest wartością prawidłową. Takie właśnie podejście z powodzeniem wykorzystuje firma SpaceX. Zamiast w jeden procesor ich systemy wyposażone są w trzy jednostki, z których każda posiada dwa rdzenie. W związku z tym każde działanie wykonywane jest sześciokrotnie, co pozwala skutecznie rozwiązać problem zabłąkanych cząstek. Czy da się jednoznacznie stwierdzić, która droga jest lepsza? Oczywiście nie, gdyż za każdym razem pod uwagę musimy wziąć kontekst zastosowania. Przeanalizujmy jednak mocne i słabe strony obu rozwiązań. Pierwsze pozornie wydaje się genialne w swojej prostocie – dodajemy osłonę i problem rozwiązany. Co jednak stanie się w momencie, kiedy osłona okaże się mniej skuteczna niż nam się wydawało, bądź jakiś kosmiczny śmieć po prostu ją uszkodzi? Szach-mat i zostajemy z niczym. Zrobiliśmy wszystko, aby nie dopuścić do wystąpienia błędu, jednak jeżeli już do niego dojdzie, konsekwencje są ogromne. W drugim przypadku dopuszczamy natomiast możliwość wystąpienia błędu, ale jednocześnie ograniczamy do minimum jego konsekwencje. Co za tym idzie, nawet jeżeli zawiedzie jeden procesor, w dalszym ciągu zostaje nam pięć, które dalej skutecznie będzie prowadziło naszego satelitę.

Teraz wróćmy więc do ziemskiego biznesu i poszukajmy podobnych sytuacji. Jedną z nich będzie choćby zapewnienie prawidłowego funkcjonowania naszego oprogramowania. Stosując podejście fail-safe, zbudujemy naszą procedurę wdrożeniową tak, aby wersja wchodząca na środowisko produkcyjne, była wolna od jakichkolwiek błędów. Z reguły przekłada się to na dość rozbudowaną fazę testów, które niezależnie od tego, czy są manualne, czy automatyczne, kosztują sporo i trwają dłużej niż byśmy sobie tego życzyli. Ponadto, często już podczas testów wdrażane są kolejne wersje, poprawiające wykryte błędy, jednak – czego nie można wykluczyć – wprowadzające także kolejne, np. w funkcjach, których scenariusze już zostały „odhaczone na zielono”. Ponadto, przyjęte podejście powoduje, że bardzo niechętnie podejmujemy poważne decyzje projektowe, takie jak odmergowanie problematycznego projektu czy wprowadzenie innej większej zamiany, gdyż powodują one potrzebę powtórzenia wszystkich zakończonych już procedur. Taki właśnie problem doprowadził do katastrofy promu Challenger. Kierownictwo projektu nie uwierzyło inżynierowi, który opisał problem mogący doprowadzić do katastrofy, gdyż byli przekonani, że tak poważna wada zostałaby wykryta na wcześniejszych etapach, a sama konieczność jej sprawdzenia spowodowałaby przesunięcie już i tak opóźnionego startu.

Jak wygląda zatem implementacja podejścia safe to fail w rozpatrywanym przypadku? Przede wszystkim musimy wyróżnić dwie grupy funkcji naszego systemu: krytyczne i pozostałe. Dla tych krytycznych tworzymy standardową ścieżkę testową i weryfikujemy je end-to-end przy każdym wdrożeniu. Praktyka pokazuje jednak, że stanowią one z reguły nie więcej niż 20% całości. Jest to już dużo bardziej realny i efektywny cel automatyzacji testów. Nie zmienia to faktu, że pozostało nam jeszcze niezaadresowane 80% systemu. Czy aby na pewno? Już w 1999 r. Kent Beck zaproponował koncepcję „test-first” stanowiącą jeden z filarów programowania ekstremalnego. Kilka lat później wyodrębniono to jako niezależną technikę zwaną „test-driven development”, której umiejętne stosowanie zapewnia od samego początku odpowiednie pokrycie testami jednostkowymi tworzonego oprogramowania, co z kolei zapewnia, że system działa zgodnie z intencją programisty. Nie oznacza to niestety jednak poprawnego funkcjonowania w rozumieniu odbiorcy końcowego. Jak już jednak wiemy, dążymy przede wszystkim do ograniczenia negatywnych konsekwencji błędów. Pierwszy krok polegający na zabezpieczeniu krytycznych funkcji już wykonaliśmy. Kolejnym będzie zatem ograniczenie „czasu życia” pozostałych błędów w środowisku produkcyjnym. Nawet drobny błąd, jeżeli występuje przez wiele dni czy tygodni, może doprowadzać użytkowników do pasji i skutkować negatywną opinią o naszym systemie. Jednak ten sam błąd usunięty w ciągu przykładowo 30 minut zostanie przez odbiorcę bardzo szybko zapomniany. Co jednak możemy zrobić, aby być w stanie przygotować i wdrożyć poprawkę w takim czasie? Przede wszystkim nie zawsze aż tak wyśrubowana reakcja będzie konieczna. Jeżeli jednak tak się stanie, należy rozbić ten czas na kilka składowych i w zależności od możliwości ukierunkować odpowiednio wysiłki:

  • czas wykrycia błędu – tym krótszy, im bardziej ułatwimy klientowi wysłanie zgłoszenia lub im szybciej zareaguje nasz monitoring,
  • czas diagnozy – tym krótszy, im mniejsza jest paczka zmian od ostatniego wdrożenia – czyli im częściej wykonujemy wdrożenia, tym mniejsze paczki zmian one zawierają i tym łatwiej znaleźć zmianę, która wprowadziła błąd,
  • czas poprawy – im lepsza jakość kodu i testów jednostkowych, tym szybciej możemy błąd odtworzyć i poprawić,
  • czas wdrożenia – im wyższy stopień automatyzacji, tym krócej trwa pipeline (procedura) wdrożenia naszej aplikacji.

Podsumowując: zrozumienie przedstawionych powyżej podejść wraz z ich konsekwencjami i odpowiednim zarządzaniem ryzykiem, pozwala nam w skuteczny i efektywny w kontekście czasu i zasobów sposób dostarczać odpowiednio wysokiej jakości oprogramowanie. O ile jednak stosowanie safe to fail wydaje się lepszym pomysłem, należy szczerze odpowiedzieć sobie, czy obecna jakość wdrażanego systemu pozwala je prosto wdrożyć. Co ciekawe, nawet negatywna odpowiedź na postawione powyżej pytanie nie wyklucza możliwości jego stosowania. Za pomocą odpowiednich technik, jak choćby wdrożeń kanarkowych czy też procedur rollback, możemy zawsze „upiększyć” rzeczywistość. Jednak jest to już temat na inny artykuł.

6
Dodaj komentarz

avatar
3 Comment threads
3 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
5 Comment authors
ZdzisławMaciejJakub KubryńskiRafMarek Recent comment authors
  Subscribe  
najnowszy najstarszy oceniany
Powiadom o
Marek
Gość
Marek

„Funkcja”, nie „funkcjonalność”!

Raf
Gość
Raf

Zawsze można mieć i alarm i polisę ubezpieczeniową 😉 Chyba najbardziej popularne podejście to coś pomiędzy: dużo testów systemowych z zapewnieniem szybkich poprawek.

Maciej
Gość
Maciej

O ile życie człowieka jest bezcenne, to np. ‚życie’ robotów już można oszacować. W przypadku Challangera ktoś podjął nieracjonalną decyzję, albo bazował na nieciekawej funkcji straty. Mając roboty jako pasażerów oraz znając koszta wdrożenia poprawek i koszta następnej misji przy pewnym prawdopodobieństwie wystąpienia błędu można już szacować koszta przedwsięzięcia.
Może istnieją systemy w których szacowanie kosztów wystąpienia błędów są łatwe. Wtedy już z pomocą przychodzą nam proste kalkulacje.

Zdzisław
Gość
Zdzisław

A kto Ci naopowiadał takich głupot, że „życie ludzkie jest bezcenne” ?
Albo inaczej – może jest dla Ciebie.
Ludzie oddawali życie za honor, wolność itp.