31 lip 2009

EA - KISS

Keep It Simple, Stupid! mówi starobrytyjskie porzekadło. Jak jest ono prawdziwe wie każdy, kto choć raz przedzierał się przez gąszcz klas, chaszcze metod i bagna logiki przesadnie zaprojektowanych aplikacji. Chęć przewidzenia przyszłych wymagań pcha nas często w pułapkę używania skomplikowanych konstrukcji tam gdzie dużo prostsze były by wystarczające. A skomplikowane struktury, gdy kod nie komunikuje jasno ich celu, rozwijają się szybko w totalnie niezrozumiałe monstra. Wystarczy dorzucić do nich jednego if-a tu, i jeszcze dwa tam, i początkowo śliczna implementacja wzorca przeradza się we włoskie danie z sosem bolognese.
W zwinnych projektach staramy się bronić przed takimi sytuacjami przez ciągłe projektowanie i refaktoryzację aplikacji (TDD). Praca w parach pozwala nam na ciągłe weryfikowanie wprowadzanego kodu niejako z zewnątrz - przez kolegę, który być może trzeźwiejszym okiem oceni nasze zapędy.
Ale to techniki które można stosować na poziomie kodu. A co z architekturą? Przecież cała idea definiowania architektury to przewidywanie przyszłości. Przecież to właśnie dzięki dobrym wstępnym decyzjom aplikację będzie można np. prawie liniowo skalować, czy umożliwić jej łatwą integrację z korporacyjnym ESB.

Żeby nie wpaść w pułapki podjęcia błędnych decyzji, lub roztrząsania zupełnie nieistotnych kwestii posługujemy się terminem last responsible moment. Jest to ten moment przed którym podjęcie decyzji może być przedwczesne (niewystarczające/niepełne informacje mogą spowodować podjęcie błędnej decyzji), a po którym jej niepodjęcie może powodować jakieś straty (np. zbyt wczesne podjęcie decyzji o wyborze Oracla a nie MySQL może narazić nas na ogromne koszty, zbyt późne podjęcie decyzji może oznaczać konieczność przepisania fragmentów aplikacji). Staramy się więc powstrzymywać od podejmowania decyzji w kwestiach które nas aktualnie nie dotyczą, ale bardzo pilnie obserwować i dyskutować te które mają wpływ na naszą aktualną pracę.

Aby móc pracować w taki sposób utrzymujemy naszą architekturę maksymalnie prostą, ale zawsze w takim stanie, by umożliwić jej modyfikację. Bo gdy założymy, że architektura ewoluuje wraz z rozwojem systemu, możemy też założyć, że ma ona wspierać wyłącznie te własności które są niezbędne TERAZ i których wymagalności jesteśmy absolutnie pewni (YAGNI). Z resztą czym prostsza architektura tym łatwiej ją komunikować w ramach zespołu i tym łatwiej zapanować nad nią zespołowi. A czym większy zespół tym bardziej jest to prawdziwe.

Podobnie jak w przypadku projektowania na niższym poziomie, staramy się cały czas patrzeć kiedy prostota techniczna rozwiązanie zaczyna przeszkadzać w utrzymaniu prostoty wyrazu. Taką sytuację można zwykle poznać po liczbie bezpośrednich zależności - jeśli jest ich za dużo, prostota techniczna naszego rozwiązania przestaje być wartością - trzeba więc poszukać rozwiązania bogatszego technicznie i poprawiającego komunikatywność, zrozumiałość naszego pomysłu. Powinniśmy cały czas utrzymywać balans między prostotą techniczną i prostotą wyrazu.

Dbamy więc symetrię, modularność (high cohesion, low coupling), rozdzielenie warstw, oddzielenie dziedziny od elementów infrastrukturalnych, uniezależnienie od zewnętrznych bibliotek / frameworków, itp. Jeśli pomyślimy o tych cechach w kontekście prostoty, to to jest to, co one faktycznie promują. Kod podzielony na małe moduliki jest prostszy od monolitycznej masy. Każdy z modułów z dobrze wydzieloną pojedynczą odpowiedzialnością jest prostszy niż masa if-ów i switch'y weryfikujących poszczególne przypadki.

Pilnowanie i dbanie o prostotę rozwiązań oraz zachowywanie zasady wysokiej wewnętrznej spójności i małego poziomu uzależnienia od zewnętrznych elementów (na każdym poziomie), ułatwia tworzenie zwinnych architektur pozwalających na reagowanie na zmiany wymagań.

2 komentarze:

KosciaK pisze...

"last responsible moment"
W teorii brzmi świetnie, ale mam dziwną obawę, że może prowadzić to do sytuacji w której na siłę odwleka się kluczową decyzję całą energię poświęcając na sprawy drugorzędne. Niby dodawane są nowe funkcjonalności, niby wszyscy pracują w pocie czoła ale tak na prawdę projekt stoi w miejscu. Takie niezauważenie, że to już jest ten "last responsible moment"

"Dbamy więc symetrię, modularność (high cohesion, low coupling), rozdzielenie warstw, oddzielenie dziedziny od elementów infrastrukturalnych, uniezależnienie od zewnętrznych bibliotek / frameworków, itp"
No i właśnie tym można się zajmować w nieskończoność zamiast podjąć tą kluczową decyzję. Dążąc do prostoty tworzyć potworka składającego sięgłównie z kolejnych warstw abstrakcji.

Paweł Lipiński pisze...

No wiesz, albo się o to dba, żeby decyzję podjąć, albo się nie dba. Jak się nie dba, to w ogóle można nie siadać do robienia projektu.
Bazując na swoim doświadczeniu mogę powiedzieć, że kwestia poświęcania energii na sprawy drugorzędne raczej się nie zdarza, bo w końcu w zespole jest parę osób i ktoś raczej zauważy, że się zespół pierdołami zaczyna zajmować. Poza tym pracę organizujemy według priorytetów (biznes) i ryzyka (technikalia), więc minimalizujemy ryzyko takiej sytuacji.

Drugiej części Twojego komentarza nie rozumiem - chodziło mi o to jakie techniki pomagają zapobiegać uniemożliwieniu ewoluowania architektury. W kontekście KISS overdesign, o którym piszesz, jest czymś na co stale zwracamy uwagę (żeby go eliminować). Nie tylko staramy się dbać o prostotę w czasie tworzenie kodu, ale również później, za każdym razem gdy coś w nim zmieniamy.