3 ms·
I've seen a couple of comments here talking about feature flags and I don't think that's quite what Carmack is getting at here. It seems he's talking more abou
by dwohnitmok 7y ago
I've seen a couple of comments here talking about feature flags and I don't think that's quite what Carmack is getting at here.
It seems he's talking more about refactoring/optimizing/debugging working code rather than adding new features. Then the old version of the code becomes a point of reference against which you test your new implementation.
Once you are very confident that your new implementation is no worse than the old implementation you have two choices.
1. Leave your old implementation around as a reference implementation, but mainly use your new implementation. This is useful if your old implementation is functional and readable but slow and your new implementation is less readable but faster. This helps other developers understand your new implementation.
2. Remove your old implementation altogether and just keep your new implementation in your codebase.
Either way the thing Carmack is advocating for is having an extended period of time where the two implementations coexist, even though one of the implementations is "redundant." He is explicitly advocating this despite the fact that you could in theory just retrieve the redundant implementation from version control and not have to "pollute" your codebase with a redundant implementation.
- cwyers 7y agoYes. And the paragraph that most stood out to me was this: > If the task you are working on can be expressed as a pure function that simply processes input parameters into a return structure, it is easy to switch it out for different implementations. If it is a system that maintains internal state or has multiple entry points, you have to be a bit more careful about switching it in and out. If it is a gnarly mess with lots of internal callouts to other systems to maintain parallel state changes, then you have some cleanup to do before trying a parallel implementation. If you have clean interface boundaries, it's easy to have two versions of something coexisting. If you don't, you can't just change the component in question, you have to change the whole program, which means you can't go this route. It's not just about deciding to do two implementations of something, it's about writing your whole program in ways that facilitate this style of development (no side effects, etc.)
- MaulingMonkey 7y agoI think Carmack is basically using feature flags - but that's just the platonic ideal polished version of this, not the fundamental goal. The less AAA/polished version is to do this in code instead of in data. Maybe you comment out old.h in favor of new.h, or have a #if 0 ... #else ... #endif chunk in your .cpp files. This can be a lot quicker than adding conditional logic to hundreds of call sites, or implementing hundreds of forwarding stubs, if you don't have a nice dynamic module boundary already. You can make a define, two build variants, and side-by-side compare things that way too. Not quite as nice as runtime switching, but potentially a lot easier, if you've kept things from spilling into common headers too much and have reasonable link times. Another alternative is to do this on your second computer, carefully synced to the same code/config/debug settings/feature flags/???, and hopefully with the same specs. This manages to work even if things spilled out into headers, but can require some painful coordination to keep in sync.
- dwohnitmok 7y agoI don't think so. I think he's suggesting something fundamentally different. Note that he specifically calls out keeping around a reference implementation indefinitely that he implies will never be called by a code path (but is continuously maintained) as a subcase. That's kind of hard to square with feature flags.
- MaulingMonkey 7y agoThere's nothing fundamentally temporary about feature flags, even if that's the popular use case. You can opt into using the reference implementation of a renderer with a flag. You can opt into using a debug implementation of an allocator with a flag. You can opt into your experimental hopefully faster renderer with a flag. There's very little that's fundamentally different in these cases - maybe some get removed from some builds at compile time - and maybe your plans for the future are a little different. But if plans change - perhaps maintenance of the reference implementation ends up being more costly and less beneficial than expected - you may very well end up deprecating, and then deleting, your reference implementation. To an outside observer not privy to your plans, this is identical to classic "I'm making my new work opt-in, then opt-out, then the only choice as development progresses" temporary feature flags. EDIT: Heck, https://martinfowler.com/articles/feature-toggles.html https://martinfowler.com/articles/feature-toggles.html even lists plenty of long-term and even static build-time configuration stuff under the category of feature toggles, so I really can't see what's hard to square, unless you're using a much narrower definition of "feature toggles" than is being used there.