Make hard coding your default choice
enterprisecraftsmanship.com
enterprisecraftsmanship.com
Everything was going fine until I got to this point. You can't tell when you'll need to adjust the log level on the fly and he's right in stating most likely you'll never do it, but when you need to do it you need to do it - and predicting that need is impossible. This is one of those rare things where it's better to have it and not need it than to need it and not have it.
Otherwise this is a good article on addressing the issue of configuration hell.
Maybe you'll need to change the log level faster than that. Maybe you'll be hit by a meteorite. But building in that kind of flexibility isn't worth it.
In that type of environment, I think it is absolutely worth the flexibility. And "building in" that kind of flexibility means to, um, add a line to the existing configuration file.
Your point is also very valid though.
Why no config files? Very simple: a change to config file was just as "expensive" in terms of deployment as a code change, so around a day overhead. Compared to that overhead, the recompile was not measurable, so there was no benefit to config files.
On the other hand, apart from making everything simpler, configurations could now be coded as Java classes, with all the subclassing/abstraction/composition goodness.
When you put something in a configuration file, you need to define who is going to change it, and how they are going to test their change. Otherwise you're in danger of muddling together different levels of abstraction, and breaking encapsulation all over the place.
While it sounds quite nice in theory to slim down your configuration to only have runtime configuration for variables that you see yourself changing, what I imagine ends up happening in practice is that you're left in a situation where you're doing far more work and only superficially benefitting by having an config that appears short and simple, but is in reality a complex system with nooks and crannies.
Here are some problems that I see with this:
* You're now on the hook for maintaining this software. Security problem? Your problem. Update? Your date, canceled, because an upgrade to the program changed everything. The version distributed by your distro had a nice post-installation hook for the new layout, but you have no such benefit. Your distribution might even have a few distro-specific patches for the software. Is tracking the upstream version, tracking your distros patches, and compiling the software actually saving you effort?
* Potentially undefined behavior. A configuration option can be in undefined and taking a default value, defined at compile time, or defined at runtime. Are you intimate enough with this code to know what's going to happen when you define it in multiple places? Maybe you add something to your runtime options, only to find out a week later that the change never happened because it was already set at compile time and that takes preference. Maybe the program has a runtime exception and crashes 3 days later at 3:00AM because it doesn't know what to do when it finds it in multiple places.
* Who the hell is looking at their configuration options enough that this actually crosses their mind? I've configured exim4, so I know exactly the situation you're describing with regard to a configuration that is just downright ugly. But I don't have a visceral reaction because I know that once I've configured this, I'm done. Maybe a few months later I change an option.
Mixing compile time and runtime configuration means you not only lose the advantages of both, but you gain their disadvantages as well.
"By hard coding, I don’t mean you should spread magic numbers and strings across your project’s source code."
The first time you use a magic number or string, put it in directly. It's much easier to find and change in its home environment, and it keeps your code simpler and easier to understand.
Of course, once you use that magic number somewhere else, then you should introduce a constant -- you don't want to get in a situation where somebody changes it in one place but not the other. But until then, don't complicate things.
If you feel that I'm a heretic, then please define the constant where you use it (when it's only used in one place), rather than putting it in a header.
5
embedded in the code, and const int maxRetries = 5;
at the top of a file. Both are still hardcoded, but there's at least context for the number.If the name is required to understand the code, then putting your "const int maxRetries = 5;" in the code is better than a comment that says the same thing. Just don't put it at the top of the file. Put it on the line before you use it.
With numRetries=5, you can see he program makes retries. Withjust 5, that fact is hidden away in a method parameter somewhere.
And unless your IDE is ed+cat, there is no hunting needed to jump to a definition of a method. Having layers of generality makes code easier to read.
I completely disagree; adding extra indirection/generality always makes your code harder to understand. Adding meaningful/intuitive abstractions makes it significantly easier though.
My pet peeve is Java interfaces that only exist because you need to be able to mock the object in testing, but otherwise only have one real implementation. Especially when the name gives no indication that it's an interface, so you only find out after you pressed F3.
I'm guessing there are different tradeoffs in dynamically typed languages since the hardcoded number will also identify the type.
# no:
MAX_FAILS = 3
MAX_RETRIES = 5
# yes:
MAX_API_CONNECTION_RETRIES = 3
MAX_COMPLEX_GRAPH_SOLVING_RETRIES = 5
Apologies for terrible names, but the point is that you should name the constant different things, so that it's clear what __kinds__ of retries you are capping.If it needs a name, name it where it's used!
I think of the "no magic constants" as a primitive precursor to the DRY principle. It was a good approximation for its time, but now we know better.
If someone were working on a different section of code and came upon a place where they needed maxRetries, but there was only a 5 defined somewhere, how would they know that it was already used once? Defining constants for anything that may be used multiple times saves the time of knowing/combing through every other section of code that could have already defined it.
It also encourages you (for better or worse) to use `maxRetries` in multiple places.
The value of 5 is, in the long run, meaningless. It could have easily been 3 or 10.
I could see this being reasonable for cloud/backend stuff, where the only people that deploy and run the software are next door to the developers, if not identical with them. In that case it really doesn't matter much if you change a line in a config file or a source file.
But for client-side software or anything else that actually gets run by someone not affiliated with you, this seems bad advice to me. It's very hard to anticipate all possible use cases and very easy to throw away features that are still in wide use.
As examples, imagine this strategy were followed by IDEs ("Darcula theme and Lucidia Console for everyone! If you want something else, file a bug and convince us!"), Samba ("No more share definitions! We'll just turn every home directory into a user share...") or Apache HTTP ("mod_mime_magic is good enough anyway..."). I don't think this would go very smooth.
Of course you should always watch the complexity and scope of your config files and avoid second system effect. If your find that your file's data model has grown from a key-value map to a tree or a graph, you don't have a config file anymore, you have a DSL. There are valid reasons for employing a DSL, but people should be aware when they do so, as the usage patterns, tooling and "audience" are different.
Keeping them all at the top of the file (connectionTimeoutSeconds = 30, maxConnectionRetries = 3, maxSimultaneousConnections = 5, etc.) lets you clearly see, at a glance, what the current configuration is and how the options relate to each other, instead of having them scattered across the code nobody-knows-where.
You don't have to go all-out and create some separate monstrous configuration file nobody will understand. Just separate out hard-coded values in constant names at the top of each file, for a start.
The author also omits many of the arguments for config files other than change, like being able to easily check what the current setting is without having to dig into the code, or reading out the configurations from other tools.
I you work on a project where "configuration hell" is an issue, maybe one should focus more on addressing the "hell" part instead of taking the configuration out of the equation.
I've found that treating configuration settings as "first class abstractions" in the Domain Model pays off quite nicely. By this, I mean grouping related settings into an immutable class which is given the configuration settings provider in one of its constructors. If there are reasonable defaults for all settings, then having a no-args constructor may be acceptable as well.
For example, in Scala I have types such as:
<pre> // Assume 'config' is what can read external configuration case class NetworkSettings (config : SomeConfigReader) { val rootPath = "company-name.project.network";
host : String = config.stringEntryOrDefault (s"${rootPath}.host", "localhost");
port : Int = config.intEntryOrDefault (s"{rootPath}.port", 1234);
// etc.
}
</pre>Of course, this pattern could be used before or instead of having the ability to externalize configuration parameters.
You have to draw the line somewhere, though. I tend to err on the side of "unconfigurable" until a request is made to make something configurable. E.g. Someone had the smart idea of having all our logging messages in a config file all the years back when the logging component was written, which is an incredibly frustrating anti-pattern. That was never needed and the messages should have been stored elsewhere.
The company that i'm currently working for uses this pattern in their software, so they can properly internationalize the messages. Unless I'm misunderstanding the context of your comment, storing the messages in a configuration file can potentially be a logical design decision.
It's .Net so the "correct" way to do that would be to use .resx, which have built-in architecture for localized messages (even though they are still XML files). There's more reasons for me disagreeing strongly with the specific implementation, but it's a tiny part of the stack that rarely gets used; I'm making a mountain out of a molehill. It was just one example that I had on-hand.
I had developers tell me “I expect mongodb at this (hardcoded) address”. Not funny.
Another funny story with “log level” was when a scala app occasionally went astray and returned more than 100GB of logs (level WARN) in less than an hour, filling my disks.
Most of the time developers can't understand deployment. They think it is the same as testing on their laptops or test VMs; run everything manually on the same machine with root privileges, delete everything once you are done.