Error handling is a balancing act. Too much and unnecessary errors will drive you insane. Too little and you have a terrible user experience. When things go wrong we are responsible for making sure that our code works.
Use Exceptions Rather Than Return Codes
Exceptions are new in the world of software development. Exceptions revolved around console logs, crafting error flags, or returning an error code.
public class UserController {
public void getUser() {
User user = _db.GetUser();
// Check the state of the device
if (user != null) {
// Do something with user
} else {
logger.log("Something happened!");
}
} else {
logger.log("Something else happened and I have no idea!");
}
}
}
The problem with these approaches is that they create clutter and require any code to check for error codes. Not fun!
For this reason it is always better to throw an exception when you encounter errors.
Try Catches Are Your Friend!
public class UserController {
public void GetUser() {
try {
User user = _db.GetUser();
} catch (Error e) {
console.log(e);
}
}
}
Exceptions and try-catches are unique in that they define a scope within your program.
When a try-catch triggers, the part of code you are at shoots to the top of the stack.
In a sense, try-catches are similar to database transactions. The try-catch must leave the program while it is executing. This helps keep the code consistent no matter what goes wrong.
Use Unchecked Exceptions
Checked exceptions are checked at compile time (hence the name). Simply put, the Java compiler forces you to catch exceptions or declare them in the method signature using throws keyword
.
Unchecked exceptions are not checked at compiled time. Java does not require that you provide an exception because the exceptions occur at runtime.
The issue with checked exceptions is that you can’t ignore them and will usually require throws
in the signature of the code. Also, because of this check exceptions are often called tightly coupled. Why? Because you will have to go back and change all the throws
if you want to modify the code.
Unchecked exceptions are not checked at compiled time. Java does not require that you provide an exception because the exceptions occur at runtime.
C# is entirely composed of unchecked exceptions, so no need to worry.
Describe Exceptions W/ Source + Location
Very similar to clean code naming conventions, appropriate naming for exceptions are important too. Here are some examples of bad exception naming:
- CriticalError
- Code12Error
- ModuleError
When we define exceptions, we should focus on how they are caught.
User user = new User();
try {
user.find();
} catch (UserResponseError e) {
logger.log("User Response Error", e);
} catch (UnknownUserError e) {
logger.log("Unkown User Error", e);
} catch (UserServerError e) {
logger.log("User Server Error", e);
}
This code contains massive amounts of duplication. Besides over-optimization, duplication is evil (in programming).
try {
user.find();
} catch (UserResponse e) {
logger.log("user error", e);
}
First, shorten down the exception and make it as small as possible.
public class UserService {
private User _user;
public LocalService(User user) {
_user = new User(user);
}
public User GetUser() {
try {
_user.getUser();
} catch (UserResponseError e) {
throw new UserResponseError(e);
}
}
This class provides a nice wrapper around our exceptions and makes our code look way better.
Too Many Null Checks
We’ve all seen code like this:
public void UpdateUser(User user) {
if(user != null) {
var dbUser = _db.GetUser();
if(dbUser == null) {
throw Exception();
}
}
}
But what causes this phenomenon? Returning null from other functions!
Also, why are null checks bad? All it takes is one missing null to send your program out of control.
If you have too many null checks, add more exceptions!