3 ms·
My golden rules of programming: • All booleans really want to be enums • All enums really want to be rich data objects • All rich data objects really want to
by jameshart 4y ago
My golden rules of programming:
• 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).
- toolslive 4y agoIt's just the second law of Thermodynamics at work, no ?
- d3nj4l 4y agoI’ve seen the general idea here called “part blindness” before: https://www.hxa.name/notes/note-hxa7241-20131124T0927Z.html https://www.hxa.name/notes/note-hxa7241-20131124T0927Z.html
- jameshart 4y agoAlso: forgot the evolution into its final form, which is a list of rule objects. 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 }
- i_am_toaster 4y agoThe truth of this comment leaves me feeling somewhat emotionally violated.
- theteapot 4y agoQuestion is whether and to what extent you can shortcut that lifecycle, and whether it's worth trying.
- jameshart 4y agoRight. I tend to think you should just be aware of it, be deliberate in choosing where to situate your code on it, and be adept at refactoring code up the scale.
- culi 4y agoI think this evolution paradigm is useful to keep in mind when designing the API, but obviously each successive step is more computationally expensive and more complex (see: error-prone) You should probably generally use the least advanced of these while maintaining a pathway for future refactors into more complex versions of this