Zacznę od tego, że jako branża, uwielbiamy wymyślać nowe terminy, a następnie kłócimy się o to, co one tak naprawdę oznaczają. Podobnie jest z zasadami programowania. Większość zasad programowania to nic innego jak opakowanie podstawowych konceptów programowania obiektowego w bardziej wyszukane definicje, które często bywają powodem nieporozumień.
Prawie każda zasada programowania dotyczy idei, że elementy, które ulegają zmianie razem, powinny być zgrupowane w jednym miejscu, najlepiej za stabilnym interfejsem i powinny być luźno powiązane z pozostałymi komponentami systemu. Czyli wysoka kohezja, hermetyzacja i niski coupling. Jeśli nie wiesz, co to jest kohezja i coupling, zachęcam Cię do przeczytania mojego wpisu: Kohezja i coupling
Problem z interpretacjią zasad programowania
Byłem świadkiem niejednej dyskusji na temat tego, czy dany kod spełnia określoną zasadę, oraz co ta zasada tak naprawdę oznacza. Weźmy za przykład najpopularniejszą zasadę od wujka Boba, czyli Single Responsibility Principle (SRP). Zasada ta głosi, że klasa powinna mieć jedną odpowiedzialność lub powinna mieć jeden powód do zmiany. Niezależnie od definicji pojęcia „powód do zmiany” czy „odpowiedzialność” mogą być różnie interpretowane.
Załóżmy, że mamy klasę do liczenia rabatów – mamy rabat kwotowy lub procentowy. Ktoś stworzy klasę DiscountCalculator
i powie, że ma ona jedną odpowiedzialność: liczenie rabatu. Jednak ktoś inny może się nie zgodzić, uznając, że liczenie procentowego rabatu to powinna być odpowiedzialność klasy PercentageDiscountCalculator
, a kwotowego – klasy ValueDiscountCalculator
.
Podobnie jest w życiu. Gdybym ci powiedział: „zorganizuj koncert”, można by to uznać za jedno zadanie. Można jednak to podzielić na mniejsze części. Przykładowo: ustalanie cen biletów, wybór miejsca, zapraszanie artystów, sprzedaż biletów i tak dalej. To jeszcze inne porównanie do jedzenia, niektórym trzeba do żarcia porównać, aby najlepiej zrozumieli. Załóżmy, że masz zasadę: możesz zjeść jeden posiłek. Jeśli zjesz bułkę i parówkę, złamiesz zasadę. Ale jeśli zjesz hot doga, to już zasadę spełniasz.
Jak widać, takie zasady dla każdego mogą znaczyć coś innego. Weź Pan teraz to jeszcze wyjaśnij juniorowi, co to jest ta jedna odpowiedzialność. Dlatego uważam, że niektóre zasady programowania wprowadzają sporo chaosu, a w gruncie rzeczy opierają się na podstawowych założeniach Object-oriented. Oczywiście, SRP wywodzi się z kohezji i couplingu i stanowi tylko trudną do interpretacji nakładkę na te koncepty.
Nie tylko SRP jest trudne do interpretacji. Weźmy jakieś inne zasady:
- KISS (Keep It Simple, Stupid) – powinniśmy utrzymywać kod w możliwie najprostszej formie. Ale co to oznacza, „możliwie najprostsza forma”?
- YAGNI (You Aren’t Gonna Need It) – zasada mówiąca, że programiści nie powinni dodawać funkcjonalności, dopóki nie są one konieczne. Ale co to oznacza, „konieczne”?
- Open/Closed Principle – klasy powinny być otwarte na rozszerzenia, ale zamknięte na modyfikacje. Co oznacza „rozszerzenie”? Czy żeby zaspokoić tę zasadę powinienem już zrobić interfejs, czy jeszcze nie?
Podsumowanie
Zasady programowania istnieją po to, aby podpowiadać nam jak właściwie implementować aplikacje. Niemniej jednak często stanowią one również źródło licznych dyskusji, które w wielu przypadkach dotyczą różnorodności interpretacji poszczególnych terminów. Próby bezwzględnego stosowania zasad programowania bez uwzględnienia konkretnego kontekstu i wymagań projektu mogą prowadzić do problemów oraz nieefektywności. Dodatkowo może się zdarzyć, że według naszej interpretacji zasady wzajemnie się wykluczają. Przykładowo Open/Closed Principle zaleca bycie otwartym na rozszerzenia, co często wiąże się z tworzeniem abstrakcji dla przyszłych funkcjonalności, podczas gdy YAGNI mówi, aby unikać dodawania funkcjonalności, dopóki nie są one rzeczywiście potrzebne.
Warto mieć na uwadze, że zasady są jedynie wskazówkami, a ich stosowanie zależy od specyfiki każdego przypadku. Bywa, że świadomie łamiemy pewne zasady, bo wiemy, że może to przynieść większe korzyści niż ich ślepe stosowanie. Kluczową wartością naszego systemu nie jest liczba spełnionych zasad, lecz to, w jakim stopniu udało się rozwiązać problem biznesowy.
Celem tego wpisu jest ukazanie, że nie warto wdawać się w spory odnośnie tego, czy konkretny kod na pewno przestrzega określonej zasady, którą można rozumieć na wiele sposobów. Oczywiście, zachęcam do robienia code review i wspólnego dyskutowania na temat tego, czy dane rozwiązanie faktycznie najlepiej rozwiązuje konkretny problem.
Z czasem wypracowujemy sobie w zespole pewne heurystyki do rozwiązywania określonych problemów. Jeśli mamy ustaloną i omówioną w zespole daną konwencję, nawet jeśli łamie ona którąś z zasad programowania, ale wszyscy w zespole rozumiemy, to jest to jak najbardziej ok. Ważne jest również, aby dokumentować takie decyzje, aby za kilka miesięcy nie pojawiały się te same pytania, a nowi członkowie zespołu mogli przyswajać tę wiedzę, nie przerywając pracy innych. W tym celu można skorzystać z ADR (Architecture Decision Record).
Pamiętajmy, spełnienie zasad jest efektem tworzenia oprogramowania, a nie celem samym w sobie!