The Trap of Over-Decomposition Software developers often treat Clean Code as a rigid checklist: small classes, short methods, and abstractions everywhere. However, Arjan Codes warns that applying these rules blindly leads to **over-decomposition**. This creates a design that looks tidy on the surface but hides a "huge monster in the closet." The problem arises when you optimize for smallness rather than **cohesion**. In the initial example, a sales report script used a Protocol and a Container for dependency injection. While these are technically advanced patterns, they served no functional purpose, merely masking a messy 200-line "run" method that handled loading, filtering, and exporting simultaneously. Real clean code isn't about the number of lines; it is about making the reasons for change visible and grouping logic that belongs together. Refactoring the Fake Abstraction To begin the cleanup, we must strip away abstractions that solve imaginary problems. If a container exists just to instantiate one class that is never swapped out, it is dead weight. By deleting the `ReportService` protocol and the `Container` class, we bring the logic back to the `main` function where it can be directly controlled. Next, we introduce a Data Class for configuration. Instead of passing five or six separate arguments through every function, we group them into a cohesive `ReportConfig` object. This allows for sensible defaults—like UTF-8 encoding or comma delimiters—while making the settings explicit and easy to modify in one location. ```python @dataclass(frozen=True) class ReportConfig: country: str = "Netherlands" min_revenue: float = 10.0 allow_negative: bool = False delimiter: str = "," encoding: str = "utf-8" ``` Making the Pipeline Explicit One of the most significant design improvements involves breaking the monolithic run method into a clear, linear pipeline: **Load → Summarize → Export**. By separating these concerns into pure functions, we gain massive flexibility and efficiency. For instance, by moving the loading logic out of the core processing function, we can load the data once and run multiple summaries against it without re-reading the file from the disk. We also introduce a `TypeAlias` for our data structures to improve readability without the overhead of heavy class hierarchies. ```python from typing import TypeAlias Data: TypeAlias = list[dict[str, str]] def load_data(source: Path, config: ReportConfig) -> Data: # File loading logic here return data ``` Modeling Results with Cohesion Instead of letting the summary data float around as raw numbers, we move behavior into the `Summary` object itself. If you need to convert a summary to text or JSON, that logic belongs to the summary, not the exporter. This shift moves the "how" of the report into the object that owns the "what." We also centralize business logic. By creating an internal `is_valid` helper within the summarization function, we isolate the filtering rules (e.g., checking revenue thresholds or refund status) from the aggregation logic. This makes the primary loop significantly cleaner and focuses the `summarize` function on its actual job: calculating totals. ```python def summarize(data: Data, config: ReportConfig) -> Summary: def is_valid(row: dict) -> bool: return float(row["revenue"]) >= config.min_revenue valid_rows = [row for row in data if is_valid(row)] revenue_sum = sum(float(row["revenue"]) for row in valid_rows) return Summary(count=len(valid_rows), revenue_sum=revenue_sum) ``` Practical Examples and Syntax Notes This approach shines when extending the system. To add a JSON export, we simply define an `export_json` function. Because our pipeline is explicit, we don't have to touch the loading or summarizing code. We just plug the new exporter into our main execution flow. When using Python for these patterns, utilize `Path` objects from `pathlib` rather than strings to handle file system operations more robustly. Additionally, the `asdict` utility from the `dataclasses` module is perfect for quickly converting your cohesive objects into formats suitable for `json.dumps` while maintaining control over the final output structure. Tips and Gotchas Avoid the temptation to abstract early. Wait until you have at least two or three different implementations before reaching for a Protocol. The most common mistake is "hiding" behavior inside services, which makes debugging difficult. High cohesion means things that change together stay together; it doesn't mean every function has to be three lines long. Focus on visibility and meaningful boundaries to keep your code truly maintainable.
Python Protocol
Products
- Mar 27, 2026
- Sep 26, 2025