7.05.2008

Specyfikacje throw(...) i bezpieczeństwo wyjątków

Czy pisać specyfikacje throw()? To pytanie zadawało sobie wielu ekspertów. Oprę się na wnioskach Herba Suttera (stąd):
  • Moral #1: Never write an exception specification.
  • Moral #2: Except possibly an empty one, but if I were you I’d avoid even that.
Czyli nie piszemy specyfikacji throw()... No to w takim razie olewać co funkcje rzucają? Lepiej nie. Sensowne wydaje się pisanie tego w komentarzu.
Warto zawrzeć informacje:
  • jeśli to funkcja wirtualna, to warto napisać, jakiej klasy funkcję zastępuje /** @note @e overrides Klasa */
  • jeśli funkcja nie jest wirtualna i przesłania inną, również warto to napisać /** @note @e hide Klasa */
  • co funkcja może rzucać /** @throws co kiedy */
  • jak się zachowa w razie wyjątku, czyli bezpieczeństwo wyjątków /** @note @e safety --- */ (o tych --- za chwilę)
Ponownie powołam się na Herba Suttera, który fajnie opisał tę sprawę.
Opis bezpieczeństwa w razie wyjątku składa się z 3 części:
  • Atomicity - niepodzielność. 3 możliwości: 1) [-] brak, 2) [a] niepodzielność - czyli całość albo wcale, 3) [A] na pewno całość - czyli gwarancja powodzenia operacji.
  • Consistency - spójność. 3 możliwości: 1) [-] brak, 2) [c] w razie niepowodzenia system pozostaje spójny, 3) [C] niezależnie od powodzenia lub nie, system pozostaje spójny. W kontekście programowania, ma sens rozpatrywanie tylko możliwości 3) [C] - każda funkcja musi po sobie pozostawiać program w stanie spójnym, niezależnie od tego czy wystąpił błąd, czy nie.
  • Isolation - brak skutków ubocznych. 3 możliwości: 1) [-] skutki uboczne występują niezależnie od powodzenia operacji, 2) [i] skutki uboczne występują tylko w przypadku powodzenia operacji, 3) [I] całkowity brak skutków ubocznych
Sutter pisze jeszcze o Durability - jak to z modelu baz danych - ale my się tym nie będziemy zajmować. Rzadko w programowaniu będziemy pilnować, czy wynik działania operacji przetrwa w razie jakichś kataklizmów typu upadek systemu...
Oznaczenia: Każdą z wymienionych powyżej 3 części opisu bezpieczeństwa będziemy oznaczać zależnie od poziomu bezpieczeństwa przy pomocy 3 znaków, z których każdy będzie odpowiadał jednej z części.
Przykłady:
  • /** @note @e safety AC- */ oznacza, że operacja zawsze się powiedzie (ekwiwalent throw()); może emitować skutki uboczne.
  • /** @note @e safety -CI */ oznacza, że w razie niepowodzenia, obiekty pozostaną w stanie spójnym[C], ale nie wiadomo jakim; niezależnie od powodzenia, nie wystąpią żadne skutki uboczne.
  • /** @note @e safety aCi */ oznacza silną gwarancję Abrahama, czyli w razie niepowodzenia system będzie w takim stanie, jakby wywołanie funkcji nie nastąpiło; dotyczy to także skutków ubocznych.

Brak komentarzy: