4 ms·
Back in my early years as a software developer I once though that I could make some really eligent code by using exceptions for control flow. I used it to do a
by dkhenry 14y ago
Back in my early years as a software developer I once though that I could make some really eligent code by using exceptions for control flow. I used it to do a form of what I now know is pattern matching. It really cleaned up the code and made it super easy to extend and read, but it was a _terriable_ idea. It was slow and since nothing was checked by the compiler I found myself often at my root catch block trying to figure out how my nested catches didn't work.
Exceptions are not suitable for control flow. Yes they work and can sometimes give nice features, but typically they are just showing that you have poorly designed code and should be doing something differently.
'/s/Exceptions/GOTO/g' and you have essentially the same argument
- mcherm 14y ago> '/s/Exceptions/GOTO/g' and you have essentially the same argument Absolutely untrue. Eliminating GOTO was a major step forward in programming language design, one better known as "Structured Programming". The advantage was that it eliminated random entry points so that bits of code ("subroutines", "methods", or "functions") could be reasoned about independent of the entire program by starting from the "top" and working forward. Arbitrary exit points (like exceptions, break, and return) still permit this reasoning; allowing GOTO to target arbitrary statements makes it impossible to reason this way. For a better explanation, see this essay I wrote on the subject: http://mcherm.com/permalinks/1/in-defense-of-the-much-maligned-goto http://mcherm.com/permalinks/1/in-defense-of-the-much-malign...
- dkhenry 14y agoSo exceptions do add structure as to where you can go _to_ but they serve the same purpose in where you can come _from_ Its a little easier to trace since you are scope bound, but its still harder then being able to trust the control flow of your code. Additionally because you can't trust control flow of your code ( since any time the sub routine can essentially longjump to some place higher in your stack) you are now required to context wrap all your code in try blocks essentially implementing bookkeeping that the compiler will do for free if you just design your code in a sane manner. This is why java implemented typed exception throwing because it the only way to have some semblance of safety when programming with exceptions. I am even more disheartened to see that the author uses my beloved scala and in still trying to do this kind of nonsense, when we have plenty of good tools at our disposal to negate the use of exceptions for control flow.
- mcherm 14y agoI'm not completely sure I am understanding you. I think that RAISING exceptions is just a non-local exit point, like a break or return which does not make it difficult to reason about one piece of code in isolation from others. I think that CATCHING exceptions is a clearly marked 'entry' point: one can only arrive in an exception handler from an exception raised within the corresponding try statement, and the place that you arrive is clearly marked. The one bit that is more confusing is throwing right through without an exception handler: in other words, code you call might raise an exception which your code does NOT catch, and thus exit your function prematurely: you might thus allocate some resources and fail to deallocate them safely. Are we saying the same thing here?
- dkhenry 14y agoSomewhat. You are correct to say were know that the exception was raised in the try block so we have an idea about where it came from, but unless each try block is one statement long you don't know where the exception came from . That's the disconnect. When in a try/catch block you know where your going ( the catch ), but you don't know where you came from. That is almost as problematic as GOTOs in that planning for all program states is once again a headache ( did I open that socket , is that memory free'd ,... )
- mcherm 14y agoI see what you are saying. I think I avoid this problem by having a policy of not modifying fields arbitrarily within a try statement. Let me try to give some examples. I write things like this: Person p = getThePerson(id); try { storePersonToDatabase(p); } catch(DatabaseException err) { generateAlert('Could not save person'); } doMoreProcessing(p); In this case I know that p was initialized because it happened outside of the try statement. But I try never to do things like this: Person p = getPrimaryAccountHolder(); try { storePersonToDatabase(p); p = getSecondaryAccountHolder(); storePersonToDatabase(p); } catch(DatabaseException err) { generateAlert('Could not save person'); } doMoreProcessing(p); If I did that, then there would be no way to know whether p was the primary account holder or the secondary after the try statement when I call doMoreProcessing(). On the other hand, it's perfectly OK for me to do this: Person p = getPrimaryAccountHolder(); try { storePersonToDatabase(p); Person p2 = getSecondaryAccountHolder(); storePersonToDatabase(p2); } catch(DatabaseException err) { generateAlert('Could not save person'); } doMoreProcessing(p); ...because with this pattern I don't know, after the try-catch, whether the variable p2 has been initialized or not, but I also don't care because it is out of scope and can't be used. If I didn't follow this practice, then I think the problem you describe would be an issue for me: not knowing which of several possible locations was the source of the exception would mean I didn't know how the variables had been set. Interesting... I learned something today.