Jak správně řešit výjimky?

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Jiří Nápravník
Člen | 710
+
0
-

Jak správně na výjimky? Jak řešíte případ, když ukládáte např. něco do databáze. Může nastat logicky při ukládání chyba, někde v modelu či až v PDO. Jak to řešíte?

  1. Pedpokládáte, že chyba nastane výjímečně a spolíháte tak na úspěch. A když už se to náhodou stane, tak tu máme errorpresenter a ten musí návštěvníkům stačit?
  2. Nebo je zachytáváte? Nastavíte adekvátní flashmessage a přesměrujete?

Já se je snažím zachytávat, ale přeci jen těch případů, kdy něco dělám s databází je mnoho a je to pomalu na upsání se. Nemluvě o tom, že když tu výjimku už odchytím, tak jí stejně musím zalogovat, ať se o ní také nějak dozvím – či-li musím volat Debugger:log, nicméně zároveň by bylo pěkné aby se mi v development módu vyhazovala laděnka, a ne se potají logovala (což Debugger::log dělá). Je to nějak řešitelné?

No a pak je tu ta otázka jde tohle vše nějak usnadnit? Koukal jsem, že někteří používají onError nad $application, ale není to přeci jen moc overkill a ne zrovna dvakrát hezké, alespoň dle mě, nebo jaký na to máte názor?

Usnadnit to chci z toho důvodu, že teď mi sice stačí jen nastavit flashmessage a zavolat Debugger::log, ale pokud budu chtít třeba nějakou tu kontrolu, zda jsem na development mašině a chci tedy zobrazit laděnku v celé kráse, tak už je to více řádku a copy pastovat to vše je hodně bad practise…

Milo
Nette Core | 1283
+
0
-

@Jiří Nápravník: Debugger se chová tak jak popisuješ. V devel módu vyskočí laděnka, v produkci se jen loguje. Pokud ale vyvíjíš na serveru a přistpuješ z jiného PC jako klient, musíš si nastavit, pro jaké IP klienta bude devel mód. Např: Debugger::enable('192.168.0.10').

Jiří Nápravník
Člen | 710
+
0
-

Filip Procházka napsal(a):

http://www.youtube.com/watch?…

Díky Filipe za odkaz na super přednášku. Či-li pokud to dobře chápu, tak v tomhle případě odchytávat vyjímku nemá smysl, jelikož je to System Failure. Protože sloupečky mám dobře namapované (a pokud ne tak je to usage exception, která musím opravit) a jinak hrozí jen nějaké selhání databáze či podobně a to neřešit… (beru pro jednoduchost, klasické ulžení, kde nejsou žádné unique apod)

kudlajz
Člen | 70
+
0
-

Podle me je asi dobre try – catchovat kazdy update, ci insert do databaze a pri chybe ji zalogovat a zobrazit uzivateli hlasku, at to napr. zkusi znovu. Slozitejsi operace davat do transakci a pri chybe rollbackovat :)

Filip Procházka
Moderator | 4668
+
0
-

Tam kde je očekávané že může nastat jiná než SQL syntax chyba (což by se stávat nemělo nikdy) tak odchytávat. Pokud očekáváš connection chyby tak to taky nemá smysl chytat, pokud ti nejede databáze nemá smysl se snažit a nechal bych to padat na error presenter do pětikila.

Jiří Nápravník
Člen | 710
+
0
-

Filip Procházka napsal(a):

Tam kde je očekávané že může nastat jiná než SQL syntax chyba (což by se stávat nemělo nikdy) tak odchytávat.

A co konkrétně tedy myslíš „jinou než syntax chybou“? kudlajz nade mnou píše odchytávat každý update/insert apod. ale já pravě od Honzy tvrdíka pochopil spíše, že to ne, jen v případě pokud nějakou chybu očekávám, jako třeba duplicate entry apod. Prootže pokud vyloučím syntax errory tak u standardnich insertů, erroru do jedné tabulky, v podstatě pak hrozí opravdu snad jen spadlá databáze, což nemá smysl chytat.

Můžeme být úplně konkrétní, když používám tvoje Kdyby\Doctrine mám odchytávat DBALException, při volání save nad jednou entitou, nebo při flushy? Pokud neočekávám nějakou specialitu s duplicate, či optimistic lockingem…

Filip Procházka
Moderator | 4668
+
0
-

jen v případě pokud nějakou chybu očekávám, jako třeba duplicate entry apod.

^this

hrozí opravdu snad jen spadlá databáze, což nemá smysl chytat.

^this

Můžeme být úplně konkrétní, když používám tvoje Kdyby\Doctrine …

Chytám jenom NoResultException, pokud by to operace (select) mohla vyhodit. Jinak vůbec nic.

Jiří Nápravník
Člen | 710
+
0
-

Super, jasné, díky!

Jiří Nápravník
Člen | 710
+
0
-

Ještě si dovolím vytáhnout tohle témá s příbuznou věcí.

Řekněme, že v modelových třídách mám
findById($id) – nedělá nic jiného než zavolá $this->dao->find($id) a vráti
a potom
deleteById($id) – maže podle id
delete($entity) – maže přímo entitu

Z presenteru chci volat logicky přes $this->facade->deleteById()

a v tom deleteById() mám
find – to mi vrátí entitu – kterou pošlu do delete($entity), která jen prostě smaže.

No a teď co udělat pokud člověk zadá pokyn pro mazání neexistujícího id. Teď mi findById vrací v tomhle případě null. Mám v delete a případně i v dalších podobných metodách kontrolovat všude, zda to náhodou není null a kdyžtak informovat. Nebo prostě u toho findById() vyhodit výjimku o nenalezení, která probublá k presenteru, kde si to odchytím a dám E404?

Snazší je pro mě bez pochyby exception, ale vidím i názory, vracet null, vždy překontrolovat (tady vidím hlavní problém – duplikace a hlavně zapomenutí kontroly), a pak vrátit případně false a kdyžtak nasměrovat e404. Jak to řešíte?

mr.mac
Člen | 87
+
0
-

Dle mého názoru se jedná o chybu, která může vzniknout a user by neměl být „odstaven“ obecným hlášením E404, i správce systému z něj nic nevyčte, pokud mu user chybu nahlásí. Já tyto chyby ošetřuji a podávám userovi zprávu (je to ale dřina).

Jiří Nápravník
Člen | 710
+
0
-

No tak, ta stránka neexistuje, takže 404 je dle mě na místě. Ale o to mi teď moc nejde, mě jde spíše o to, jak to obstarat z pohledu modelu. Jestli mám u toho findu vyhazovat exception a nechat probublat až na presenter, kde to ošetřím. Nebo zda find vyhodit null, a u delete/update vracet false a pak to ošetřit. Mě se více líbí výjimky na othle, ale nevím jak to řeší profíci.

Milo
Nette Core | 1283
+
0
-

Jestli se metoda jmenuje findById(), vracel bych NULL. Kdyby se jmenovala getById() vyhodil bych nějakou NotFound vyjímku.

Šaman
Člen | 2666
+
0
-

Milo napsal(a):

Jestli se metoda jmenuje findById(), vracel bych NULL. Kdyby se jmenovala getById() vyhodil bych nějakou NotFound vyjímku.

Osobně používám konvenci, že find* metoda vrací pole, nebo kolekci (takže v tomto případě prázdnou), get* vrací konkrétní záznam, nebo NULL.

V každém případě bych při nevyhledání záznamu výjimku neházel, protože nikdy nevíš, jestli budeš hledat klíčový prvek (který pokud nenajdeš, budeš házet 404) anebo můžeš hledat něco nedůležitého a neexistenci záznamu budeš řešit třeba jen nezobrazením nějaké sekce v šabloně.

Milo
Nette Core | 1283
+
0
-

Anebo si také můžeš napsat dvě metody, jedna vyjímku vyhazovat bude, druhá ne. Nebo druhý parametr findById($id, $require = TRUE).

Editoval Milo (23. 12. 2013 20:12)

Jiří Nápravník
Člen | 710
+
0
-

Díky za reakce.

Jde mi o „akademicky nejčistší“ řešení a zatím to vypadá, že vede spíše názor vracet NULL a kontrolovat. Osobně jsem čekal spíše opačný názor, už jenom kvůli tomu opomenutí kontroly na NULL.

No pak je tu taky ta varianta, přejmenovat find na get, protože já v podstatě to findById, používám jako vy getById :-)

Nicméně pokud tedy teď ohlédneme od nějakého názvosloví. Tak jak vy řešíte takový klasický use case na smazání položky. Konkrétně tu fázi kdy vyhledávate entitu ke smazání/update. Vyhazujete tam výjimku nebo kontrolujete na null?

Šaman
Člen | 2666
+
0
-

Konvence get/find, findOne/find, apod… jsou na tobě. Já se tu svoji naučil používat v ORM od Petra Procházky a vyhovuje mi. Viděl jsem ale různé a pokud nepracuješ v týmu (pak je to na domluvě), tak je to jen na tobě.

Jde o to, že řešit co se má stát při nenačtení záznamu by měl až presenter, protože otázka, jestli zobrazit chybovou stránku, nebo jen něco tiše nevypsat je dle mého prezenční logika. Varianta s parametrem $required vypadá použitelně. Já osobně si kontroluji výsledek a rozhoduji se podle toho, zda je NULL.

Jiří Nápravník
Člen | 710
+
0
-

Určitě souhlasím, že zobrazení chybové stránky je věc presenteru. Taky to nikam do modelu netahám. Jde mi jen o to zda dát vědět presenteru v tomhle případě přes vrácení null (nebo v případe delete/update spíše false) nebo přes výjimku, kterou vyhodí už find.

duke
Člen | 650
+
0
-

Já to vidím tak, že nízkoúrovňové služby (např. repozitáře – přesněji DAO) by v metodách jako findFoo či getBar při nenalezení záznamu výjimky vyhazovat neměly (spíše bych vracel NULL, prázdné pole, či nějaký speciální objekt představující nulový výsledek) (a když už, tak jen s použitím parametru $require či $required, jak již bylo nastíněno). To, že repozitář nenalezl záznam podle zadaných kriterií přeci není nijak výjimečné. Ale situace, kdy nadřazená služba používající takovýto repozitář má za úkol něco, k čemuž nutně onen záznam potřebuje, a takový záznam od repozitáře nezíská, to už je pro mě jasný kandidát na vyhození výjimky (tj. z nadřazené služby).

Takže pro to mazání bych nejspíš v ideálním případě vyhazoval výjimku v nějaké službě, která by byla v hierarchii služeb mezi presenterem a repozitářem, resp. DAO.

Jiří Nápravník
Člen | 710
+
0
-

duke: díky za reakci já to vidím velmi podobně. Já tady neřeším, repository (resp. dao) ale až o tu úrpveň výše, co mám fasádu…