Ultimate Open-Closed Code Evolution Cycle

Open-Closed Principle (OCP) is one of the SOLID principles. Code should be open to extension but closed to modifications. It was originally formulated for classes and objects, but I find that it is applicable in more contexts transcending object-oriented programming (OOP).

When you have a useful (usually quite small) well-designed component, instead of modifying it for your needs, you find a way to compose over it and get the new behavior without modification. Composing immutable functions and immutable data structures is a staple of functional programming and clean software design. Preserving behavior of public API via versioning is a cornerstone of good libraries.

With long-living successful software, customers come with new requirements, continuously asking for new behavior from it. But existing behavior exhibited by it is also valuable to someone somewhere, who’ve built their workflows around those original behaviors. We do not want to disappoint the existing userbase. Adhering to OCP is one way to achieve that (another critical thing is having good tests for use cases).

I have seen the following cycle to happen with real world applications.

Stage 0: Everything is hardcoded

It the beginning, there are not many customers asking for varying functionality. At this stage, it makes sense to avoid premature over-generalization (because YAGNI) and just hardcode the desirable behavior.

There is only one behavior, and it has not been extended yet.

Stage 1: Configuration knobs appear

Some clients want a particular feature to work in a particular way. At the same time, there are other customers that want something completely incompatible out of the same thing. A solution? Let the customer configure the desirable alternative behavior via configuration knobs. It could be called flags, options, etc. The exact mechanism does not matter much, as long as these knobs allow to alter behavior without forcing you to release a new copy of software.

Why does this conform to OCP? That is because users now can change behavior without the need to change the underlying code. Supported range of “extensions” is limited and predetermined, but it exists.

Stage 2: Modules appear

Your software becomes too big to be built and shipped as one monolith. It gets split into individually deployable pieces. This opens another opportunity for extending the behavior without modifying code, by choosing which modules to include and which to exclude from every specific deployment.

Here, another principle: “Prefer convention over configuration” — comes to play: if a certain module is discovered to be present in some predetermined place (e.g., a dynamic library in an installation folder), its behavior is automatically included. No explicit start-up configuration is required from customer.

Stage 3: Configuration files appear

There are too many modules and too many knobs. It is unwieldy to deal with all of that every time the application is started.

The solution is to save your desired configuration choices somewhere persistent and reuse them across many runs. A typical place is a configuration file. In its early stage, it is usually something that allows to store a bunch of pairs key=value. It could become a relational or another kind of a database, but its function will remain.

By editing configuration file, your customers can configure your software for a wide range of behaviors without you needing to add a single line of code.

Stage 4: Configuration files hierarchy appear

A single configuration file has grown to be too unwieldy. An increasing number of customers would be editing it concurrently with each other, which creates conflicts. Many customer groups are not interested in the whole file but only in its subsections.

Naturally, configuration’s individual pieces are extracted and placed in their own smaller files. Include or import directives are added to the configuration language to allow files to link to each other.

Now your customers can fine-tune quite complicated variations of behavior that your software product exhibits.

Stage 5: Configuration language is introduced

There is too much behavioral variation requested by customers. Static configuration files cannot adequately reflect those needs. Decisions on what to ask from your software need to be taken very late, right before its start, or even dynamically during its operation.

This can be made achievable by making configuration files “alive”, i.e., to become programs on their own, likely written in an interpreted language. These instructions are then parsed and executed by an interpreter which controls your application.

This can be traced back to the Interpreter design pattern from the classic list of design patterns. Alternatively, this is the effect of Greenspun’s tenth rule.

Back to square one

You have an ultimate flexible system now op top of your original software. With a scripting/interpreted language available for controlling it, your customers can request almost any behavior from it.

The original system is still fully OCP-compliant: it is up to the scripting environment to decide which pieces of it to invoke and in which order, and it is achieved without modifying a single line of code of that system. All the variation is contained in the configuration language.

The problem is, not many of your customers want to program in that language. After all, they paid you money to write the software in the first place. The new configuration layer is also a program now, another one, most likely as expressive as the inner one. But this new layer is not yet OCP-compliant: someone has to modify its code to achieve new behavior from it.

You are essentially back to the beginning, only this time you have to find a way to apply OCP for a new layer of abstraction, with different domain vocabulary.

If you have been clever enough from the start and anticipated it, you can prepare for the cycle to close. Make it so that the inner software and its new configuration wrapper are written using the same programming language!


Written by Grigory Rechistov in Uncategorized on 23.10.2025. Tags: solid, ocp, oop, interpreter,


Copyright © 2025 Grigory Rechistov