jul
15
2010

No Exceptions made

Naar aanleiding van een bevinding tijdens een interne project code review  afgelopen vrijdag en een artikel in het laatste Java Magazine hadden we een interessante discussie over de redenen om exceptions toe te passen. Uiteindelijk kon ik zelf achter twee vuistregels staan, een die ik zelf bedacht had, de ander van een collega van mij.

Ik ga bij de onderstaande vuistregels uit van de context van een ontwikkelplatform die de mogelijkheid biedt om ze te gebruiken en ontwikkelaar die als basisregel voor het herkennen van de situatie om een exception in te gebruiken de volgende regel hanteert: gebruik een exception als je niet het normale executiepad meer kunt volgen, met andere woorden, als er iets uitzonderlijks gebeurt of is gebeurd.

Aanvullende vuistregels
Op zich een logische veronderstelling, aangezien de naam Exception in het Nederlands ook letterlijk vertaalt naar Uitzondering. Toch is dit te naïef en horen daar volgens mij de volgende vuistregels bij:

1. Gebruik geen exceptions en als je toch een reden gevonden denkt te hebben om een exception te gebruiken, denk daar dan nog eens goed over na.

2. Gebruik geen exceptions in een (objectgeoriënteerd) domeinmodel.

Onderbouwing
Uiteraard verdienen deze vuistregels wel enige onderbouwing. Ik zie de volgende redenen:

Ten eerste is het gooien van een Exception niet zonder kosten:

  • De runtime engine heeft vaak wat extra execution time nodig voor  het opgooien en afhandelen van een exception
  • Een exception doorbreekt het normale executiepad, zeker als deze niet direct door de aanroepende methode wordt afgevangen. Dit betekent dat invariants niet zonder meer afgedwongen worden. Je zult dus een soort transactioneel mechanisme nodig hebben om de situatie te herstellen , anders moet je ervan uitgaan dat de runtime corrupt is en weggegooid dient te worden.
  • Het betekent een extra return mogelijkheid uit een methode, een extra pad om te volgen. Het verhoogt de complexiteit van een methode
  • Het try-catch mechanisme dat noodzakelijk is om een exception correct af te vangen kost meerdere regels code en leidt af van de werkelijke werking van de code.
  • Het betekent in feite dat een door een gebruiker of andere actor geïnitieerde actie niet voltooid kon worden. In het beste geval kan het nog een keer geprobeerd worden, in het slechtste geval moet de initiator een nieuwe poging doen.

Bovenstaande kosten leiden met name tot vuistregel 1. Als je een exception kunt voorkomen, everything else being equal, dan verdient dat de voorkeur. Er zijn denk ik een aantal situaties waarin het logisch lijkt om een exception te gooien, terwijl er betere alternatieven zijn:

Pre-condities op argumenten kunnen beter declaratief vastgelegd worden
Je zou bijvoorbeeld een exception kunnen gooien als de argumenten waarmee de een call gemaakt wordt, niet voldoen aan de gestelde eisen. Met andere woorden: voor het afdwingen van pre-condities op de argumenten. Dit is echter veel beter te doen door deze eisen expliciet te maken en in een declaratieve manier in je methode signature te verwerken. Door argumenten te verwachten die alleen in een valide toestand kunnen zijn, dwing je de aanroepende methode om zelf te zorgen voor een juiste aanroep. Als deze aanroepende methode dit vervolgens ook doet, dan borrelen deze pre-condities naar boven, zodat ze declaratief en pre-conditieel zijn op de externe interface van de unit of code die je op dat moment bewerkt.

Met andere woorden: ze zijn in de vorm van een (code) contract vastgelegd. Als deze externe interface bijvoorbeeld een web service is en het contract vastligt in xml schema, kan een aanroep voor wat betreft pre-condities al afgekeurd worden door de schema validator, zonder dat de achterliggende runtime aangeroepen wordt en dus zonder dat die in een invalid state terecht kan komen.

Pre-condities op de interne state kunnen beter uitgewerkt worden
Je zou bijvoorbeeld kunnen zeggen dat een bepaalde aanroep alleen bij een bepaalde inner state mag plaatsvinden. Om een voorbeeld te gebruiken wat tijdens de discussie naar boven kwam: een lesmodule bevat slides. De module mag alleen weggegooid worden als alle slides reeds verwijderd zijn. Door de eis zo te stellen,  zorg je ervoor dat de aanroepende methode deze state moet kunnen bepalen (je raakt dus encapsulatie van state kwijt) en bovendien blijft de mogelijkheid van race condities bestaan: er is net een slide toegevoegd voordat je de module wilt gaan verwijderen.

Beter is om in zo’n situatie op zoek te gaan naar nieuwe mogelijkheden. Start daarvoor in dit geval met de vraag: waarom moeten die slides eigenlijk eerst verwijderd worden? Stel dat het antwoord in dit geval is: omdat slides mogelijk ook door andere modules gebruikt kunnen worden en als ze dus zomaar mee verwijderd worden, kan een gebruiker onbewust de inhoud van een andere module verwijderen.

Over het algemeen blijkt dan dat er betere alternatieven zijn. In het bovenstaande geval wordt eigenlijk duidelijk dat slides losstaande aggregates zijn en dat deze dus überhaupt niet meeverwijderd zouden moeten worden met de lesmodule die ze gebruikt, maar dat alleen de link ernaar verwijderd mag worden. Aanvullend zou je een extra mogelijkheid moeten aanbieden om zwevende slides automatisch of handmatig op te zoeken en te verwijderen (garbage collection).

Invariants op de interne state kun je beter afdwingen door locks te gebruiken of geen threading
Op het moment dat de executie reeds gestart is en de interne state door een andere thread tegelijkertijd gewijzigd wordt, is de beste manier om hier bescherming tegen te bieden met het gebruik van locks: dit is namelijk preventief en zorgt er in principe voor dat beide executiepaden vervolgd kunnen worden. Locks hebben uiteraard ook weer nadelen (onduidelijke code, deadlocking) Het gebruik van het actor model zorgt er natuurlijk voor dat je al deze problemen bij de wortel aanpakt.

Overblijvende use case: pre-condities en invariants op de state van de externe omgeving
Uiteraard blijft dan nog wel een bepaalde situatie over voor het gebruik van excepties: de state van de (externe, niet-beïnvloedbare) omgeving voldoet voor of tijdens de executie niet aan alle condities. Het canonieke voorbeeld hier is natuurlijk de database die niet up is voor of down gaat tijdens de uitvoering van een request. Dit kan een goede reden voor het gooien van een exceptie zijn. Uiteraard is door het ruimer nemen van de verantwoordelijkheid hier ook wel iets aan te doen: bijvoorbeeld door als verantwoordelijkheid te nemen dat de huidige runtime state uiteindelijk gesynchroniseerd moet worden met de server  i.p.v. dat deze altijd direct bij aanroep gesynchroniseerd moet worden, is het niet up zijn van de netwerkverbinding niet direct een reden tot het gooien van een Exception meer.

Een (goed) domeinmodel heeft geen afhankelijkheden naar een externe omgeving
Aangezien de externe omgeving eigenlijk alleen een reden zou moeten zijn voor het gooien van een Exception, volgt eigenlijk vanzelf dat een domeinmodel geen enkele reden zou moeten hebben om een Exception te gooien. Deze zou namelijk nooit afhankelijk moeten zijn van een externe omgeving en puur een representatie moeten zijn van de werkelijke omgeving buiten het model, gezien door de lens van het betreffende domein. Die is niet nooit afhankelijk van een database of netwerkverbinding in het systeem waarin het model zich bevindt (zou natuurlijk wel kunnen dat het domein zelf over databases en netwerkverbindingen gaat, maar dan zou het dus ook gaan om domeinobjecten in het model).

Conclusie
Door de bovenstaande vuistregels voor het toepassen van Exceptions te gebruiken, zijn systemen robuuster, eenvoudiger, sneller en bruikbaarder te maken. Uiteraard is het werk dat gepaard gaat met beter modelleren ook een kostenpost en het kan zijn dat dit zich niet altijd uitbetaald, maar binnen het hart van je applicatie, het domeinmodel, zou dit toch wel het geval moeten zijn. Tenzij je applicatie als geheel weinig toegevoegde waarde heeft, maar dan is het misschien sowieso geen goed idee om eraan te beginnen.

Geschreven door Rick | Tags: , , , , , , |

Reageren? - Geef een reactie »

Rss feed voor commentaar op deze post. TrackBack URL

Geef een reactie

Powered by WordPress | Aeros Theme | TheBuckmaker.com WordPress Themes

Better Tag Cloud