Make hard-coding your default choice

Hard coding is often considered an anti-pattern. Having values that can change over time hard-coded in the source code requires recompilation every time these values actually change.

While this statement is true, I think that hard coding should be the default choice when developing an application.

Hard coding vs configuration file

When you work on a project or feature, there always are some magic numbers or strings that potentially can change in future. The first impulse is to make such changes configurable so that you could easily modify them later.

In most cases, such decisions complicate further maintainability. What we have here is a classic dilemma "simplicity vs agility". As the application grows, it becomes easier to change some of its parameters because they are extracted to the configuration file, but at the same time the overall burden of maintenance increases.

In an extreme case, you might end up having tens or even hundreds of configurable parameters, which make it utterly hard to maintain the application. Such situation is called configuration hell.

Just as with many other software design decisions, we need to appeal to the YAGNI principle. Do all these parameters really need to be configurable right now? Or do we just make some up-front arrangement? If the latter is the case, we are better off cutting the configuration file and keeping it small up to the moment when the need for it becomes apparent.

Hard coding should be the default choice unless the necessity for the otherwise is proved. By hard coding, I don’t mean you should spread magic numbers and strings across your project’s source code. They still need to be gathered and put in a single place as constants. Yet, that means you should remove them from the config file.

Logging example

Let’s take some example and see how we could apply what was said above in practice.

My favourite logging library is NLog. It has a tremendous amount of features, each of which is easily configurable.

Here’s a configuration file for a typical NLog setup:

<nlog>
  <variablename="logFile" value="C:\logs\log-${shortdate}.txt"/>
 
  <targets>
    <targetname="trace" xsi:type="AsyncWrapper" queueLimit="5000" overflowAction="Block">
      <targetname="file" xsi:type="File" encoding="utf-8"
              layout="Date: ${longdate}\r\n Level: ${level}\r\n Message: ${message}\r\n"
              fileName="${logFile}.txt"
              archiveFileName="${logFile}.{#####}.txt"
              archiveAboveSize="2097152"
              maxArchiveFiles="200"
              archiveNumbering="Sequence"/>
    </target>
  </targets>
 
  <rules>
    <loggername="*" minlevel="Warn" writeTo="trace"/>
  </rules>
</nlog>

While the setup itself is quite reasonable, I’d like to raise a question: is it really necessary to keep all these settings in the config file? Are we going to change them? In most cases, the answer is no. Even if you are doubtful of it, that also means "no" due to the YAGNI principle.

Luckily, NLog allows us to use its Configuration API in order to configure it in code. So, instead of counting on the config file, we can easily move the settings to the source code. Let’s take a closer look at the example and see which of the settings we can get rid of.

First of all, you can see in the targets section that we use an async wrapper for the actual target. Do we really want it to be configurable? No, such setting barely ever gets changed. Okay, what about the other target? It sets a lot of useful things, e.g. log entry’s layout, file name, maximum log file size and so on. Do we really need to have an opportunity to change them without recompilation? Most likely, no.

What about the rules? This part is not as obvious as the one with targets. 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.

Alright, what we have ended up with? Only a single setting is left, which is the path to the log file itself:

<appSettings>
  <addkey="LogFilePath" value="C:\logs\log-${shortdate}.txt" />
</appSettings>

All the other settings now reside in the source code:

string layout = "Date: ${longdate}\r\n" +
    "Level: ${level}\r\n" +
    "Message: ${message}\r\n";
 
var config = new LoggingConfiguration();
 
var target = new FileTarget { FileName = fileName, Layout = layout /* Other params */ };
var asyncWrapper = new AsyncTargetWrapper(target)
{
    QueueLimit = 5000,
    OverflowAction = AsyncTargetWrapperOverflowAction.Block
};
var rule = new LoggingRule("*", LogLevel.Error, asyncWrapper);
config.AddTarget("asyncWrapper", asyncWrapper);
config.LoggingRules.Add(rule);
 
LogManager.Configuration = config;

As you can see, we removed the nlog section completely and moved the remaining setting to the appSettings section. It is now an ordinary member of our configuration file.

This single setting is the only one that really needs to have different values depending on the environment being used in. What we did here is we reduced the configuration surface and thus made the solution more maintainable at the cost of making it less flexible. And I strongly believe this is a good trade-off.

Later on, we might find ourselves changing one of the hard coded settings too often. That would signalize we do have a good reason to move it to the configuration file. But up to this point, just make hard coding your default choice.

Summary

I apply this rule regularly to all of the settings that could potentially be moved to configuration files. It helps keep them small and maintainable. Also, I noticed that even if I occasionally need to change one of such settings, making the change directly in the source code is sufficient in most cases.

Update

I need to note that the content of the article is applicable to in-house software only. 3rd party library development is a different story.

Also, I really appreciate all the feedback I got on that topic, I didn’t expect there would be so much of discussion here. But please, don’t confuse the main point of the article - which is making hard coding your default choice - with making hard coding the only choice. If you do need to extract some values out of the code and make them configurable - you are good to do it. The only thing I advocate you do is asking yourself whether such extraction is really needed.

Subscribe


I don't post everything on my blog. Don't miss smaller tips and updates. Sign up to my mailing list below.

Comments


comments powered by Disqus