Waar en wanneer Exceptions afhandelen?
Ik zit een beetje met de vraag waar en wanneer ga ik exceptions opvangen. Ik ben bij deze vraag gekomen nu ik bezig ben aan een Database class. (met PDO)
Wanneer de verbinding met de database niet kan gemaakt worden, wordt er een exception gegooid. Leuk, maar waar en wanneer van je hem op? Ik zou eerder geneigd zijn om voor de GUI te gaan. Door specifiek die van de database te catchen zou ik dan naar de gebruiker toe kunnen melden dat er een fatale fout optrad.
Langs de andere kant, moeten fouten die optreden met de database zelf niet binnen de database class afgehandeld worden? Mijn constructor van mijn database class die ziet er als volgt uit:
Nu begin mij echter sterk af te vragen of het wel verstandig is om dat al dan niet zo te doen. Graag jullie meningen!
Bump
voorbeeld 1: je hebt een request class die alle user parameters inleest en controleert. Tijdens een check merk je dat de parameter een string is en geen integer. De request class gooit een exception om hiervan melding te maken. De class zelf vangt die direct weer af, want bij een foute invoer geef je gewoon een default waarde terug. De rest van de applicatie merkt hier dus helemaal niets van. Die krijgt gewoon een correcte waarde terug waarmee verder kan worden gegaan.
voorbeeld 2: je hebt een query in je database waarvoor je bepaalde parameters absoluut nodig hebt. Nu blijkt in de method waarin je de query wilt uitvoeren dat een parameter geen correcte waarde heeft. Hier kan je dan een exception gooien, want de query kan niet worden uitgevoerd. De aanroepende class vangt die op en bepaalt dat er dan maar een lege resultset teruggestuurd wordt. De rest van je applicatie kan gewoon door, maar laat alleen een 'lege' pagina zien (wat in veel omstandigheden best acceptabel kan zijn).
voorbeeld 3: je database connectie werkt niet. Hier laat je de database class ook een exception gooien. Dit kan de database class zelf niet verder afhandelen. De aanroepende class ook niet, want dit heeft meer gevolgen dan alleen een lege pagina (bijvoorbeeld je kan een gebruiker ook niet inloggen, je kan geen pageviews registreren etc etc). Deze exception borrelt dus helemaal op naar de applicatie/controller class die dan bepaalt dat er een specifieke foutpagina moet worden getoond.
Maar heeft het dan zin om een log-object mee te geven aan de database class? Ik twijfel hier een beetje aan.
Wat een betere optie is is om het via een setter te plaatsen. Je kan bijvoorbeeld na constructie van het object de factory of service container als je zoiets gebruikt het logobject laten setten. Een andere methode, die ik gebruik, is om een observer interface te bouwen die alle standaard classes implementeren. Via die interface kan een logobject zich dan zelf aanmelden bij het databaseobject. Het databaseobject broadcast vervolgens alle foutmeldigen en het logobject kan zelf bepalen bij ontvangst van een foutmelding wat ermee te doen. Het databaseobject weet in zo'n geval niet eens of er wel een logobject bestaat.
Ik neem aan dan dat je in de container gaat instellen wat de logger is voor de database? Maar waar ga je dan loggen als het fout loopt met de database?
Ik zou een logger via een setter meegeven, een constructor is inderdaad niet heel handig, en dan de klasse zelf de exceptions laten loggen. Je kan dan per log bepalen of het slechts alleen een Notice level wordt of dat het problematisch wordt en je het een Critic level moet meegeven.
Het observer pattern klinkt allemaal geweldig, maar die dan dat niet. Of wacht, nu zeg ik wat verkeerd. Je kan natuurlijk als 2e parameter bij het gooien van een exception een level meegeven die de observer dan kan uitlezen ($e->getCode()).
Alleen dat iedere class hetzelfde interface implementeert staat mij nog niet erg aan.
Wouter J op 23/07/2012 15:32:55:
Ik zou een logger via een setter meegeven, een constructor is inderdaad niet heel handig, en dan de klasse zelf de exceptions laten loggen. Je kan dan per log bepalen of het slechts alleen een Notice level wordt of dat het problematisch wordt en je het een Critic level moet meegeven.
Klinkt theoretisch geweldig. Ik zie alleen niet gelijk in hoe. Neem nu dat mijn wachtwoord voor de database verkeerd is. Dan wordt er in principe een exception gegooid. (door PDO in dit geval) Het kan volgens mij echter niet de bedoeling zijn deze exception in de database class te gaan vangen. Ik wil namelijk graag de mogelijkheid hebben om een boodschap naar de buitenwereld te sturen. Aan een gewone bezoeker bijvoorbeeld 'Er trad een fatale fout op. De pagina kan niet worden weergegeven'. Aan een admin zou ik dan iets meer informatie kunnen geven of dergelijke.
Ik zie alleen niet in, als je de exception opvangt binnen de class zelf, hoe ga je dan nog een boodschap naar buiten toe tonen?
Wouter J op 23/07/2012 15:32:55:
Alleen dat iedere class hetzelfde interface implementeert staat mij nog niet erg aan.
In mijn logger worden 3 dingen meegegeven: de class(naam) die de fout broadcast, de melding en de severity. Aan de logger om er iets mee te doen.
Verder is de functionaliteit hiervoor volkomen opgenomen in een basis class waar alle classes van afstammen. Geen enkele class hoeft er nog iets voor te doen, de methodes in de basis class handelen het af.
@write down,
Afhandelen en toch nog door laten bubbelen naar boven is ook nog een optie. Je kan de fout bijvoorbeeld in de class zelf eerst afvangen, loggen en dan opnieuw een exception gooien zodat het toch nog op een ander niveau terecht komt. Zo kan je en de fout juist loggen en een juiste foutpagina tonen.
Want wat Erwin zegt vind ik ook wel een 'leuk' idee. Alleen vind ik het dubbel werk. Langs de andere kant, ik vind het logischer dat de exceptions in class zelf worden gelogd. Maar is het in dit geval niet handiger allemaal 'eigen' exception class te maken? Bijvoorbeeld een exception voor de database, een exception voor config, ... En in die exception class de logging inwerken?
Ten tweede wil je ook zorgen dat je in elke class een afhandeling hebt voor fouten die je verwacht en die je ook kan afhandelen.
Met andere woorden, wil je alles op een nette manier kunnen afhandelen dan heb je toch al op meerdere niveau's foutafhandeling zitten. Kleine moeite dus om een fout door te sturen op het moment dat je hem onderschept en hem eigenlijk niet kan afhandelen. Door middel van verschillende Exception classes kan je dan heel gericht de ene fout wel afhandelen en de andere niet.
Code (php)
En dan kun je dat weer verder uitbouwen naar andere typen exceptions (zoals PHP er ook al wat heeft:
@Wouter
En injecteer je in sommige van die classes dan een log-object?
Meneer Exception gooi je uit het vliegtuig, er is iets mis gegaan. Dan moet Meneer Exception zichzelf niet gaan opvangen, dat kan hij niet. Daarom moet je onderaan, op de aarde, een groot kussen leggen waarin je hem kan opvangen. Of je vangt Meneer Exception al eerder op door in een vliegtuigje onder hem te gaan vliegen en hem zo in de bijrijdersstoel te laten vallen.
Je moet proberen dat laatste te doen en mocht het echt zo kritiek zijn dat je hele applicatie niet meer verder kan dan moet je hem gewoon naar beneden laten vallen en de stootkussen gebruiken om de foutmelding weer te geven.
Het voordeel hiervan is dan dat je gemakkelijker verschillende catch-blokken kan gebruiken. Of zie ik dit verkeerd?
Code (php)
Gewijzigd op 24/07/2012 11:58:28 door Write Down
Kan er iemand meer uitleg geven over de vorige vraag van Write Down? Wouter, Erwin of Write down zelf?
Ja, het voordeel is dat je dan de fouten anders kunt afvangen en ook op een ander moment. Je kan sommige exception al opvangen in een klasse, terwijl de andere helemaal opborrelt.
Toevoeging op 07/05/2013 01:18:37:
Dus je catcht'm en dan gooi je hem weer weg?
Ik ben dan ook wel benieuwd... heb je eigenlijk per se Exceptions nodig, of kun je ook alles afvangen met bijv. een trigger_error()? Is het wezenlijke verschil waarom je eventueel Exceptions zou gebruiken dat je die op een hoger niveau kunt afvangen?
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public function index($args)
{
$view = new View('Jds::WelcomeView');
$view->setParam('pageTitle', 'Hello World');
try
{
$articlemapper = new \Jds\Models\Article\ArticleMapper();
$orderLineMapper = new \Jds\Models\Order\OrderLineMapper();
$view->setParam('android', $articlemapper->findAvaibleByCat(1));
$view->setParam('ios', $articlemapper->findAvaibleByCat(2));
$view->setParam('win', $articlemapper->findAvaibleByCat(3));
$view->setParam('new', $articlemapper->getLast(4));
$view->setParam('mostPopular', $orderLineMapper->getMostPopular());
}
catch(\Jframe\Storage\Adaptors\Exceptions\CouldNotConnectException $e)
{
$view = new View('Jds::Errors/ErrorMessageView');
$view->setParam('pageTitle', 'Er liep iets mis');
$view->setParam('message', $e->getMessage());
}
$view->render();
}
{
$view = new View('Jds::WelcomeView');
$view->setParam('pageTitle', 'Hello World');
try
{
$articlemapper = new \Jds\Models\Article\ArticleMapper();
$orderLineMapper = new \Jds\Models\Order\OrderLineMapper();
$view->setParam('android', $articlemapper->findAvaibleByCat(1));
$view->setParam('ios', $articlemapper->findAvaibleByCat(2));
$view->setParam('win', $articlemapper->findAvaibleByCat(3));
$view->setParam('new', $articlemapper->getLast(4));
$view->setParam('mostPopular', $orderLineMapper->getMostPopular());
}
catch(\Jframe\Storage\Adaptors\Exceptions\CouldNotConnectException $e)
{
$view = new View('Jds::Errors/ErrorMessageView');
$view->setParam('pageTitle', 'Er liep iets mis');
$view->setParam('message', $e->getMessage());
}
$view->render();
}
Maar eigenlijk wil ik dat standaard elke methode dat try - catch block omvat. Is er een slimme manier om dat te doen? EN zou ik het implementeren in de bestanden van mijn framweork of zou ik daar geen exceptions gaan afhandelen?