In the fast-paced world of software development, managing complex functions without sacrificing efficiency can be a daunting task. Developers often have trouble with complicated code and need to make sure every possible input is handled smoothly.
This can lead to complicated and confusing code.
It's all too common to find software bugs because of inputs or edge cases that aren't handled properly. As code, it affects how well software works and makes both developers and end users unhappy. As things become more complicated, there are more chances of mistakes and more time needed to fix them.
Enter ✨ Guard clauses ✨ — a valuable tool in a developer's arsenal designed to streamline functions by “failing fast.”
This blog post will show you how to implement the guard clause in your code to enhance readability and maintainability.
What is a Guard?
A guard clause is a check that immediately exits the function, either with a return statement or an exception. Another definition I like is from NuGet who says the Guard clause is a software pattern that simplifies complex functions by “failing fast”, checking for invalid inputs upfront and immediately failing if any are found.
Why using Guard clause
When you write your main function code, and then you write else statements to deal with error cases, this involves inverting your current workflow. So the benefit of using a Guard clause is your code will be less deeply indented, shorter and simpler.
Implementing Guard clause in Java code
In order to practice Java code in this post, you will need all these maven dependencies.
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.16.0</version>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.guicedee.services</groupId>
<artifactId>slf4j</artifactId>
<version>1.2.2.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
First take a look at this function that doesn’t use a Guard clause
import org.apache.commons.lang3.exception.ContextedRuntimeException;
public class UserRegistration {
public String registerUser(String username, String password, String email) {
String validationMessage = null;
if (username == null || username.isEmpty()) {
validationMessage = "Username is required.";
} else if (password == null || password.isEmpty()) {
validationMessage = "Password is required.";
} else if (email == null || !email.contains("@")) {
validationMessage = "Valid email is required.";
}
if (validationMessage != null) {
throw new ContextedRuntimeException();
}
// Proceed with registration if all validations are passed
saveToDatabase(username, password, email);
return "User registered successfully.";
}
private void saveToDatabase(String username, String password, String email) {
// execute saving to a database
System.out.println("User" + username + " with email: " + email);
}
}
This code can be refactored to eliminate the need for the else clauses. This is accomplished by simply inverting the logic of the if statements and putting the exception throwing statements inside of these if statements.
The result looks like this:
public class UserRegistration {
public String registerUser(String username, String password, String email) {
if (username == null || username.isEmpty()) {
throw new IllegalStateException("Username is required.");
}
if (password == null || password.isEmpty()) {
throw new IllegalStateException("Password is required.");
}
if (email == null || !email.contains("@")) {
throw new IllegalStateException("Valid email is required.");
}
saveToDatabase(username, password, email);
return "User registered successfully.";
}
private void saveToDatabase(String username, String password, String email) {
System.out.println("User saved: " + username);
}
}
Repeating the check for null and common behavior of throwing a particular type of exception is a violation of the DRY principle
. To fix that violation, we can use a helper class 🪄 to handle all the Guards logic, like this.
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.exception.ContextedException;
import java.util.Objects;
import static java.lang.Boolean.TRUE;
@Slf4j
public class UserGuard {
private UserGuard() {
log.info("Utility class {} should not be instantiate", this.getClass().getName());
}
public static void againstInvalidUser(Object username) throws ContextedException {
if (againstNull(username) && againstEmpty(username)) {
throw new ContextedException("User name is required.")
.addContextValue("Username", username);
}
}
public static void againstInvalidPassword(Object password) throws ContextedException {
if (againstNull(password) && againstEmpty(password)) {
throw new ContextedException("Password is required.")
.addContextValue("Password", password);
}
}
public static void againstInvalidEmail(Object email) throws ContextedException {
if (TRUE.equals(againstNull(email)) && email.toString().contains("@")) {
throw new ContextedException("Should should be a valid email.")
.addContextValue("Email", email);
}
}
public static Boolean againstNull(Object object) {
return Objects.isNull(object);
}
public static Boolean againstEmpty(Object object) {
return ObjectUtils.isEmpty(object);
}
}
Now you can use the static methods of a Guard helper class, and the code will look cleaner
import org.apache.commons.lang3.exception.ContextedException;
import sample.helper.UserGuard;
public class UserRegistration {
public String registerUser(String username, String password, String email) throws ContextedException {
UserGuard.againstInvalidUser(username);
UserGuard.againstInvalidPassword(password);
UserGuard.againstInvalidEmail(email);
// Proceed with registration if all validations are passed
saveToDatabase(username, password, email);
return "User registered successfully.";
}
private void saveToDatabase(String username, String password, String email) {
// Simulate saving to a database
System.out.println("User saved: " + username);
}
}
The code snippet of this post is available on my GitHub here.
I hope you enjoyed reading this, and I'm curious to hear if this tutorial helped you. Please let me know your thoughts or opinions below in the comments section.
You can have more of my content by following me on LinkedIn • GitHub or Medium