5

TL;DR why do people pick YAML/JSON/ini/TOML/XML/plain text to configure applications/packages instead of having the configuration be defined in source files the application/package is written in? I mean, I can see some obvious reasons that pertain to particular cases:

  • Your code is written in multiple languages and they all have to be able to read the same config
  • The language you are using makes it cumbersome to describe data structures this way: if there aren't data literals and everything is SomeOOInterface.add(foo) for 100 lines it's easier and clearer to just parse a data format
  • Configuring a pre-compiled binary where the consumer of the thing can't just alter the source
  • For security reasons you don't want the people configuring the thing to be able to run arbitrary code (from JonasH in the comments)

Just to name a few. Yet I often see these choices being made even in contexts where none of those hold: JSON configs in Javascript projects. Pip using a regular text file with an ad hoc format for dependency management in Python projects. Situations where the people writing the code and doing the configuring are the same people.

For contrast, I called out Javascript a minute ago but many Javascript tools will actually accept Javascript source files for configuration. The built-in Swift package manager has the developer configure projects in an actual .swift file. LISP is the obvious trope-namer here: everything is data, so the division between data and code doesn't even exist.

Configuration languages vary in power but none of them are as expressive as the source language, JSON doesn't even allow comments, and there are times when the ability to perform logic/assign vars/annotate metadata/etc are useful if not necessary. The arbitrary distinction between data and code may be fine for a textbook, but it seems to break down in practice in this particular case.

So when a configuration language is being used without one of the reasons outlined above applies, is it just voodoo chicken coding based on examples where that choice makes sense for the reasons stated? Or is there something I'm missing here?

EDIT

Just discovered this gem: https://matt-rickard.com/heptagon-of-configuration

12
  • there are also potential security issues. You might not want everyone that has access to the configuration to be able to run arbitrary code.
    – JonasH
    Commented Feb 15 at 14:48
  • @JonasH I'll add that to the list of reasons, but at least for source based projects like Python and JS they already have access to the source and can run arbitrary code. Commented Feb 15 at 14:52
  • 3
    You should ask Apple. They just released the Pkl language: "Pkl — pronounced Pickle — is a configuration-as-code language"
    – Peter M
    Commented Feb 15 at 15:15
  • 1
    @JaredSmith I'm in no position to evaluate Pkl, but when I saw your question I was like "Hmm, where have I seen that concept recently??"
    – Peter M
    Commented Feb 15 at 15:41
  • 3
    None of the languages you list are configuration languages. YAML and JSON are serialization formats, XML is a markup language. INI and TOML might come closest, but they are still generic data languages. The data structures that can be represented in TOML are comparable to JSON or the standard schemas in the YAML spec. TOML calls itself a "configuration file format", but really what it is is a way to write nicely formatted hash maps. It doesn't have any semantics of its own. Commented Feb 15 at 16:00

6 Answers 6

8

To perhaps state the obvious, configuration is a different activity than coding. In my experience, configuration is something that is applied at the point of deployment. For example, you might need to configure the URI of a dependency differently depending on where the application is running. This configuration step is something that needs to happen after the code is reviewed and tested. That is, no one should be writing untested code and applying it in production or even formal testing.

I would say that's the primary reason. Having a fully Turing-complete language for configuration is a problem, not a solution. Introducing untested code at configuration time is irresponsible. As a developer, you want your configuration to have limited expressiveness because it allows you to know the bounds of what configuration can be applied at deployment.

As a corollary to that, it's useful to clearly delineate configuration from code. You can name your code artifacts so that they say 'configuration' but apart from that, if your configuration is code there's no clear distinction between what is configuration and what is code. I've been tasked with fixing source where there was no configuration and the developers had hardcoded things like hostnames in variables. It was a mess. I take it that you are suggesting this would be somehow isolated in its own source files but that's a paper-thin wall, IMO. I've regularly dealt with stakeholders who try to work around a proper software development lifecycle (SDLC) by using support staff to make changes (i.e., no requirements or testing.) The approach you are suggesting would be perfect for that kind of abuse.

I don't want to rehash too much of what the other answers have stated but you note:

Just to name a few. Yet I often see these choices being made even in contexts where none of those hold

Which suggests that there are real deployment scenarios where security isn't a consideration. For non-trivial systems, I reject that notion. The use of an executable code for configuration clearly has security risks and you aren't denying that. The idea that such concerns can be ignored in various deployment scenarios is a dangerous way to think about security. At the very least, the approach you are talking about will make it more difficult to ensure that an application deployment has not been modified improperly.

2
  • "The approach you are suggesting would be perfect for that kind of abuse" yikes I've been fortunate enough to never work at a place where people did that. "I take it that you are suggesting this would be somehow isolated in its own source files" yes, I usually define a constants file(s)/package(s) in my projects. Depending on the needs and complexity, also/instead sometimes a runtime configuration manager. I realize something based on this answer I need to stress more in my question: I'm mostly thinking of cases where people writing the code and people doing the configuring are the same people Commented Feb 15 at 18:29
  • 1
    @JaredSmith When I write little scripts that I run locally (mostly Python) I typically won't bother with configuration. A middle ground approach I sometimes use is to use a hardcoded dict for 'configuration' which I can replace easily with a toml file if it makes it past the experimentation phase.
    – JimmyJames
    Commented Feb 15 at 18:50
2

For contrast, I called out Javascript a minute ago but many Javascript tools will actually accept Javascript source files for configuration. The built-in Swift package manager has the developer configure projects in an actual .swift file. LISP is the obvious trope-namer here: everything is data, so the division between data and code doesn't even exist.

These are usually the odd ones out. You do not want a scripting language as a configuration, and I'd argue using one is a poor design choice because it increases the complexity of your configuration and therefore the maintenance overhead of your code base. In my time I've seen someone trying to use C# code to configure his (also) C# enterprise software. It ended up with an unmaintainable black hole of bugs, security issues and mind bending runtime errors cause the C# configuration could never be debugged. Not pretty.

Your code is written in multiple languages and they all have to be able to read the same config

Configuring a pre-compiled binary where the consumer of the thing can't just alter the source

In large scale software development this happens far more often than you seem to think. Almost all enterprise software use several programming languages, so you will always need a lowest common denominator to exchange configurations.

Some other benefits:

  1. It is easier to create a parser for a data only language. A config in C# for example would require full compiler support or a stripped down version of the language which kind of defeats the purpose. It is generally not allowed to have developer toolchains in production environments, so if you need to modify the configuration, you need to first do it in a dev environment, and then copy it to prod. This is also not nice with CI/CD tools. Sure, you can do it, but it is a huge maintenance overhead.
  2. People or systems who interact with it don't need to know the language. Sure, you can configure your javascript using javascript, but try selling that to someone who only knows java and has no interest in learning javascrips. The people who operate the system usually don't know and shouldn't know the language in which the system is created. Forcing them to learn it means you need more expensive people in your day to day operation. This gets worse when CI/CD tools get involved. Those usually know how to parse and modify a handful of config languages. If they need to suddenly learn to create correct script/compiled language syntax, their prices will go up. So this is also an economic shortcut.
  3. Security: you don't want random packages insert random scripts in your organization. The pattern of loading precompiled modules at runtime from unknown sources at develop time is known as plugin architecture. This follows a specific design pattern that becomes a maintenance overhead when its flexibility is not needed. This is used in web browsers, media players, OSes and has been a constant source of security issues.
3
  • Hmmm... I dunno. Why would you need to create/use a parser for the config vs just importing it via the language's module system if it's in the host language? "You don't want random packages insert random scripts in your organization" - every 3rd party dependency you haven't audited already is this. "The C# configuration could never be debugged" why not? CI/CD is the pre-compiled binary thing, same for stuff like nginx. The rest of your answer seems petty reasonable, especially the first part of bullet #2. Commented Feb 15 at 15:03
  • 1
    I think bullet #2 should cover all your concearns. It is the people that operate the system don't know and shouldn't know the language in which the system has been written in. If you need to do a modification at runtime thats written in a compiled language, what do you do? you bring in a compiler in production to give you a new binary for your configuration? You do the configuration on a local machine? Maintenance overhead. I will expand on this in the answer.
    – Ccm
    Commented Feb 15 at 15:13
  • Also, the module loading approach is a well known application architecture (micro architecture or plugin architecture). This follows a well established pattern that becomes a maintenance overhead when you don't need the flexibility it provides. Only makes sense for some specific software (like web browsers) and this has historically been a source of security holes (flash and java plugins for browsers being famous for this), which is why it fades out of favor.
    – Ccm
    Commented Feb 15 at 15:26
2

What are the benefits of configuration languages over just using the source language?

I think the perceived benefit is often that configuration languages, like DSL source code, concentrate a number of variables about the program behaviour in one place, often in a far terser or better visually-structured way than if those variables are encoded directly in source code.

I also think there is not necessarily any hard distinction between a "configuration language" and ordinary source code, except that custom configuration languages tend to be more limited and inferior than would ever be tolerated for commercially-retailed programming languages.

"Configuration languages" perform similar functions as certain common patterns and arrangements of ordinary source code. We all try structure our programs in ways that co-locate related functionality, and in ways that isolate boilerplate from the things we regard as essential. For example, constants which control a variety of global behaviours, would often end up in one place in ordinary source code.

The error people make is that they often don't perceive how much mental resource they are allocating to the development and understanding these custom configuration languages, how difficult they can be to "cold-read" (i.e. infer their workings when you are not the developer, or when you have forgotten the details of something you wrote 6 months ago), or how much additional complexity is being incorporated into them so that they become part of the overall burden the programmer has to bear rather than an overall source of relief.

The edit to the question refers to a "heptagon of configuration", effectively a treadmill of representations.

Things go around in circles because so does the programmer. He struggles with the program, so he writes a config language to "simplify" once and for all. 3 months later, he struggles with the config language, so he rewrites in ordinary source code to "simplify" once and for all.

Basically, if you remove the alternation between different forms, what the programmer is finding is that the code in question is so complicated that he is having to rewrite that code from scratch every time he deals with it.

What happens with each iteration isn't necessarily that he simplifies it, but that the process of rewriting every detail is the process of re-analysing, re-learning, and re-loading the knowledge into his own mind, that knowledge and familiarity being what is necessary to adapt the program to a new purpose. It's basically a massive flywheel spinning up, as the brain trains to cope with the details of a specific programming problem area.

And because of the alternation of form each time, the naive programmer is more energetic and hopeful, because there is novelty in the new form, and because he believes this time he has found a solution to the complexity, rather than being resigned to the fact that in 3 months he will be rewriting exactly the same code.

So whenever programmers have a choice to alternate forms, they often will alternate, because it adds hope and novelty, increasing their psychological resources and motivation, and because it makes it less obvious even to themselves (let alone their managers) that they are retreading ground now, and will again.

It's not necessarily in the interests of management to stop this alternation, because you will undermine the internal enthusiasm and make the drudgery clear to the developer, and thus reduce performance and results (for any given level of complexity being tackled).

1

Data serialization formats might not be expressive, but they're consistent and predictable. The fundamental difference is the ease of static analysis, which then impacts the tooling ecosystem.

Consider this simple example JSON:

[
  { "id": 0, "name": "Alice" },
  { "id": 1, "name": "Bob" },
  { "id": 2, "name": "Charlie" },
]

There's an obvious structure here, which can be expressed easily with JavaScript:

["Alice", "Bob", "Charlie"].map((name, i) => {
    return { id: i, name: name }
})

(Obviously this is just a toy example, you can imagine that if the object structure was larger and more complicated, that having some notion of looping can really make the config simpler.)

The issue comes when you want a machine to read or write to this.

On the read end, the config can only understood by executing the JavaScript code. To do this, you need a safe sandbox environment (e.g. to protect from maliciously-crafted configs from accessing the disk, network, etc.). This won't be a problem for main application, but consider all the auxiliary tools that might need to understand the config.

On the write end, it's even worse. If a tool wanted to change this JSON(e.g. a web configuration UI that persists settings to JSON), it would have no predictable way to do that. E.g. if I wanted a tool to add a new person record, that tool would have to somehow understand that the correct way to do that is to append into that names array.

In general, such tooling would need to be able to reverse engineer arbitrary JS code, to understand what "root" values to change, to achieve the desired rendered output. This might eventually possible with LLMs, but we're still far off.

2
  • 1
    "This might eventually possible with LLMs" – I'm pretty sure it's equivalent to solving the Halting Problem. It feels related to the Function Problem. Commented Feb 15 at 15:58
  • 1
    For arbitrary and opaque code, yes, but presumably the executable code isn't so complicated, because humans wrote it for the expectation that it can be read in the future. If humans could figure out "To achieve this output at the end, I need to edit this root value here," then so could an LLM, hypothetically
    – Alexander
    Commented Feb 15 at 16:11
1

The question is asked on a false premise - sometimes a dedicated configuration syntax is preferable, sometimes it is not. The decision to use one is highly context sensitive.

I'd like to provide some examples to broaden your view:

  • DWM - a window manager in C, users edit the code themselves to configure
  • gamedev uses various embedded scripting languages like Lua, Godot, Python to configure and customize C engines.
  • vim editor uses vimscript as configuration
  • Emacs editor uses ELISP
  • ROOT scientific data processing system uses a C interpreter for configuration and scripting

In other words, the configuration is just another language in your project with properties best suited to perform quick changes with less harmful impact. Just like in Python, you use C to achieve performance gains, you can embed Python in C to avoid recompilation, or use JSON in javascript to enforce declarative and safe initialization.

Use the best tool in your toolbox to achieve whatever goal you set. The possibilities and objectives are endless, no needed to box them in.

0

Say you have written Microsoft Word. Everyone who installs and configures it might not know how to program in c++. But they still might want to set the default page size. You can't put in the manual "First learn C++ and install gcc, open config.c and change lines 23 and 55 using the enum pageSize..."

The same is true for most* applications, the person configuring the installed application, doesn't necessarily know how to program. They need an easy way to set whatever variables are needed. Config files or command line arguments etc allow this

*in my experience

6
  • 2
    Meh, you can still edit a file with a set of variable definitions and run a script to compile them in (installing dev dependencies automatically). Also, see DWM project.
    – Basilevs
    Commented Feb 15 at 19:20
  • sounds like a "config" file
    – Ewan
    Commented Feb 15 at 23:00
  • Config file in C syntax, sure
    – Basilevs
    Commented Feb 16 at 2:12
  • if only there was a way to make that config file editable without knowing how to program?!
    – Ewan
    Commented Feb 16 at 12:15
  • Syntax to assign a variable is very simple. And there is no need to learn any other syntax to change configuration.
    – Basilevs
    Commented Feb 16 at 13:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.