Clean Code
Chapter 7
- Error handling should not obscure logic
- Code bases should not be completely dominated by error handling
Exceptions
-
Use exceptions, not return codes
- Return codes require the caller to check for errors immediately
- Throwing exceptions makes code cleaner
- Enforces single responsibility principle, decouples multiple types of error checking
-
Write the try-catch-finally statement first
- When you execute code in try it means the execution could abort at any time
- The runtime will resume within the catch statement
- Helps define what the user should expect from that code, no matter what goes wrong
-
Use unchecked exceptions
- Checked exceptions violate the open/closed principle
- Throwing checked exceptions from a method where catch is a few levels above means that exception must be declared at each intermediate method
- A change at a low level can force changes on multiple higher levels
-
Provide context with exceptions
- There should be information regarding the source and location of the error
- Create informative error messages
-
Define exception classes based on how exceptions should be caught
- Chaining catch statements tend to have duplication because error handling is usually pretty standard despite the type of error
- The code can be simplified by wrapping it and making sure it returns a common type of exception
- A wrapper class just catches and translates exceptions
ACME port = new ACMEPort(12); try { port.open(); } catch(DeviceResponseException e) { reportPortError(e); } catch(ATM1212UnlockedException e) { reportPortError(e); } catch(GMXError e) { reportPortError(e); }
LocalPort port = new LocalPort(12); try { port.open(); } catch(PortDeviceFailure e) { reportError(e); } /* Wrapper */ public class LocalPort { /* Constructor to initialise port with ACME */ public void open() { try { innerPort.open(); } catch(DeviceResponseException e) { throw new PortDeviceFailure(e); } catch(ATM1212UnlockedException e) { throw new PortDeviceFailure(e); } catch(GMXError e) { throw new PortDeviceFailure(e); } } }
-
The idea is to push error detection to the edges of the program
- We wrap external APIs to throw our own exception
- Define handlers at a high level so it can deal with any aborted computation
- However, sometimes you have to create a class or configure an object to handle a special case for you (called the Special Case Pattern)
- The client code shouldn't have to deal with exceptions, that behaviour is encapsulated in the special case object
Null
-
Don't return null
- Creates more work for ourselves by having to check for null
- A missed null check would potentially mean an uncaught NullPointerException from the depths of your application
- The issue is there are too many null checks, consider throwing exceptions or returning special case objects instead
-
Don't pass null
- Unless an API expects a null pass, avoid passing null to functions
- There's not really a good way to deal with passing null except forbidding it in the first place
- Could check for null then throw our own exception, but then we have to define a handler for it
- Could use a set of assertions which provides good documentation, but doesn't solve the problem of getting a runtime error