Machine learning products accumulate technical debt because their requirements and business context keep changing during development.
2
Refactoring requires fast tests and experimental operational symmetry, which means running the same code on a laptop, in testing, during evaluation, and in production.
3
Clean architecture with dependency injection, adapters, factories, and strategies keeps business logic stable while data sources, infrastructure, and models change.
Summary
Laszlo Sragner explains why machine learning products need software engineering practices. These products often have long lifespans, solve fuzzy business problems, and change while they are being built. Teams also create friction when data scientists hand exploratory work to machine learning engineers, since feedback then takes longer to reach the original code. Sragner defines technical debt as taking a planned step toward knowledge with a way to correct it later. Refactoring pays that debt down, but only when code can be tested quickly and run consistently across environments. His clean architecture puts domain models and business logic at the center, with data sources, infrastructure, databases, and external services around them. Dependency injection and inversion of control reduce coupling. Adapter, factory, and strategy patterns isolate external services, data construction, and model choices. The result is code that can be explored in notebooks, tested with small data, and moved into production without rewriting the central logic.
Machine learning products accumulate debt because their problems keep changing
Sragner distinguishes an ML product from infrastructure and data analysis. An ML product is an autonomous business process with a long lifespan and an end-to-end input-output relationship. Its requirements are hard to specify because real-world problems contain edge cases and ad hoc rules. The business problem also changes while the team is solving it, so the product is always catching up. The long lifespan makes removing technical debt worthwhile. He also separates debt from unfinished work: missing documentation, infrastructure, features, and broken models are not automatically technical debt.
Technical debt is planned learning, and refactoring is how teams repay it
Sragner uses Ward Cunningham's original idea of technical debt: a team takes a step in an unknown direction while keeping a plan for returning to the desired design. He rejects the credit-card comparison because debt should produce a useful return. Paying it back requires code that is clean enough to refactor. Refactoring means changing the code without changing its behavior. Once a problem has been solved, the team can reshape the implementation so that future changes are easier. This requires fast testing and a consistent way to run the code.
Experimental operational symmetry keeps one implementation across environments
Experimental operational symmetry, or EOS, means running the same code on a developer's laptop, in testing, during model evaluation, during training, and in production. Sragner presents this consistency as a condition for safe refactoring. If a class depends directly on a database or a GPU that is unavailable on a developer's machine, the team cannot run a test locally. Without a reliable test, it cannot confidently change the implementation. EOS makes it possible to replace the surrounding environment while keeping the central business code unchanged.
Decoupling separates business logic from infrastructure
Decoupling reduces dependencies between system components. It lets a team split a problem into smaller parts and hide database, computing, and external-service details from the ML product. Sragner describes this as breaking the situation where changing one thing changes everything. Inversion of control lets infrastructure call the main entry point of the product instead of the product directly controlling every dependency. Dependency injection then connects an abstract Python class to external interfaces. Tests can inject files, a file system, or mocks, while production can inject real services.
Clean architecture puts the domain model at the center
The central part of Sragner's architecture contains the domain model, business logic, and the models that automate the business problem. Data sets, feature engineering, databases, computing infrastructure, and external services sit around that center. Most attention and refactoring happen in the central code. The outer components can be replaced for testing, evaluation, or production without changing the business logic. This arrangement also lets teams distribute responsibility, since infrastructure components can be handled by other teams without forcing changes in the product's core.
Three patterns isolate the changes that happen often in ML systems
Sragner recommends three patterns for this structure. An adapter wraps an external service, such as a SQL database, behind an interface so local tests can use a file-based implementation. A factory creates domain-specific objects from a data source, allowing the rest of the product to work with terms such as a customer instead of raw data frames. A strategy isolates algorithmic choices. The main code can call a model through a common interface without knowing whether the implementation is a deep model, a random forest, or another algorithm.
A small test pipeline and explicit interfaces support fast iteration
His suggested workflow starts by defining data classes and the business logic. Data sources are wrapped with factories, then a test environment runs the same pipeline on one percent of the data. Sragner says this is enough to catch typos and stylistic errors. The team defines interfaces as the actions it wants to perform on the data and groups them into service classes. External production services are wrapped with adapters. The same code can then move from testing to production, while new experiments use strategy implementations instead of edits to production logic.
Notebook exploration can use the same product code
Sragner does not ask data scientists to abandon notebooks. Factories can be instantiated in a notebook to load domain data, which can then be converted into data frames for analysis and plotting. Because the main program is a class, it can also be instantiated in the notebook with the appropriate plugins. This lets the data scientist work with code that resembles the production setup and try new strategies locally. The notebook remains an analysis environment while the product logic stays in the same structure used for testing and deployment.