3 ms·
> you should prefer different functions for each case instead. No I shouldn't. I use this frequently in c++ to deduplicate common functionality that only diff
by klyrs 4y ago
> you should prefer different functions for each case instead.
No I shouldn't. I use this frequently in c++ to deduplicate common functionality that only differs by a single line. The "flag" is a zero-size struct and checking the flag is done at compile time.
struct do_bar_tag {}
template<typename do_bar_t>
int foo(int x, do_bar_t) {
if constexpr (is_same<do_bar_t, do_bar_tag>::value) {
bar(x);
}
...
}
The compiler will emit two implementations, as you suggest is proper, but my maintenance overhead is reduced. And the cost is zero, in time and space, at runtime.
Of course, this isn't always the right thing to do, and you can definitely overdo it. But in moderation, it's fine. "Code smell" is too often a needlessly strong opinion.
- snarfy 4y agoIt never stays that way. It starts as foo(int x, do_bar_t) With time, it turns into foo(int x, int y, do_bar_t) and eventually foo(int x, int y, int z, int w, do_bar_t) It's too tempting to add 'just one more' flag. You de-duplicate common functionality but increase overall complexity.
- mycall 4y agoPut the flags into a class and pass the class around.
- 0xCMP 4y agos/class/typed object/ -> "How to write Typescript"
- klyrs 4y agoOnly, I don't use ints as "flags," so you're doing it wrong. And you're using the slippery slope fallacy, assuming that I'm a DRY absolutist. I'm not. Like I said, it's not always a good thing, and you can definitely overdo it. The valid complaint about the implementation I show would be that the do_bar logic is at the very top with no preamble, and it may be simpler for the caller to simply call bar(x) in that case. But adding necessary-looking complexity would detract from the clarity of the implementation (and damn c++'s verbosity, it already looks like hot garbage IMHO)
- kwhitefoot 4y ago> Only, I don't use ints as "flags," so you're doing it wrong. And you're using the slippery slope fallacy, assuming that I'm a DRY absolutist. I'm not. You might not "use ints as flags" but a lot of maintenance programmers who know nothing about your idea, have never seen the code before, don't know how the application works or even what it does, will do just that.
- klyrs 4y agoWhat you're describing is multiple policy failures. Why are these "maintenance programmers" implementing novel features? Why are they novices that are unfamiliar with the language and its best practices? Why are these novice-rogue-developer-maintainers' contributions not being reviewed by more competent developers? Moreover, if these unrestricted-novice-rogue-developer-maintainer-rhinoceri are going to do whatever the hell they feel like, completely disregarding the patterns and practices that I have established in my code, does it matter what I have done?
- xupybd 4y agoBecause novice programmers are cheaper and businesses make decisions based on short term cost. Especially when projects get old.
- klyrs 4y agoI know all that. But you didn't address my point: if management has thrown all good practices to the wind, how am I to blame for the horrendous outcomes?
- nomel 4y agoThis is a tangent, but I’ve never seen the real world work this way. You’re being paid to be the DRI for the code base. You will be blamed because you’re being paid for it to be your responsibility. Actually horrendous outcomes, which can only be related to breaking functionality rather than code prettiness, can be prevented.
- mixedCase 4y agoPart of the job is to know when to call it quits and refactor, there's no hard rule. In most cases I'll take a 130 line procedure with three flags that don't heavily interact with each other over three almost-identical 100 line functions.
- culi 4y agoPorque No los Dos? In languages with good support for functional features, you can curry and compose functions to your heart's content, allowing you to reuse similar logic and take better advantage of type systems like TypeScript's
- valand 4y agoIt comes down to how deep the type system of a language support this kind of operation. It'll work well with TS, Rust, but it doesn't add much to golang, C++. But well, usually dynamicizing statically-known types yields premature abstraction
- culi 4y agoI would actually call it "atomization" more than abstraction, but I agree it can often be premature
- gpderetta 4y agoAnd the issue with too many behavioural flags is that now the function (and the implementor) has to deal to all possible combinations, even though by specs only a small subset would be allowed.
- semicolon_storm 4y agoFor most modern programming problems that we get paid to solve, a bit of duplicate or similar looking code is the least of our problems. Conceptually integrity and managing dependencies/coupling is far more important.
- klyrs 4y agoWho is "we" and do you have statistics behind that "most?" And why is "paid to solve" relevant to the discussion? It's okay to say that what I'm saying about my personal experience doesn't resonate with your personal experience, but hand-waving at a nebulous authority isn't making a case. I use compile-time flags to deduplicate logic because refactoring is a nightmare if tens to hundreds of lines of code are turned into boilerplate. That is to say, I ensure conceptual integrity by maintaining a single source of truth. The nice thing about relatively dry code is that if you need to decouple it, you can easily just duplicate the code in question and run with that. If you have a dozen slightly-different copies of the same boilerplate, it's very rare in my experience for the variations to be well documented, so it ends up taking a lot of work to figure out why the variations exist -- and the real pitfall that I've seen time and time again is that a bug is found in one variant, fixed there, and not propagated to the other variants.
- rcarr 4y ago> Who is "we" and do you have statistics behind that "most?" I love that you've called this out. So many people try and justify their coding preferences by referring to this mythical 'we' or the 'coding community' who have apparently all agreed to to some certain standard and everyone should just fall in line and get behind it without asking any questions. It's such a load of bullshit.
- tacotacotaco 4y agoJust because you do it frequently doesn’t necessarily make it good. Your approach increases the function’s cyclomatic complexity. The struct requires the caller to dictate the function’s inner logic instead of the function logic being driven by application data. Maybe your solution is a good fit in your production code. I can’t say. Maybe there is a different approach to solve your boilerplate concerns. There will be exceptions but generally I don’t agree with the example as provided.
- klyrs 4y ago> Just because you do it frequently doesn’t necessarily make it good. Fair point, but in my ~20 years of experience, I've learned what does and doesn't make for more work down the road. To wit: if something is always bad, I don't keep doing it. But I didn't just say I do it frequently, I said that I do it frequently for big-ish functions when it's only toggling a line or so. That context is important: I am not arguing that this is always a good thing. I'm saying that there's a time & place for it, and with a language like C++, it's a zero-cost abstraction. > ...I don’t agree with the example as provided. Yeah, neither do I. As I pointed out elsewhere, its a contrived example which can be more simply factored out into two function calls. So unless it's being used tens to hundreds of times, nothing is gained there.