W różnych dyscyplinach inżynierii oprogramowania istnieje wiele filozofii dotyczących tego, jak biblioteki powinny radzić sobie z błędami lub innymi wyjątkowymi warunkami. Kilka z tych, które widziałem:
- Zwraca kod błędu z wynikiem zwróconym przez argument wskaźnika. To właśnie robi PETSc.
- Zwraca błędy według wartości wartownika. Na przykład malloc zwraca NULL, jeśli nie może przydzielić pamięci,
sqrt
zwróci NaN, jeśli podasz liczbę ujemną itp. Takie podejście jest stosowane w wielu funkcjach libc. - Rzuć wyjątki. Używane w deal.II, Trilinos itp.
- Zwraca typ wariantu; na przykład funkcja C ++, która zwraca obiekt typu,
Result
jeśli działa poprawnie i używa typuError
do opisania, w jaki sposób nie powróciłbystd::variant<Error, Result>
. - Użyj assert i crash. Używany w p4est i niektórych częściach igraph.
Problemy z każdym podejściem:
- Sprawdzanie każdego błędu wprowadza wiele dodatkowego kodu. Wartości, w których wynik zostanie zapisany, muszą zawsze zostać zadeklarowane jako pierwsze, wprowadzając wiele zmiennych tymczasowych, których można użyć tylko raz. To podejście wyjaśnia, jaki błąd wystąpił, ale może być trudno ustalić, dlaczego lub, w przypadku głębokiego stosu wywołań, gdzie.
- Przypadek błędu można łatwo zignorować. Co więcej, wiele funkcji nie może mieć nawet znaczącej wartości wartownika, jeśli cały zakres typów danych wyjściowych jest wiarygodnym wynikiem. Wiele takich samych problemów jak # 1.
- Możliwe tylko w C ++, Python itp., Nie w C ani Fortran. Może być naśladowany w C za pomocą setjmp / longjmp sorcery lub libunwind .
- Możliwe tylko w C ++, Rust, OCaml itp., Nie w C ani Fortran. Można naśladować w C za pomocą makro-magii.
- Prawdopodobnie najbardziej informacyjny. Ale jeśli zastosujesz to podejście, powiedzmy, dla biblioteki C, dla której następnie napiszesz opakowanie Pythona, głupi błąd, taki jak przekazanie tablicy spoza zakresu do tablicy, spowoduje awarię interpretera Pythona.
Wiele porad w Internecie na temat obsługi błędów napisano z punktu widzenia systemów operacyjnych, programowania wbudowanego lub aplikacji internetowych. Awarie są niedopuszczalne i musisz martwić się o bezpieczeństwo. Aplikacje naukowe nie mają tych problemów w prawie takim samym stopniu, jeśli w ogóle.
Inną kwestią jest to, jakie rodzaje błędów można naprawić, czy nie. Awarii malloc nie można odzyskać, aw każdym razie zabójca braku pamięci w systemie operacyjnym dostanie się do niego, zanim to zrobisz. Indeks poza zakresem dla rozmiaru tablicy również nie jest możliwy do odzyskania. Dla mnie, jako użytkownika, najprzyjemniejszą rzeczą, jaką może zrobić biblioteka, jest awaria z informacyjnym komunikatem o błędzie. Z drugiej strony, niepowodzenie, powiedzmy, iteracyjnego liniowego solwera w zbieżności można odzyskać, stosując bezpośredni solwery faktoryzacji.
W jaki sposób biblioteki naukowe powinny zgłaszać błędy i oczekiwać ich usunięcia? Zdaję sobie oczywiście sprawę, że zależy to od języka, w którym biblioteka jest zaimplementowana. Ale o ile wiem, w przypadku każdej wystarczająco użytecznej biblioteki ludzie będą chcieli nazywać ją z innego języka niż ten, w którym jest zaimplementowana.
Nawiasem mówiąc, myślę, że podejście nr 5 można znacznie ulepszyć dla biblioteki C, jeśli zdefiniuje wskaźnik funkcji globalnego modułu obsługi asercji jako część publicznego API. Moduł obsługi asercji domyślnie raportuje numer pliku / linii i ulega awarii. Powiązania C ++ dla tej biblioteki zdefiniowałyby nowy moduł obsługi asercji, który zamiast tego zgłasza wyjątek C ++. Podobnie powiązania w języku Python definiują moduł obsługi asercji, który korzysta z interfejsu API CPython w celu zgłoszenia wyjątku w języku Python. Ale nie znam żadnych przykładów, które przyjmują takie podejście.