Press "Enter" to skip to content

When Should We Use Exceptions?

Let me start with a digression: I did two things at the company that I think were quite meaningful. The first was setting up a PHP mailing list, and the second was setting up a Hi group. Both of them now have more than 500 phpers in them. I've always believed that building a communication platform, where people can communicate smoothly and simply, is the foundation and precondition for fostering a positive technical-learning atmosphere. Making sure that one person's question doesn't become everyone's question is the most direct benefit. (Postscript: many people have asked for the mailing list address. I'm really sorry, but this mailing list is an internal company one, and Hi is also internal. Thanks.)
Yesterday, a colleague raised a question on the mailing list:

When should PHP use Exception? And what's its performance like?

This question is also a classic that's been debated for a long time. Let me share my personal view.
Exception and its counterpart, the error code (or status code) — what are each of their pros and cons, and how should we use them?

Error codes

First of all, the exception mechanism came after the error-code mechanism, so by evolution, exceptions naturally avoid some of the shortcomings of the error-code mechanism. These shortcomings include:

1. Not rich error information

A function can only have one return value (of course, Lua can return multiple, but that's actually equivalent to returning an array in PHP). The function documentation we've seen most often is: returns *** on success, returns FALSE on error. Yet when a function fails, there can be multiple causes, and even more kinds of failures. A simple FALSE cannot tell the caller the specific error information.
So we've also seen some function documentation like this: if the return value is greater than 0, it indicates a success status code; if the return value is less than 0, it indicates an error status code.
However, this requires the function to return an integer (or a number). For some other functions, we can't judge by 0, >0, <0. And even by such a means, we still need to use the returned error code and some predefined macros (or call something like strerror()) to get the specific, readable error information. So some functions use a global error code and error message to save the specific error information. At this point we see function descriptions like: returns *** on success, returns FALSE on error, and the error code is stored in the global variable $errno (at least most Linux library functions are described this way, hehe). Okey, this way does work, but doesn't it feel... ugly?

2. Adding an error status code may require changing the function signature

Suppose you wrote a function. This function is very simple, very simple, and you believe it will absolutely never fail, so you declare it as (using C as an example; PHP has no return-type hints):

void dummy() {
}

But later you gradually modify this function and give it more functionality, at which point this function might fail. And now you simply can't add an error return code to this function.
Some might say PHP has no return-type restrictions, but think about PHP's constructors: constructors have no return value. When an error occurs, if you don't use exceptions, I think your only choices are die, or to use the method from point 2 to continue execution on error.
Besides, in a well-designed software system, the return type is actually also a convention. When none of the places that use the function check the return value, you still can't add an error return code to the function.

3. Error status codes may be ignored

When your function fails and returns an error status code, but the caller doesn't check this return value, what happens? -_#. On the other hand, checking the return status code everywhere makes the code very, very ugly:

<?php
  if (!call1()) {
      die();
  }
  if (call2() != SUCCESS) {
     die();
  }
  if (call3() < 0) {
      $msg = error_get_last();
      die($msg["message"]);
  }

The exception mechanism

So now let's look at the exception mechanism. If we adopt the exception mechanism, the code above can be written as:

<?php
try {
   call1();
   call2();
   call3();
} catch (Exception $e) {
   die($e->getMessage());
}

More conveniently, if your code is just a middle layer and your caller is responsible for handling errors, you can even simply write:

<?php
function myFunc() {
   call1();
   call2();
   call3();
}

And an exception object can carry richer error information, such as the error message, error code, the line number of the error, the file, even the error context, and so on — avoiding the "1. not rich error information" shortcoming.
We can also add exceptions to a void-returning function without changing its function signature, so there's no "2. adding an error status code may require changing the function signature". For PHP, if a newly introduced error is not caught, we don't need to worry — it will clearly error out. So "3. error status codes may be ignored" won't happen either.
However, there are also voices opposing the use of exceptions:

1. Performance

As the question at the start of the article asked: "What's its performance like?" The exception mechanism is indeed somewhat more expensive than the return-status-code approach. For C++, when an exception occurs, there's also stack unwinding (for PHP, there's no such logic; for details, you can refer to an article I wrote before: Deeply Understanding PHP Internals: The Exception Mechanism ).
Performance and convenience are often a pair of contradictions. I can only say you need to weigh them. If you're writing a small module, and its lifetime might be short, and it doesn't need any special design patterns, then I think you can skip exceptions.
Whereas if you're developing for a large piece of software, I think what you should value more is its extensibility and maintainability.

2. Too many possible Uncaught Exceptions

If you call a function that might throw an exception but don't catch the exception, okey, it's a Fatal Error. So our code ends up looking like:

<?php
try {
} catch () {
}
....
try {
} catch () {
}
....
try {
} catch () {
}

However, this can be avoided through good design. For example, when I was designing Yaf, I provided global exception handling — that is, something like adding a try/catch at the very top level, where all exception/error-handling logic goes into it. You can also conveniently add your own exceptions in there.

Conclusion

People often criticize me as being fence-sitting, hehe. But after everyone has understood the pros and cons above, would you also, like me, come to think that: there's no fixed conclusion on this matter? Everything starts from the actual situation. 🙂
Having said all this, consider it a brick to attract jade (a humble opening to invite better input). Welcome additions and discussion: here or here.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.