2008-10-21 10 views
9

Czy zawsze należy sprawdzać parametry i zgłaszać wyjątki w .NET, gdy parametry nie są zgodne z oczekiwaniami? Na przykład. puste obiekty lub puste łańcuchy?Zawsze sprawdzaj parametry i rzucaj wyjątki

Zacząłem to robić, ale potem pomyślałem, że to rozwali mój kod dużo jeśli zrobi się to na każdej metodzie. Czy powinienem sprawdzić parametry dla metod prywatnych i publicznych?

W końcu generuję wiele wyjątków ArgumentNullException ("name"), mimo że kod obsługujący wyjątek nie może zrobić niczego innego programowo, ponieważ nie ma gwarancji, że "nazwa" nie zmieni się w przyszłości.

Zakładam, że te informacje są przydatne podczas przeglądania dziennika pełnego informacji o wyjątku?

Czy to najlepsza praktyka, aby zawsze "gładzić na najgorsze".

Odpowiedz

3

To zależy od konsumenta twojej klasy/metody. Jeśli wszystko jest w środku, powiedziałbym, że to nie jest tak ważne. Jeśli masz nieznanych/3rd-party konsumentów, to tak, chciałbyś rozległe sprawdzanie.

1

Moja filozofia w tej sytuacji polega na powiadamianiu i kontynuowaniu działań (jeśli dotyczy). Pseudo-kod byłoby:

if value == not_valid then 
#if DEBUG 
    log failure 
    value = a_safe_default_value 
#elsif RELASE 
    throw 
#endif 
end 

W ten sposób można łatwo iteracyjne w trakcie rozwoju i mają użytkownicy przetestować aplikację przy czym nie jest to akt frustracji.

5

Nic nie jest gorsze niż ściganie komunikatu "odniesienie do obiektu nie ustawionego na instancję obiektu". Jeśli Twój kod jest wystarczająco złożony, trudno jest przewidzieć, co jest wadliwe - szczególnie w systemach produkcyjnych, a zwłaszcza w rzadkich warunkach brzegowych. Wyraźny wyjątek stanowi długą drogę w rozwiązywaniu problemów. To jest ból, ale jest to jedna z tych rzeczy, których nie będziesz żałować, jeśli zdarzy się coś złego: .

12

Moje dwa centy: Wszystkie twoje publiczne metody powinny zawsze sprawdzać ważność przekazywanych parametrów. Jest to zazwyczaj nazywane "programowaniem według umowy" i jest świetnym sposobem na szybkie wychwytywanie błędów i unikanie ich propagowania przez prywatne funkcje które wielu argumentuje (w tym mnie) nie powinno być bezpośrednio testowane w jednostce. Jeśli chodzi o odrzucanie wyjątków, jeśli funkcja lub program nie może poprawić samego błędu, powinien on rzucić wyjątek, ponieważ został zgłoszony do stanu nieprawidłowego.

6

Dla metod publicznych, a następnie tak: zdecydowanie konieczne jest sprawdzenie argumentów; dla połączeń wewnętrznych/prywatnych, Eric Lippert może zakwalifikować je jako "boneheaded" (here); jego rada nie polega na złapaniu ich ... naprawienie kodu!

Aby uniknąć nadymania kodu, warto rozważyć opcję AOP, na przykład postsharp. Aby to zilustrować, Jon Skeet ma atrybut postsharp, który sprawdza null-argument, here. Następnie (zacytować jego przykładem), można po prostu przypisują metody:

[NullArgumentAspect("text")] 
public static IEnumerable<char> GetAspectEnhancedEnumerable(string text) 
{ /* text is automatically checked for null, and an ArgumentNullException thrown */ } 

Kolejna przydatna sztuczka tutaj mogłoby być rozszerzenie metod; Metody rozszerzeń mają ciekawą funkcję, dzięki której możesz wywoływać je w instancjach o wartości zero ...dzięki czemu można wykonać następujące czynności (w przypadku gdy stosowanie leków generycznych zamiast „obiekt” jest więc przypadkiem nie nazywają go przez boks typu wartości):

static void ThrowIfNull<T>(this T value, string name) where T : class 
{ 
    if (value == null) throw new ArgumentNullException(name); 
} 
// ... 
stream.ThrowIfNull("stream"); 

I można zrobić podobne rzeczy z uliczne o zasięgu itp

1

podejście biorę jest sprawdzenie argumentów i rzucać wyjątki na publicznie widocznych członków - wszelkie publicznie widoczny znaczy poza granice montażowe (więc każdy public, protected lub protected internal metoda na klasy public tego produktu. jest tak, ponieważ ty (zazwyczaj) projektujesz zespół, aby działał jako autonomiczna jednostka, więc wszystko, co mieści się w granicach zespołu, powinno być zgodne z regułami Cokolwiek jeszcze tam będzie.

Dla każdego niepublicznie widocznego członka (tj. internal lub private członków lub klas) używam Debug.Assert, aby wykonać sprawdzenie zamiast tego. W ten sposób, jeśli którykolwiek z dzwoniących w zespole naruszy kontrakt, dowiesz się od razu w czasie projektowania/testowania, ale nie masz żadnych kosztów wydajności w ostatecznym wdrożonym rozwiązaniu, ponieważ te instrukcje są usuwane w kompilacjach RELEASE.

0

Cóż, to zależy. Jeśli twój kod i tak odbierze wartość zerową, a następnie wyrzuci wyjątek, prawdopodobnie lepiej będzie mieć pewność, że masz sensowny kod oczyszczający. Jeśli nie można tego wykryć w inny sposób, czyszczenie może być długotrwałe, lub może być wywołanie poza procesem (np. Baza danych), prawdopodobnie lepiej nie próbować zmienić świata niepoprawnie, a następnie zmienić go z powrotem.

1

Wiele z moich doświadczeń dotyczy systemów ze stosunkowo ograniczonym dostępem, w których niepożądane nadpisywanie kodu jest niepożądane. Tak więc moim własnym instynktem jest użycie twierdzeń typu debug-only lub całkowite ich pominięcie. Można mieć nadzieję, że wszelkie potencjalne nieprawidłowe dane pojawią się podczas testowania wywołującego, który podaje złe wartości, więc pod warunkiem, że testujesz w trybie debugowania, a także w trybie zwolnienia, zobaczysz diagnostykę. W przeciwnym razie debugujesz awarię, kiedy to się wydarzy.

Jeśli rozmiar kodu i wydajność nie mają znaczenia (a prawie we wszystkich kodach proste sprawdzenie wartości zerowej lub zakresu nie wpływa na wydajność), to im więcej twierdzi, że zostawisz kod w trybie zwolnienia, tym większa szansa mieć możliwość diagnozowania usterek bez potrzeby ponownego tworzenia błędu w trybie testowym. To może być duża oszczędność czasu. W szczególności, jeśli Twój produkt jest biblioteką, znaczna część raportów "błędów" jest spowodowana błędami klientów, więc żadna ilość testów przed wydaniem nie może ich powstrzymać w środowisku naturalnym. Im szybciej udowodnisz klientowi, że jego kod jest błędny, tym szybciej mogą go naprawić i możesz wrócić do znajdowania własnych błędów.

Mimo to w języku C/C++ stwierdzam, że szczególny przypadek sprawdzania wskaźników zerowych jest jedynie pomniejszą pomocą. Jeśli ktoś poda ci wskaźnik, wtedy warunek pełnej ważności nie jest "nie może być zerowy". Musi wskazywać na pamięć, którą można odczytać (być może również można zapisać) przez bieżący proces do pewnego rozmiaru i zawiera poprawny typ obiektu, prawdopodobnie w pewnym podzbiorze wszystkich możliwych stanów. Nie musi zostać zwolniony, aby nie zostać usuniętym przez przepełnienie bufora w innym miejscu, prawdopodobnie nie być jednocześnie modyfikowanym przez inny wątek, itp. Nie będziesz testował tego wszystkiego przy wprowadzaniu metody, więc nadal możesz pominąć nieprawidłowe parametry. Wszystko, co prowadzi ciebie lub innych programistów do myślenia "ten wskaźnik nie jest pusty, więc musi być ważny", ponieważ przetestowałeś tylko jedną małą część warunku ważności, jest mylący.

Jeśli w ogóle przejeżdżasz przez wskaźnik, oznacza to, że jesteś już na terytorium, na którym musisz zaufać rozmówcy, aby nie dawał ci śmieci. Odrzucenie jednej konkretnej instancji śmieci nadal pozostawia Tobie zaufanie, że osoba dzwoniąca nie da ci żadnego z niezliczonych śmieci, które mogą wykreować, które są trudniejsze do wykrycia.Jeśli zauważysz, że wskaźniki zerowe są typowym śmieciem od twoich konkretnych rozmówców, to na wszelki wypadek testuj je, ponieważ oszczędza czas diagnozowania błędów w innym miejscu systemu. To zależy od oceny, czy znalezienie błędów w kodzie wywołującym z objawem "przekazuje mi wskaźnik zerowy" jest warte wzdrygnięcia twojego własnego kodu (być może w rozmiarze binarnym, a na pewno w źródle): jeśli takie błędy są rzadkie, to ty " Prawdopodobnie tracisz czas i sprawdzasz, czy nieruchomość jest dla nich sprawdzana.

Oczywiście w niektórych językach nie można przejść do wskaźnika, a osoba wywołująca ma ograniczone możliwości uszkodzenia pamięci, więc jest mniej miejsca na śmieci. Ale na przykład w Javie przekazywanie niewłaściwego obiektu jest nadal częstszym błędem programowania niż błędnym zerem. Wartości null są zwykle dość łatwe do zdiagnozowania, jeśli zostawisz je w środowisku wykonawczym do wykrycia i spojrzysz na stos. Tak więc wartość sprawdzania zerowego jest nawet ograniczona. W C++ i C# można używać pass-by-reference, gdzie wartości null byłyby zabronione.

To samo dotyczy innych konkretnych nieprawidłowych danych wejściowych, na które można przetestować i dowolnego języka. Pełne testowanie przed i po kondycji (jeśli to możliwe) jest oczywiście inną kwestią, ponieważ jeśli możesz przetestować całą umowę na połączenie, to jesteś na dużo silniejszym gruncie. A jeśli możesz użyć tkactwa lub czegokolwiek, by uzyskać umowy bez dodawania kodu źródłowego samej funkcji, to nawet lepiej.