Rich Hickey o prostocie i łatwości

Grafika

W projektowaniu systemów składających się z wielu zależnych komponentów prostota pozwala dbać o porządek i sprawia, że możliwe jest elastyczne zarządzanie procesem zmian. Dzięki prostocie możemy budować systemy złożone. Nie należy mylić jej z łatwością, która choć istotna, jest jednak wartością względną.

W załączonym na końcu tego wpisu klipie wideo twórca języka Clojure opowiada o tym, jak programiści i architekci systemów sami utrudniają sobie życie przez wprowadzanie skomplikowanych mechanizmów, które trudno potem integrować z innymi czy zmieniać.

Prostota i łatwość

Prostota (ang. simplicity) jest cechą rzeczy lub procesów, która oznacza, że w zależności od przyjętego wymiaru są one spójne pod względem budowy, odgrywanej roli, spełnianego zadania bądź realizowanej koncepcji. Warto zaznaczyć, że nie jest to równoznaczne z istnieniem wyłącznie jednego egzemplarza czy wykonywaniem tylko jednej operacji – chodzi tu bardziej o jednolitość niż o pojedynczość. Rzeczy proste cechuje brak zagmatwania między składnikami, a nie niska ich liczba bądź mała różnorodność.

Prostota jest obiektywna i można zbadać jej poziom, porównując właściwości, natomiast łatwość (ang. easiness) jest cechą względną, zależną na przykład od aktualnych warunków środowiskowych, umiejętności czy wiedzy.

Dobrze zrealizowana prostota przekłada się na łatwość, ale zbytnie dążenie do ułatwień i komfortu niekoniecznie efektuje prostotą i może mieć zgubne skutki. Szczególnie wśród programistów technologii webowych, którzy korzystają z nowoczesnych środowisk bazujących na szybko generowanych szkieletach aplikacji, możemy zaobserwować tendencję do użytkowania gotowych, zewnętrznych komponentów, które są łatwe w obsłudze i szybko pozwalają osiągnąć zadowalający efekt. Jest to zaletą dopóki aplikacja nie wymaga zbyt głębokich zmian bądź integracji z innymi komponentami. Gdy pojawia się konieczność wyodrębnienia części takiego gotowca, trzeba zainwestować dużo czasu – najpierw w poznanie jak dokładnie działa, a następnie w dostosowanie go do wymogów.

Złożoność

Złożoność (ang. complexity) jest przeciwieństwem prostoty. Już sama nazwa wskazuje, że mamy do czynienia z właściwością polegającą na połączeniu wielu składników w jeden zwarty i złożony kompleks.

Ewidentnym przykładem problemu złożoności jest komputerowy system przetwarzania, którego poszczególne części są tak powiązane i wzajemnie od siebie zależne, że trudno o dokonywanie zmian w każdnym komponencie z osobna. Aby ulepszyć fragment takiego systemu lub poprawić jego część, należy mieć wiedzę dotyczącą całości i współzależności wszystkich elementów. Ten wymóg sprawia, że zadanie takie jest relatywnie trudne, a tym samym wpływa negatywnie na skuteczność zarządzania zmianami.

Co ciekawe, zmiany zawsze są nieuniknione i sumarycznie poświęca się na nie więcej czasu, niż na tworzenie pierwszej działającej wersji oprogramowania. Tak naprawdę budowanie aplikacji czy systemu to przede wszystkim praca ze zmianą. Dlatego właśnie prostota jest tak istotna, a projektowanie oprogramowania z myślą o niej jest fundamentem tzw. zwinnego (ang. agile) podejścia. Jeżeli coś tworzone jest w sposób, który nie unika złożoności już na etapie projektowania, to metodyki typu agile będą tylko sposobem komunikacji i organizacji czasu pracy, a nie kompletnymi recepturami, które naprawdę pomogą szybciej i taniej osiągać cele.

Ogólną przyczyną tego, że mamy do czynienia z systemami, które trudno potem rozwijać, jest więc projektowanie ich w sposób, który dopuszcza, aby komponenty realizujące różne zadania były ze sobą trwale połączone. Mowa tu o tzw. zależności oprogramowania (ang. coupling), oznaczającej stopień powiązania jednego modułu z innymi, np. przez odwoływanie się do wewnętrznych struktur, współdzielenie pamięciowych obiektów, poleganie na nieznormalizowanym protokole komunikacyjnym czy korzystanie z różnych części tych samych struktur danych. Zależność pojawia się również wtedy, gdy mamy jeden moduł, który realizuje zadania dwóch lub więcej modułów tylko dlatego, że następują one po sobie w czasie bądź zależą pod względem wysyłanych i przyjmowanych danych.

Architektura i planowanie

Radzenie sobie ze złożonością zaczyna się już na etapie planowania. Dla niektórych programistów szczegółowe projektowanie jest stratą czasu. Mogą wtedy powiedzieć, że to tzw. przedwczesna optymalizacja (ang. premature optimization) – zajmowanie się detalami, nad którymi warto będzie się pochylić dopiero później, gdy zajdzie taka potrzeba. Jednak z przedwczesną optymalizacją mamy tak naprawdę do czynienia, gdy z myślą o potencjalnym, przyszłym wykorzystaniu wyposażamy oprogramowanie w nowe funkcje, a nie wtedy, gdy staramy się zadbać o szczegóły.

Rich jest zdania, że dobry architekt systemu czy aplikacji już podczas planowania będzie dzielił skomplikowane komponenty na coraz mniejsze, aż do momentu, gdy każdy z nich stanie się wystarczająco prosty i w miarę niezależny od pozostałych.

Prostota przekłada się również na interoperacyjność i urzeczywistnianie zasady DRY (z ang. Do not Repeat Yourself – „nie powtarzaj się”). Dobrze zaprojektowany komponent powinno dać się bez większego problemu wydzielić z zastanego otoczenia i przystosować do pracy w innym kontekście.

Upraszczanie

Teza:

Możemy tworzyć takie same programy,
lecz składające się ze znacznie prostszych komponentów.

Rich wspomina o użyciu znacznie prostszych języków programowania, narzędzi, technologii i podejść do rozwiązywania problemów. Jako przykład podaje proste zamienniki mechanizmów, z którymi spotykamy się na co dzień:

  • wartości zamiast stanówobiektów,
  • funkcjeprzestrzenie nazw zamiast metod,
  • zarządzalne referencje zamiast zmiennych,
  • polimorfizm zamiast dziedziczeniadopasowywania do wzorców,
  • dane zamiast składni (niezależność od pozycji w ustalonym porządku),
  • kolejki zamiast modeli aktora,
  • wyrażenia deklaratywne zamiast ORM-ów,
  • reguły zamiast konstrukcji warunkowych,
  • spójność zamiast niespójności (np. w bazach danych).

Uporządkowanie

Problem z uporządkowanymi strukturami danych (np. typem tablicowym czy argumentami) polega na tym, że wprowadzają one złożoność przez splatanie znaczenia elementów z ich pozycjami lub kolejnymi elementami (w przypadku typów sekwencyjnych).

Zmiana uporządkowanej struktury (np. dodanie obsługi nowego elementu) zmusi programistę do poszukiwania wszystkich miejsc w kodzie, w których następuje przetwarzanie. Prosty przykład uporządkowanego wektora zawierającego dane użytkownika:

  • przed zmianą: [imię nazwisko e-mail]
  • po zmianie: [imię nazwisko telefon e-mail]

Jeżeli jakieś fragmenty programu polegają na kolejności, powstanie problem – trzeba będzie je przepisać. Wybierając tego typu strukturę, chociaż nie było wymogu zachowywania porządku, programista już na etapie planowania zasiewa ziarno trudu.

Przykłady zamienników innych, szkodliwych mechanizmów związanych z uporządkowaniem:

  • parametry nazwane lub mapy zamiast argumentów pozycyjnych,
  • powszechne struktury zamiast specyficznej składni,
  • rekordy asocjacyjne zamiast typów produktowych,
  • programy deklaratywne zamiast programów imperatywnych,
  • kolejki zamiast łańcuchów wywołań,
  • JSON zamiast XML-a.

Receptą na większość kłopotów z uporządkowaniem danych jest korzystanie z generycznych sposobów przetwarzaniastruktur asocjacyjnych (np. asocjacyjnych tablic czy tablic mieszających, pot. haszy).

Tworzenie bytów ponad miarę

Problemem złożonych systemów może być również to, że ich architektura jest skomplikowana ze względów estetycznych, proceduralnych czy związanych z konwencjami. Przykładem może być budowanie zbędnych języków dziedzinowych (ang. Domain-Specific Languages, skr. DSL), klas wyposażonych w metody operujące na danych o specyficznym znaczeniu (zamiast metod generycznych) czy komponentów, w których warstwa prezentowania danych jest połączona z logiką biznesową.

Wymieniony wyżej ostatni przykład można skonkretyzować przywołując środowiska do tworzenia aplikacji webowych, gdzie powszechne jest odwoływanie się w tzw. widokach (ang. views) do metod, których nazwy zależą od nazw konkretnych tabel i ich kolumn w bazie danych. W takim systemie zmiana nazwy tabeli bądź kolumny wiąże się ze zmianą kodu widoków.

Wideo

Jesteś w sekcji

comments powered by Disqus