Na początku kariery każdy z programistów chce jak najwięcej pisać. Znamy oczywiście okrojone zasady pisania czytelnego kodu: nazywać zmienne opisowo, używać wcięć, korzystać z już napisanego kodu, zamiast tworzyć go na nowo. Jest to zrozumiałe – na początku drogi najbardziej zależy nam na tym, aby zadanie, które przed nami postawiono zostało wykonane, a nowa funkcjonalność – po prostu działała. Podczas dodawania kolejnych funkcjonalności, w kodzie pojawia się coraz więcej błędów, niedbałych ifów i metod rozciągających się na wiele linii, z niewyobrażalną ilością wcięć. Zderzenie z rzeczywistością powinno przyjść dość szybko, przy czym, moim zdaniem, im szybciej – tym lepiej. Im szybciej bowiem przyswoimy sobie tych kilka zasad clean code – tym szybciej zaczniemy pisać kod wysokiej jakości. W tym wpisie chciałbym poruszyć trzy podstawowe mnemoniki dotyczące programowania obiektowego w dobrym stylu: SOLID, KISS, oraz DRY.
SOLID – jest to zbiór pięciu zasad: pojedynczej odpowiedzialności (Single responsibility – S), otwarte/zamknięte (Open/Close – O), podstawienia Liskov (Liskov’s substitute – L), segregacji interfejsów (Interface segregation – I), odwrócenia zależności (Dependency inversion – D). Szczegółowo o zasadach poniżej:
Single responsibility principle, czyli zasada pojedynczej odpowiedzialności. Jej formalne tłumaczenie, to że klasa zachowująca tę zasadę ma dokładnie jeden powód do zmiany. Ok, ale… co to oznacza? No właśnie. Zachowanie tej zasady prowadzi do tego, że jedna klasa będzie odpowiedzialna dokładnie za jedną funkcjonalność. Innymi słowy – jeśli mamy klasę LoginController to powinna ona być odpowiedzialna tylko i wyłącznie za obsługę logowania. Dodawanie tam metod, które obsłużą rejestrację, czy mapowanie DBO na DTO będzie błędne.
Open / close principle, czyli zasada otwarte/zamknięte. Znów tłumaczenie brzmi, że klasa jest otwarta na rozszerzenia, ale zamknięta na modyfikacje. Co to znaczy? Aby zrozumieć tę zasadę, najprościej jest zapamiętać to, że klasa (ale też moduł, czy nawet cała aplikacja) powinna otwarta na rozbudowę, czyli umożliwiać zwiększenie funkcjonalności w dość prosty sposób (np. dziedziczenie), oraz zamknięta na modyfikacje, czyli, aby zwiększenie tychże funkcjonalności nie było przeprowadzone kosztem modyfikacji istniejącego kodu.
Liskov’s substitute principle, czyli zasada podstawienia Liskov (co ciekawe – to nie jest Pan Liskov, a Pani Liskov, warto o tym pamiętać, żeby nie palnąć gafy, mówiąc o “podstawieniu Liskova”). Jest ona jakoby konsekwencją zasady otwarte / zamknięte. Dzieje się tak, ponieważ definicja tej zasady brzmi Musi istnieć możliwość zastąpienia typu bazowego dowolnym typem pochodnym. Co to z kolei oznacza? Że np. jeśli mamy metodę, która przyjmuje jako argument obiekt słynnej już klasy Animal (lub Punkt2d) i wykonuje na niej pewną operację, to powinna ona (metoda) działać tak samo (w sensie logicznym), jeśli jako argument przekażemy obiekt klasy Cat (lub Punkt3d).
Interface segregation principle, czyli zasada segregacji interfejsów. W dużym skrócie – im mniejsze interfejsy, tym lepiej. Należy pamiętać, że jeśli klasa implementuje dany interfejs, to musi implementować wszystkie metody tego interfejsu. Nieimplementowanie metod prowadzi do błędów kompilacji, a tzw. puste implementowanie (czyli utworzenie metody, która jedynie rzuca NotImplementedException) zwyczajnie wprowadza użytkownika (danego API) w błąd. Lepsze jest implementowanie czterech interfejsów po dwie metody, niż jednego z ośmioma metodami. Głównie dlatego, że mniejsze interfejsy mogą być bardziej abstrakcyjne i reużywalne.
Dependency inversion principle, czyli zasada odwrócenia zależności. Definicja tłumaczona jako wysokopoziomowe moduły nie powinny zależeć od modułów niskiego poziomu, a abstrakcje nie powinny zależeć od szczegółowych rozwiązań, tylko rozwiązania powinny zależeć od abstrakcji. No dobrze, ale co to oznacza? Wyobraźmy sobie aplikację, która uwierzytelnia klientów banku (powiedzmy, że coś w rodzaju 3d secure). Na wysokim poziomie tejże aplikacji mielibyśmy klasy, które kolejno: pobierają id klienta, wyciągają jego numer telefonu, wysyłają sms z kodem, odbierają kod od klienta za pomocą formularza webowego, w końcu sprawdzają w bazie danych, czy kod wysłany do klienta smsem jest taki sam, jak ten, który wpisał. Co byłoby na niskim poziomie? Mógłby to być serwer aplikacyjny i baza danych. Złamanie zasady DIP oznaczałoby, że zmiana na przykład systemu zarządzania bazą danych wymuszałaby zmianę formularza webowego. Z kolei dzięki zachowaniu zasady odwracania zależności mamy pewność, że modyfikacja czegoś “na dole” nie wpłynie na rozwiązanie, które mamy “u góry”.
DRY, czyli don’t repeat yourself (ang. nie powtarzaj się). Oznacza to tylko tyle i aż tyle, ile jest zawarte w rozwinięciu skrótu. Wielokrotnie pojawiające się w kodzie metody realizujące to samo, kilkukrotne powtórzenie tworzenia obiektów w ten sam sposób, czy kopiowanie fragmentów kodów do innej części projektu przede wszystkim owocuje większą ilością miejsc, w których można popełnić błąd. Drugą rzeczą jest to, że gdy będziemy musieli przeprowadzić modyfikację jednego z miejsc, należy wtedy zawsze pamiętać, aby dokonać tychże w każdym miejscu, gdzie ich użyliśmy. To z kolei może powodować, że nasza aplikacja przestanie być spójna.
KISS, czyli ostatnia z zasad to Keep it simple, silly. Mimo, że bezpośrednie tłumaczenie tej zasady to nie kombinuj, głuptasie, to o wiele lepszym tłumaczeniem jest tłumaczenie razem z akronimem – bez udziwnionych zapisów, idioto, w skrócie BUZI. Jest to zasada, która mówi nam, że powinniśmy pisać w sposób najprostszy z możliwych – najłatwiejszy do zrozumienia i odczytania. Chodzi tu oczywiście nie tylko o sposób zapisu kodu, ale także o nazewnictwo elementów naszego projektu (paczek, klas, metod aż do zmiennych). Wszystko powinno być zapisane w taki sposób, aby bez potrzeby dogłębnej analizy kodu można było zrozumieć do czego służą dane metody czy zmienne. Innymi słowy – kod powinien być w miarę możliwości samokomentujący się.
Jedna myśl w temacie “Solidny suchy buziak – czyli jak to jest z tym Clean Codem?”