The problem
Let's think of a system, where a user should be able to create a new account if the following validations are passed:
- the username is valid
- the email is valid
- the phone is valid and verified
Here's an example of a scenario while constructing the create_account
function, the primary thought process was to validate the corresponding requirements until arrived at the desired outcome.
function create_account( $user_name, $email, $phone_number ) {
if ( $user_name->is_valid() ) {
if ( $email->is_valid() ) {
if ( $phone_number->is_valid() ) {
$is_verified = send_verification_code( $phone_number );
if ( $is_verified ) {
// Finally, create the account.
} else {
// Handle verification error.
}
} else {
// Handle invalid phone.
}
} else {
// Handle invalid email.
}
} else {
// Handle invalid user name.
}
}
All right, we served the requirements and constructed a function which does exactly what we needed. But What can be observed in this approach?
- It's tough to figure out which "else" goes with each "if," making error handling difficult to read, especially when the "if" block is long
- Arrow anti-pattern: because of nested conditions and loops, the code begins to resemble an arrow
The Solution
Let's rethink and take a different perspective to refactor the code.
Return early is the way of writing functions or methods so that the expected positive result is returned at the end of the function and the rest of the code terminates the execution (by returning or throwing an exception) when conditions are not met
function create_account( $user_name, $email, $phone_number ) {
if ( ! $user_name->is_valid() ) {
// Handle invalid user name.
return;
}
if ( ! $email->is_valid() ) {
// Handle invalid email.
return;
}
if ( ! $phone_number->is_valid() ) {
// Handle invalid phone.
return;
}
$is_verified = send_verification_code( $phone_number );
if ( ! $is_verified ) {
// Handle verification error.
return;
}
// Finally, create the account.
}
This is performed by:
- Reversing the "if" conditions
- Handling the relevant errors
- Returning or throwing an appropriate exception, thus completing the function's execution
Let's analysis the new approach:
- There is only one level of indentation in the code. It is feasible to read it in a straight line.
- The expected positive outcome is easily found at the function's conclusion
- This approach places a greater emphasis on locating mistakes first and then carefully implementing business logic subsequently, resulting in fewer bugs
- On errors, the function terminates quickly, preventing the execution of more code unintentionally
Conclusion
The "return early" pattern is a great approach to avoid functions becoming too complicated. This does not, however, imply that it can be used at all times. Even with the option of isolating the code to other functions, it is unavoidable to have certain nested "ifs" throughout sophisticated business logic.
The preferable method is to coordinate with the appropriate team, exchange ideas, and select which patterns to adopt in each scenario, ensuring that everyone is programming with the same attitude. Because developers spend more time reading code than writing it.
Top comments (0)