Skip to content

Instantly share code, notes, and snippets.

@p-jackson
Last active March 9, 2016 21:35
Show Gist options
  • Save p-jackson/f96ff7796a91ab289a3b to your computer and use it in GitHub Desktop.
Save p-jackson/f96ff7796a91ab289a3b to your computer and use it in GitHub Desktop.
How I might go about reading from a settings file
#include <string>
#include <unordered_map>
using namespace std;
// I've left the types explicit for clarity, but in real code
// I'd probably use `auto`
// The settings object is a hash table of keys and values. Everythings strings,
// no fighting the type system, no 3rd party libraries, everything is C++ 101.
const unordered_map<string, string> userSettings = parseSettings("path/to/settings.ini");
// Returns a new settings object that has default settings applied.
const auto settings = applyDefaultSettings(userSettings);
// Abort app if there's some issue with the settings file.
if (!validateSettings(settings))
return;
// Get some setting that is supposed to be a string
const string url = settings.at("servicePublicUrl");
// What, convert to `int` every time you want to read the setting?
// Yeah why not? Why create an abstraction if there's no bugs?
// This will throw if the string doesn't represent an integer. But I
// think an exception is appropriate here since we called
// `validateSettings` earlier.
const int port = stoi(settings.at("somePortSetting"));
// `toBool` is your custom function that converts "yes",
// "true", etc. strings to a boolean.
const bool fastMode = toBool(settings.at("doFastMode"));
#include <string>
using namespace std;
// The settings file is now represented by a struct. Awesome type safety
// benefits. Performance benefits because the compiler can inline things.
// It might "feel righter" to because you aren't parsing the non-string
// values over and over.
// Only downside is any change to the settings file format requires a
// change here, which will probably require a re-copmile of your entire
// app.
struct Settings {
string servicePublicUrl;
int somePortSetting;
bool doFastMode;
vector<string> someUserList;
};
// The one and only parse of ints/bools/lists will happen here.
const Settings userSettings = parseSettings("path/to/settings.ini");
// Returns a new settings object that has default settings applied.
const auto settings = applyDefaultSettings(userSettings);
// Abort app if there's some issue with the settings file.
if (!validateSettings(settings))
return;
// Such type safety, wow.
// Mistype the property name? Get a compile error.
// Use the wrong type for a property? Get a compile error.
// Ground breaking stuff!
const string url = settings.servicePublicUrl;
const int port = settings.somePortSetting;
const bool fastMode = settings.doFastMode;
@p-jackson
Copy link
Author

At first this feels yuck right? The DRY principle is gospel isn't it?
Yes, calls to stoi and toBool will be scattered throughout your code. I'm kind of assuming most of the configuration options for dRunner are strings so it won't be that bad. But still, it doesn't seem right.

Lately I've been thinking more about these ideas:

  1. Don't abstract after the second or even the third time—abstract once it causes bugs
  2. It's better to have no abstraction than the wrong abstraction

I've already mentioned 1 to you, but 2 is something I'm hearing more of around the place too. I first heard it from the React people, but this article also puts it very well.

Obviously I don't think a special abstraction for a bag of properties is bad. It's a pretty useful in lots of situations, which is why property_tree got added to Boost. And I also regularly cheat on both properties and preemptively abstract when it feels right. But I've been trying to lean in this direction more.

@p-jackson
Copy link
Author

Oh and I think the whole thing hinges on calling validateSettings up front so that exceptions in stoi and toBool would be just that: exceptional.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment