Polite Exceptions - Fixing the stepchild of API design
Every API has an visible, an invisibile and a hidden part. The visible part is obvious: public methods and properties but also constants and parameter values. That's the most visible part to any client (read: user) of your API. The invisible part is everything private, you can't really see it and - more important - you can't use it (except if you resort to reflection). The hidden part consists of all the protected symbols, as you can't really see them until you extend a class. The other hidden part are Exceptions. You can't really see them and there is no common expectation what methods throw what kind of exception. Yes, throws@-docblocks help, but that's mostly all we have.
Exceptions handling: the problem
The usability of hidden parts of an API is all about expectations: people love languages like Ruby because once you learned a certain set of API (e.g. the string API), you can instinctively infer a large part of other APIs. This is good and keeps learning costs down. PHP, on the other hand, with its historically grown standard extension is on the opposite site of the fence: various parameter order the naming scheme is unreliable at best.
The future is multi-lingual, you need to know more than one programming language and speed of learning matters. Like, a lot. Because aoX programmersa, for any value of X, are weak players. What type of exception a class might throw should be defined by clear expectations for the general case. If you use a preconceived HTTP client httpFoo, call method request() and want to handle exception cases, what exactly do you catch?
Talent borrows, genius steals
Zend Framework 2 has a lot of problems but there are two things they did particularly well: naming of abstract classes and interfaces and how they treat exceptions. Every component (see, component is a lie here, as they aren't really stand alone components but I digress) has its own exception subpackage which has extension specific exceptions. Those exception all implement a single marker interface called ExceptionInterface. If you use Zend\Something and want to handle all exceptions, just catch Zend\Something\Exception\ExceptionInterface.
Programming transaction costs
Time to relevant data is the new time to market. We no longer optimize for feature-complete products shipping on a certain date but relevant changes generating relevant data as soon as possible. Therefore programmer round-trips matter. I consider everything that is not core domain or core UI a round trip:
- I need to create another config file
- I need to write another test
- I need to ad a few more specific exception classes
- I need to write a new contract
These steps aren't worthless, they are worth less from a business perspective as they don't generate revenue very soon. However they are needed to keep revenue over time. So let's make those things cheaper.
When dealing with Exceptions in Symfony 2 projects, two steps are particularly expensive:
- Creating the initial Exception infrastructure for a bundle
- Creating new specific Exception classes for bundles
Especially the latter can be simplified quite dramatically.
To simplify Exception handling, we just open sourced a bundle we developed at InterNations. Let's create a few custom exceptions:php app/console exception:generate app/src/MyVendor/MyBundle "MyVendor\MyBundle" \ ExceptionInterface RuntimeException DomainException RuntimeException:SpecificRuntimeException Create directory app/src/MyVendor/MyBundle/Exception Writing app/src/MyVendor/MyBundle/Exception/ExceptionInterface.php Writing app/src/MyVendor/MyBundle/Exception/RuntimeException.php Writing app/src/MyVendor/MyBundle/Exception/SpecificRuntimeException.php Writing app/src/MyVendor/MyBundle/Exception/DomainException.php
Let's rewrite an existing bundle to use custom exceptions:php app/console exception:rewrite app/src/MyVendor/MyBundle "MyVendor\MyBundle" Found bundle specific exception class BadFunctionCallException Found bundle specific exception class BadMethodCallException Found bundle specific exception class DomainException Found bundle specific exception class InvalidArgumentException Found bundle specific exception cl
Truncated by Planet PHP, read more at the original (another 1517 bytes)