"The possibility to change the minimum log level required to trigger the rule seems sensible because we might want to adjust it on the fly for debugging reasons. Nevertheless, odds are we never appeal to it in practice. So, we are better off removing this setting as well."
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.
It was also going fine for me up to that section but for different reasons: I think it makes sense to postpone/avoid externalizing configuration, but in case where a configuration is the default format (e.g. logging), going the extra mile to define that in code is both time-consuming and produces a less readable result. So I think the goal should not be to avoid configuration files, just avoid wasting time in making things configurable which probably never need to be re-configured.
I find the happy medium here is a command line parameter. Hard code the default to something sensible, and have a command line parameter (-v) which overrides the sensible default. No need to recompile, no need to worry about the parameter 99% of the time.
If you can't deploy your application in half an hour and without interrupting service, you have bigger problems; solve those first.
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.
Since the site is Enterprise Craftsmanship, I read it from that angle. In my experience, deployments must be requested several days in advance and require approvals from managers, testers, analysts, and functional experts. Quick "emergency" changes require extra approvals and are reviewed periodically. Minor configuration changes don't have nearly as many restrictions.
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.
I'd go even further and disagree with this statement too:
"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.
I agree with this too. Pulling constants out to the top a source file is often a mistake. It's useful if someone is looking at the source file to find the constant, but usually it's more likely that they're reading the code to understand it, and then need to know the value of the constant. In these cases it can be better to have a constant but define it close to where it is used.
Personally, if I'm reading the code, seeing `maxRetries` is more meaningful than 5. `maxRetries` both abstracts away the 5 (which could change at whim), and makes it so I don't have to parse the surrounding code to identify exactly what that loop is doing (unless it's not working, in which case I have to parse that loop anyways).
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.
1000 doesn't change the meaning at all - it's still retrying a block of code some finite number of times. 0 does change it a bit, but only in that the code is not being executed, not that there is suddenly no retry logic which exists in the code.
I'd far rather have the 5 embedded in the code. It should be obvious from the code that the 5 is the number of retries, and it's often important to know at least the magnitude of the number when reading the code. Don't make me hunt for the number unless you have a good reason for doing so.
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.
Factoring out constants has a nice effect of turning your header into a sort of declarative program, a quick summary of the interesting behavior choices the program makes, a guide to understanding the program at a glance.
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.
> 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.
If you put the const declaration just before you use it and then you or someone else uses it somewhere else, you are going to have an even harder time finding it. If it needs a const, declare it at the top so I know where to look.
You say it should be obvious what a value embedded in code is. OK, but for one reason or another it often is not. So why not have your coding standard be name constants well at the top of the file or function. I think that is more consistently readable and reusable.
This is silly. If you don't have a single constant, you could have the situation where one piece of code had a maxRetry of 5, and another with a maxRetry of 2. Good luck trying to figure out why things are failing when they should retrying 5 times.
The problem is you might (sometimes should) have different retry counts depending on what you're retrying. So MAX_RETRY (or whatever) isn't actually that meaningful when you run into the constant. It might actually be misleading.
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.
I think if you can't reliably find all occurrences of a value then it shouldn't be hardcoded - 6.022e23 is easier to find in a large source file than 5. If you work in a statically typed language with a decent IDE you can usually see the constant value just by hovering over the identifier.
I'm guessing there are different tradeoffs in dynamically typed languages since the hardcoded number will also identify the type.
I'm all for extracting constants, but not (always) to the top of the file: why not put them as close as possible to where they are used - especially for one time uses?
I think defining the constant the first time you use it (if you're assuming you might only use it once) may be a reasonable solution to this, but if you plan on anyone else to ever be looking at your code I feel like magic numbers should absolutely be attached to some sort of name.
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.
YES! No system is harder to maintain than one that consists of a huge mesh of configuration files.
No kidding, I have worked on a large company-wide reporting system where the easiest changes to make were to the code. Code changes could be made, tested and released in a fairly straightforward way.
The harder things to change were to configuration files (Spring, things like that). Because they had global effect, and contained elements that were specific to a dev/test/production environment, making and deploying changes to one were more difficult.
But the very hardest things to make changes in were the report and process parameters stored in the database. They were impossible to isolate the effects of since we had no easy way to trace to what functions they might affect, and were generally treated as immutable legacy that you worked around changing rather than touch.
(I wouldn't claim this was a good system)
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.
I mostly agree with exception of: it heavily depends on where you work. The exact logging configuration example is a prime example of what an ISV customer would want to change - many of our customers (and QA) go as far as writing their own adapters so they need to be able to completely customize the configuration.
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.
"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.
> The company that i'm currently working for uses this pattern in their software, so they can properly internationalize the messages.
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.
The "configuration hell" described here is what working in Spring was like (Spring.NET in my case). That style of coding takes the no-hard-coding mantra to an impressively ridiculous degree. And it really was hell, partially because your app is half "real code" and half XML, but also because you could break everything at any time by making a mistake in the config. Easy enough to check for that with smoke tests but it's still a terrible way to write apps. I think I'm still recovering.
For me the issue with spring-style configuration isn't so much the sheer volume, as almost everything has sensible defaults, it's the loss of type-checking. When everything is configured via xml, you have to run the app before you know if the configuration is even valid, and refactoring becomes hell because settings are all referenced via strings instead of qualified names.
I always use an in-between system. My configuration library allows me to set a default, if the parameter is not found in the config file. I always set a reasonable default, and then only override if necessary. This keeps the configuration files small, keeps the default value with the code, but allows you maximum flexibility, just in case.
Another tactic that has worked well for me is to expose a flat namespace of flags and tunable parameters in config while shunning instantiation of complex object graphs. Complex nesting in configuration tends to be too "raw" for users and sysops, and hurts usability.
I do the same. I also keep the configuration centralized so all access goes through one class. I also allow defaults in the config file along with specific values based on what environment you are, such as production or UT, etc...
Yes. At the BBC project I've talked about before (µservices to monolith, 100x - 1000x faster), I also removed all external configuration files. This turned out great, and it made "deployment" as trivial as possible. Copy jar file to directory, start (maybe with a command line argument, don't remember 100%).
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.
The way I like to do things is mostly as you describe, but also to set up the configuration to be (de)serializable by your favorite xml library. The defaults are in the code, but they can be overridden on a per-deployment basis.
But many times the hard-coded values get used multiple times, have relationships between each other, it's unclear where they are in the code, etc.
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.
I mostly agree with this article. Nitpicking a point, however, I don't consider a constant to be a "magic number". If you've given it a clear variable name, such as "defaultRetries" then it is no longer "magic".
I believe you're actually agreeing with the author here. I took his comment to mean "Don't have unnamed numbers/strings/etc., refactor any 'magic numbers' into constants, that way they will be in one place with a sensible name."
I suspect that a lot of this has to do with simply having shitty configuration loading from horrible (XML) config files. This small example already gives me enterprisey nightmares.
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 believe this is absolutely awful advice. No offense to you OP, but if I took a job somewhere and saw that they were doing this, I would cut my losses and leave immediately.
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.
I would assume the author is talking about in-house software development. If you are delivering software to a client, you don't want them to have to recompile sources to make changes. If the developers of the software are also responsible for its runtime configuration (which is very common in banking, telecoms, etc.) then changing code can be as easy or easier than changing config.
If you are not the one responsible for deploying your code, please avoid hard-coding everything.
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.
Take the sensible middle-ground and use a constant. If you're using a good language, you can later make the constant a configuration option, or vice-versa.
This thread may have already gone stale, but just in case not...
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.
I don't know... I think whether this approach works or not really depends on the kind of software you're developing.
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.
52 comments
[ 2.3 ms ] story [ 80.8 ms ] threadEverything 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.
Your point is also very valid though.
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.
"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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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";
} </pre>Of course, this pattern could be used before or instead of having the ability to externalize configuration parameters.
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.