• All booleans really want to be enums
• All enums really want to be rich data objects
• All rich data objects really want to be discriminated unions
That is, whenever you write some code which accepts a Boolean parameter that drives its behavior, at some point you will regret making it a bool and inevitably need to refactor it to be an enumeration type with more than two values. But then eventually you will realize that you have one behavior in your code that applies to more than one of those enum values (all the ‘type a’ cases but not the ‘type b’ cases), and you will want those enum values to themselves have a Boolean property telling you whether they are of type a or type b (and note that that Boolean will also be subject to this same golden rule in time)
Eventually you’ll find that those different enum values (the type a and type b ones) need different sets of dependent data (type a values all have a ‘target’ as well, but type b ones don’t, say) - so you end up needing a discriminated union to capture the various data objects involved.
All of which leads us to: every if statement and switch statement (over a parameter or input value) is a disguised match on a discriminated union type. Over time, this fate is inevitable.
And if your language doesn’t have discriminated unions, learn a pattern to fake them (usually it’s the abstract factory pattern).
A practical example:
Your program starts out accepting a config file that looks like this:
enableLogging: true
Then it changes into: logLevel: WARN
Then that becomes: logging: {
level: WARN
file: system.log
}
Then: logging:
fileLogger: {
level: WARN
file: system.log
}
And finally: logging:
- fileLogger: {
level: WARN
file: system.log
}
- consoleLogger: {
level: DEBUG
}You should probably generally use the least advanced of these while maintaining a pathway for future refactors into more complex versions of this
Then somewhere down the line someone realizes that actually in some cases of “forceFromDisk == true”, we still want to load the data from the cache, so the function becomes “loadData(forceFromDisk: bool, forceCache: bool)” where “forceCache == true” overrides “forceFromDisk”.
Then somewhere down the line someone realizes that actually in some cases of “forceCache == true”, we still want to load from the disk… Repeat ad infinitum.