software-design-principles

1 posts

line

Code Quality Improvement Techniques Part 30 (opens in new tab)

Code quality often suffers when functions share implicit dependencies, where the correct behavior of one relies on the state or validation provided by another. This "invisible" connection creates fragile code that is prone to runtime errors and logic mismatches during refactoring or feature expansion. To solve this, developers should consolidate related logic or make dependencies explicit to ensure consistency and safety. ## Problems with Implicit Function Dependencies When logic is split across separate functions—such as one for validation (`isContentValid`) and another for processing (`getMessageText`)—developers often rely on undocumented preconditions. * **Fragile Runtime Safety:** In the provided example, `getMessageText` throws a runtime error if called on invalid data, assuming the caller has already checked `isContentValid`. * **Maintenance Burden:** When new data types (e.g., a new message type) are added, developers must remember to update both functions to keep them in sync, increasing the risk of "forgotten" updates. * **Hidden Logic Flow:** Callers might not realize the two functions are linked, leading to improper usage where the transformation function is called without the necessary prior validation. ## Consolidating Logic for Single-Source Truth The most effective way to eliminate implicit dependencies is to merge filtering and transformation into a single function. This ensures that the code cannot reach a processing state without passing through the necessary logic. * **Nullable Returns:** By changing the transformation function to return a nullable type (`String?`), the function can signal that a piece of data is "invalid" or "empty" directly through its return value. * **Simplified Caller Logic:** The UI layer no longer needs to call two separate functions; it simply checks if the result of the transformation is null to determine visibility. * **Elimination of Redundant Branches:** This approach reduces the number of `when` or `if-else` blocks that need to be maintained across the codebase. ## Establishing Explicit Consistency In scenarios where separate functions for validation and transformation are required for clarity or architectural reasons, the validation logic should be defined in terms of the transformation. * **Dependent Validation:** Instead of writing a separate `when` block for `isContentValid`, the function should simply check if `getMessageText` returns a non-null value. * **Guaranteed Synchronization:** This structure makes the relationship between the two functions explicit and guarantees that if a message is deemed "valid," it will always produce a valid text output. * **Improved Documentation:** Defining functions this way serves as self-documenting code, showing future developers exactly how the two operations are linked. When functions share a "red thread" of logic, they should either be merged or structured so that one acts as the source of truth for the other. By removing the need for callers to remember implicit preconditions, you reduce the surface area for bugs and make the codebase significantly easier to extend.