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ń.
Etykiety: agile, architektura, zwinność