Reużywalność jest niczym tęczowa breja

Dawno temu w deszczowej Szkocji, przemierzając rowerem krzywy wiadukt kolejowy, doznałem olśnienia. Mym oczom ukazały się wielokolorowe puzzle, zbite w olbrzymią, tęczową breję. Każdy z kolorów symbolizował inną domenę biznesową. Ktoś zmieszał te wszystkie, niepasujące do siebie elementy razem. Dlaczego? W imię wielokrotnego użycia kodu!
Reużywalność!
Nieistniejące w słowniku języka polskiego, słowo klucz! Jakże pożądane w kręgach biznesowo-programistycznych. Czy słusznie? Nim odpowiemy, zastanówmy się, co daje powtórne wykorzystywanie kodu i zadajmy sobie pytanie, cytując Kaję….
Po co? Po co? Po co?
By oszczędzić czas i uniknąć robienia dwa razy tego samego. Dość logiczne, prawda? Mało tego. Takie podejście poza możliwą oszczędnością czasu, pozytywnie wpływa na spójność tworzonego kodu.
Miej na uwadze, że jest to potężna broń. Chwila nieuwagi, a odwróci się przeciwko Tobie. Stanie się przekleństwem, generującym zależności, zwiększając współczynnik stabilności(o którym przeczytasz w książce Wujka Boba „Czysta Architektura”) do niebotycznych wartości.
Zły przypadek
Zacznę od przykładu dającego złudne poczucie porządku. Przyjrzyjmy się klasie sprawdzającej uprawnienia zalogowanego użytkownika. Ktoś zaprojektował ją tak, by weryfikacja uprawnień znajdowała się w jednym miejscu. Założenie jak najbardziej szlachetnie, acz co z tego wyszło? Na przykład coś takiego:
public class PermissionsService {
public bool CanBuyProduct() { ... }
public bool CanSellProcucts() { ... }
public bool CanCreateCategory() { ... }
public bool CanInviteNewUser() { ... }
}
Wygląda ok? Absolutnie nie. Takie podejście powoduje przeplatanie się, w niekontrolowany sposób, domen biznesowych. W związku z tym stanie się on praktycznie niezmienialny, gdyż będzie od niego zależeć każda cześć aplikacji. W mojej wizji wyglądał on, miej więcej tak:
Lepiej
Nie szukaj wspólnych elementów. Wręcz przeciwnie, kombinuj jak ukryć konkretną implementację przed światem zewnętrznym. Wskazówki jak podzielić kod znajdziesz w poście o refaktoringu. Dzięki takiemu zabiegowi zamkniesz całą funkcjonalność w pojedynczym module. Co prawda, ryzykując koniecznością powtórzenia kodu, ale skoro dotyczą one innych funkcjonalności, warto spróbować. W imię osłabienia powiązań.
Przykład? Proszę bardzo. Oto powyższa klasa, rozbita na kilka mniejszych, tak by skryły się w cieniu modułów.
public class UserPermissionsForProductChecker {
public bool CanBuyProduct() { ... }
public bool CanSellProcucts() { ... }
}
public class UserPermissionsForCategoryChecker {
public bool CanCreateCategory() { ... }
}
public class UserPermissionforUserManagingChecker {
public bool CanInviteNewUser() { ... }
}
Jeżeli, mają one wspólny kod, dobierający się do konkretnej struktury uprawnień, to zrób jeszcze jedną klasę. Pamiętaj jednak, by używać jej tylko wewnątrz, jako szczegół implementacyjny. Nigdy bezpośrednio! Co możesz zabezpieczyć testami konwencji.
public class UserPermissionChecker {
public bool CheckPermission(int userId, requiredRoles: Role[]) { … }
}
Przy takim podejściu w mojej głowie ukazał się obraz:
Na koniec
Podjęcie decyzji, kiedy skopiować, a kiedy reużyć, zawsze jest trudna, bo nie znamy przyszłości. Warto jednak poświęcić chwilę, by tę decyzję podjąć świadomie. Czy zgadasz się z przedstawioną przeze mnie tezą? A może widzisz poważne luki w moim rozumowaniu? Koniecznie daj mi znać, najlepiej w komentarzu poniżej.
8 Komentarzy
Radek Maziarka · 2018-11-26 o 13:03
Problemem z klasami typu PermissionsService jest zbyt developerskie postrzeganie spójności – patrzymy wyłącznie przez pryzmat technicznych detali. O wiele lepszą drogą jest patrzenie na spójność biznesową – pozwolenia na sprzedaż / kupno produktów będą czymś kompletnie innym niż pozwoleniami na zarządzanie użytkownikami.
Jerzy Wickowski · 2018-11-26 o 13:23
Siemka @Radek,
O to to to. Właśnie o to chodzi. :)
Arek BL · 2019-11-06 o 21:55
No przykład średni IMHO, bo podstawową cechą reużywalnych komponentów będzie ograniczenie ich własnych zależności. Takie łączące serwisy czy menadżery są niesłychanie wygodne, ale mają właśnie tą wadę że wnoszą ogrom w dużej mierze nie potrzebnych zależności. Coś jak zrywasz gałązkę, a się okazuje że trzymasz w ręku dąb z korzeniami. O ile te zależności są częścią jakiegoś ambientu… Common, Infrastructure, czy w jednej wspólnej konfiguracji/bazie to da się żyć. Ale trzeba jasno powiedzieć, że to nie jest reużywalny komponent. A jest to właśnie taki ambient… dodatkową uniwersalna zależność, warunek wykonania jak dla ryby woda.
Jerzy Wickowski · 2019-11-07 o 22:10
Cześć @Arek,
Dziękuję za komentarz. Przykład jak przykład, ale zgadzam się z Tobą. Nie należy popaść z patologi w patologię i zawsze złoty środek jest ważny :)
Grzegorz Strzelecki · 2018-12-13 o 00:42
Mądry post. Często gęsto mnie ta reuzywalność uwierała. Nie mówiąc o tym że pół Solida w postaci Single responsibility i open close’a leżała i kwiczała.
Jerzy Wickowski · 2018-12-19 o 12:29
Dziękuję Grześku za komentarz.
Jarosław Stadnicki · 2018-12-19 o 11:45
Dawno przestałem wierzyć w reużywalność kodu. Reużywalne są biblioteki czy frameworki. W produktach najczęście nic nie jest łatwo reużywalne.
Jeśli tak było tworzone, to prędzej czy później będą tam kawałki kodu, które są zbyt skomplikowane, nie używane, mocno zduplikowane.
Jerzy Wickowski · 2018-12-19 o 12:28
Cześć Jarosław,
Dokładnie tak. Z tym się jak najbardziej zgadam. Chociaż czasem może zaistnieć potrzeba stworzenia własnej biblioteki, aczkolwiek warto zadbać, by nie zawierała logiki biznesowej, a ogólne komponenty.