Czy generujesz argumentexception lub argumentnullexception z prywatnych metod?

20

Właśnie przeglądałem jakiś kod, który napisałem jakiś czas temu i widzę, że mam kilka prywatnych metod, które generują argumentnullexceptions i / lub argumentexceptions, jeśli występują problemy z parametrami metod.

Wydaje mi się, że moim uzasadnieniem jest to, że pomaga w przyszłości sprawdzić aplikację, jeśli ktoś spróbuje „niewłaściwie użyć” tej metody w przyszłości. Jednak biorąc pod uwagę, że jest to metoda prywatna, a osoby, które mogą wywołać tę metodę, mogą zobaczyć powiązane komentarze i kod, po prostu nie trzeba jej rzucać. Na pewno nie zaszkodzi mieć je, choć dodaje bałaganu.

Mam wrażenie, że te wyjątki są na ogół bardziej przydatne w przypadku czegoś takiego jak interfejs API, który zostanie ujawniony publicznie.

Panie Moose
źródło

Odpowiedzi:

22

Zwykle w przypadku metod prywatnych nie rzucasz wyjątków, ponieważ podczas pisania programista powinien wiedzieć, jak i skąd wywołuje metodę. W związku z tym zmienne przekazane jako parametry do metody prywatnej należy sprawdzić poza metodą, to znaczy przed jej wywołaniem. Zgłaszanie „InvalidArgumentException” i inne tego rodzaju wyjątki są uważane za dobrą praktykę w przypadku metod publicznych (niezależnie od tego, czy piszesz „API”, czy nie).

W przypadkach, w których chcesz zgłosić „InvalidArgumentException”, warto wspomnieć, że Assertw Spring API dla Java istnieje klasa od wersji 1.1.2. Bardzo pomocne - przynajmniej dla mnie - było pisanie mniej kodu do przeprowadzania kontroli.

Możesz jednak użyć „aserts” do sprawdzenia parametrów metodami prywatnymi. To jest jeden z ich prawdziwych celów. Są to kolejne powody, aby z nich korzystać, sprawdź poniższy link, który również dokładnie wyjaśnia, kiedy używać twierdzeń, a kiedy wyjątków. Asertów nie należy włączać do kodu produkcyjnego, a kompilator domyślnie je usuwa. Są więc tym, czego szukasz: pomoc dla programistów, niewidoczna dla użytkowników. W Javie musisz użyć specjalnej flagi („-ea”), aby poinformować kompilator o włączeniu asercji. Możesz uznać ich za „debugujących” znajomych.

Oto jak korzystać z zapewnień w:

Jalayn
źródło
1
Apache Commons ma także klasę Validate, która działa podobnie do klasy Spring Assert
Rosa Richter,
@cantido tak i dodałbym również, że klasa Warunków wstępnych w Google Guava, która działa w ten sam sposób. Dzięki za informację :-)
Jalayn
2

Jak wszystko inne, zależy ...

Jeśli metody publiczne są prostymi opakowaniami, które wywołują metodę prywatną (na wzór prywatnej metody przeciążonej), sensowne może być zgłoszenie wyjątku w metodzie prywatnej zamiast sprawdzania każdej metody publicznej.

Zasadniczo, jeśli nie spełnia powyższej definicji, zwykle nie sprawdzałbym argumentów / nie rzucałbym wyjątku na metodę prywatną. Chociaż istnieją inne przypadki, generalnie robię to w metodzie prywatnej przed wykonaniem jakiejś kosztownej operacji, która może zakończyć się niepowodzeniem, jeśli argumenty są nieprawidłowe.

Ken Henderson
źródło
2

Zdaję sobie sprawę, że chociaż pytanie nie ma znacznika językowego, prawdopodobnie domyślnie mówi o „językach kawy”. Ale dla zachowania kompletności chciałbym wspomnieć o nieco rozbieżnym pozornym konsensusie w świecie C ++.

Są trzy rzeczy, którymi programiści C ++ zwykle będą się interesować:

  • Czy będzie miał zero kosztów ogólnych w zoptymalizowanych kompilacjach? (Czy można to „skompilować”?)
  • Czy mogę go użyć do pułapkowania debuggera w momencie wykrycia błędu?
  • Czy mogę go użyć do zgłaszania problemów z zadeklarowanymi funkcjami noexcept?

W przeszłości podchodziłem do pierwszego problemu pisząc taki kod

int
factorial(const int n)
{
  if (CHECK_ARGS)
    {
      if (n < 0)
        throw std::invalid_argument {"n < 0"};
    }
  int fac = 1;
  for (int i = 2; i <= n; ++i)
    fac *= i;
  return fac;
}

gdzie CHECK_ARGSjest #defined stałą czasową kompilacji, aby kompilator mógł całkowicie wyeliminować cały kod sprawdzający argumenty w zoptymalizowanych kompilacjach. (Nie twierdzę, że kompilacja czeków jest ogólnie dobrą rzeczą, ale uważam, że użytkownik powinien mieć możliwość ich skompilowania).

Nadal podoba mi się to rozwiązanie, że kod sprawdzający argumenty jest wyraźnie widoczny w grupie if. Jednak druga i trzecia kwestia nie są przez to rozwiązane. Dlatego teraz bardziej skłaniam się ku użyciu assertmakra do sprawdzania argumentów.

Standardy kodowania Boost są z tym zgodne:

Co z błędami programisty?

Jako programista, jeśli naruszyłem warunek korzystania z biblioteki, nie chcę odwijania stosu. To, czego chcę, to zrzut pamięci lub równoważny - sposób na sprawdzenie stanu programu dokładnie w momencie wykrycia problemu. To zwykle oznacza assert()lub coś w tym rodzaju.

Na CppCon'14 odbyła się bardzo interesująca rozmowa zatytułowana Defensive Programming Done Right ( część 1 , część 2 ). W pierwszej części swojego wystąpienia omawia teorię kontraktów i nieokreślonego zachowania. W drugiej części przedstawia bardzo dobrą propozycję systematycznego sprawdzania argumentów. Zasadniczo proponuje makra potwierdzające, które pozwalają użytkownikowi wybrać, jaki budżet (pod względem wykorzystania procesora) jest skłonny przekazać do biblioteki w celu sprawdzenia argumentów i sprawi, że biblioteka mądrze wykorzysta ten budżet. Dodatkowo użytkownik może zainstalować globalną funkcję obsługi błędów, która zostanie wywołana w przypadku wykrycia zerwanej umowy.

Jeśli chodzi o aspekt, że funkcja jest prywatna, nie sądzę, że oznacza to, że nigdy nie powinniśmy zmuszać jej do sprawdzania jej argumentów. Możemy bardziej ufać naszemu kodowi, aby nie naruszać kontraktu funkcji wewnętrznej, ale wiemy również, że nie jesteśmy doskonali. Sprawdzanie argumentów w funkcjach wewnętrznych jest tak samo pomocne w wykrywaniu własnych błędów, jak w funkcjach publicznych do wykrywania błędów w kodzie klienta.

5gon12eder
źródło
1

Rozważ następującą strukturę:

  1. Wewnętrzna logika: funkcja ta zakłada się, że jest wywoływana z poprawnymi parametrami, dlatego wykorzystuje twierdzenia do weryfikacji warunków wstępnych, końcowych i niezmiennych w celu sprawdzenia wewnętrznej logiki.

  2. Interfejs użytkownika wrapper: Funkcja ta owija funkcję wewnętrzną i wykorzystuje InvalidArgumentExceptions do obsługi błędnych wartości i poinformować użytkownika, aby poprawić jego wejścia: Assert(x).hasLength(4);, Assume(y).isAlphanumeric();, Assert(z).isZipCode();, Assume(mailAdress).matchesRegex(regex_MailAdress);, Reject(x).ifEmpty();, itd

  3. Opakowanie interfejsu wsadowego: Ta funkcja otacza funkcję wewnętrzną i wykorzystuje rejestrowanie, oznaczenia ważności i statystyki do obsługi błędnych wartości bez przerywania długotrwałego zadania. Oznaczenia mogą być później używane przez osobę sprawdzającą i czyszczącą bazę wyników.

  4. Opakowanie interfejsu wiersza poleceń: ta funkcja otacza funkcję wewnętrzną i ponownie prosi o ostatnie wejście.

Powinieneś używać zarówno - twierdzeń, jak i wyjątków - w różnych metodach dla różnych zadań. Należy oddzielić logikę wewnętrzną od sprawdzania parametrów. Porównaj to z rozdziałem Model, Widok, Kontroler.

Ralf K.
źródło
0

Są lepsze sposoby na uniknięcie zerowego sprawdzania referencji: skorzystaj z kontraktu kodu lub struktury AOP, aby sprawdzić za Ciebie. Umowa Google „c # code” lub „postsharp”.

Codism
źródło
Wydaje mi się, że moje pytanie dotyczyłoby również umów kodowych. Czy istnieje potrzeba sprawdzenia warunków wstępnych metody prywatnej (tj. W celu sprawdzenia w przyszłości lub powstrzymania cię przed strzelaniem sobie w stopę)?
Pan Moose