Kevlin Henney
Kevlin HenneyThought Provoker @ Curbralan

Nazewnictwo wyjątków

Kiedy kod wyrzuca lub przechwytuje wyjątek, nazwij go w sposób, który będzie coś komunikował, nie przekazuj go dalej bez zastanowienia
28.01.20226 min
Nazewnictwo wyjątków

Jedną z najtrudniejszych rzeczy w rozwoju oprogramowania jest nazewnictwo. Nazewnictwo produktów, paradygmatów oraz poszczególnych części kodu. Jest ono trudne, ale również niezwykle ważne, ponieważ jest to akt komunikacji; bez dobrego nazewnictwa twój kod może być równie dobrze napisany, można powiedzieć, kodem. Nazwa nie jest jedynie etykietą: ma za zadanie informować i naprowadzać mentalny model czytelnika. Konkretna nazwa może zmienić jego sposób myślenia. Dobra nazwa to okazja do przekazania innym pewnego procesu myślowego; zła natomiast to stracona okazja do nauki i powiedzenia, co nam chodzi po głowie.

Często przyjmujemy jakąś konwencję, aby ułatwić niektóre aspekty nazewnictwa. Taka konsekwencja może być dobra, ale nie wszystkie praktyki, które są konsekwentne, muszą być dobre. Posiadanie powszechnego słownika konceptów przydaje się w bazie kodu, ale nazwane koncepty powinny być postrzegane jako użyteczne informacje — konsekwentne zakłócenia to dalej tylko zakłócenia.

Mocne nazwy

Homeopatyczne nazewnictwo jest jednym z najczęstszych nawyków, w które programiści mogą się zaplątać, nie zdając sobie nawet z tego sprawy: wydłużanie nazw poprzez dodawanie kolejnych słów, w celu nadania kodowi większego znaczenia, w praktyce oznacza jego osłabianie z każdym dodanym Factory/Manager/Object/Controller/Value/Service/.... Osłabianie rzeczy osłabia je; nie czyni ich lepszymi, silniejszymi, czy przekonującymi. Dodawanie elementów z klocków Lego do identyfikatora nie potęguje ani nie uwydatnia jego znaczenia, a raczej często służy podkreśleniu, że być może w ogóle nie było tam żadnego znaczenia. Konwencja nazewnictwa nie powinna być używana do akceptowania słabych nazw. Jeśli jakaś nazwa nie komunikuje czegoś zbyt dobrze, to musimy to zauważyć, aby móc się nią zająć, zamiast wygładzać, ukrywać za ścianą z klocków Lego.

Jednym z homeopatycznych rytuałów nazewniczych, pojawiających się w świecie .NET i Javy, jest dodanie przyrostka Exception do klasy, aby oznaczyć, że jej instancje są wyjątkami. Ta sztuczność jest wyryta w pamięci nieświadomych niczego programistów tak bardzo, że stała się ona już ich nawykiem — niekontrolowane i niezmienne wchłonięte z otoczenia ustawienia fabryczne wyssane z mlekiem środowiska.

Wyjątki w tych językach są uprzywilejowane i wyróżnione, co oznacza, że w zależności od kontekstu, klasy i obiektu, które są wyjątkami, są widocznie i składniowo różne od klas i obiektów, które nimi nie są. Mają one wyłączne prawa do występowania w określonych i wymuszonych przez kompilator miejscach twojego kodu: w throw, w catch, a w przypadku Javy w liście throws (sprawdź, czy możesz tego uniknąć). Z definicji rzeczy, które pojawiają się w tych miejscach, mogą być tylko wyjątkami — kompilator oraz czytelnik wiedzą, że muszą to być wyjątki, więc nie ma nic więcej do dodania.

DRY

Jedną z często cytowanych, ale często niezrozumiałych wytycznych jest zasada DRY z Pragmatycznego programisty:

W pierwszym wydaniu tej książki kiepsko wyjaśniliśmy, co rozumiemy przez Don't Repeat Yourself (Nie powtarzaj się). Wielu ludzi zrozumiało to jako odniesienie tylko do kodu: myśleli, że DRY oznacza “nie kopiuj i nie wklejaj linijek ze źródła”

Jest to tylko jedna strona medalu DRY, ale jest to niewielki ułamek i dość trywialna część.

DRY dotyczy powielania wiedzy, celu.

DRY odnosi się do nazw, konstrukcji językowych i metadanych w takim samym stopniu, jak do powielania kodu, dokumentacji i ręcznego powtarzania zautomatyzowanych procesów.

Jeśli twój język programowania określa klasy abstrakcyjne słowem kluczowym, takim jak abstract, nie powtarzaj się, umieszczając Abstract w nazwie lub mówiąc czytelnikowi, że niezbędnym zastosowaniem jest klasa bazowa poprzez nazwanie jej Base. Jeśli klasa jest klasą konkretną, to z definicji jest klasą implementującą, więc nie powtarzaj się, umieszczając Impl w nazwie. Jeśli twój kompilator i IDE potrafią odróżnić liczbę całkowitą od ciągu znaków, nie powtarzaj się, kodując ten szczegół w nazwie zmiennej, jak spopularyzował to niegdyś popularny węgierski schemat szyfrowania. Jeśli twój framework każe Ci oznaczać testy za pomocą adnotacji Test, atrybutu lub makra —  i pojawia się on wewnątrz klasy, pliku lub folderu o nazwie Test nie powtarzaj się, włączając Test do nazwy twojego testu. Użyj pasma nazwy, aby powiedzieć czytelnikowi rzeczy, których powinien się dowiedzieć, a nie po to, żeby powtarzać to, co wszyscy już wiedzą. Wykorzystaj swoje pasmo dla sygnału, a nie dla zakłóceń — i używaj mniejszej przepustowości

Wiemy, że klasa i jej instancje reprezentują wyjątki poprzez swoją pozycję syntaktyczną, ale nie jest to jedyna ważna kwestia. Fakt, że dana klasa reprezentuje wyjątek, powinien być oczywisty ze względu na jej pochodzenie lub nazwę. Nazwa powinna przedstawiać problem, i powinna to robić jasno i konkretnie. Dodanie Exception na końcu jest albo zbędne — więc możesz od razu to usunąć — albo wskazuje na złą nazwę — więc zmień nazwę.

Rozważmy następujące klasy wyjątków Javy:

ClassNotFoundException
EnumConstantNotPresentException
IllegalArgumentException
IllegalAccessException
IndexOutOfBoundsException
NegativeArraySizeException
NoSuchMethodException
TypeNotPresentException
UnsupportedOperationException


Po usunięciu Exception otrzymujemy następujące nazwy:

ClassNotFound
EnumConstantNotPresent
IllegalArgument
IllegalAccess
IndexOutOfBounds
NegativeArraySize
NoSuchMethod
TypeNotPresent
UnsupportedOperation


Nazwy te są zwięzłe i opisowe. Nawet nie widząc kontekstu ich użycia, nie ma wątpliwości, że chodzi tu o złe wiadomości. Wyjątki .NET również używają negatywnych sformułowań, a także innych przerażających słów, takich jak Invalid czy Bad. Nie ma tu miejsca na nieporozumienia.

Dobra, ale co z poniższym, również pochodzącym z Javy?

ArrayStoreException
ClassCastException
InstantiationException
NullPointerException
NumberFormatException
SecurityException


Po usunięciu Exception otrzymujemy:

ArrayStore
ClassCast
Instantiation
NullPointer
NumberFormat
Security


Okej, niezbyt oczywiste. Ale czy problemem jest pozbycie się Exception? Czy może problem ujawnia się dopiero po jego usunięciu? Spróbujmy zmienić nazwy tych klas na nazwy problemów, które faktycznie sygnalizują:

IllegalArrayElementType
CastToNonSubclass
ClassCannotBeInstantiated
UnexpectedNullReference
InvalidNumberFormat
SecurityViolation


Powyższe nazwy są już dokładne i precyzyjne, oraz wskazują na rzeczywisty wyjątek, bez zbędnych niejasności i słów, które tylko zakłócają przekaz. Czy bezpieczeństwo jest uzasadnieniem dla stosowania wyjątku? Nie, ale naruszenie bezpieczeństwa już tak. Wyjątek dotyczy bezpieczeństwa, ale nazwa dalej nie wykorzystuje okazji, by być bardziej konkretną. I podobnie w przypadku, kiedy NumberFormat nie jest sama w sobie problemem, ale próba użycia ciągu znaków z InvalidNumberFormat już tak.

Precyzyjne nazwy

Czy warto robić wyjątek, jeśli wskaźnik (yyy, referencja) jest wartości null? Nie, w języku z referencjami, które mogą być ustawione na null, null jest akceptowalną wartością. Sygnalizowanym problemem jest albo to, że null został poddany dereferencji do wirtualnej maszyny Javy, takiej jak wywołanie metody przez referencję null, albo to, że null został przekazany do kodu, który uważa null za niepoprawny jako argument. Tak czy inaczej, null pojawia się tam niespodziewanie.

Bardziej konkretne nazewnictwo pomaga również podkreślić niejednoznaczności i niejasności, jak w tym przypadku: w praktyce Javy nie czyni się wyraźnego rozróżnienia między błędami sygnalizowanymi przez język, takimi jak dereferencja nulli, a błędami sygnalizowanymi przez kod, takimi jak odrzucenie argumentu null jako nieprawidłowy. W .NET, pierwszy przypadek jest wyrażony za pomocą NullReferenceException (tj. NullDereferenced), a drugi za pomocą ArgumentNullException (tj. InvalidNullArgument), który jest ArgumentException (tj. InvalidArgument). To czy kod Javy jest napisany w taki sposób, aby rzucać wyjątki NullPointerException lub IllegalArgumentException, zależy w dużej mierze od bazy danych, zespołu, osoby, fazy księżyca itp.

Tak jak nie oznaczasz czasowników za pomocą Verb lub rzeczowników za pomocą Noun, kiedy piszesz lub mówisz, tak nie ma też powodu, aby dodawać Exception na końcu nazwy klasy wyjątków. Raczej jest to uważane za code smell niż za praktykę, którą należy stosować, która często pozbawia programistów możliwości znalezienia lepszej nazwy.

Jakiś wyjątek od tej zasady? Klasa ogólna, która wskazuje, że jej potomkowie są wyjątkami: Exception. Ale jeszcze raz, to nie jest przyrostek: to jest cała jego nazwa i koncepcja, którą reprezentuje, więc może występują jakieś nieliczne wyjątki w nazewnictwie Exception.

Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>