I recently found myself in a discussion on whether or not exceptions could be used to control program flow in software (specifically in PHP applications). That triggered me to post a tweet:
Exceptions should not be used to control expected application flow. Discuss.... @skoop
This triggered quite a bit of discussion, which gave me a lot of input on this topic. I want to thank everyone who joined that discussion for their input, which was really valuable. In this blogpost I'll do a summary of the different arguments in the discussion, and give my opinion on this.
What is program flow?
First of all, what exactly do I mean with program flow and using exceptions to control program flow. The reason for the discussion was an exception thrown in a persistence layer in the situation that there were no results in a
findBy* method. This is a slightly different situation from for instance errors with database connections or API connections, things that can be expected but are not meant to be happening. When you do a
findBy*, you're effectively searching, meaning 0 results would be a valid situation. This also was reflected in the discussion.
@mvriel @skoop @rdohms Find implie Search. Search implies zero results is a valid output. Excepting in a Find-er seems odd to me.Make it a Get-er or wrap in Option @n0x13
This triggered a whole discussion on whether for instance
findById is actually searching and whether it would be a valid use case that this would return 0 results. For instance:
@n0x13 @skoop @rdohms Finding one item specifically by ID implies that it exists; non-existance is equal to an error 400. @mvriel
A great definition of what an exception is and when it should be using was given by Chris:
@thomas_shone @skoop Exceptions are only where the code throwing the exception cannot deal with it. Calling code can always expect the exception and cope! @choult
I quite agree with this definition of exceptions. If your code can not deal with a certain situation anymore (such as a missing connection to the database, or an error 500 response from an API) then it should throw an exception. Basically, when something should be there and is not there. In all other situations, your program flow should handle "errors" by itself.
What is the intent of your code?
I had not really expected this when I first asked the question, but eventually I think the discussion stopped being about exceptions, and started being about naming and intent. Now, we all know there are two things that are extremely hard in software programming:
- Cache invalidation
- Naming things
- Off-by-one errors
Let us focus on the middle one: Naming things.
Naming things is hard. It is extremely hard. It is so hard people do talks about the subject. But in essence, naming things is easy. Names should be clear, descriptive and describe the intent of the code. Yeah, that sounds easy, but once you start to think of what the right name is, it gets harder. Actually finding the right name is extremely hard.
So, let's go back to the example of
findById(). Given several tweets in the discussion, different people interpret this method and its intent differently. There's basically two interpretations:
findimplies search, which means you're going to search for a record with the given ID. When you search, one of the options would be that no results are found
ByIdimplies that you're asking for a specific record, because ID is usually a unique key. If you know that key, then the record must exist. If it does not exist, this is an exceptional situation
And both interpretations are valid. Which basically means the naming is off. Jaap has a good solution for that:
@mvriel @skoop @rdohms Find can return null, getbyid should throw an exception @jvotterdijk
I like this idea; when you search (represented in this tweet by find) 0 results is a valid situation, but when you
getById() you expect it to be there, so that may result in an exception if it is not there. Nicolas later confirms this as well:
@skoop Agreed, exceptions shouldn't be used for flow control. Although valid when ex: has(): bool, get(): Object (throws Exception) @nicholasruunu
So, naming things...
... is still very hard. But it is extremely important. So please think hard before you name things, and avoid ambiguity. Always choose names that are clear and that communicate the intent of the code you are writing. Because it isn't wrong to throw an exception when you
getById() and you find nothing, but when you
searchById() it is OK to get an empty result which should not result in an exception.