12 ms·
Named Booleans prevent C++ bugs and save you time
- dietr1ch 4y agoI think that these bundled conditions are bad, and that having negative booleans is terrible. Using and almost abusing early returns on bad input almost always leads to easier to follow code. if (someLongNamedVarIsNotUnknown && someLongNamedMapCountIsZero) return false; else return true; This is not the same, but I'd suspect there might be a bug in the code above if (someLongNamedVarIsUnknown) { return false; // Can't do much } if (someLongNamedMapCountIsZero) { return false; // Nothing to do } // Do stuff return true The named booleans suggested here help adding clarity, but more often than not they'll end up being single-use (const) variables that just paraphrase their definitions. In these cases I'd rather avoid introducing a variable, and rely in comments if I really need to explain something.
- andix 4y agoTrue, functions with return values are so much better in such a case. I really miss if expressions and switch expressions in most languages. Most functional languages (and also c#) have them. Something like (let’s do JavaScript in this example): const value = if (a) { return true } else { return switch (b) { case 1: return false default: return undefined } }
- baq 4y agoInterestingly Rust does this and I agree JavaScript with const feels only 80% there without this feature.
- monocasa 4y agoAs an aside for those wondering what the name for this is to see what other languages support it, this is a case 'if' being an expression rather than a statement which allows it to be composed within other expressions.
- smitty1e 4y agoA predicate?
- tialaramex 4y agoNo, if, and match, and most things are expressions, and thus have a (typed) value. In a typical programming language we can write something like x = 5; assigning 5 to the variable x. That 5 there is an expression, we can write x = (3 * 2) - 1; with the same effect, that's a more complicated expression. We can call a function here: x = f(3); or if our language has methods we can even call those: x = some.function(parameters); In Rust I can write x = if it.should_be_nine() { 9 } else { 5 }; // because if is an expression Since Rust's loops are also expressions, x = loop { let stuff = get_stuff(); if stuff.is_good() { break stuff } }; // ... once the stuff is good, break out of the loop and we'll assign that stuff to x
- smitty1e 4y agoUnderstood, these things are expressions, but must they nit evaluate to a boolean in order for if/switch/match to branch correctly? My informal understanding of a predicate may need refining.
- tialaramex 4y agoI think the thing you're missing is that in some languages we're allowed to use these constructs as expressions. Not that they take expressions which is common in modern non-toy languages, but that they themselves are expressions. It might be you've just always used languages where you can do this, or that you assumed you can but actually you never tried in a language you use. In C for example we can't write this: int x = if (1 == 2) { 3 } else { 4 }; // Not valid C syntax C provides a special operator, the only one in the language which has three operands and thus it's often just called "the ternary operator" although other ternary operators can technically exist, to achieve the same thing: int x = (1 == 2) ? 3 : 4; // Valid C syntax using ternary operator But, how about this? int x = switch 3 { case 1: 4; case 2: 8; default: 0; }; // Can't do this either There's no way to express that in C or C++ today. The closest is the "Immediately Invoked Lambda" from C++ which is illustrated nearby in this thread. Barry Revzin apparently proposed a way to do this in C++ 26 because C++ wants to have pattern matching and pattern matching kinda sucks without this feature.
- tialaramex 4y agoApparently C++ 26 might get statement expressions in some form, it sounds like Barry Revzin sketched something at Issaquah (the recent WG21 meeting) which can do this.
- deathvyp3r 4y agoyou can do this in c++ as well with a lambda const value = [&]() { if (a) { return true; } return switch (b) { case 1: return false; default: return undefinied; } }();
- andix 4y agoSure, in a lot of languages this is possible, but usually much slower, even slower than named functions. Can the c++ compiler in-line the lambda and remove the function call completely? Still the syntax is a bit confusing. In ML languages (f# and also rust are somehow descendants of ML) you can just use if .. else if .. else expressions. They work like ternaries! But easier readable. Nested ternaries are useful, but get really hard to read very fast. (x = condition1 ? A : condition2 ? B : C;)
- monocasa 4y ago> Can the c++ compiler in-line the lambda and remove the function call completely? LLVM and GCC can at least.
- somewhereoutth 4y agoAnd indeed almost exactly like that in JavaScript - just substitute ()=> for the [&]()
- wruza 4y agoYou can’t directly call a function definition in js, so it will look like this: const value = (()=>{ … })()
- tialaramex 4y agoAn immediately invoked lambda pattern, but, check out how ugly that was compared to: let value = if a { true } else { match b { 1 => false, _ => undefined } }; And notice that because now we're a function our relationship to the world changed, how does your immediately invoked lambda do this? let value = if a { true } else { match b { 1 => false, 2 => return foo, _ => undefined }}; With expression syntax returning from the function we're in is fine† but in your lambda you can't do that, you need control flow logic outside the lambda to handle it. † You might wonder about the inferred type of this expression since it might return. The type of the return expression in Rust is ! aka Never, which is an empty type. In the same way that N + 0 == N in integer arithmetic, any type plus an empty type is the original type again in type arithmetic, so the type of the expression isn't altered.
- pyjarrett 4y agoAda calls these "if expression" and the switch-variant "case expressions". It's the same syntax as the control flow ones, just wrapped with parens. value := (if a then True else ( case b is when 1 => False, when others => Undefined ));
- monocasa 4y ago> The named booleans suggested here help adding clarity, but more often than not they'll end up being single-use (const) variables that just paraphrase their definitions. In these cases I'd rather avoid introducing a variable, and rely in comments if I really need to explain something. Interesting. With the same axioms I come to the opposite conclusion. That comments are to be avoided except when strictly necessary, and giving names to concepts via intermediate const variables is one of the best weapons in that fight. Ie. make your code read as close to what the comment would be (but simply via naming) makes it very clear when the names no longer match the behavior, in contrast to comments which have a tendency to bit rot.
- Sai_ 4y agoThis is what I used to love about Obj-C - idiomatically written Obj-C code was always so much easier to read and understand without needing comments.
- simplotek 4y ago> The named booleans suggested here help adding clarity, but more often than not they'll end up being single-use (const) variables that just paraphrase their definitions. That's ok. The worst tradeoff in the "named booleans" pattern proposed in the blog post is that it misses the whole point of chaining predicate evaluations, which is leveraging short-circuiting to prevent further predicates from being evaluated. Chaining logical comparisons and the way they short-circuit is specially important when a predicate has preconditions and/or is expensive and/or has side-effects. If your predicates are hard to read you don't fix that mess with "named booleans". You fix them by extracting them to a pure function like isNotUnknown(_someLongNamedVar) && isNotEmpty(_someLongNamedVar)
- toxik 4y agoNest the if statements then, perhaps in an else branch. Relying on short circuiting to avoid side effects or improving performance meaningfully is pretty shaky.
- BeeOnRope 4y ago> Relying on short circuiting to avoid side effects or improving performance meaningfully is pretty shaky. Says who? IME relying on short circuiting is useful and idiomatic in C and C++ code, e.g.: if (callback && callback->run()) { ... } In your opinion would it be better to write: if (callback) { if (callback->run()) { .. } } The situation is even more awkward if you try to avoid || short-circuiting, since you need to duplicate the body of the conditional.
- DubiousPusher 4y agoI tend to agree with toxic here. Side effects inside conditionals should be avoided, in short circuit conditionals even more so. In a case like this, my preference would be to give callback a dummy value and always run it. I subscribe pretty heavily to the Carmack philosophy of always running the code as similarity as possible and making a decision at the end of a procedure.
- finnh 4y agoearly return solves so many readability problems. i structure my code in favor of it.
- bakugo 4y agoPersonally, I prefer to just split long if statements into multiple lines. if ( (_someLongNamedVar != FooLongNameEnum::Unknown) && (_someLongNamedMap.count(_someLongNamedVar) == 0) ) { // do something }
- seydor 4y agoright, but putting the && at the beginning of the next line makes it more obvious this is a continuation statement
- heleninboodler 4y agoI actually like putting the && on its own line in between.
- rzzzt 4y agoFor bonus alignment points and malleability, start with 'true' and place the closing parenthesis on a new line: if (true && condition1 && condition2 && condition3 ) { ... } If you ever need to change the set of conditions, the diff will cleanly add/remove whole lines.
- masklinn 4y agoOr borrow a page from the FP language folks, which gets most of the way there (prepending an items is still a -1+2 diff) without the weird initial clause: if ( condition1 && condition2 && condition3 ) { ... } Obviously the best alternative is to switch to a prefix language and sidestep the issue entirely: (when (and (condition1) (condition2) (condition3)) ...)
- Animats 4y agoThe only problem with this approach is that you evaluate all the conditions, even if you don't need to. Sometimes that's inefficient, and sometimes it's invalid, as in char *p = NULL; ... if (p && *p != ' ') { ... } If you write bool p_non_null = (p != NULL); bool char_non_space = (*p != ' '); if p_non_null && char_ non_space { ... } Oops.
- MithrilTuxedo 4y agoDon't they just want a function with a clear name that returns a boolean value, something to mask the implementation details so it's not part of your cognitive load at that level of abstraction.
- bandika 4y agoThe correct rewriting would be: bool char_non_space = p_non_null && (*p != ' ');
- tpoacher 4y agobut then 'char_not_space' is a misleading name, as it could denote the presence of a space due to p being null
- wruza 4y agoBack in mid-00s I was thinking about languages and features. One of the keywords I still miss in mainstream is “alias”. Alias is a symbol that only evaluates lazily. alias not_null = (bool)p; alias not_space = (*p != ' '); if (not_null && not_space) { This may bite you in another way, but living in a world where everything gets evaluated asap-y isn’t syntactically easy either. Would be also nice to have this: alias __cache foo = <heavy(expr())>; if (foo && foo.length && foo[0].x) { `foo` only evaluates once.
- yunruse 4y agoI was curious about the concept of inverting this – requiring that all variables lazily evaluate, and in the case that evaluation is needed now, some keyword obtains it. But when I think about it, that's sort of just `await`. I'm curious as to if it's feasible to syncretise these two aspects of variable evaluation into a shared feature that makes async and sync code feel alike.
- zabzonk 4y agothe problem is the length of the variable/enum names. if they were shorter, the problem would be much more obvious - as it is the code is unreadable. the lengths of names of entities, in any programming language, should be related to their scope - small scope, small names. and as limiting scope is what you should be doing all the time, almost all names should be short. also, unless you really know what you are doing, don't use leading underscores, or underscores in general, in c or c++. here is something i wrote a while back, which admitedly does not too much address the single underscore issue: https://punchlet.wordpress.com/2009/12/01/letter-the-second/ https://punchlet.wordpress.com/2009/12/01/letter-the-second/
- throwbadubadu 4y agoOr just put parentheses around your operands defensively even if by precedence rules not necessary as some style guides demand... went from hating and finding it too verbose to accepting and seeing advantage in that. if ((_someLongNamedVar != FooLongNameEnum::Unknown) && (_someLongNamedMap.count (_someLongNamedVar) == 0)) really not too bad? And easily enforcable and automated checkable by your favourite tool.. Named vars for subexpressions often helpful too.. but especially here you loose short-circuiting? (Not very C++ specific topic btw..?)
- secondcoming 4y agoI'm a massive fan of using parentheses even when not strictly necessary. I find it helps me visually parse the code better then a long chain of boolean expressions. I don't bother with it when a term in the expression is simple, e.g if (b1 && (b2 != b3)) rather than if ((b1) && (b1 != b3))
- mike_hock 4y agoI think the discussion is silly. 99.9% when the condition is "too hard to read" it's because the condition is too hard to read, period, and the correct fix is to restructure the code to have readable branch conditions, not to throw parentheses on there to make a monster expression slightly less confusing. It's like discussing the chemistry of air fresheners instead of taking the trash out more often.
- wruza 4y agoDo you think that any other fix that makes it readable is incorrect?
- usrusr 4y agoI'm in the opposite camp: more names means more identifiers to keep track of while reading in case they might be referenced more than once, even if it's just a short but nonetheless distracting check "ok, that's where the scope ends". There are certainly times when I take the name approach nonetheless, but more like a last resort, or when I expect full contact stepthrough with a debugger. If I deem it likely that I will get by with merely brackets and copious amounts of whitespace, that's what I'll do.
- fifticon 4y agoI do this and sometimes also something else: A local enum as a return value, where you have to compare the enum to use its value (ie you can't just 0/1 it). I of course don't always do this. I do it, when the semantics of the function value is a bit tricky. It is to protect against bugs "oh i'm pretty sure I know what returning true/false here means. It both helps the caller, as well as the implementation. when you are writing the return statements for edge cases, it can be a help that you return not just 'true', but IREALLYINSISTTHISMEANTWECOULDPARSEITANDCONTINUE. Again, I don't do this for simple obvious code.
- jasonzemos 4y agoIf one of the variables preceding the `if` has side-effects then it's inferior to the specified lazy-evaluation of the `if`. Any external function not annotated pure will be called to assign those variables which otherwise wouldn't have been if it was packed into the `if`. This is actually something I think the C++ standard should address soon. There are cases when extern functions which aren't appropriately pure are still "optional." The example par excellence is debug logging, should one choose to not use macros.
- zone411 4y agoThis article seems to be targeted towards beginners. From the title, I was expecting it to cover something like https://github.com/joboccara/NamedType https://github.com/joboccara/NamedType, which is a technique I would recommend.
- lpapez 4y agoAlternative solution: just write a damn test. How did that inverted condition get past testing?
- MaxBarraclough 4y agoThat would be a good addition, but I wouldn't call it an alternative. Good test coverage shouldn't be an excuse for unreadable style.
- deleted 4y ago[deleted]
- bandika 4y agoAll those bools in the article should be const.
- einpoklum 4y agoMy take: 1. Avoid overly long and complex condition expressions - break them up for readability. 2. If a part of a long expression has obvious meaning, assign it to a temporary (probably saving a comment); if it doesn't, break a line after it. I don't like assigning variables with arbitrary names that simply duplicate the expression calculating them. 3. `true` and `false` are magic numbers. Calling `my_function(my_data, false)` is quite the hazard in my view: It makes the reader rather likely to fail to notice the exact semantics. I would expect, say, enum : bool { dont_save_copy = false, save_copy_to_file = true }; my_function(myData, dont_save_copy) 4. This is relevant to almost any language! Even functional ones, where you would be encouraged to use "let" or "with" instead of super-complicated unbroken expressions. If you're writing APL though... you need super-memory and a discipline of steel :-)
- ahartmetz 4y agoWhile at it, also don't do if (condition) return true; else return false; Boolean is a perfectly cromulent data type that you can return from a function directly.
- Twisol 4y agoI agree most of the time, but it can make sense in certain contexts. One common example is comparison methods: if (x1 < y1) return true; if (x2 < y2) return true; if (x3 < y3) return true; return false; I wouldn't want to replace the last two lines with `return (x3 < y3)`, even though that's functionally the same thing. In this contrived example, we could just return a single compound condition, but hopefully it's clear that there are cases where doing so would make more of a mess, too.
- wruza 4y agoFor my mind it only works if a meaning of a return value is “compatible” with an expression. E.g. if a function returns a status, but an expression is an integer comparison, the flow of logic skips an important conversion step. bool is_db_open() { return (db != NULL); } bool is_time_to_report() { if (n_errors >= limit) { return true; } return false; // a little confusing way return n_errors >= limit; } Also, if there’s a chance of an additional condition, they can be easily be added and documented. if (n_errors >= limit) { return true; // too many errors } if (now() >= deadline) { return true; // periodic } return false; For the same reason I often do this: if (x == 0) { return 0; // not `x`
- gpderetta 4y agoMany of these seen in the wild are due to workarounding an extremely annoying and useless MSVC performance warning.
- ChrisMarshallNY 4y agoThis is a good technique. Not sure I’d consider it particularly novel, though. It’s pretty much boilerplate for how coding is taught. I tend not to work that way, but it’s really a matter of personal style. I do it to reduce the verbosity of my code (which tends to be fairly verbose, in other ways –especially comments). I will, occasionally, encounter the bug he mentions, but not particularly frequently.
- personjerry 4y agoIt's not really a new technique. It's pretty common in large scale software development to break down complex logic like this. The reader will note that the possible loss in performance is usually more than worth the gain in readability, and hence makes the code much more desirable at scale.
- zooweemama 4y agoPretty sure that the compiler optimizes this and there is no performance loss.
- meindnoch 4y agoYou're wrong. if (cheapCheck() && expensiveCheck()) { ... } is not equivalent to bool cheap = cheapCheck(); bool expensive = expensiveCheck(); if (cheap && expensive) { ... } unless we're in a lazy pure functional language.
- lazypenguin 4y agoCan you prove this?
- drdec 4y agoIt's easy enough to verify, just add a side effect to the second function. Just think of them as process functions returning success flags.
- ninkendo 4y agoI think the idea is that if there's observable behavior in unconditionally calling the expensive function, the compiler can't skip it without altering the program behavior. Whereas if they're called as part of the if condition, the compiler can always safely skip the second function (and it's documented that it must do so, due to "short circuit" semantics) if the first function is false. The ability of the compiler to skip the expensive function in both cases are characteristics of a "lazy" language, ie. you can assign an expensive function to a value, but the actual function isn't run until the variable is needed. Languages like C++ can't do this because it would alter the program's behavior if cheapCheck() does return true, because suddenly it would have to call expensiveCheck() after cheapCheck() (rather than before), which is an observably different behavior for the optimization to cause.
- pwdisswordfishc 4y agoThe original bug would have been prevented by not overusing parentheses. This: if (_someLongNamedVar != FooLongNameEnum::Unknown && _someLongNamedMap.count(_someLongNamedVar) == 0) does exactly what it’s supposed to.
- LudwigNagasena 4y agoThat is what happens when people zealously try to not be "too clever". They make everything as explicit as possible for "clarity" even when it obviously hinders it for anyone who cared to learn the most basic things about the language they use.
- shrimp_emoji 4y ago>the most basic things about the language they use This is C++. The most basic thing has been deprecated but is still supported for backwards compatibility.
- rwalle 4y agoSo the topic here really has nothing to do with .count, STL, C++ 20 or even C++ itself. It could happen in almost just every language. A very confusing article.
- TheHideout 4y agoThis could have also been prevented using Yoda Notation with 0 == _someLongNamedMap.count(...). https://en.wikipedia.org/wiki/Yoda_conditions https://en.wikipedia.org/wiki/Yoda_conditions
- sidlls 4y agoFirst thing I thought of when I saw the original wrong code. I prefer the author's approach, though, as I do believe it is more clear, unless there is something performance critical about it that requires short-circuits for maximum efficiency.
- dcuthbertson 4y agoThanks for the reference to Yoda conditions. I hadn't heard of Yoda code until a few weeks ago when a Go linter complained about some code I wrote a few years ago. I realized I've been writing Yoda code in C/C++ for quite a while and that habit had continued (somewhat inconsistently) into Go programming. It's nice to skim through pages of code and see the value that will cause the computer to enter an if-block. The linter made me feel like Yoda code was somehow wrong. It's good to know that others think it can help in some situations.
- cesarb 4y agoSince nobody seems to have done the traditional "blame the programming language" yet, let me get it out of the way: this particular bug wouldn't have happened in a language which doesn't have implicit conversion between boolean and number. The compiler would reject the code both because the numeric result of ".count(...)" is being treated as a boolean by the "&&", and because the boolean result of the "&&" is being treated as a number by the "== 0". One example of a language without that implicit conversion is Java. It's fairly annoying to have to put a "!= 0" whenever you want to convert a number to a boolean, but it does avoid a lot of programming mistakes within if conditions.
- LudwigNagasena 4y agoIf you want, you can disallow implicit conversion between boolean and number using a linter.
- quietbritishjim 4y agoI imagine that you'd get a load of false positives due to textual inclusion of header files.
- jcelerier 4y agoCompilers have no trouble ignoring header files outside of your project for the sake of warnings with -isystem instead of -I, it's not different for linters
- inetknght 4y ago> Compilers have no trouble ignoring header files outside of your project for the sake of warnings with -isystem instead of -I, it's not different for linters In this [0] example I shared in other related comments, the static analyzer suppressed warnings in such headers. > 126 warnings generated. > Suppressed 124 warnings (124 in non-user code). [0]: https://gcc.godbolt.org/z/nK7MdEcMj https://gcc.godbolt.org/z/nK7MdEcMj
- 4y ago
- andrepd 4y agoIsn't this (1) the most trivial programming advice ever (give names to things), and (2) not equivalent because of short circuiting?
- kgeist 4y agoAs an alternative, you can also create private helper functions to evaluate conditions. You get the best of two worlds: function names are self-explanatory and it supports lazy evaluation.
- osigurdson 4y agoThere may not be anything wrong with it in isolation, but cognitive load is increased when every identifier is effectively a sentence or paragraph.
- rerdavies 4y agoBut cognitive load also increases when you have no idea what the significance of a variable is.
- osigurdson 4y agoAgree, but it is an optimization problem: maximize meaning while minimizing identifier length. Recognize that it is possible to have identifiers that are too long.
- jeffrallen 4y agoRust and Go also avoid C++ bugs. Why are we even still talking about C++?
- anthomtb 4y agoNeither Rust or Go hit Github's Top 10 languages (though they are among the fastest growing): https://octoverse.github.com/2022/top-programming-languages https://octoverse.github.com/2022/top-programming-languages. There is a lot of code out there already written in C++ and a lot of people working on that code. Not to mention an active standards body. Expect to be hearing about C++ for a long time to come
- jeffrallen 4y agoDommage.
- kqr 4y agoYou can also sometimes combine the techniques in the article above to skip booleans entirely. Instead of passing in a `bool primeMember` that gets used in a calculation like deliveryTime = 3; if (primeMember) { deliveryTime = 1; } you can pass in a `Membership memberType` value that contains the required information, so the above conditional turns into just the assignment deliveryTime = memberType.deliveryTime; This is more amenable to extension and contraction: you can add or remove different classes of delivery times without changing the logic in which it's used. You can even use private constructors and public static fields to create a list of "constants" containing the known member types, like the `Membership.REGULAR` and `Membership.PRIME`, retaining the property of the code not being able to use an invalid membership type. This can be used also for more complicated conditionals like the one in the article, by having a factory method that copies the right "constant" composite value based on whatever logic is desired.
- AgentOrange1234 4y agoThis assignment-then-conditional-reassignment is admirably compact and, in languages like C/Java might well be the most elegant way to handle this. On the downside, this breaks single-assignment, binding style variables. That is, now deliveryTime can’t be final/val/const. I wish I had a better solution. Lisp did this so well with if/cond/etc as expressions.
- overgard 4y agoRelated, one technique I like to use even though it's more verbose is to replace booleans with enumerations. For instance, maybe you have something that could be selected or not. I generally think instead of having "true" be selected and "false" be not selected, it's better just to have ListItem::Selected and ListItem::Unselected. That way when you pass things to a function, instead of reading a lot of true/false things in random places, you can see "ListItem::Selected" instead. Small upfront cost for a lot of readability down the line. Typescript is nice for this in that you can use strings but enforce that they're in some set.
- msm_ 4y agoI agree, but without pattern matching converting "boolean-like" enums get tedious. For example: ConnectionMode connectionMode; if (useHttpsCheckbox.selected == ListItem::Selected) { connectionMode = ConnectionMode::Https; } else if (useHttpsCheckbox.selected == ListItem::Unselected) { connectionMode = ConnectionMode::Http; } else { shouldNeverHappen(); } In the real world there are often even longer chains like this - I found that bool-like variables ofter mean slightly different things at different layers of abstraction. And also, you could simplify that by ignoring the `else` entirely, but there are no guarantees that it won't break later. For example another contributor may add ConnectionMode::TryHttpsElseHttp, and compiler won't help us. Languages with pattern matching don't have this particular problem.
- gpderetta 4y agoYou can of course use a switch statement and the compiler will warn if the cases are not exhaustive for the enum.
- arximboldi 4y agoThe post peaks with this example is this one: bool usersHairColorMatchesThisMonthsAdsColour = _user.hair == HairColour::Red; bool userFeetSizeFitsInShoe = _user.feetSize <= _requestedShoe.size; bool shoePriceFitsInUserBudget = _requestedShoe.price <= BudgetHelpers::Calculator(_user); bool shoeIsProbablyOkayForUser = usersHairColorMatchesThisMonthsAdsColour && userFeetSizeFitsInShoe && shoePriceFitsInUserBudget; if(shoeIsProbablyOkayForUser) That they compare, with the "uglier": // this months ad campaign color is red if(_user.hair == HairColor::Red && _user.feetSize <= _requestedShoe.size && _requestedShoe.price <= BudgetHelpers::Calculator(_user)) I very much prefer the latter, I'm sorry (except it needs some new lines, of course). It performs better (that `BudgetHelper::Calculator` doesn't look very cheap ;) but crucially it is, to me personally, more "readable". First, I find a comment easier to read than a variable name, particularly when you use the not so readable dromedaryCaseToCrambleWordsInLongSentences. Thus, a variable adds no value when it's only used once (arguably, it adds more noise, since one has to go twice through the same text). And it is often debatable how a programmers poor translation of code into English adds any value: in fact, I find `_user.feetSize <= _requestedShoe.size` to be way more succint, precise and easier to parse than `userFeetSizeFitsInShoe`. We really need to get over this school of thought that "the more wordy your variables the more readable" because it is simply not true. You don't have to go all the other way to the other end and write code using greek letters like in math. Meet somewhere in the middle, be aware of context and use the tools of the language (e.g. lexical scoping) to your power. Unlike many of these bloggers want us to believe, you can't just boil "readability" down to some simple universal rules, and only experience makes the master. Taste, preference, purpose and context take a big part. As such, I find it useless to talk about "readable" code: readable for who? A law text, a maths paper, a school maths book, a user manual, a tutorial, a novel, a HN comment... they may all be written in English and with great skill in consideration for their "readability" by their target audiences, yet manifest completely different styles even when discussing the same topics. For me, I like my code like I often enjoy most good writting: succint and precise. Wordy variables that add no value or abstraction are of little use to me.
- DangitBobby 4y agoI find that people want to find a name for some condition or computed result so they can box away a concept and use it without thinking about it anymore. That's great when you're writing software and you have lots of time to consider and remember your names, but not always so great when you revisit software and now you need to actually _know what those things are and whether they are logically correct_. Sometimes even the best name isn't enough to describe what it is you have in your hands right now, and instead of seeing it where it's used, you have to go find where it was originally defined, and you potentially have to repeat this process for several named objects at once, keeping them all in your head. IMO you need a better reason to name something than the example from the article, or you actually sacrifice clarity and readability.
- cassepipe 4y agoI use this technique a lot to make the code more readable and less error prone but I have never bothered checking if this was trivially optimized or not. Do you know ?
- fooker 4y agoIsn't this partly why `enum class` was introduced, and is now the idiomatic way to express enums?
- GnarfGnarf 4y agoExcellent suggestion. I use this style myself in many places. Makes the code more legible. As has been pointed out, there are cases where it's not efficient or correct, but in general it's very useful. (Been coding since 1965, not always C++ obviously).
- pflanze 4y ago> Effectively the if statement was inverted. If this wording means to convey that the complete test in the wrong code version is always giving the inverse of the correct version, then that statement is wrong. The truth tables, using 0 and 1 for false and true ("false" and "true" look too messy here): The buggy code: 0 1+ <- second condition 0 1 1 1 1 0 ^- first condition The fixed code: 0 1+ <- second condition 0 0 0 1 1 0 ^- first condition i.e. the test as a whole is not inverted if the first subcondition is true.
- _0w8t 4y agoThe bug in question would not happen if a reviewer demanded to split the complicated if. Yes, it would add a few lines depending on the overall control flow, but code in question is hard to read and must be written in a less dense way.
- sebazzz 4y agoCode Complete 2 is still relevant and advocates useful practices like these.
- nuancebydefault 4y agoI read comments about need for lazy evaluation. In fact all constructs that ensure lazy evaluation try to solve a not really existing problem,in a hard to review manner. What the heck is wrong with just explicit nested if statements? Not cool enough? ``` if (p!=nullptr) { if (*p == ' ') { } else { } } ``` keep it simple