Good analysis but too timid in its conclusions.<p>At <i>every</i> level of complexity, hard-coding a solution is the least evil option. Your codebase is a living expression of your business rules, and it's already using the best format you have for expressing your business domain logic (if this isn't true, use a better language). When the business rules change, the codebase should change. You already need to be able to deploy code changes quickly (e.g. to fix code bugs), so having to do a release/deploy to make a change to your business logic should not be a scary proposition; if it is, improve your release process.<p>Hard-code everything.
REALLY worth reading the Graham Poulter comment in the original post about the difference between spatial and temporal variations.<p>The idea that you can hard-code everything assumes there is no variation in deployments and use of a piece of software.<p>If that were the case, you're talking about a SUPER simple piece of software. So sure, hard code everything.<p>But as soon as you're talking about a piece of software that will be used in multiple locations and with possibly different release / roll-out schedules, "there will be issues".
The driver here is needing to make changes to the behavior of the app in specified ways, faster than the release cycle. My advice is to go no further than key-value configuration settings, and keep a documented set of Postman requests in the repo to serve as your UI for <i>developers</i> to invoke.<p>This way you get the immediacy of being able to change prod behavior outside of the release cycle, the safety of knowing only your devs can make those changes, and the ability to easily build a real UI later if the hidden features become features you want visible to non-technical users or your customers.<p>A rules engine is where the descent into madness begins. Every single thing the rules engine tweaks needs to be an actual feature with actual RESTful routes dedicated. Overloading a configuration regime, which is <i>only</i> supposed to handle keys and values, into the key instrumentation for the <i>entire</i> application, bolts inevitably poorly-documented semantics onto the application.<p>Different devs or departments will see the two competing regimes and pick whichever one they like the most to add on to. You'll end up with two kingdoms at war. You want peace reigning throughout your empire.<p>Configuration is <i>part of your application infrastructure</i>. Rules engines generate competing semantics. Semantics are how the brain understands systems. You want one overarching paradigm, one source of truth for how things get done in your application.
>Initially there was hope that non-technical business users would be able to use the GUI to configure the application, but that turned out to be a false hope<p>Entire companies, products, and developers encapsulated in one beatiful sentence.
I once worked at a place that reached DSL on the clock; no one understood the undocumented DSL (not even the programmers supporting it) but a few power users. I advocated re-writing rules in Python, using modern CI/CD techniques to allay fears of hard-coding. But it was too big of a philosophy change. The counter argument was "We don't want end users writing code!" but of course they were already writing code, just in a non-Google-able language...
A thread from 2017: <a href="https://news.ycombinator.com/item?id=14298715" rel="nofollow">https://news.ycombinator.com/item?id=14298715</a><p>2016: <a href="https://news.ycombinator.com/item?id=11155128" rel="nofollow">https://news.ycombinator.com/item?id=11155128</a>
Good article, interesting topic. I think this clock represents the layers of abstractions an application or piece of code goes through during its lifetime. At first, no abstraction, just literal values. As you abstract the code you use the language as a tool to hide anything that repeats and expose only the essentials (a.k.a. abstracting). After DSL's at 9am in order to avoid getting back to hard coded values at 12pm, <i>you HAVE to start abstracting at the conceptual/"business" level of the application</i>. This would be the domain, however if the domain, when implemented, is causing a trip around the clock, this means the initial concepts are breaking the abstraction.
Here is the guidance I usually provide:<p><i>Problem Statement:</i><p>There are application features and behaviors that all users need, majority of them need, a minority need, some need. Users have personal preferences and individual information that the software needs to know.<p>We tend to handle some of the above using code, some using configurations.<p><i>Guidance #1</i><p>Configuration is not a solution across the board. Configurations are often a premature optimization towards saving future efforts.<p>Code _is_ configuration for the processor. Coding has development methodologies and tools designed by the industry over decades, most of which is not applicable to configurations. So don't make code run-time configurable. Change the code as and when needed. Refactor.<p>Exceptions:<p>- For personal preferences and individual information.<p>- If runtime behavior of the code <i>must</i> be changeable without rebuilding the code.<p><i>Guidance #2</i><p>If the software behavior can be changed with lesser number of lines of configuration than the number of lines of code, develop better abstractions in the code. (Do not invent DSLs, create better abstractions in the code itself.)<p>Keep code configurable via hard coded configurations at an appropriate place somewhere within the code. This encourages modularity. However, limit the flexibility to at most 30% development effort overall overheads above and beyond the currently known requirements. If development efforts overheads for the flexibility is much more, that flexibility is a premature optimization (keeping in mind, you aready have flexibility via ability to change the code). If you are not thinking above and beyond the currently best known requirements, you may find the requirements changing faster than what you can keep pace with.<p><i>Guidance #3</i><p>Instead of configurations, find a more specific alternative.<p>- Machine Learning models are technically code configurations, though we do not see it that way. ML comes with needed tooling to manage.<p>- Knowledge graphs.<p>- Data exchange file formats.<p>- Etc.<p><i>Guidance #4</i><p>Configurations are not for SDEs.
Identify the owner who would be responsible for changing the configurations and see them as customers, in the current phase of development. Think of what help and tools are you providing them to manage the configurations.<p><i>Guidance #5</i><p>Do not let the space of configurations multiply. Configurations parameters must be modular (i.e., independent) just like code.<p>Just as functions having more than three parameters should be avoided, same applies to configurations impacting the behavior of a function. Avoid more than three of them taken together for any function.<p><i>Guidance #6</i><p>A configuration is also a contract. Pay no less attention to it than to function interface or API design.<p>Configurations need to be equivalently documented. Think of them as command line arguments. If the user needs to know the implementation internals to understand the command-line arguments, default against having them.<p><i>Guidance #7</i><p>Backward compatibility and blast radius reduction are not valid arguments to have distributed configurations. Depend on automated and manual testing instead. Reason correctly about how many of the users would need the variation in software behavior.<p>If a code change is to be made (i.e., it makes sense), try to apply it everywhere. (I presume when Microsoft went to fixed and mandatory Windows update cycles, it would have helped them a lot.)<p><i>Guidance #8</i><p>If different Product Managers serving different regions or types of users ask for different requirements and you as developers see no valid reason for it, make them talk to each other and document their collective reasoning before getting back to you.