Arjan Codes reveals why blind clean code rules destroy software design
The Trap of Over-Decomposition
Software developers often treat
In the initial example, a sales report script used a

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 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.
@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.
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.
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 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