Jak dużo czasu poświęcacie na zrozumienie kodu, gdy trzeba w nim coś zmienić? Czasem kod jest na tyle czytelny, że wszystko widać od razu, a czasami trzeba poświęcić po kilka-kilkanaście godzin, aby zrozumieć co się w nim dzieje. Z czego to wynika? Mam kilka przemyśleń na ten temat.
Mój kod – ja go rozumiem
Bardzo często tworząc jakiś kod, świetnie go rozumiemy w momencie jego pisania. Skoro go rozumiemy, to nie zawracamy sobie głowy tym, aby spróbować go napisać prościej. Ponadto w momencie jego powstawania, wydaje nam się on na tyle prosty, że myślimy, iż każdy bez problemu będzie mógł go zrozumieć. Niestety rzeczywistość bywa okrutna. Bardzo często po kilku dniach/tygodniach/miesiącach sami mamy problemy ze zrozumieniem tego (własnego!) kodu.
Patrzcie jakie znam sztuczki
Często również wielu z nas ma nieodpartą chęć pokazania jak „dobrymi” są programistami. Chcemy się pochwalić tym, jakie znamy sztuczki, które pozwolą zaoszczędzić nam kilka linijek w kodzie. Niektórzy z nas mają chęć pokazania tego, że wiedzą dużo lub że wiedzą więcej niż inni. Niestety skutki tego są takie, że powstaje kod, który wymaga dużo czasu, żeby go zrozumieć, a co za tym idzie w utrzymaniu. Ten kod robi, co ma robić. Działa. Tylko… liczba godzin potrzebna później na zrozumienie go powoduje, że prosta funkcjonalność zamiast godzin, zajmuje dni. Z tego co zaobserwowałem, to występuje to głównie u młodych programistów – sam taki byłem. Doświadczeni programiści już się parę razy na tym sparzyli i są bardziej rozważni, ale też nie zawsze i nie w każdym przypadku.
Ten kod można uwspólnić
Kolejną rzeczą, którą zaobserwowałem, to próba uwspólnienia kodu, który z założenia nie powinien być wspólny. Wydaje nam się, że kod robi to samo, więc używamy go w wielu miejscach. Nie zastanawiamy się nad tym, jak ten kod może się rozwinąć – czy w przyszłości jeśli będziemy potrzebowali go zmodyfikować, będzie to zmiana dla wszystkich miejsc w których on występuje, czy tylko dla jednego przypadku. W skutek czego bardzo często mamy potem mnóstwo dziwnych „ifów” i parametrów w metodach. A to wszystko dlatego, że często, aby nie duplikować kodu, wykorzystujemy klasy i metody zdefiniowane dla innego przypadku biznesowego. Efekt końcowy jest taki, że funkcjonalności, które powinny być niezależne, zostają połączone w kodzie i po jakimś czasie mało kto jest w stanie bezproblemowo przeczytać i zmodyfikować taki kod. Zasada DRY jest super, ale tylko wtedy, kiedy jest zastosowana z głową.
Przykłady „nieprostego” kodu
Przykład 1
Dość klasycznym przykładem może być taki kod:
x = x ^ y;
y = y ^ x;
x = x ^ y;
Pewnie każdy z nas wie co on robi, prawda? Przecież to takie proste! Chyba że… no właśnie. My może i wiemy, ale jak spojrzy na ten kod ktoś, kto mniej rozumie operacje na bitach, to może mieć problem ze zrozumieniem co się tutaj dzieje. Takim kimś może być nawet świetny programista, doświadczony, niekoniecznie „junior”. Wystarczy, że nigdy nie potrzebował używać operacji xor (^) do zamiany zmiennych i może mu trochę czasu zająć, zanim zrozumie, że efekt tego kod jest w zasadzie taki sam jak takiego kodu:
a = x;
x = y;
y = a;
Oczywiście w niektórych przypadkach zamiana zmiennych bez wprowadzania dodatkowej zmiennej ma sens, jednakże w większości sytuacji jest to przerost formy nad treścią.
Przykład 2
Innym przykładem, z którym spotkałem się ostatnio, jest następujący kod:
if (!arrayList?.Any() == true)
Pewnie są tacy, którzy od razu wiedzą o co chodzi. Mi trochę czasu zajęło zrozumienie, że to jest to samo co:
if (arrayList != null && arrayList.Any() == false)
Wychodzi na to, że bazowy kod był odpowiedzialny za sprawdzenie, czy lista jest zainicjalizowana i nie zawiera żadnych elementy. Niby nic skomplikowanego, ale napisane zostało to w taki sposób, że nie od razu się ten kod rozumie.
Przykład 3
Raz spotkałem się z kodem, który wyglądał mniej więcej tak:
static double Calculate(IEnumerable<int> values, bool round, bool isFromMarketing)
{
var sum = values.Sum();
if (isFromMarketing)
{
if (round)
{
return Math.Round(sum * 0.9, 2);
}
return Math.Round(sum * 0.9, 4);
}
if (round)
{
return Math.Round(sum * 0.95, 4);
}
return sum;
}
Kod niby prosty, niby czytelny, ale… ciężko zrozumieć o co tu tak naprawdę chodzi i dlaczego to tak działa.
Historia za tym jest taka, że marketing wymyślił nowy sposób sprzedaży produktów. Należało wyliczyć sumę, po jakiej dane produkty będą sprzedawane. Początkowo miała ona działać dokładnie tak samo, jak dla już istniejących sposobów sprzedaży. Programista znalazł pasującą metodę i ją wykorzystał. Marketing cały czas modyfikował swój sposób sprzedaży i w związku z tym wyliczanie sumy zaczęło ulegać zmianie. Programista nie zduplikował kodu, tylko zaczął modyfikować już istniejący. W efekcie powstał taki dziwny twór. To wszystko zadziało się dlatego, że ktoś postanowił wykorzystać już istniejący kod, zamiast napisać osobną metodę dla marketingu.
Reguła KISS
Czyli akronim od Keep it simple, stupid, to reguła, która mówi, że powinniśmy tworzyć coś na tyle prostego, aby nawet mało uzdolniona osoba, była w stanie to zrozumieć. Początkowo ta reguła była używana w przemyśle inżynierskim, aby tworzyć takie konstrukcje, żeby zwykły inżynier mógł je naprawić, gdy coś się popsuje. Oszczędzało to czas specjalistów.
Ta reguła sprawdza się również podczas pisania kodu. Jeśli napiszemy prosty kod, dowolna osoba będzie w stanie go zmienić. Jeśli potem okaże się, że coś nie działa tak jak powinno, łatwo będzie jej taki kod naprawić.
Dlatego pisząc kod, nie powinniśmy go pisać tak, aby był zrozumiały dla nas. Powinniśmy starać się pisać go tak, aby był zrozumiały dla każdego innego, bo nie tylko my będziemy na nim w przyszłości pracować. Nasz kod powinien wyglądać tak, żeby nawet ktoś mało doświadczony, był w stanie go bez problemu zrozumieć.
Oto kilka sugestii jak to można osiągnąć:
- Nazywajmy odpowiednio klasy – im bardziej precyzyjna nazwa klasy, tym łatwiej zrozumieć, jaki jest jej cel. Jeśli nazwa klasy zaczyna być za długa, to może ma ona za dużo odpowiedzialności i powinniśmy ją podzielić na kilka mniejszych klas.
- Twórzmy małe klasy – klasa powinna mieć tylko takie metody, które są zgodne z jej nazwą. Jeśli jakaś metoda nie do końca pasuje do nazwy klasy, powinniśmy umieścić ją w nowej klasie.
- Nazywajmy odpowiednio metody – nazwa metody może być długa, to nikomu nie przeszkadza. Im ta nazwa będzie bardziej precyzyjna, tym łatwiej będzie zrozumieć, co dana metoda tak naprawdę robi.
- Twórzmy małe metody – im mniejsza metoda, tym mniej kodu do zrozumienia i łatwiejsza nazwa do wymyślenia.
- Nazywajmy odpowiednio zmienne – dobrze nazwane zmienne znacząco wpłyną na zrozumienie kodu. Jeśli nie wiemy jak nazwać zmienną, to prawdopodobnie kod jest zbyt skomplikowany i powinniśmy zastanowić się nad jego uproszczeniem (np. może warto rozbić go na kilka mniejszych metod).
- Ograniczajmy liczbę zależności – im klasa lub metoda ma więcej zależności, tym prawdopodobnie robi za dużo i może być trudna w zrozumieniu. Dlatego jeśli ilość zależności zaczyna być zauważalna (z reguły więcej niż 3-5), powinniśmy rozbić daną klasę czy metodę na kilka mniejszych.
- Nie bójmy się duplikacji kodu – im bardziej staramy się uwspólnić nasz kod, tym bardziej skomplikowany się on staje. Czasem lepiej jest po prostu skopiować już istniejący kod. Wtedy gdy zajdzie potrzeba jego modyfikacji, będziemy mogli go bez problemu zmienić, nie tworząc zawiłych metod czy klas.
- Usuwajmy niepotrzebny kod – często zostawiamy kod, który może w przyszłości się przydać. Skoro nie przydał się do tej pory, to raczej w przyszłości nie przyda się również. Jeśli jednak będzie potrzebny, to zawsze będzie można go wydobyć z repozytorium. Nie będziemy umieli go potem znaleźć w repozytorium? Może pora nauczyć się dawania odpowiednich opisów zmian, które wykonujemy, aby w przyszłości łatwiej było nam coś znaleźć w repozytorium.
Słowo na koniec
Prosty kod nie musi robić jednej rzeczy – chodzi o to, aby był on łatwy w zrozumieniu. Powinien być on również łatwy do modyfikacji i testowania. Nie twórzmy wielkich obiektów, które robią dużo rzeczy. W tym przypadku „małe jest piękne”. Nie próbujmy być „sprytnym” programistą.
Praktycznie każdy kod da się napisać w sposób prosty lub skomplikowany. Działać będzie tak samo, ale ilość czasu potrzebna na jego zrozumienie będzie znacząco różna. Sami powinniśmy się zastanowić, która opcja jest naszym zdaniem lepsza – czy czasem aby nie warto napisać trochę więcej kodu, aby uczynić go bardziej zrozumiałym.
Oczywiście prosty kod nie wystarczy, aby projekt był łatwy w utrzymaniu – do tego przydadzą nam się reguły takie jak SOLID czy GRASP. Jeśli jednak nie zadbamy o to, aby nasz kod był prosty, utrzymywanie go może okazać się bardzo trudne, a czasem wręcz niemożliwe.
Pingback: dotnetomaniak.pl
Możliwość komentowania została wyłączona.