Code Quality Improvement Techniques Part 23 (opens in new tab)
While early returns are a popular technique for clarifying code by handling error cases first, they should not be applied indiscriminately. This blog post argues that when error cases and normal cases share the same logic, integrating them into a single flow is often superior to branching. By treating edge cases as part of the standard execution path, developers can simplify their code and reduce unnecessary complexity.
Unifying Edge Cases with Normal Logic
Rather than treating every special condition as an error to be excluded via an early return, it is often more effective to design logic that naturally accommodates these cases.
- For functions processing lists, standard collection operations like
maporfilteralready handle empty collections without requiring explicit checks. - Integrating edge cases can lead to more concise code, though developers should be mindful of minor performance trade-offs, such as the overhead of creating sequence or list instances for empty inputs.
- Unification ensures that the "main purpose" of the function remains the focus, rather than a series of guard clauses.
Utilizing Language-Specific Safety Features
Modern programming languages provide built-in operators and functions that allow developers to handle potential errors as part of the standard expression flow.
- Safe Navigation: Use safe call operators (e.g.,
?.) and null-coalescing operators (e.g.,?:) to handle null values as normal data flow rather than branching withif (value == null). - Collection Access: Instead of manually checking if an index is within bounds, use functions like
getOrNullorgetOrElseto retrieve values safely. - Property Dependencies: In UI logic, instead of early returning when a string is empty, you can directly assign visibility and text values based on the condition (e.g.,
isVisible = text.isNotEmpty()).
Functional Exception Handling
When a process involves multiple steps that might throw exceptions, traditional early returns can lead to repetitive try-catch blocks and fragmented logic.
- By using the
flatMappattern and Result-style types, developers can chain operations together. - Converting exceptions into specific error types within a wrapper (like a
SuccessorErrorsealed class) allows the entire sequence to be treated as a unified data flow. - This approach makes the overall business logic much clearer, as the "happy path" is represented by a clean chain of function calls rather than a series of nested or sequential error checks.
Before implementing an early return, evaluate whether the edge case can be gracefully integrated into the main logic flow. If the language features or standard libraries allow the normal processing path to handle the edge case naturally, choosing integration over exclusion will result in more maintainable and readable code.