7 ms·
Make hard coding your default choice
- taylodl 11y ago"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.
- vambo 11y agoIt 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. Your point is also very valid though.
- falcolas 11y agoI 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.
- lmm 11y agoIf 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.
- wallyhs 11y agoSince 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.
- bryanlarsen 11y agoI'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.
- AndrewDMcG 11y agoI 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.
- falcolas 11y agoPersonally, 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.
- bryanlarsen 11y agoBut what if maxRetries is 0 or 1000? (I've seen both). That really changes the meaning of the code you're reading.
- 11y ago
- AndrewDMcG 11y agoYES! 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.
- zamalek 11y agoI 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.
- droffel 11y ago"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.
- zamalek 11y ago> 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.
- serve_yay 11y agoThe "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.
- kedean 11y agoFor 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.
- ajanuary 11y agoI read this as an interesting extension of "sane defaults" in library design: adopt new sane defaults for your applications particular domain.
- drig 11y agoI 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.
- zwieback 11y agoThat's the correct approach, in my opinion.
- jvoorhis 11y agoAnother 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.
- ashmud 11y agoWe do this. For bigger apps, we usually have a db layer, so local config file -> db config -> default in code.
- zbuf 11y agoI've found this method invaluable where we are centrally supporting the same software in multiple environments and in multiple configurations.
- markbernard 11y agoI 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...
- mpweiher 11y agoYes. 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.
- tokenizerrr 11y agoThe 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.
- crazygringo 11y agoBut 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.
- codazoda 11y agoI 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".
- Roodgorf 11y agoI 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."
- makeitsuckless 11y agoI 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.
- nickysielicki 11y agoI 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.
- AndrewDMcG 11y agoI 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.
- andmarios 11y agoIf 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.
- AnEngineer 11y agoThis 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.
- TazeTSchnitzel 11y agoTake 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.
- xg15 11y agoI 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.