What Objective-C can learn from Java, Part 5 (Exceptions)

Posted by Thoughts and Ramblings on Tuesday, April 17, 2012

This is one past the last is a series of blog posts I’m writing on things that Objective-C can learn from Java. I’m writing this because I’m seeing a series of ignorant tweets stating how much Java sucks. The other parts can be found here:

Anyone who programs in Java for a short period of time becomes well aware of Java’s exceptions. To the new programmer, they may be a bit of an annoyance, but to anyone designing enterprise-level code, they are a necessity. Objective-C has exceptions too, but they are so weakly defined in the language and so overlooked, it’s almost as if they were nothing more than an afterthought. In fact, one can argue that the exception handling in C++ is superior to what one would find in Objective-C.

In Java, the exception handling is of the familiar:

try {
   <code that throws exceptions here>
} catch (ExceptionTypeA e) {
   <code to handle exception type a here>
} catch (Exception e) {
   <code to handle other exceptions here>
} finally {
   <code to execute regardless of whether an exception was thrown or not>

Of course, in the above, the catch statement is optional if one wants the exception to propagate outside of the block and the finally is optional if one doesn’t have code that needs to be executed in all cases. This block is even more powerful in Java 7, with its try-with-resources ability, but I’ll leave that point out for now. In Objective-C, the block looks like the following:

@try {
   <code that throws exceptions here>
} @catch (ExceptionTypeA e) {
   <code to handle exception type a here>
} @catch (Exception e) {
   <code to handle other exceptions here>
} @finally {
   <code to execute regardless of whether an exception was thrown or not>

It looks very similar. At a first glace, one may think that the two are the same, but appearances are deceiving. In Java, there are two different kinds of exceptions, checked and unchecked. Checked exceptions are ones that a function may throw, that all callers must handle. This is the familiar throws declaration. Objective-C only has unchecked exceptions, or runtime exceptions. Additionally, exceptions are heavily discouraged in Objective-C, to quote the documentation:

In many environments, use of exceptions is fairly commonplace. For example, you might throw an exception to signal that a routine could not execute normally—such as when a file is missing or data could not be parsed correctly. Exceptions are resource-intensive in Objective-C. You should not use exceptions for general flow-control, or simply to signify errors. Instead you should use the return value of a method or function to indicate that an error has occurred, and provide information about the problem in an error object.

One part that is missing from the above is that a thrown exception bypasses ARC, meaning that throwing an exception will very likely cause a memory leak. It should be noted that stack objects are freed and even have their destructors called in C++ when an exception is thrown. Here is a case where C++ is superior to Objective-C (I never though I’d see myself write that).

So, from this, one can conclude that the use of exceptions in Object-C is discouraged at best, worthless at worst. Apple’s documentation encouraged using an error object, but this method is very lacking. These error conditions are rarely checked in practice, and the object tends to be used again causing more errors that are not checked, and so forth. In a system using exceptions, where one is forced to handle the exceptions, the error handling can be put in a single location for all the functions in the frame which can throw that exception, avoiding the tedious error checking after every single function call.

Additionally, returning an error code is lacking in terms of context. Java’s exceptions contain stack traces and the ability to nest exceptions. One common method, especially in library code, is to contain a thrown exception inside another exception, providing additional information, and throw the new resulting exception. Then, when this exception is finally caught, a lot more information is available than a simple “this type of error occurred.”

So, what does it take to fix this? The changes are likely very significant. Apple did improve exception handling in Objective-C 2, but they still failed to bring it up to where Java has been in the beginning. Their changes seem to suggest they knew that Objective-C’s exception handling was lacking, but they didn’t invest enough resources to bring it up to par. This either means they don’t care, or that the changes were too significant. I’m going to lean toward the latter since my knowledge of the Objective-C runtimes leads me to believe that it cannot really be made to handle exceptions properly.

Impact on Runtime: Guessing here: Significant. The runtime would need to be completely reworked to handle exceptions at every stage. ARC would need significant work and throwing/catching exceptions would need to be made significantly cheaper.

Impact on Code: Adding checked exceptions to the code would require adding more syntax sugar to method calls and a lot of code would need to be reworked to handle exceptions instead of checking error codes. Random errors would be less likely to be swallowed, and users will have a better idea as to the true nature of their problem (no more stepping through assembly in a library tracking down what should have been a thrown exception as I’ve had to do in the past).

Legacy Comments:

thirdman - May 5, 2012

I’ve just started coding in XCode, and I agree with most of what you about Obj-C’s shortcomings. Especially the lack of proper exception-handling and the lack of namespaces keeps programmers from creating concise and clear code. Listening to Obj-C defenders typically defending these obvious shortcomings as saying “it’s because Obj-C is trying to retain as much compatibility with old C” makes me think they’re still cringing to the computer world back in 80’s. I’m just playing around with Obj-C as a hobby, but I’m personally very glad that I don’t have to deal with this (in many senses) archaic language professionally.

Jim - May 15, 2012

I agreed with most of your previous complaints, but I have to disagree with this one. The way Objective-C handles error conditions is a much more elegant system than Java’s Try/Catch blocks. It doesn’t matter that exceptions leak memory under ARC because they truly do indicate exceptional circumstances. They are indicative of programmer error and need to be fixed, not caught. The beauty of Cocoa and UIKit’s error handling is that it can safely be ignored when it doesn’t matter. If you only need to know if a method fails, look at the return value and ignore the error. The error handling is also self-documenting. You don’t need to consult the documentation to see if a method throws an error; if the method takes an NSError parameter than you can expect that error to be populated should something go amiss.

Graham Booker - May 15, 2012

Jim, You seemed to have completely missed the point. First of all, exceptions are not always programmer error but rather, especially in production code, they are indicative of something else, such as error conditions. In this case, there is nothing to fix in the code, but rather an unusual circumstance that needs to be addressed. Additionally, it needs to be addresses quickly (in terms of amount of code that’s executed) to minimize side effects which assume its success. My mention of ARC’s shortcomings in this regard was to exemplify the fact that Objective-C cannot use exceptions for error handling in any type of production code. In order to accomplish the above in Objective-C’s paradigm, one has to check the return value of every single call. You mentioned checking the return value, which is the minimum, but this alone can become very tedious very quickly. Take a function which uses a resource in 15 calls, and that resource could become unavailable due to external influence. Checking the return value on each call is a lot of code, and skipping some or checking the last one can be dangerous because the implementation may not hit the underlying resource in every call. The same is accomplished with a single try/catch block. Lastly, the comment about self-documenting is moot. You mentioned not having to consult the documentation, but you failed to mention that consulting the documentation is necessary to see that it takes an NSError ** parameter. The same place where one would find this, via the function declaration or the IDE handling it, also displays/handles the throws declaration.

Erik - Aug 9, 2012

Graham, I think you are glorifying exceptions. You only mention the positive sides without mentioning any of the many negatives. I am surprised that you talk about checked exceptions as consensus seems to be that that was a bad choice in Java. Hence why they purposefully didn’t do it in C#. Exceptions is not some magic solution for handling errors. I totally agree with the Authors of the Go programming language in why they chose to not support it. With regards to self documenting. When looking at a line of code you can see if an error object is returned. You can not always see if an exception may happen at that line. I think your 15 calls example is rather contrived. More often than not the only thing I see being done at the catch is to log the problem to console. You can easily do the same in Objective-C by logging directly in the methods were the resource becomes unavailable instead of throwing. Anyway I have developed for 11 years without exceptions and it is simple not such a huge problem. Your 15 calls example seldom if ever pops up in practice.

Graham Booker - Aug 9, 2012

Erik, I’ve only found that the “consensus” that checked exceptions are a bad choice in Java are from those who are not interested in designing robust code. Additionally, it should be added that both C# and Go have unchecked exceptions, which actually renders most of the argument against exceptions (such as flow control) moot. Exceptions are self-documenting; you experience is just poisoned by the lousy IDE that is Xcode. If I click on an exception in either a catch or a throws statement in Eclipse, it highlight every single call that can throw that particular exception. The same is not true of errors. Don’t forget, many functions report error conditions by returning a negative value. The 15 calls is not contrived in the least; it is actual code. In this case, it was an OpenGL renderer, which makes numerous render calls to draw some objects in a scene. Any one of those calls can fail, and the failure is indicated by the return value alone. As far as catching an exception simply to log it, that’s a case of exception/error mishandling. Anyway, I’ve developed for 20 years, 14 of which since I learned exceptions. It has solved a large number of problems which were previously silent failures (of which I still encounter several when writing code on Apple’s platform).