Słyszałem, że zalecane jest sprawdzenie poprawności argumentów metod publicznych:
- Czy należy sprawdzić wartość zerową, jeśli nie spodziewa się wartości zerowej?
- Czy metoda powinna zweryfikować swoje parametry?
- MSDN - CA1062: Sprawdź poprawność argumentów metod publicznych (mam tło .NET, ale pytanie nie jest specyficzne dla C #)
Motywacja jest zrozumiała. Jeśli moduł zostanie użyty w niewłaściwy sposób, chcemy natychmiast rzucić wyjątek zamiast nieprzewidzianego zachowania.
Niepokoi mnie to, że błędne argumenty nie są jedynym błędem, który można popełnić podczas korzystania z modułu. Oto kilka scenariuszy błędów, w których musimy dodać logikę sprawdzania, jeśli postępujemy zgodnie z zaleceniami i nie chcemy eskalacji błędów:
- Połączenie przychodzące - nieoczekiwane argumenty
- Połączenie przychodzące - moduł jest w złym stanie
- Połączenie zewnętrzne - zwrócono nieoczekiwane wyniki
- Połączenie zewnętrzne - nieoczekiwane skutki uboczne (podwójne wejście do modułu wywołującego, przełamanie innych stanów zależności)
Próbowałem wziąć pod uwagę wszystkie te warunki i napisać prosty moduł z jedną metodą (przepraszam, nie-C # faceci):
public sealed class Room
{
private readonly IDoorFactory _doorFactory;
private bool _entered;
private IDoor _door;
public Room(IDoorFactory doorFactory)
{
if (doorFactory == null)
throw new ArgumentNullException("doorFactory");
_doorFactory = doorFactory;
}
public void Open()
{
if (_door != null)
throw new InvalidOperationException("Room is already opened");
if (_entered)
throw new InvalidOperationException("Double entry is not allowed");
_entered = true;
_door = _doorFactory.Create();
if (_door == null)
throw new IncompatibleDependencyException("doorFactory");
_door.Open();
_entered = false;
}
}
Teraz jest bezpiecznie =)
To dość przerażające. Ale wyobraź sobie, jak przerażający może być w prawdziwym module z dziesiątkami metod, złożonym stanem i wieloma zewnętrznymi wywołaniami (cześć, miłośnicy wstrzykiwania zależności!). Zauważ, że jeśli wywołujesz moduł, którego zachowanie może zostać zastąpione (klasa nie zapieczętowana w języku C #), wówczas wykonujesz wywołanie zewnętrzne, a konsekwencje nie są przewidywalne w zakresie wywołującego.
Podsumowując, jaki jest właściwy sposób i dlaczego? Jeśli możesz wybrać jedną z poniższych opcji, odpowiedz na dodatkowe pytania.
Sprawdź użycie całego modułu. Czy potrzebujemy testów jednostkowych? Czy są przykłady takiego kodu? Czy zastrzyk zależności powinien być ograniczony w użyciu (ponieważ spowoduje to więcej logiki sprawdzania)? Czy nie jest praktyczne przeniesienie tych czeków na czas debugowania (nie są uwzględniane w wersji)?
Sprawdź tylko argumenty. Z mojego doświadczenia wynika, że sprawdzanie argumentów - szczególnie sprawdzanie wartości zerowej - jest najmniej skutecznym sprawdzaniem, ponieważ błąd argumentu rzadko prowadzi do skomplikowanych błędów i eskalacji błędów. Przez większość czasu dostaniesz NullReferenceException
w następnym wierszu. Dlaczego więc sprawdzanie argumentów jest tak szczególne?
Nie sprawdzaj użycia modułu. To dość niepopularna opinia, czy możesz wyjaśnić, dlaczego?