Beyond the Banana: Mastering Precision for Clean, Maintainable Code
The Cost of Imprecision in Software Design
Writing clean code isn't just about aesthetics; it's about being mathematically and logically precise with your data. When developers write imprecise code, they inadvertently create systems that are harder to test, difficult to reuse, and prone to breaking during refactoring. Precision means being mindful of exactly what data a function requires and what it promises to return. By tightening these constraints, you reduce cognitive load for your teammates and ensure your software remains flexible as it scales.
Avoiding the Gorilla and the Jungle
A common architectural trap is requesting more data than a function actually needs. This violates the Law of Demeter (the principle of least knowledge). For example, if a function needs a coordinate but you pass it an entire Location object containing metadata, timestamps, and history, you've over-coupled that function. You cannot use it elsewhere without providing that heavy object. As Joe Armstrong, the creator of GeoLocation—to keep the function focused and decoupled.
The Logic of Strict Input and Single-Type Returns
Functions that accept a "grab bag" of types (Union types like str | int | bytes) often seem convenient but lead to internal complexity. These functions require internal if/else logic to handle every variation, complicating your unit tests. Instead, strive for strict input types. If you need convenience, use factory methods like from_int or from_string to convert data before it hits your core logic.
Similarly, return types should be predictable. Returning a URI object or a bool (for failure) forces the caller to write boilerplate error handling everywhere. A more precise approach uses exceptions. By raising a custom exception for invalid data and returning only the success type, you simplify the calling code from a dozen lines of checks to a clean, three-line execution flow.

Leveraging Generic Parameters and Specific Returns
While inputs should be strict in value, they should be generic in capability. In list, you should often require an Iterable or Sized type. This allows your function to accept tuples, sets, or even custom objects without changing a line of code. However, the return type should remain highly specific. By returning a list rather than a generic Iterable, you tell the caller exactly what methods (like .append() or indexing) are available. This asymmetry—generic inputs and specific outputs—is a hallmark of professional-grade software design.

Fancy watching it?
Watch the full video and context