Při důsledném používání výjimek jsou stavy nepotřebné. Funkce vrací hodnotu. Procedura něco dělá, ale návratová hodnota je void. Místo chybových stavů se v obou případech použijí výjimky, které ten chybový stav popisují mnohem lépe, než pouhý návratový kód.
Lze to i otočit - při důsledném používání stavů jsou výjimky nepotřebné. Tím nechci říct, že výjimky jsou k ničemu, jen mi zrovna toto nepřijde jako ultimátní argument. Chybový stav může popsat návratový stav stejně dobře jako výjimka, jde jen o to udělat v tom systém, respektive "udělat dokumentaci".
Jistě, bývaly doby, kdy se výjimky nepoužívaly a také to fungovalo.
Ve výsledku je třeba ošetřit obojí - při použití výjimek vyřešit "chybu" a případně nahradit návratovou hodnotu nějakou smyslupnou - při použití návratových kódů je opět třeba to stejné. Upřímně moc ten rozdíl nevidím, jen se výrazně liší syntax, která je imho o kus komplikovanější. Nutno ale dodat, že nejvíce mám naprogramováno v Céčku a Haskellu, kdy v prvním výjimky úplně nejsou a v druhém jsou de facto jen v IO.
Ale toto je asi na trochu jinou diskusi (které se nebráním, ba naopak!), tady jsme trochu OT. 
Pokud z nějaké knihovny obdržím návratový kód, tak ho mohu zpracovat, ale také ignorovat. Když tento kód převedu na výjimku, což zpravidla znamená vytvořit objekt, do kterého se uloží všechny potřebné informace o chybě, tak takový objekt není možné ignorovat. Jinak propadne až ven z aplikace a vypíše se, co bylo příčinou pádu.
Syntaxe je záměrně jiná, výsledek je naopak mnohem jednodušší. Neošetřuje se jednotlivě každá výjimka, ale celá skupina. Není tedy třeba se po každém provedení příkazu ptát, zda proběhl v pořádku, což způsobovalo dlouhé špagety. Místo toho se udělá sekvence příkazů, ve které se výjimky ošetří společně. Není ani potřebné, aby výjimka byla ošetřena v metodě, ve které vznikla - je to spíš nežádoucí právě kvůli zjednodušení.
Ale já stále nechápu, proč by "chybový stav" měl být nějak privilegovaný oproti jakémukoli jinému "normálnímu" stavu. Řekl bych, že ten komunikační protokol tu byl zmíněn dost trefně - když mi nepřijde ACK, tak je to "chybový" stav, nebo "normální" stav? CRC error je chybový stav, nebo normální? NAK mám generovat v rámci "normální" reakce, nebo v nějakém catchi?
Takovouhle záležitost prostě vyřeším formou stavového automatu, v němž jsou si všechny stavy rovny, na každý se nějak reaguje, což má opět za následek změnu stavu. Když chci otevřít soubor, který neexistuje - proč bych to měl ošetřovat někde jinde? Když nedostanu požadovanou paměť, když v parseru detekuji syntax error, když uživatel nějakého kreslícího programu neuzavře uzavřenou křivku, když v nějakém formuláři klikne na cancel místo ok - jsou to chyby, nebo "normální" stavy?
Právě naopak - přes výjimky vzniká špagetový kód, protože narušují tok programu. Jsou to skoky, ale nejen uvnitř procedur, ale dokonce napříč mezi procedurami a úrovněmi! Už samotný název - výjimky... Co je na chybě výjimečného? Proč bych ji měl řešit někde jinde než tam, kde vznikla? V mém kódu jsou takové situace tak výjimečné, že přesně zapadají do "tolerovaného" použití
goto.
Pokud chci ošetřovat chyby tak, jak se má, tak mi všelijaké ty try-catche akorát zkomplikují práci a znepřehlední program. Pokud na to kašlu a chyba mi propadne někam dál, těžko se z takového místa na ni dá adekvátně reagovat - co bude dělat nějaká abstraktní vrstva s low-level chybou? To už je celé pak spíš debugovací nástroj.