13 June 2019
Exception design: the art of making debugging more accurate
Wow, really – an article about exception design? Don’t judge just yet and let me fill you in. When I got promoted to the Head of PHP position, I gained a new perspective and set of responsibilities – quality analysis and broadly understood research. I quickly noticed that exceptions in PHP lack proper attention. It’s usually presented as programming construct that you should use, and it’s rarely discussed beyond that. Shame, because exception design is a very wide topic. Therefore, I would like to share my discoveries with you – this article will cover why exceptions are usually ignored by PHP developers, when to use them, and how to create them in such a way that they’ll make debugging easier.
Getting started with exception design in PHP
Meet Ron – my imaginary friend. He’s a freelance developer who makes landing sites and portfolios. Nothing complicated. All jokes aside, he’ll help us to illustrate some problems here. So, Ron decided he’s fed up with the accounting and debt collection, so he got a job in an IT company which provides services to various clients. He got his first task: a general abstraction on the filesystem (he got an ID representing the resource).
He eagerly went to his desk and came up with this:
We have the year 2019 and PHP 7, which is why at Code Review one of his colleagues recommended using types and signalling somehow that an error occurred. Ron reviewed and adjusted his code:
Then he heard that it won’t work because PHP errors are converted into exceptions in the project, and he should consider SPLFileInfo in the solution. Well, he went back to the documentation and created this:
I’m not exaggerating here. You’ve probably seen this type of code in your own experience.
Why PHP developers forget about exception design?
Now, why so many developers misuse exceptions or don’t understand them? From my personal working experience with PHP developers, I can point out three major reasons:
- For starters, PHP is a very easy language which is awesome. However, its availability has a drawback. Having such a mature community, it’s easy for beginners to skip some basics. You can use one or two frameworks or libraries and you can do pretty advanced stuff without deep knowledge. In other words, it’s easy to skip exceptions in the learning process.
- PHP has historical baggage which misleads and confuses not only beginner programmers. Just by looking at the documentation you’ll see what I mean. The language is schizophrenic by having many equivalents both in a procedural and objective style. Now, as a beginner, what would lure you more? For many people, procedures seem to be a simpler and more effective choice. This way, bad habits and primitive driven development is advocated.
- Many PHP veterans remember the times when PHP was purely procedural (good ol’ “back in the days”). You’d be surprised how many of them haven’t really advanced. They carry outdated knowledge and transfer it while mentoring.
Ok, having that covered, let’s check on Ron.
Are exceptions necessary in every business case?
Our developer has learned from very helpful colleagues that exceptions are a nice mechanism that allows you to objectively signal problems in your code. On his second day at work, Ron was asked to implement a mortgage calculator. Learning from his new experiences, he created the following code.
Question merges – was it necessary to use an exception here? Assuming that Ron expects the $loadDeptorRequest object should contain data allowing correct calculation of the loan – that would be totally fine.
However, let’s imagine a business case where you have to check profiles on a mass scale and speed up the process. You assume that the data doesn’t necessarily have to be complete and when it happens, you return the information. Since we expect a set of data to be incorrect, is this exception still in place? In my opinion, no – the situation is no longer special. You can get rid of it and choose something more accurate, for example, Result Object. If you don’t know what a Result Object is, I’ll present it at the end of the article.
When should you use exceptions?
There are a lot of opinions on where exactly you should use exceptions. Excluding the official definition, I’ve selected two most distinguished thoughts in the IT community:
- Exceptions should be used when the situation is exceptional – everywhere where you step out from the happy path.
- The exceptions serve primarily to improve readability, so you use them in such a way that the readability of the code increases.
Personally, I am in favour of the second option. The object-oriented programming was created to make it easier to manage code’s complexity, and to manage it well, it must be readable. However, it’s no revelation. That’s why I am talking about the art of exceptions design. Everyone interprets art in their own way and it’s the same with exceptions. You need to decide something and stick to it to the very end.
Having this in mind, it may be difficult for us to design an exception somewhere in our code, especially when we start to be aware of exception design. So how to approach this? Where to place them? It’s quite simple but tricky.
Before making a decision, I always look at these two factors: problem’s likelihood and application or business severity.
Seems obvious and probably you already know it but sometimes there are situations where this isn’t so easy.
This is quite simple for probability because the rule is trivial – if something is really likely or often to happen, you probably need something else than an exception (and by that, I don’t mean to fall back to primitives right away). There’s plenty of other objective ways to handle problematic situations.
For instance, an object repository. Should it throw an exception on a failing object fetch or maybe just pass back a null or a Null Object? Well, of course, it depends. What if this is a domain-specific repository? Or just an ordinary doctrine repository? In these situations, you should ask yourself how critical it is in your business context to treat it as something exceptional?
If it’s fairly critical, then the answer is simple – use an exception.
You know already that the inclusion of exceptions depends on the business context and the problem probability, as we have covered that, but how do you create exceptions so they are useful and readable? I think everyone will say that there’s nothing hard about designing exceptions – easy-peasy. Well, let’s see what Ron is up to…
He used a very typical exception that I’ve seen a million times before. At first glance, everything seems fine, but let’s analyse how helpful is it.
- We don’t really know the reason why our document couldn’t be signed.
- The name of the exception is very generic and doesn’t contribute much to understanding our problem.
- We didn’t receive anything that could help us diagnose the problem.
In order to fix it now, we need to work on the first and second point. It would be nice to know where the problem lies. In our example, we can obtain this information using the status code. Let’s name the exception after the problem.
Better, isn’t it? Now, everybody looking at this will have a better understanding of the problem’s origin.
Next, we need to think about contributing to future maintainers. Is there a better place where you can find information about the problem than the actual source of the problem? Since we detect and design exceptional situations, there is a good chance that while writing the code, we already have most of the information about the occurring problem.
For HTTP requests, the problem’s explanation is most often described in the HTTP response of the service. Use it to your advantage!
Now, someone might think that exception for every possible error case is an exaggeration. Absolutely not! It would be quite easy to commit Object Explosion anti-pattern. Remember that handling errors is an art and you just have to make decisions consciously? You have to ask yourself whether your actions are balanced and then introduce value into the code. In the end, maybe exceptions won’t be the best solution, and it’ll be worth considering other options?
While creating exception messages, is advisable to check if your messages don’t contain sensitive data. If that’s the case then you should think about an additional sanitization layer that would mask the sensitive data in the messages.
We got rid of the ‘Exception’ suffix in our classes. Why? Personally, I think that this suffix is unnecessary when we have namespaces. Besides, you cannot get anything else than an exception in the try-catch.
What about exception names?
If you have problems with naming classes, and the XYZ method doesn’t seem appealing, I have an alternative for you – CPS(R): Context Problem Solution (Reason). Oh, I think Ron got a new task and he used this exact method.
Context: Top-up prepaid account system
Problem: User account is inactive
Solution: Pass the reason code
Let’s try another example…
Context: Custom query defined in a configuration file
Problem: Unrecognized token found in the string
Solution: Pass the place where the token starts
In what situations do we care to be verbose? When we debug the code of course! The exceptions are part of this process, which is why there is nothing wrong with being verbose in the nomenclature and structure in the content of the exceptions’ body. In the case of finding errors, being obvious or expressive is nothing bad – rather the opposite.
What are the benefits of expressive exceptions?
- This way adds to the philosophy of writing code so that it reads like prose
- You facilitate the diagnosis of errors for future developers
- You give a clear signal of what we predict could go wrong
- There’s a good chance that you won’t have to investigate the cause of the problem, but you’ll solve it at once without any problems
What should you keep in mind while designing exceptions?
Modern debugging tools will help you visualise the chain of causes that might give you a good understanding of the exception’s source.
- Different exception stacks
Depending on the project’s size, you may want to have different stacks of exceptions for clarity.
- Exceptions are still objects
Applying Object-Oriented Principles is still a good practice and can help your team out.
- Somebody will read your work in the future
Check, whether it will it have any sense to other developers in the future.
- Less is better
Apparently, this applies to everything in life…
Don’t just follow patterns and opinions. Try to experiment and see what works best for you and your team.
Four things to learn from exception design
We (Ron included!) have learnt that:
- Exceptions can be really helpful.
- There are other mechanisms that can signal a potential problem.
- Inserting an exception instead of another mechanism is a discretionary matter and depends on the context of the project.
- Call and create exceptions to best reflect the nature of the problem.
I’m 100% sure that after that practical experience, Ron will be an experienced PHP exception designer and will pass his new knowledge forward to new Rons in the software company.
Bonus! Result Object
The result object is a simple structure that tells us whether the operation was successful and carries the result of this operation. It can also be enriched by the reason of the failure. A good substitute in a place where we would naturally mix types to signal a problem in the interface. It’s a good alternative for exceptions if you really expect a certain situation to be problematic.
Traditional open example
Example with the Result Object
It would work best with generic types, however, PHP doesn’t include generics. 🤷♂