Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Jak teda s těma výjimkama v busines logice?

| Moje práce

Na develu se rozvíjí debaty o tom, zda je zpátečnické odrazovat od vyhazování výjimek. Já patřím mezi zpátečníky. :) Tady je moje odpovědˇ, kterou je IMO škoda nemít veřejně dostupnou, takže ji publikuju i tady na blogu.

Na začátek je dobré se opřít o nějakou autoritu, proto tady pro začátek ocituju .net Framework Design Guidelines doporučení k výjimkám:

  1. Nevracejte chybové kódy. K tomuto účelu jsou určeny výjimky.
  2. Vyhazujte výjimky pokud dojde k běhové chybě. Pokud člen nemůže úspěšně provést úkol za nějž nese zodpovědnost, mělo by to být považováno za běhovou chybu a měla by být vyhozena výjimka.
  3. Pokud se dostanete do stavu, kterému nepomůže žádné ošetření výjimky, proveďtě ukončení procesu pomocí Environment.Fa­ilFast().
  4. Nepoužívejte výjimky k formálnímu řízení toku programu. S výjimkou systémových selhání by vždy měl existovat způsob jak se vyhnout vyhazování výjimek.
  5. Zvažte důsledky vyhozené výjimky na výkon.
  6. Vždy zdokumentujte všechny výjimky vyhazované veřejnými členy z důvodu porušení jejich kontraktu.
  7. Nemějte veřejné členy, které mají nebo nemají vyhazovat výjimky na základě nějakého parametru. Type GetType(string name, bool throwOnError)
  8. Nemějte veřejné členy které vracejí výjimku jako návratovou hodnotu nebo out parametr.
  9. Nastavte všechny relevatní vlastnoti vyhazované výjimky.
  10. Preferujte užití systémových výjimek před vlastními.
  11. Vlastní výjimky vyhazujte v případě, že vyžadují odlišný způsob ošetření než existující typy výjimek.
  12. Nikdy nevytvářejte vlastní typ výjimky, jen proto, aby váš team měl nějakou vlastní výjimku.
  13. Vyhazujte co nejkonkrétnější výjimky.
  14. Nevracejte chybové kódy z obav o výkonostní dopady při vyhazování výjimek.
  15. Používejte vzory jako TryParse, které snižují negativní dopady na výkon způsobené vyhazováním výjimek.

Z texu je zřejmé, že vyhazování výjimek má své opodstatnění. Důležité je však v jakých scénářích se výjimky vyhazovat mají a v jakých ne.

Často se tu objevují příklady s validací vstupních hodnot nebo třeba autentikace uživatele. Začněme validátorem:

Primární zodpovědností validátoru je validovat hodnoty. Je nevalidní hodnota příčinou nemožnosti korektní práce validátoru (důvodem k vyhození výjimky)? Není (viz bod 2). Můžeme to řešit vracením primitivy jako je bool. Já si však myslím, že taková hodnota je nedostatečná. Potřebujeme výsledek validace zpropagovat někam, kde se s ním bude dále pracovat. Vyhodíme tedy výjimku a tu pak odchytíme? Tím se ale dostáváme k bodu 4. Lepším řešením je definovat komplexní návratový typ:

type ValidationResult =
  | Valid
  | Invalid of ValidationError list

Pokračujme s autentikací uživatele. Běžnou praxí je vyhazování SecurityException pokud se nepovede uživatele autentikovat. Výjimka se pak nechá probublat do UI vrstvy, kde se ošetří a uživateli se zobrazí hláška, že zadal špatné jméno nebo heslo. Opět však jde o porušení 4. bodu. Autentikace tam může vracet opět bool nebo rovnou instanci User a v případě nevydařené autentikace null. Tím se zbavíme bezpečnostních výjimek, ale zase si zavedeme potenciální chybu v podobě NRE… Proto si opět myslím, že by se měl vracet adekvátní typ:

type AuthenticationResult =
  | NotAuthenticated
  | Authenticated of User

Autentikační modul samozřejmě může vyhazovat výjimky, ale většinou budou spojené s nefungující infrastrukturou (LDAP, SQL nebo jinej auth provider), to je totiž validní důvod, proč nemůže autentikace doběhnout.

Našli jste v článku chybu? Máte námět na reportáž? Založte mi ticket.