Jak odczarować magic number

Zacznę tak:
Nie wszytko co nie przypisane do zmiennej należy traktować jako magic number.

TL;DR;
YouTube na dole.

Rysunek osoby, która myśli o magic number

Ale o co chodzi?

Praca w grupie, różni ludzie, różne poglądy na kod i na to jak powinien on wyglądać. Wszystko to miesza się w trakcie code review. Stało się tak, że kilka razy zwróciłem uwagę na to, że używane są tzw. magic numbery.

Czy jest magic number?

Jest to wartość (np. 42) która pojawia się znikąd w kodzie, przekazana gdzieś do funkcji czy wykorzystana do sprawdzenia czegoś (patrz przykład). Wartość ta ma jakieś znaczenie, ale często znane jest one na początku tylko jednej osobie (z czasem i sam autor może zapomnieć, czemu akurat taka wartość), np.:

if (password.length() > 7)
{
throw new InvalidArgumentException(„password”);
}

Patrząc na kod powyżej, można zadać sobie pytanie dlaczego akurat siedem? Czemu nie sześć czy osiem? Ten kto pisał pewnie wie, ten kto czyta już nie.

Jak żyć?

Rozwiązać to można na kilka sposobów, jednym z nich jest przypisanie tej siódemki do zmiennej:

var MAX_PASSWORD_SUPPORTED = 7

W taki sposób mówi się jawnie, że to jest maksymalna wspierana długość hasła – brakuje uzasadnienia, ale to zupełnie inny temat. Można oczywiście zmienić to na const, czy umieścić gdzieś w globalnym konstach, czy nawet zaczytać z konfiguracji, chodzi o to, aby ta wartość była nazwana.

Mięsko Kod

I teraz właściwa część wywodu: kiedy magic number nie jest magic numberem? Kod do popatrzenia:

Na początku znajduje się FooObject który zawiera ważną dla przykładu właściwość ModelType. Data jest tylko żeby nie było zupełnie goło. Dalej widać serwis FooService który pracuje z tymi modelami danych. Na koniec enum ModelType który także będzie brał udział w demo.

I teraz zaczynamy od linijki @37 – tworzymy serwis z którego mamy coś załadować, aby to zrobić należy przesłać parametr. Parametr przekazujemy jako 2 (słownie dwa) @41, potem jako zmienną o nazwie modeltype (jej wartość to ModelType.Complex) @42 i wreszcie samo przekazanie ModelType.Complex @43.

Pierwsze

2 – to klasyczny magic number. Tutaj nie ma o czym dyskutować. Nie wiesz co oznacza dwójeczka (bez skojarzeń proszę), nie wiesz dlaczego została użyta, nic nie wiesz, nie wiesz nawet w jakim kontekście zostanie użyta. A może to identyfikator obiektu? A może ilość obiektów? A może rodzaj modelu?

Drugie

Wywołanie metody Load z przekazaniem jej parametru modelType, który został zainicjalizowany w linii @39 jest słabe. Słabe ponieważ nazwa parametru nic nie mówi. Słabe ponieważ gdy nazwiemy ją np. complexModelType, nic/nikt nie zabroni mi przypisać tam innej wartości takiej jak ComplexType.Simple. No dobra, jest trochę lepsze niż pierwsze podejście, tutaj przynajmniej można się domyślać do czego może posłużyć ten parametr

Teraz pytanie do was, kiedy przypisanie mogłoby mieć sens?

Trzecie

Wreszcie idziemy do wywołania metody Load gdzie przekazujemy ComplexType.Complex jako argument. Taki zapis i wykorzystanie enuma NIE JEST magic numberem. Powtarzam to nie jest magic number, enum posiada jawną nazwę i opis (miejmy nadzieję) tego co oznacza wartość stojąca za cyferką 2, który reprezentuje ComplextType.Complex – w tym przypadku nie potrzeba nic tłumaczyć. Programista który przyjdzie do projektu czy do miejsca w kodzie gdzie taki enum jest wykorzystany.

Uwaga!

Taki kod jest zły:
#define one 1
#define two 2

Reszta

Podobnie na czytelności zyskuje kod w linijkach 45-47, spójrzcie że w pierwszym wywołaniu musimy rzutować na enum. W drugim podejściu jesteśmy jeszcze dalej od początkowej inicjalizacji zmiennej modelType i jeszcze bardziej zapomina się oryginalnej wartości która tam została przypisana. Aż dochodzimy do ostatnie linii, gdzie właściwy enum jest na miejscu i kod wygląda tak jak powinien.

Ja tutaj użyłem przykładu z enumem, ale nic nie broni aby stworzyć kod gdzie magic number zostanie przeniesiony do klasy, tylko uwaga na to co można trzymać w takich constach 😉

I koniec.

Wersja wideo:

Dodaj komentarz