Czym są zapachy kodu wyższego rzędu i jak z nimi walczyć
Zapachy kodu to koncepcja dobrze nam wszystkim znana. “Zapach” wiele nam tutaj mówi: można to porównać do otwierania lodówki po weekendzie spędzonym poza domem - od razu wiesz, że coś się popsuło.
Do wykrywania zapachów kodu mamy kilka popularnych narzędzi (Checkstyle, SonarQube itd.). Zapachy zostały również poddane taksonomii w wielu pracach badawczych - określono również ich relatywne koszty. Fajnie jest czasem w ramach ćwiczeń nadawać im nazwy i je klasyfikować oraz zastanawiać się nad tym, które z nich są najgorsze. Najbardziej jednak interesuje mnie to, w jaki sposób one się w ogóle pojawiają.
A pojawiają się w grupach. Chaos rodzi chaos. Różne zapachy kodu łączą się w unikalne aromaty. Nazwę je zapachami kodu wyższego rzędu, gdzie wiele rzeczy idzie nie tak jednocześnie.
Chciałbym podzielić się z wami jednym taki zapachem wyższego rzędu - kto wie, może będzie ciąg dalszy.
Obiekt o n-wymiarach
Jest to metoda albo konstruktor, które mają zależące od siebie parametry z prawidłowymi i nieprawidłowymi kombinacjami.
Charakteryzuje się (1) niekontrolowanymi skutkami ubocznymi (2) zbyt dużą ilością parametrów oraz (3) złożonością cyklomatyczną.
A obiekt o n-wymiarach stara się robić o wiele za dużo.
Nieprawidłowa kombinacja argumentów będzie na pierwszy rzut oka wyglądać dobrze, ale spowoduje skutki uboczne (1). Co więcej, próbuje wcisnąć wiele parametrów w jedno miejsce, a każdy będzie służył do czegoś innego (2). Wiele odmian danych wejściowych dotrze do różnych gałęzi kodu i ciężko będzie o nich w jakikolwiek sposób myśleć (3).
Najgorsze obiekty n-wymiarowe mają naprawdę dużą przestrzeń możliwości, jakie tworzą: iloczyn kartezjański danych wejściowych. Każdy taki obiekt zastawia pułapki. To trochę tak, jak w tej grze, w której jesteś dentystą krokodyla. Tylko, że tutaj Ty jesteś jedynym dentystą - oraz krokodylem.
Przestań robić sobie krzywdę
Oto przykład.
Mamy model, którego użyjemy z ekranem Profile i który jest w stanie pokazywać indywidualny element User, grupę użytkowników oraz kilka kontrolek dla adminów:
class Profile(
/**
* The userId if this is a user profile, or the groupId if this is a user group.
*/
val id: String?,
/**
* The admin access token or null if this isn’t an admin.
*/
val adminToken: String?,
/**
* The list of this user’s group memberships by their group ids,
* or a list of this group’s member ids, or an empty list if this is an admin.
*/
val groupIds: List<String>
)
Zanim staniesz się zbyt pewny siebie, muszę Ci coś powiedzieć: dokumentacja nie jest aktualna.
Obiekty n-wymiarowe pojawiają się w miejscach, w których prosta funkcja nabiera nowych odmian bez korygowania modelu danych. Czasami wiele decyzji projektowych prowadzi do interfejsu użytkownika z wieloma różnymi „smakami” i permutacje wymykają się spod kontroli. Innym razem jest to kontener UI - na przykład Fragment
lub Activity
w Androidzie, które ma wiele różnych trybów inicjalizacji.
Poprawka powinna być prosta i klarowna, biorąc pod uwagę przyczyny: podziel klasę na różne typy, z których każdy zawiera własne niestandardowe argumenty. A może nawet ją skomponuj?
class User : GroupMembership, Profile
interface GroupMembership {
val groupIds: List<String>
}
interface Profile {
val profileId: String
}
interface Administrator {
val accessToken: String
}
Naprawianie zapachu kodu wyższego rzędu w ten sposób prowadzi do serii innych potrzebnych poprawek: stare strony wywołań będą teraz bardziej czytelne, a obsługa warstwy UI będzie miała do dyspozycji jasne ścieżki w kodzie, bez zbyt wielu rozgałęzień. Poświęcenie czasu na wykonanie dalszych poprawek w dużym stopniu wyjaśnia, jak taki zapach może utrzymywać się przez jakiś czas.
I to by było na tyle! Dziękuję za uwagę!
Oryginał tekstu w języku angielskim możesz przeczytać tutaj.