Hacker News new | past | comments | ask | show | jobs | submit login
Clear is better than clever [pdf] (cheney.net)
498 points by ngaut 16 days ago | hide | past | web | favorite | 318 comments



It's interesting that the comparator function is one of the examples, because that's something which shows how people often confuse verbose with clear; I think it's best written in a single line:

    return a < b ? -1 : a > b ? 1 : 0;
That's one line, compared with the 10(!) of his proposed method using a switch statement. Having worked with some "modern" codebases, I think verbosity is a bigger problem that's become especially prevalent recently, since the idea of making a simple thing simple is so enticing and easy, but emphasising "micro-readability" to that extent (that's 10 lines of code for two simple decisions) will bloat the code and make "macro-readability" harder, and it's the latter that really matters for the understanding of complex software.

Anyone who has the experience of reading lots of highly trivial functions like those, and gotten the feeling "I've read a lot of code, and I understand what each little bit does, but what is the whole thing trying to do?" That's a symptom of excessive verbosity.

To offer another (subjective) point, I think the lack of semicolons or other statement delimiters does make code harder to read, as it makes it look similar to a run-on sentence. Natural languages have punctuation for a similar reason --- you can quickly scan over sentences by finding their delimiters.

Also, the counterargument: https://www.linusakesson.net/programming/kernighans-lever/in...


Fully agree!

Combinations of if/else can seriously damage readability and maintainability.

I (all the time) face verbose code that pretend to be readable and that requires lots of attention just to find out you are setting a single variable across 10-20 lines.

Example:

  if (cond1)
  {
    myVar = 1;
  }
  else if (cond2)
  {
    if (cond3) 
    {
      myVar = 10;
    } 
    else
    {
      myVar = 100;
    }
    ...
  }
  else
  {
    myVar = defaultValue;
  }
* huge number of lines

* not obvious (until we read all) that we setup a single variable

* combination of conditions difficult to trace for each given value

* high risk of forgetting cases...

Instead of

  myVar =
    (cond1)             ?   1 :         // comment case blabla
    (cond2) && (cond3)  ?  10 :         // comment case bla
    (cond2) && (!cond3) ? 100 :         // comment case blablabla
    ...
                          defaultValue; // comment default
* one line per given value

* operation on a single variable obvious

* match between conditions and values obvious

* high visibility on all the cases

* even more readable with proper vertical alignment

* a comment can be added on each line to document the case

[sorry for the multiple edits: I had a hard time getting { } properly display, they ate up the new lines]


You can write it without resorting to one-line conditionals:

  if(cond1){
   myVar = 1;
  }
  else if(cond2 && cond3){
   myVar = 10;
  }
  else if(cond2 && !cond3){
   myVar = 100;
  }
  else {
   myVar = 4;
  }


But when I encounter this kind of situations I have other problems than code formatting anyway:

with 3 conditions you have 2^3 possibilities to check: are you really really sure (!cond1 && !cond2 && cond3) should give you defaultValue ? Do you really want 1 even if !cond2 ? etc...

When possible these conditions should be avoided in the first place (depending on context of course)


If you're running through that many conditionals, it may be clearer to work them into a state enum that you can use with a switch.


Golang doesn't have enums


Neither does JS, but there are ways to accomplish the same thing[0].

[0] https://stackoverflow.com/questions/14426366/what-is-an-idio...


TypeScript does and it’s one of my favorite features.


That's not really the same. That's badly designed and error prone.


That makes code formatting inconsistent because many prefer the other way. The VB.Net style is more compact and readable in my opinion:

        if cond1 then
            myVar = 1
        else if cond2 and cond3 then
            myVar = 10
        else if cond2 and not cond3 then
            myVar = 100
        else
            myVar = 4
        end if
The VB.Net style also makes it easier to identify mismatched blocks because the "enders" are named differently. (One can also put the assignment on the same line as the conditional, but I generally don't recommend it.)

Some may complain it's slightly verbose, but few if any will claim it's not clear.


IMHO, better but it’s still confusing when you have multiple places where you name the variable. Functional programming style forces you to be more explicit and pushes you to separate out the assignment expression. In Elixir I’d do:

    myVar = 
      cond do
       cond1 -> 1
       cond2 && cond3 -> 10
       cond2 && !cond3 -> 100
       true -> 4
      end
ML languages have similar constructs.


Maybe, but that misaligns the values. I'd rather see them lined up. And often more complex logic may be added to the sub-blocks such that the simple pattern goes away. Code should be able to "degenerate well", meaning it shouldn't require lots of rework when the initial pattern fades or changes in the future. It's one of the reasons I often use If/else instead of switch/case statements.


You assigned “myVar” in every branch, right? Let me reread that again to make sure you really are assigning myVar in every case.

That’s a problem. One I’ve seen all too often, but a problem, nonetheless.


As I mention above, it "degenerates" better in that if more code is needed in each sub-block, it's easy to add. I'm okay with syntax that could factor such out if it doesn't require overhauling the block when the starting simple pattern goes away over time. As they say, the wrong abstraction is often worse than no abstraction. Change can kick the simple pattern away.


Alternative solution: put the if/else block in an “IIFE” (which Go supports, just like JavaScript) and change the assignments to returns.


If you are worried with all the cases, you can very easily display your code as a truth table with nearly no overhead in this way:

  myVar =
    (!cond1) && (!cond2) && (!cond3) ? myValue_0 :   // case 0 0 0 blabla 
    (!cond1) && (!cond2) &&  (cond3) ? myValue_1 :   // case 0 0 1 bla
    (!cond1) &&  (cond2) && (!cond3) ? myValue_2 :   // case 0 1 0 blablabla
    (!cond1) &&  (cond2) &&  (cond3) ? myValue_3 :   // case 0 1 1 
     (cond1) && (!cond2) && (!cond3) ? myValue_4 :   // case 1 0 0 
     (cond1) && (!cond2) &&  (cond3) ? myValue_5 :   // case 1 0 1 
     (cond1) &&  (cond2) && (!cond3) ? myValue_6 :   // case 1 1 0 
     (cond1) &&  (cond2) &&  (cond3) ? myValue_7 :   // case 1 1 1 
                                       defaultValue; // uninteresting default (null, -1...)


I think its a poor example. How often you are going to write something like that in RL scenario?

In case of more complex data generation you could use supplier pattern and combine it with strategy.

No need for any if else statements at all.

Sure it requires a lot more of work but is future proof and clear.


I've not heard of the supplier pattern, any chance you would direct me to a resource about it?


A Supplier is when a function needs a value, and instead of providing a value as an argument, you pass in callable:

Foo(int x){return x+1} becomes Foo(Supplier x){return x() + 1}


Function that supplies value, can also encapsulate alot of data generation code. After that you can write a strategy to select which data generation algorithm (supplier) shpuld be used.

Thats my cause.


Thanks.


Im pretty sure you know what i meant, no need for nitpicking.

(Does not make me any less right)


Just maybe he genuinely doesn't know what you mean. I mainly use Python and have no idea what you are talking about.

(You might consider reflecting on your tone and message. It presupposes bad faith on the other party, is dismissive and condescending, and you are probably less right than you think you are.)


I, too, have no idea what a supplier pattern is. Don't think that's a very mainstream notion.


I'm not OP, but I don't know what you mean... Googling it leads me to Supplier in Java 8, which is probably not what you meant?


I can't nitpick what I don't know about. I know the strategy pattern, not the supplier pattern.

It sounds interesting, that was all.


> Combinations of if/else can seriously damage readability and maintainability.

Yes, that's why for selections we usually use switch/case. But of course every programmer worth his money just knows that ?: is right associative and that '&&' is on precedence level 11 while '?:' is on 13. If that's not "clever code", I don't know what is...


> every programmer worth his money just knows that ?: is right associative

I would hope every programmer worth their money are very comfortable with it, because chaining like this is a very common syntactic pattern in other people's code:

  cond1 ? val1 : cond2 ? val2 : cond3 ? val3 : other
It is made less readable if you turn it into a right-nested mess of parentheses, so that is rarely seen.

Just as nobody thinks of 'if..else' as right associative, but it actually is, and everyone uses that fact without thinking about it.

Same for '?:'. The associativity is just a formality that nobody thinks about.

> '&&' is on precedence level 11 while '?:' is on 13

On this I agree. I routinely put parentheses around complex conditions on the left side of ternaries for this reason.

I do remember the precedence, but I think leaving the parentheses out can raise a little doubt in the reader's mind, because it is common to use logical operators for control flow in languages like JavaScript, and control flow is at the same conceptual level as '?:'.


This came up recently on the Swift Evolution forum (which is the official forum for discussing changes to the Swift programming language). Dave Abrahams said, “[…] I keep meeting experienced programmers (really smart people!) that have no trouble reading [a chain of if/else statements] and yet are confused by the analogous ternary construction: [a chain of ?: expressions]”.

https://forums.swift.org/t/pitch-if-else-expressions/22366


My favourite solution to this problem is how rust does it. In rust every block can evaluate to an expression, so if/else is the ternary operator.

    let x =
      if cond1 { expr1 }
      else if cond2 && cond3 { expr2 }
      else { expr3 };
It’s more verbose this way (‘?’ Vs ‘else if’) but there’s no question of readability because it’s just if/else. You can format it however you like, and add statements into the blocks later if you need to, too.

Rust also has the match statement, which is cleaner whenever your conditions are mutually exclusive.


Very lispy :-)

Some would say top-level blocks returning the last value in the block is an anti-pattern, because functions which aren't meant to return a value end up leaking the value of the last thing called in the function, which might be another function, which called another function. Or it might be in various branches of an 'if', which aren't being examined for being an acceptable return value.

Perl does this, (like ECMAScript's 'do'), and while it's usefully concise sometimes, for API-level functions I think it's poor to accidentally leak values to the caller, that should never escape. The safe way to deal with this is an explicit void return at the end of API functions, but that's ugly and hard to remember.

I think JavaScript made the right choice in requiring explicit return from functions with blocks to return a value, with 'undefined' returned if nothing explicit is. Accidents are avoided.

Rust has taken an interesting approach of requiring a return type to be specified, which stops accidental leaks at least. Respect.


Yep! Coffeescript does it too. It was always weird seeing random values pop out of functions while debugging. Also coffeescript's loops evaluate to a list, which meant that functions which ended in a loop in coffeescript would end up constructing and returning arrays that would never be used.

Rust will also only return the last expression in a block if it doesn't end in a semicolon. This can be a bit subtle when you're reading a long function, but combined with explicitly specified return types its hard to mess up while writing code. Because of the choice about that semicolon, its an explicit choice whether you want a block to evaluate into that expression or not. And for functions you can always just use an explicit return statement if you want anyway.


Perl6 has remedied the accidental leak of values to the caller by allowing you to give a return value in the signature.

This will ignore the last result and return Nil instead:

    sub foo ( $_ --> Nil ) {…}
This only works with literals, constants and Nil.

If you specify a type instead, it only enforces that the result is of that type.


> like ecmascript's "do"

Keep in mind this is only a stage 0 proposal, and thus is not really part of the language. This is how statements behave when entered into the repl, e.g. the browser dev console.


(COND ...), anyone? :-)

Ruby has an if/else structure like the Rust/Lisp thing, as well. Tasty.


There's are two pretty simple explanations for that.

1. It's rare, so not many programmers have the pattern matching built up to read it easily.

2. Chained if/else statements have the benefit of indentations helping to show structure. The moment you add newlines to help show structure, a chain of ?:?:?:'s becomes much easier to read.

Most ?: expressions I see are uses inline, such as foo(a ? b : c). When you do that, you sometimes have to mentally unwind "ok so if a... what's a? why a? ok, so if a, then foo(b), else foo(c)". Putting the if(a) up front means you're already thinking about it by the time you get to the function call. So I reserve using ?: for when the difference between the two outcomes is minimal.


>because chaining like this is a very common syntactic pattern in other people's code

If it's very common where you work, then run, don't walk, away...


No, it's very common in code people will encounter if they read a wide range of other people's code outside work.

If you haven't encountered it often enough that it's familiar, than I think you probably don't read much code outside a small bubble.


"readability" is largely a matter of familiarity, so it's very hard to make such sweeping statements accurately. The difference between "common idiom" and "unreadable mess" is "common", not the code itself.


Strongly agree with this. My company has its own coding standards and openly acknowledges that they are not objectively "the right way to do it", because such a thing does not exist. Instead, they define rules to make our code (relatively) safe and consistent.

The Linux kernel has another set of rules that differ in numerous ways (e.g. not wrapping single-line statements under a conditional in braces). Their rules are not strictly better or worse, just different and define their set of common idioms.

Another thing that the MD said to me during my interview was "code ought to be boring, testing can be interesting".


Never thought the ternary operator required high expertise for associativity/precedence reasons and could sentence me to "clever code"...

Besides switch/case do not apply to a mix of true/false conditions but to the different values of a single variable.

Funny :-) Thanks.


> Never thought the ternary operator required high expertise for associativity/precedence reasons

If you chain or nest them.

> Funny :-)

I'm glad I could brighten your day a little bit.


I guess I'm in a minority here, but I wish more non-functional languages would feature pattern matching, of the sort seen in Haskell and OCaml. [0]

This language-design question is essentially a solved problem, but few new languages (outside the pure-ish functional ones) make the effort.

Trivial example in OCaml:

  let imply v = match v with 
       (true,x)  -> x
     | (false,x) -> true;;
[0] https://caml.inria.fr/pub/docs/oreilly-book/html/book-ora016...


It's becoming more popular. Kotlin, Swift and now C# 8.0 have pattern matching (well, close enough at least). I sure hope other languages take note, because you are right about it essentially being a solved problem.


Scala, too.


Yep! Scala's pattern matching is so far ahead of Kotlin's, vis a vis destructuring, that it strains the conscience to say Kotlin has pattern matching at all. FWIW I find both to be enjoyable languages in which to work.


Or break them out into a function, then you can "short-circuit" with a return:

    int logic()
    {
        if (cond1)
        {
            return 1;
        }
        
        if (cond2)
        {
            if (cond3) 
            {
                return 10;
            }
            
            return 100;
        }
        //  ...
        
        return defaultValue;
    }
Since it's a function, it's also apparent that you are only touching one variable:

   myVar = logic();

I love the ternary operator, but I find it's harder to step it in a debugger though.


Short circuiting is poor for readability in my view because not only do you have to parse the syntax but you need to mentally walk through each case to understand what’s going on.

Traditional if/else conditionals express clarity by embedding the decision for myVar and visually show how it would be assigned in various cases.

As @hotBacteria said:

  if(cond1){
   myVar = 1;
  } else if(cond2 && cond3){
   myVar = 10;
  } else if(cond2 && !cond3){
   myVar = 100;
  } else {
   myVar = 4;
  }

  // Now return myVar
  return myVar;
I’ll take this syntax over short circuiting any day.


Clear conditionals like this (or short circuiting) are very nice when it comes to merging.

One liners and mammoth tertiaries that are too long can lead to conflicts and more problematic merges/diffs/etc.

One operation per line makes repos and merging, as well as blaming, reading and reviewing, easier to parse.


I actually prefer the long version. But I think the real trick is to be consistent in style. Parsing ternary operators is a learned skill and so is parsing long if statements. You can get used to both.


Programmers have to learn to read different styles, to read code from other projects. So use the construction that is clearest in its local context.

Style is a matter of taste, just like writing English: there are synonyms and idioms for every idea that you want to express, so use that freedom to choose the clearest communication. Like English, programming code is meant to communicate to other humans, so don't artificially limit your vocabulary in the name of consistency.

Consistency is a hobgoblin.


> Consistency is a hobgoblin.

A foolish consistency is the hobgoblin of little minds.

The whole quote is important. Cutting parts out changes the meaning of the whole thing.


I don’t think consistency is a hobgoblin (I hope I understand the meaning correctly). When you are new to a project you should follow the style of what’s already there and not do something completely different. Obviously there may be good reason but that should be discussed and you also should ask yourself if you are just not willing to adapt or if you are actually making things better.


I have also seen switch statements used (abused?) to do a similar thing. Since a switch executes the first statement that is equivalent to the control expression you can pass true as the control expression, e.g.:

  switch(true) {
    cond1: return 1;
    cond2 && cond3: return 10;
    //etc
  }
Not sure I'd recommend it but it works in a few languages :)

Elixir has cond that does this nicely without resorting to strange switch statements:

https://elixir-lang.org/getting-started/case-cond-and-if.htm...


I prefer to put it into a function and do early return/guard clauses for complex conditionals:

    const pick = (param1, ...) => {
      if (cond1) return 1;
      if (cond2) return 10;
      return 100;
    };


I much prefer the if/else, thanks very much.


I usually write if in such cases.

Or sometimes for multiple condition branches I use bitwise operators to create integer bit mask with couple of bits, then switch(), or array indexer, with all possible 4-8-16 values.

Your version is more error prone because contains more code than necessary. Note how it’s just “else” in original version, and manual negate statement, (cond2) && (!cond3), in your version. Easy to screw up when modifying the code at some later point. When you then need to replace cond3 with cond4, forget about second branch and the algorithm will break.


What we should be able to write in Go, but can't because the compiler (wrongly) imposes formatting:

   if      a       { v =   1 }
   else if b &&  c { v =  10 }
   else if b && !c { v = 100 }
   else            { v = d }


No. This kind of alignment is basically always a mistake and hurts software maintainability in the long run.

The problem is that if one of the conditions changes, for example due to a variable renaming, you likely have to change the alignment on all lines.

Aside from being very tedious even if you're a lone coder, this creates noise in diffs that makes automatic merges fail much more frequently.


I strongly disagree; creative formatting like this means that as a reader, you have to squint and readjust to this new style to see what is going on.

I mean, this whole thread reads like a holy war about code style - something the Go developers EXPLICITLY want to avoid because discussions about code style are a waste of time.

Everyone reading the above segment of code will have a different opinion on how to format it, while the real question should be "What does this code do". I mean this is highlighted in the preface of the presentation: "When you or I say that a foreign codebase is unreadable, what I think what we really mean is, I don’t understand it."

When I read this code I don't understand it, and it's not because of the formatting per se. Playing with the formatting does not make the purpose of the code clearer.

This whole thread where people argue about how to best format a structure is missing the point of the article completely.


> When I read this code I don't understand it

?


Personally when I find myself with these constructs of complicated and nested conditions I try to simplify the branches and condition checking to functions.

This is not always possible, but when it is it's much easier to follow the flow of code and it reduces the number of lines of the condition tree.


  enum situation { boring, clear, cleaver }
  
  selector(context)
    if context.proves(cond1)
      return cleaver
    if context.proves(cond2)
      return clear
    return boring

  adjudicator(context)
    switch( selector(context) )
      cleaver: make_something_cleaver()
      clear: make_something_clear()
      default: make_something_stupid_simple()
Not completely the same situation, though, as there are no assignation here. For simple cases (`v = e0 ? e1 : e2`), ternary operator sure is fine, but for more complex cases, `some_var = selector(context)` tend to be a clearer path. That will also be better rendered in your API as the function fine documentation will have more chance to be extracted properly.


About the only change I would make to your layout is to put a line break after the “?”, with the value sub-indented on the next line, to avoid “guessing” /maintaining how far to tab over the value column.

Thus, it would be layed out like an else-if chain, but without the extra verbiage, particularly the repeated assignment.

(I can’t put in a proper example from my iPad, as it wants to capitalize all the lines, etc)

Disclaimer: Ruby was very influential to me in the mid 2000s, even if I never wrote any for pay. The if (else-if...) statement in Ruby works like a ternary chain in C based languages.



Great info, thanks!


Or

  if(cond1) myVar = 1;
  else if(cond2 && cond3) myVar = 10;
  else if(cond2 && !cond3) myVar = 100;
  else myVar = 4;


As long as verbosity is meant to make your code more explicit I'm fine with it.

Personally, I find that ternary example you posted to be terrible code. We are not using parchment anymore, there is nothing to be gained by saving a couple of lines.


"Personally, I find that ternary example you posted to be terrible code."

It's not about the line count, it's about expressing the main idea clearly and succinctly, which this code does.

My nitpick would be with the language constructs themselves. The ternary operator is simply a crutch for lack of an if-then expression returning a value.

    if a < b then -1 elsif a > b then 1 else 0
Or can add some white space to make the structure a little more clear:

    if a < b then
      -1
    elsif a > b then
      1
    else
      0
    end
Expressions like the ternary operator micro-optimize by replacing tokens recognizable as natural language words with arbitrary punctuation. But that's a language level complaint, which you generally don't have any control over once you've started the project or when editing existing code.


by replacing tokens recognizable as natural language words with arbitrary punctuation

It's far from "arbitrary" --- and I suspect that this dogmatic, misguided way of (not) thinking about it is responsible for the majority of the complaints and aversion, since as others have mentioned, it is completely equivalent in structure to if/else!

What do I mean by "far from arbitrary"? Well... what is the character used in basically all Latin (and even some widely-used non-Latin) languages to denote a question? I've already used that character twice in this post so far, and you should've found the previous two sentences to be pretty clear, so stop pretending the ternary operator is something scary and "unreadable" and see it for what it is: it is literally asking a question!

    understand ? done() : keep_thinking();


True, but that second part kind of falls flat when we continue your analogy.

We ask questions with ? but we don't offer alternative answer to the question with :. If anything, maybe it should be ; or even .?


> It's not about the line count, it's about expressing the main idea clearly and succinctly

I disagree. I find no clarity in using nested ternaries, but I understand it's a matter of opinion.


I find German inscrutable, and yet Germans don't. Everything's hard to read until you learn to read it.


Or don't learn German but continue speaking in English which is inherently same as German in functionality but at least you and people around you can understand each other.


it's also nice with Haskell's Multiway If:

  x = if | a < b     -> -1
         | a > b     ->  1
         | otherwise ->  0
 
(and `cond` in lisps)


> It's not about the line count, it's about expressing the main idea clearly and succinctly, which this code does.

Thats very subjective. As a beginner dev, the ternary series is almost un-understandable for me. While the if/else blocks are far easier to glance at and digest.


In Python you can write it like this:

  if      a < b: x = -1
  else if a > b: x =  1
  else:          x =  0
Which a lot of people seem to hate for some reason, but I always appreciated the compromise between terseness and clarity.


Python has its version of the ternary operator as well. It may look unusual due to the different order of the operands, but it works quite nicely, in my opinion:

  x = (-1 if a < b else
       1  if a > b else
       0)
This emphasizes the possible values that x may be assigned to.


I agree, the ternary style is better for this sort of variable assignment situation. Usually if I am using the above pattern I am calling different functions as the result of the conditionals.


Python uses the `elif` keyword if you want to place an `else if` on one line. And column indentation isn't very "pythonic", apparently.


> We are not using parchment anymore, there is nothing to be gained by saving a couple of lines.

This is not what the OP is talking about - we are not using parchment anymore, but we still have the same brains, being able to fit more code in our head with less i/o allows is a more holistic view.

There is a balance in between terse and verbose that produces a reasonable length of code for what it is doing, and that is subjective, to the individual and the code - so you are never going to agree on everything. But I say fuck no to people who want to lower everything to the common denominator, that is mediocrity, I don't want my grandma to be able to read the code if it makes macro comprehension horrible to the point of making it unmaintainable spaghetti.


It really is a balance, and it's very hard to get it "just right". I mean I can appreciate a succinct oneliner (which is why I really like functional programming, doing a .map() instead of a for loop for example; it's a lot more compact without losing expressiveness, however, you do need to learn the lingo at first).

When I first went from Java to Scala, I found that while you can perform the same logic in 10 times less lines of code, each line is also 10 times as powerful and complex. A random example I found in code from back then:

    private def fetchDevices = (for (device <- devices.find()) yield device.getAs[String]("deviceId").get).toList
Super concise but at the same time the code doesn't actually do much as far as I can decode now, it's a lot of type wrangling.


> It really is a balance, and it's very hard to get it "just right".

Yup, and you will find bad examples at both ends of the spectrum. And sometimes it doesn't even have anything to do with terse vs verbose, it's just bad code, not because of that one part of code but because of the context requiring it - just like in your example, I don't know scala, but if it's doing a lot of "type wrangling" then the real problem is probably further upstream from that point in the code, and no amount of terseness or verbosity is going to make it more clear - we are only discussing one dimension of the many subjective and objective dimensions that make code clear or confusing.


I disagree, what is "saved" is your time when reading/understanding... why spend time to read 10 lines and figure out that it actually does what a simple ternary operation would accomplish??

On that same vein, if a ternary operation is going to throw someone for a loop, I've got some bad news about their career in programming.


> why spend time to read 10 lines and figure out that it actually does what a simple ternary operation would accomplish??

Because when something is expressed in 10 lines explicitly, generally you can simply skim over the code to get an idea of what's going on without having to go into the details.

"Smart and clever" code, like using nested ternaries, demands you to focus and try to figure out what is going on.


> Because when something is expressed in 10 lines explicitly, generally you can simply skim over the code to get an idea of what's going on without having to go into the details.

Which is why important details get missed when reviewing verbose code.


These are both extremes and both bad. The 10 line switch statement to switch a variable is a high cognitive load for no good reason but so is a nested ternary under pretty much any circumstances, but especially with single character variables and magic numbers.

>On that same vein, if a ternary operation is going to throw someone for a loop, I've got some bad news about their career in programming.

You're right about someone having and issue if they can't understand this on it's own. What was the last application you worked in where that single line is all you had to understand to accomplish whatever task you were working on?


> What was the last application you worked in where that single line is all you had to understand to accomplish whatever task you were working on?

Exactly.

The bigger constructs expressed in functions, classes, etc, are where the attention should really be.

The smaller ideas expressed in single lines of code are, should be, insignificant in terms of cognitive load.

There are some exceptions to this, in particular very high performant code. We all know that "premature optimization...".


The bigger constructs are made out of these smaller pieces. Focusing on just the high level abstractions will end up with your application being difficult to fix when something lower level breaks and focusing on just the lower level code will cause your application to end up unscaleable or refactorable because of poor design.

It's a question of trade offs but you cant entirely ignore one or the other


This is what good function names and default code folding is for...


Because if the structure is different from what you're 'used' to, you spend ten times as much time reading (or as the original presentation coins, "decoding") that one line to understand what it means.

You don't read ten lines, you decode them. If it's ten simple lines, decoding them is easy. If it's a nested ternary, it's a lot more cognitive load - unless you're used to nested ternary expressions.

However, I'd say it's a fair assertion to make that most people aren't used to reading nested ternaries.

And I think that's what Go (and this presentation) is about; you shouldn't need to get used to a certain code style to be able to decode it. You should be able to open up a file and not be surprised. If I were to come across a nested ternary, my first reaction is a raised eyebrow and a "wtf?". The wtfs / hour is one of the metrics that Go language is based on.


>We are not using parchment anymore, there is nothing to be gained by saving a couple of lines.

Parchment, no, but do you not value vertical terseness when evaluating a function for code clarity?


I couldn't disagree more. Nested ternarys are NEVER okay. They are confusing and misleading to anyone new to your code base.


I wouldn't call that example nested; I'd call it sequentially chained, and I think that pattern is quite clear even with many conditions in the sequence.

That pattern is the expression equivalent of if..; elsif..; elsif..; elsif...

Writing it out in long form using actual if statements doesn't add much clarity, and costs in verbosity, as the OP says, macro-clarity versus micro-clarity.

I agree with the sibling to this comment, though, that a ternary is more readable with newlines and indentation:

  return (a < b ? -1
          : a > b ? 1
          : 0);
That's the style I use, except for extremely short and ternaries where the verbosity adds nothing, like (A > B ? A : B).

In my view it becomes more complex to understand when there's a ternary inside the first branch, because then it's equivalent to if...(if...else...)...else... At that point I'd consider using if statements, if there is no reason to stay with an expression.


> I agree with the sibling to this comment, though, that a ternary is more readable with newlines and indentation:

I think it's even clearer like so:

    return a < b ? -1:
           a > b ?  1:
                    0;
Conditions/guards on the left, values on the right. So I emphatically disagree with the OP that nested ternary expressions are never ok. In most sane languages with proper precedence and evaluation order, they work great when formatted as above. I say sane languages, because the nested ternary doesn't work the way you'd expect in JavaScript...


I think your version is reasonably clear, but it won't survive automatic indentation in any tool.

I use the parantheses the way I do, because editors will auto-indent the code that way. In other words if I press <tab> in Emacs, things won't move around in my example, so I know it's indented properly.

However, within that style, some people do prefer to put the operator at the end of a continuation line, and some prefer it at the beginning.


This is a natural point of disagreement. The question is what you're trying to do.

Is this the lowest level of decision making in something like a large drawing application, or some CAD or financial package, then for the love of God, take the concise approach ! If you consistently take the longer approach, may God provide mercy on your soul (and a very large monitor) when you get to vector multiplication or matrix math.

If you're writing 3 business rules in something that's important and needs reliability and therefore should not have complexity ? Then it might be better to write it out.

(in both cases, because of the potential for stupid mistakes, I'd add tests)

But there's no single solution for all situations. High complexity software ? Concise will help out more. Low complexity software ? Write it out for clarity.


> If you're writing 3 business rules in something that's important and needs reliability and therefore should not have complexity ? Then it might be better to write it out.

I'd give you multiple upvotes if I could for this.

Absolutely agree with all your points.

I'd add further, that if it's 3 important business rules, then sometimes expanding it further to have well-named functions and well-named variables is well worth doing, even if the business rules are trivial logic:

  // This rule was recommended by the accountant on 2019-05-06
  // and must be reviewed by the CFO before release.
  function receipt_needs_itemised_tax_record(amount: Money): bool {
      return amount >= 1.00;
  }

  // Show itemised tax records on receipts that need it.
  if (receipt_needs_itemised_tax_record(receipt.total_paid)) {
      ...
  }
Versus:

  // Writing this and other low-level code in 25 lines per function
  // does not make the 10kloc rendering library easier to understand.
   function transform_pixel(bg: RGB, fg: RGB, opacity): RGB {
      opacity = clamp(opacity, 0.0, 1.0);
      let blend_bg = 1.0 - opacity, blend_fg = opacity;
      return RGB { r: clamp_rgb(bg.r * blend_bg + fg.r * blend_fg),
                   g: clamp_rgb(bg.g * blend_bg + fg.g * blend_fg),
                   b: clamp_rgb(bg.b * blend_bg + fg.b * blend_fg) };
  }


These are great examples, and I totally agree with both of you. However, the code most people write is somewhere in the middle, so it is a more subjective decision.


This I think is obviously personal preference, but to me nested ternarys are pretty readable as long as they're wrapped and indented.

When they are, you basically get something that looks visually like a decision tree. This can read really nicely in situations where declarative style code fits better - for example embedding nested ternarys in JSX is quite a popular pattern for this reason.

The caveat is, you have to be 'used to' reading the ? and : symbols and instantly mapping them to if/else, but I certainly don't think getting used to this in a short time frame is beyond expectation for someone who's not already, i.e. new team members etc.


Writing for yourself is a fallacy. We can all read our own code. Write for the next guy who has to look at it and figure out what is going on.


> We can all read our own code

Six-months-ago-myself thought so as well. Turns out six-months-ago-myself is a goddamn moron.


It's also a fallacy that a ternary operator is some impossible to understand concept that will make the code totally unreadable for everyone but you. C programmers should be fine with the use of ternary operators for something like the example shown here.


Who should you be writing for? Everyone is different. What's clearer for one person might be harder to read for someone else.


totally agree, but life is full of compromise. I like to write code so that someone new to field could without too much effort follow what's going on but also so that someone better than I isn't so bored that they miss obvious things in the code (which does happen).

It's a balancing act which I have found to be one of the more fulfilling aspects of my work. Having people of different skill levels review my code has made this much easier.

One thing that I do a lot though is format code in a way that certain patterns appear for those of us that like to skim code. This generally means avoiding ternary operations except for very simple things where that pattern is easily recognized. But when to do that depends on the team(s) involved.


I completely agree with you :-)

But as I say above, I think in certain situations they can be more readable for everyone.


If someone is hired to work on your code base, presumably they know the language. They know ternarys work...

If they don't then they need to learn.

You can't say don't use specific language features because some devs can't be bothered to learn them.


I know ternary operators very well but I still have to think twice when I see them nested

“You can't say don't use specific language features because some devs can't be bothered to learn them.”

Would you say the same about C++? In my view it’s a good practice to use only a subset of a language consistently and not use all features. I have seen JavaScript code where I had to do research for half an hour before I could figure out what it really meant.


> I have seen JavaScript code where I had to do research for half an hour before I could figure out what it really meant.

My view on this has flip flopped several times during my career. I think every spec has some dusty corners that most people don’t know about, but when a situation calls for it knowing about some obscure features can make your code far more readable. Sometimes it’s better in the long run to train your team about a feature they might not know about rather than code for the lowest common denominator. This thread is a great example - I spent my first decade of programming scared of chained terneries. One day I spent a couple of hours goofing around with them, playing with different ways I could write my code and internalising their semantics. Now they seem fine. I wish I’d taken the time to do that years ago. Ten years being afraid saved me 2 hours of time learning.

One of the best programmers I worked with reads the specs of tools he uses for fun. He says specs seem daunting but you can read them in far less time than you think and there’s always some fascinating stuff in there. My HTML knowledge got way better working with him - and its funny seeing how many tools struggle with correct HTML because the authors didn’t bother actually learning it.

Some more examples: html void elements, html/body tag auto insertion, JS tagged break/continue, C/JS/etc’s comma operator.


I've been taking the approach of writing code at the highest level of trickery/abstraction that is well-supported by my automated refactoring, debugging, and static analysis tools. This usually means keeping code quite simple. I can then play around with the code much easier if I need to make any changes, and I don't have to focus on the details much because they are quite explicit.

If something is so detailed that it gets too long to be quickly readable I extract it into a separate properly-named function.

I'm definitely drawn to using all the tricks of a language and I feel like it would be a great way to show off how smart I am, but I'm not sure its worth it for sacrificing comprehension and ease of adding stuff in later. Maybe if everyone that will ever work or use that code is top-notch, that would be a dream.


>If someone is hired to work on your code base, presumably they know the language. They know ternarys work...

It's not whether some constructs are parts of the language (languages can have any crap in), it's whether some constructs are bad, confusing, and should be avoided (whether you know what they do or not).


If people are confused by a ternary statement I don't want them editing code I have to maintain.


If someone thinks people can't or shouldn't be confused by a ternary statement, then I don't want them editing code I have to maintain either...


how many branches/how much depth in a ternary statement would you consider to be too many/too much?

Is there never going to be any level of depth in a ternary statement which you will think is perhaps too great, and want to switch to some more verbose syntax?


A lisper would say that there is absolutely no limit, and that in fact all programs should be structured like that.


To be clear, a lisper would not solve the problem by switching to a more verbose syntax, but that doesn't mean no depth is too deep. Breaking a big, deeply-nested function up into smaller ones is a perfectly lispy thing to do.



And that's why lispers are at most 1% of the total number of programmers out there, 50+ years after the language was invented.

You could say that most programmers are crap, but those crap programmers seem to make the world work, so they must be doing something right :-)


> You could say that most programmers are crap

Because they are

> but those crap programmers seem to make the world work

For certains value of "work". Most software is crap too and it's getting worse not better.


Yet it makes people's live better day by day... I feel that as insiders we don't really realize that software works quite well compared to many every day things.


The problem is not knowing how ternaries work, it's about how explicit is that line of code.


Exactly; to use the phrasing used in the presentation, it's about how difficult it is to decode the statements. I'm not a C guy; a regular ternary is fine, as long as it's simple enough. A nested ternary? That gives me all kinds of red flags because I have to lean in and frown and go over it a few times to decode it.


I would make the exception that if you judiciously indent then across multiple lines, to reflect the nested structure the way the if/else equivalent would, then they're fine. But yeah, nested ternaries on one line are impossible to follow.


If you even need to indent them then why you don't write a full if else statement instead? Nested ternaries shouldn't be used at all.


If/else chains are worse because they allow problems like this:

    if (cond) {
      var1 = x;
    } else if (cond2) {
      var2 = y;
    } else {
      var1 = z;
    }
Did you notice the second block assigned to var2 instead of var1? Best case it’s a gotcha for anyone reading the code later. It might be a bug by the original author; we can’t tell at a glance if this behaviour was intentional. Ternaries remove this problem. The intent of the author is clearer, and (with proper formatting) there’s no gotchas for anyone reading the code later.


If I understand correctly, what you're really advocating isn't ternary vs if/else, but expressive conditionals vs imperative conditionals. A statement that is something vs a statement that does something. Using expressions instead of imperative code that mutates state is a staple of functional programming, and is basically always preferred where possible. In some languages you can do this:

  var1 = if cond {
    x
  } else if cond2 {
    y
  } else {
    z
  }
Which in my opinion is strictly better than using a ternary, if the language supports it.


Yes exactly. I mentioned in another comment that rust allows this, and having used it I think the best of all worlds.

My favorite part is that its easy to add extra statements into the conditional blocks if you want. Doing that with ternaries requires either using the comma operator, repeating the condition on another line or refactoring the whole expression back out into if-else chains. All of those options are bad.


True.

When in Java, I've gotten into the habit of using the final initializer to mitigate these kinds of issues, like this:

    final T var1;
    if (cond) {
      var1 = x;
    } else if (cond2) {
      var2 = y;
    } else {
      var1 = z;
    }
In this specific case, the compiler will catch the lack of initialization in the second block. It's also very helpful with a complicated/nested conditionals to guarantee that you initialize the variable through every code path.


For languages in which if/else is unfortunately only imperative, not an expression


IMO if if/else can be used as an expression, your language shouldn't have a ternary operator at all


As someone that would be new to his code base, I neither find that confusing nor am I mislead by it.

Here's a blog post I'm becoming more and more persuaded by as time goes on:

https://medium.com/javascript-scene/nested-ternaries-are-gre...


Nested ternary is fine as long as it’s written on multiple lines because then it exactly mirrors the if/else-if/else structure.

    return (
        a < b ?
        -1 :
        a > b ?
        1 :
        0)


>then it exactly mirrors the if/else-if/else structure.

But it doesn't mirror the if/else-if/else structure. Rather the mirrored semantics looks as follows:

  if(a < b)
    return -1;
  else 
    if(a > b)
      return 1;
    else
      return 0;
It's interesting that for all the claims in this thread about how obvious the ternary operator is, almost every single person got it wrong.

Is there a functional difference in this example? No. There isn't. Is there a functional difference in any example? I don't think so, but I'm not 100% sure. And the nice thing about not being clever, I don't have to worry about it.


They are confusing and misleading to anyone new to your code base.

But why should that be the main thing to be concerned about and prioritise? In what way are they "misleading"? Is it really the state of the industry where a new person cannot learn a codebase, has no time or effort, and cannot read a single line of the language they were hired to work on, and that's the person we should build everything for?

Does it work like that in any other industry?


> Does it work like that in any other industry?

Yes, it does. Imagine you were a new underwriter at an insurance company and your predecessor used the shorthand nomenclature for every detail on the accounts you're taking over, and failed to write out any of the detailed reasoning behind why they chose to approve or deny claims. How much longer would it take for you to be able to fill their shoes than if they had been just a little bit more descriptive in their documents?

> "Programs must be written for people to read, and only incidentally for machines to execute." - Harold Abelson


No, it doesn't. In most other industries, you are simply considered incompetent if you don't possess the skills or aren't willing to learn the ways of the trade. Look at aviation for example: pilots and ATC talk in jargon, acronyms, and abbreviations like it's a completely different dialect of English, anyone who complains that they can't understand would be laughed at and told to keep learning, and that's the way it should be.

Yet the software industry seems to have developed an illness of perception, that encourages the continual lowest-common-denominator dumbing-down of code and an aversion to learning. From what I've seen "collaboration" as a justification for this, and from the types of people who are proponents of it, it appears to be related to the effort of managers to attempt to turn developers into disposable and easily replaceable drones; something which everyone actually writing code should be against. In the trades, there's no lack of teamwork either, but the prevailing notion is "the least skilled/experienced learns from the most" --- only in software does there seem to be a strong counter-trend to that. Perhaps we should not encourage the degradation of our craft.

Maybe that quote should be modified slightly too: "Programs must be written for suitably qualified people to read"


> Look at aviation for example: pilots and ATC talk in jargon, acronyms, and abbreviations like it's a completely different dialect of English, anyone who complains that they can't understand would be laughed at and told to keep learning, and that's the way it should be.

Yes, but the jargon and language used is standardized; pilots don't get to put their own twist on e.g. formal communication with the tower when they come in to landing. You don't get to switch to idk, km/h when communicating your airspeed.


Yes, but the jargon and language used is standardized

...so is the ternary operator.


Did you see that it was me quoting that exact quote as something to argue against?

I don't know what shorthand nomenclature of insurance underwriting is, but if it's a standard part of insurance underwriting then I would expect a new hire to be familiar with it, or need time to become familiar with it.

If it's something separate like actual Gregg or Pitman shorthand English which the one underwriter used personally, unrelated to underwriting skills, then I wouldn't compare that to a programming language's built-in ternary operators.


> I would expect a new hire to be familiar with it, or need time to become familiar with it

Which equals more overhead than is necessary. Elegance over accessibility is always more expensive, no matter the product. In my experience, folks who are obsessed with "perfect" code over readable code introduce the most long-lasting, destructive bugs, because, at best, nobody wants to and, at worst, nobody can decipher their stuff.


I once had to maintain a chat app in which the first developer had written something like the following at some point:

if (statement = ref) { print(statement) } else { print('have you done everything right?') }

was I wrong to be confused as to what he might have intended with statement = ref? Because he was in fact using the language functionalities to express what he wanted. I just thought it looked confusing and misleading.


FWIW modern C compilers recommend double-bracing exactly for that reason e.g. in clang, by default, it triggers:

    test.c:3:17: warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
      if (statement = ref) {
          ~~~~~~~~~~^~~~~
    test.c:3:17: note: place parentheses around the assignment to silence this warning
      if (statement = ref) {
                    ^
          (              )
    test.c:3:17: note: use '==' to turn this assignment into an equality comparison
      if (statement = ref) {
                    ^
                    ==


>Is it really the state of the industry where a new person cannot learn a codebase, has no time or effort, and cannot read a single line of the language they were hired to work on, and that's the person we should build everything for?

Seasoned 10x developers, titans of the industry, and all agree on simplicity and even they frequently make mistakes on easily confusable constructs such as nested ternaries (or even BS like forgetting a break in a switch etc).

It's not some bad "state of the industry" that calls for no such constructs.

If anything, it's amateurs and newbs, more self-confident than they should be (and less humble) that call for using any old crap in a language, and think that such constructs are only problematic if developers are "not paying attention" or are "ignorant".


Yes, they all agree on simplicity. Part of the problem is that simplicity is not entirely objective.

As an extreme example, people familiar with mathematical notation have a hard time when they start using a programming language that uses '=' for assignment, because they intuitively understand it as asserting equality, which it isn't (x = x + 1 would be paradoxical). And yet we all here presumably think of assignment as something rather simple.

There are plenty of code bases where the ternary operator is used frequently. It's a more succinct (but less versatile) variant of the if/then/else expression that a number of languages have - do you find that confusing when nested?

The ternary operator is just as simple as if/then/else expressions. You may not be familiar with it, which can make it seem non-simple, but then we're just back to the observation that simplicity isn't entirely objective but subject to your background.


Or it could be that it’s subjective and there is no truly objective answer.


Well, it could be if we had more research.

From my experience, and from my reading on tons of books, code, clean code suggestions, bug reports, etc, patterns of bugs are more commonly found on such code bits, that sacrifice clarity for succinctness or "clever points" (in fact that's the very premise of TFA).

Most respected elders in development also warn against those types of code.

Lacking objective research, that's the best we can get, and I'd say better than "it works for me".


The mistakes that people make are not subjective. They can detect when they make a mistake, and must do so to correct it. When people make mistakes by using a syntactic construct, that's something can be measured, and people do measure it, which is one of the ways they develop syntax preferences. The problem is that we don't ever measure it in an unbiased and representative manner.


Yes. Go is intended for BIG codebases - millions of lines of code. You can't afford to have to squint and decypher one particular author's code style when you have to go through that amount of code.

It's much easier to go through a codebase if everyone uses the same code style and you get the least amount of surprises. This does however require you to give up your own ego, that is, you have to conform to the standard.


Because you can already read your own code. Writing for yourself is bad code. Write for the next person that has to figure it out.


I personally find this one-liner mentally difficult to parse in a glance. Yes, I can fully understand what it's actually doing, but when I glance at the code I have to focus on it too long before I am sure of what it is doing.

For better or worse, I operate mainly in the PHP world and the recently added spaceship operator is a solution to this. While I think it's a crazy operator on the one hand, on the other it's much simpler to use. https://www.php.net/manual/en/migration70.new-features.php#m...

  return a <=> b;
EDIT: fixing typos


The multi-line comparators are definitely clearer and more readable to me.

The 10(!) lines of code you referenced are actually only 8(!) lines compared to your 1(!) line because you included the function declaration and the final brace in your count. The line you proposed is 34(!!) characters wide. I wonder if your statement will continue to grow past 80(!!!) characters as your operand expressions grow in width? Will you transform it back to multi-line when that happens?

However, I'm not sure why we're exclaiming line counts at all and now, apparently, character counts.

Agreed on the semi-colons though.


Maybe you'll prefer

  return 
    (a < b) ? -1 :   // a smaller
    (a > b) ?  1 :   // a greater
               0;    // equal


What's the point? Why not just make it an if-elseif-else block and make it obvious?


Nested ternary is obvious when formatted as above, and it's dramatically more compact. Try it for a bit, you'll see how simple and clear it is.


You see the problem though, I need to try it and practice it to see how simple and clear it is. I can write code that I can easily understand. I can write terse, clever code that I can easily understand. But I want to write code so that the next person can also easily understand it. More importantly, code that's easier to understand has less chance of getting buggy when it inevitably gets rfactored or extended.

>and it's dramatically more compact

Why do I want that?


> You see the problem though, I need to try it and practice it to see how simple and clear it is.

You need to practice it to overcome your skepticism resulting from your ingrained habits that prejudice you against it, not because it's inherently unreadable. It literally takes 5 seconds to understand the idiom: conditions/guards on the left, value on the right. It's essentially a truth table.

> Why do I want that?

To add to the other poster: the more context you can fit on your screen, the less scrolling, jumping you need to do to understand a program's behaviour. Compactness that doesn't sacrifice readability speaks for itself. The code sample we're discussing is compact and very readable.


>It literally takes 5 seconds to understand the idiom

I understand how ternary operators work. It's still hard to read if you inline multiple ternary operators the way OP suggested is easy. It adds cognitive complexity, and hides bugs because your brain will fill the details on what it assumes it does, versus the subtleties of what it actually does.

In fact, it is obviously so confusing that to make it work someone suggested the introduction of white-space and multiple lines, as follows:

  return
    (a < b) ? -1 :   // a smaller
    (a > b) ?  1 :   // a greater
               0;    // equal
And they still got it subtly wrong, because the semantics of their 'fix' makes it seem like it is the equivalent of:

  if(a < b)
    return -1;
  else if(a > b)
    return 1;
  else
    return 0;
which isn't quite true. It's actually:

  if(a < b)
    return -1;
  else 
    if(a > b)
      return 1;
    else
      return 0;
Will this make a difference in this case? No - but there is a subtle semantic difference that you have to stop to consider when you're scanning this code.


> It's still hard to read if you inline multiple ternary operators the way OP suggested is easy.

I disagree. The ternary version is much easier to read than your if-else. If-else statements can feature compound statements and side-effects where the ternary version is simpler because it only returns a value. There are fewer corner cases to consider.

> It adds cognitive complexity, and hides bugs because your brain will fill the details on what it assumes it does, versus the subtleties of what it actually does.

It adds no complexity. It's significantly easier to understand than if-else.

> which isn't quite true. It's actually:

Right, there's literally no semantic difference between those two.

> No - but there is a subtle semantic difference that you have to stop to consider when you're scanning this code.

You really don't. There's nothing special to consider, no corner cases. It's literally condition-on-left-value-on-right.


But those two pieces of code are parsed exactly the same way in most languages--the only difference is the newline between `else` and `if`


Compactness and readability are related, in the sense that your working memory also works somewhat in terms of lines of code.

There's a natural trade-off in the sense that in order to make something more compact, you have to rely on the context to provide whatever information you remove. For example, the ternary pattern of chaining ?:'s. You have to be used to it. However, once you know it, the more compact pattern works fairly well.


With the ternary it's more obvious that you're always putting the result in the same place.


Is it obvious that the same variable is assigned in every branch, though?


Ternary isn't the same as a branch and you want your this simple comparison function to get inlined every time


> Ternary isn't the same as a branch

It should be. The C ternary is just an expression-oriented version of if/elseif/else.

Languages with a functional bend simply make if/elseif/else an expression in the first place e.g. in Rust it'd be

      return 
        if a < b { Ordering::Less }
        else if a > b { Ordering::Greater }
        else { Ordering::Equal }
Though obviously that specific version is an overly complex way of writing:

    return a.cmp(b);


Most compilers will probably reduce if-else and ternary to the same instructions. If it's a conditional value binding it might not be a branch but a conditional move instruction.


i find it really odd that of all the knocking back and forth on this thread, you are the only person to suggest that using symbolic values for 'greater than', etc, improves readability.


It's not really my suggestion though, it's just what Rust does (inherited from Haskell, interestingly enough OCaml does not do that) and I figured I'd post "proper" rust code rather than a bastardisation.


If you need to optimize to that extent, then do whatever you need to do. In the vast majority of applications it makes no difference.


This is a beautiful way to express multiple ?: expressions.


I don't agree. It occludes the nested structure.

We can regard the condition as the head of a clause, and the ? and : as heads of sub-clauses:

   A ? B
     : C
which leads to:

   return a < b ? -1
                : a > b ? 1
                        : 0;


What you're writing here is equivalent to insisting that people do:

  if (a < b) {
    return -1;
  } else {
    if (a > b) {
      return 1;
    } else {
      return 0;
    }
  }


Nope, since I haven't added any non-whitespace characters, rather it is like insisting that people do:

  if (a < b) {
    return -1;
  } else
    if (a > b) {
      return 1;
    } else {
      return 0;
    }
Unlike your perfectly formatted code there, this has a bit of a visual problem: a one-liner consequent is fully braced, whereas a multi-line alternative isn't.

Ternary operators are different. They have confusing nesting and do not support the equivalent of the "if/else ladder" pattern very well.


> It occludes the nested structure.

The nesting is irrelevant to understanding the semantics, so this objection doesn't fly. The idiom is basically just a truth table with conditions on the left and the matching value on the right. You read it left-to-right, top-to-bottom, just like all other code. The first condition on the left that matches returns the value on right-hand side.


> The nesting is irrelevant to understanding the semantics

The nesting is absolutely relevant to producing the semantics.

The following uses deceptive whitespace to suggest a nesting that is contrary to the actual nesting, interfering with understanding:

  if (foo)
    if (bar)
      xyzzy();
  else
    flop();
The ternary operator A ? B : C is the goofy invention of demented mind. In nested situations, it is mind-bendingly unreadable. It behooves us to style it in a way that reveals the abstract syntax tree structure.

In C, I would in fact recommend:

  #define if3(a, b, c) ((a) ? (c) : (c))

  return if3(a < b, -1, if3(a > b, 1, 0));
Now we can have it in one line, yet it's clear.


> The following uses deceptive whitespace to suggest a nesting that is contrary to the actual nesting, interfering with understanding

Fortunately, the ternary pattern doesn't exhibit this problem. So again, this objection doesn't fly.


still less clear


> return a < b ? -1 : a > b ? 1 : 0;

This depends on the right-to-left associativity of the ternary operator, so this definitely counts as 'clever'.


I tend to avoid the ternary operator because in my experience even people who think they know how it works are often unaware of its edge cases when it comes to silent numeric type conversion (looking at you Java).

If you don't put the same type on each return sides of your ternary, you might be in for a nasty surprise.


“If you don't put the same type on each return sides of your ternary, you might be in for a nasty surprise.”

Definitely true for JavaScript and PHP. I am always surprised what expressions can evaluate to true or false.


It's not 10 lines, it's 8. Your implementation would need the function declaration and the } at the end too.

Now consider this:

        return a > b ? 1 : a < b ? -1 : 0;
Or:

        return a == b ? 0 : a < b ? -1 : 1;
Or:

        return b < a ? 1 : a == b ? 0 : -1;

Are all these equivalent?

Figuring that out is much more difficult than their 8-line counterpart, at least for me. My eyes have to keep jumping from left to right in order to do the checks, like they would do in a piece of uglyfied javascript.

That said, context is everything: if your one-liner was the single line in a function called `comp` with `a` and `b` as parameters, I would not comment on it in a code review.

However, if this line was part of a bigger function, and if instead of `a` and `b` we had `someLongVariableName` and `someOtherVariableName` I would not approve the pull request until this "oneliner" was changed into something else (possibly a call to the `comp` function).


> Are all these equivalent?

But is that really what you're looking for? I see that idiom and I think, "Ok, this is a comparator" and I move on. If I suspect a _bug_ in the comparator, then I'll look at it more carefully, but the context is about writing code whose intent is clear, not writing code that can be scanned for bugs easily - which would be a tall to impossible order anyway and isn't made any easier by sprawling your code across 10 lines where one will do.


Well that is what I think if it's on the first case I mentioned. I see it contained inside a comp function and I move on.

If I see it "on the wild" (as part of a bigger function), however, my train of thought goes "Wait, why is this piece of code here instead of a function call called `comp`? Is this a comparator or is this something else? Ok geez time to do the javascript eye dancing thing. You know what? I'm just not going to approve this and demand that this piece of code is at least given a proper name and put somewhere. Possibly with unit tests"


>It's interesting that the comparator function is one of the examples, because that's something which shows how people often confuse verbose with clear; I think it's best written in a single line: return a < b ? -1 : a > b ? 1 : 0;

That's nowhere near clear. I frequently use a single level ternary, but this is an abomination and very easy to make a subtle error in.


also, try to step through this in a debugger.


>To offer another (subjective) point, I think the lack of semicolons or other statement delimiters does make code harder to read, as it makes it look similar to a run-on sentence

YES!! And what do you think the statement

  return a < b ? -1 : a > b ? 1 : 0;
looks like to many people? One big 'run-on sentence'. It took me a few moments to parse that line. I had a good idea what it was doing, but I had to do a double take to make sure there wasn't some subtle bug in that line.

And that's the point here. In some sense, you are writing to the lowest common denominator. You're a clever guy who can optimize their code to minimize line count and terse statements are obvious to you, but your code will be touched and looked at by developers of all levels. Developers that will have to fix your bugs, and extend your code, or refactor it - years after you write it.

The standard for collaborative programming is to write code to be understood, not to show how clever you are, or to minimize character count or to minimize line count.


FWIW, I write a decent amount of "clever" code (think C right out of K&R) but this took me a couple extra seconds to parse. Maybe it's an acquired skill?


Clear code is supported because it can be maintained easier by other people, and the original developer won't need to remember the cleverness he/she did in the first place, when he/she returns to the code six months later.

If I write clever code, I document it extensively on top of the clever code, with references if possible.

It's about the life cycle, maintainability and longevity of the code in the first place.


Presenting '{' on newlines feels like a bit of a strawman to me, I'd expect the more verbose styles to still look something like

    if (a == b) {
        return 0;
    } else {
        return a < b ? -1 : 1;
    }


Thank you for mentioning micro- vs macro-readability, I feel that too often people concentrate on the former without considering the latter.


In my experience, verbosity is a slight detriment to clarity -- concise code tends to be easier to understand, although the relationship is not very strong and there are many counterexamples.

The key to clarity and maintainability for me, though, is in the structure. If the code is structured in manageable blocks with clear description on what each block does and how blocks fit together I care little whether within each function the code is concise or verbose.

As a data point, in larger projects with good structure, I generally saw more use concise code than verbose, which may account for my original "concise is better" bias. Just my 2c.


Nitpick: I often break nested conditionals in several lines, like this:

  return a < b ? -1
      :  a > b ?  1
      :           0;
Reads like a switch statement, yet not overly verbose. I can parse it even more easily than the one liner.

Your point about "macro-readability" is spot on though. Nobody should care about the readability of an isolated piece of code. We should care about the readability of pieces of functionality and that generally requires understanding of a whole function, or even a whole class/module.


Using semantically meaningless structure is unlikely to survive being edited.

  if (a < b) return -1;
  if (a == b) return 0;
  return 1;
Is simple boring code, has the advantage of looking odd when something odd happens.


That is extremely hard to read, at least without parentheses to show where to split the expressions at a glance.


I find it interesting to see so much emphasis on small syntax nit-picking while totally forgetting the large scale question.

Although I've used a ternary operator or 'if' statement from time to time, in general I would like to model the boundrary conditions themselves and separate condition and action (reduces coupling and enhances the possibility of reporting what is going on).

In your case, the a < b statement is most likely part of the natural ordering of the type of whatever variable a and b are. So we should be much more interested in describing the natural ordering of this type, so we can say things like:

    intList.sort(IntOrdering.reverse())
to sort a list of ints in reverse order, or

    intStringList.sort(IntOrdering.compose(StringOrdering))
to sort a list of tuples of ints and strings in natural ordering.

So, creating this natural ordering needs to be done only once, over all the possible programs that can be expressed with ints. Maybe that one, single, time you'd be writing the actual expression you wrote above. But this lower-level logic will be well hidden by the abstractions used to reason at a higher level about correctness when placed orthogonally with other low-level code.

In other words, every time I write ternary expressions or long if-then-else expressions, I am either writing something low-level, or I need to abstract away.


this is one of the most informative and clear response for this topic. thank you very much!


Thanks for replying. So many words but not sure whether the message came across.


I disagree. Reading the verbose example takes less time to understand, not just because the concept is clearer, but it is also visually easier to separate lines, even if that is 10 times as long as yours. If you see that somewhere, it will take a blink of an eye to understand, but your one line solution has to be read from left to right carefully considering colons and separating parts in your head.


But there are other benefits. You need to make a compromise and there is no win-win situation. It is true that the 10 lines version is totally obvious for everyone, but I do not agree it will make easier to understand a complex piece of code. If I fill my screen with a function that makes a simple comparison, I will need an IDE and at least a couple of monitors for a function that actually makes something, I will not be able to understand the code because I will be too busy navigating it.


   return a < b ? -1 : a > b ? 1 : 0;
That's a nice approach. However the document is about Go, and Go doesn't have a ternary operator. So that probably leaves the switch solution as the clearest one.


  return -1 if a < b
  return 1 if a > b
  return 0


The fact that half the people in this subthread think that your version is less readable and half think it's more is really strange to me. If you have to traverse more lines and scroll down and switch between lines to track variables and conditions, that's unpleasant to parse. On the otherhand, excessively dense code where you have to figure out which of multiple function calls or syntax constructions to start at, then that's unreadable, too. But if you can do one thing and do that one thing in one line with predictable syntax, that's the ideal solution. I have a hard time empathizing with those who prefer verbosity over succinctness.


I think part of the problem is that it'sn't visually obvious that ? and especially : are lower precedence than < and >. I suspect that most of the people with genuine objections would feel better about:

  a<b ? -1 : a>b ? +1 : 0
  # or maybe
  a < b ?? -1 !! a > b ?? +1 !! 0  # perl6 style
  # or
  a < b then -1 else a > b then +1 else 0
not for verbosity vs succinctness but for the mnemonic that visually-smaller operators bind more tightly.


Couldn't you just break the ternary up?

  if a == b { return 0 }

  return a > b ? 1 : -1
Also it would be ~7 lines, not 10?

  if a > b {
    return 1
  } else if a < b {
    return -1
  } else {
    return 0
  }


While code of this size and scope can be unclear, it is not where the real problem lies. In general, what's not clear is some version of "what does this do" and "why is this being done" where "this" is often a function call with an arbitrary amount of complexity behind it.

When the code is mostly calls to well-documented APIs, clarity is achievable (though not guaranteed.) if it is more complicated than that, I do not know that there is any way to achieve clarity other than to structure it, internally, in that style.


Nested ?: is hard to read due to the precedence. It's better to spend a few lines and break it up:

   return a < b
          ? -1
          : a > b
            ? 1
            : 0;
If that is too much:

   return a < b
          ? -1
          : a > b ? 1 : 0;
Other possibilities:

   return a < b ? -1
                : a > b ? 1
                        : 0;

   return a < b ? -1
                : a > b ? 1 : 0;


In Smalltalk, we had return values from ifTrue:ifFalse: statements. This served the function of the ternary ( ? : ) while enabling if-then style brackets. Also, deeply nested logic and methods longer than 10 lines were regarded as "code smells" in the community, so a lot of that logic was implemented through polymorphism.

Anyone who has the experience of reading lots of highly trivial functions like those, and gotten the feeling "I've read a lot of code, and I understand what each little bit does, but what is the whole thing trying to do?" That's a symptom of excessive verbosity.

Smalltalkers sometimes solved this through executable comments. There would be some code, with a message to "debug this," and we could just highlight the code and quickly and easily walk through it in the debugger. This demonstrates one of the advantages and disadvantages of Smalltalk: Everything could be embodied in the interaction of relatively small objects, so everything tended to be modular, more easily. The disadvantage comes from the fact that the interaction has to be understood to understand the system as a whole.


Compilers are very good at doing the ternaries for you. With good ol' Javascript you get things like the Google Closure Compiler that will return yet more Javascript rather than some bytecode, this informs me that my use of ternaries is a waste of time. I might as well go for the if/then/else type of approach and refactor it when it gets ugly into some functions.

My excuse for taking this approach in all languages is accessibility, with accessibility applying to people who can't really code. Whatever the language you can hack a few if statements even if you don't know the language. There are people who are learning and you want to make it 'accessible' for them.

Let the compiler do the work and stay off the bleeding edge features. That includes spaceship operators in PHP. I also think it is worth keeping code a few versions old, so in the case of the spaceship operator, I can do if/then/else things for the compiler to do the work and have my code work on someone else's box that is still on PHP 5.4 because the main software they use doesn't work on PHP 7.3.

Or in Javascript, it is obviously best practice to use the fancy new things but if your web page dies in the console on some Apple iPad running an old version of Safari then you can end up refactoring those best practice iterators into for loops. Invariably these refactoring exercises are hastily done, so you can come a cropper.

Another thing is that if I had to explain my code to someone then the nearer to natural language the better. The example ternary would require me to stop and think for a moment, if it was in boring if statements I could explain it to a not-so-technical person in terms they could understand, even if they could not write it themselves.


The example ternary would require me to stop and think for a moment, if it was in boring if statements I could explain it to a not-so-technical person in terms they could understand, even if they could not write it themselves.

On the contrary, even a "not-so-technical person" is likely to be able to guess at the meaning of a question mark; and as a bonus, it's not specific to English either.


Depending on what syntax constructs are available, it's possible to achieve clarity without the verbosity or "line noise factor." For example, in Rust you could write that same code as:

  match a.cmp(&b) {
      Ordering::Equal => 0,
      Ordering::Greater => 1,
      Ordering::Less => -1
  }
It's easy to see what the possible outputs are and what is produced in each case. The compiler even checks it to make sure you didn't forget any situations.

The clarity comes at the cost of some verbosity, but there's only 4 lines of functional code (not counting the closing brace) here compared to the article's 10. You'd get around the same number if you added line breaks to make the ternary example more readable.


IMHO this is pretty hard to read, unless I guess you work on code written this way every day (and most of us don't). It's not that I can't understand it, but it takes unnecessary extra effort and time to process. I want to be able to scan quickly throw a code, concentrating on understanding the logic behind, not on syntax itself, and that's very hard when it's written very dense like this. For me ternary operators are great when used on simple expressions, without nesting, and always with brackets around expressions to help eyes to navigate. In your example I'd always prefer if/elseif/else structure in place of this (unless there's a spaceship operator of course).


In recent times, this verbosity has been accompanied by the (to me) insane decision in some JS prettifier plugins (opinionated even) of effectively putting one word per line. Now every if condition spans 4 lines on average. Delightful!


I always feel like the idea of "clear" vs. "clever" is missing the point.

When I've come across - or written - "clever" code in my career, it's always been about making something that normally wouldn't fit into memory - or perform slowly - viable. The examples of this are numerous and still happen plenty today on modern hardware (e.g. games, VMs, NaN tagging). There is nothing wrong with "clever" code so long as it's _clearly_ documented and abstracted away with various types, macros, functions, etc.

Then there's all other code. And what I feel the author wanted to say was that anyone coming along later shouldn't have to think twice about what a given line of code is doing.

If there's a bug, sometimes I'll be lucky enough to have a debugger, sometimes not. But, I shouldn't have to second guess a line of code to determine whether or not the bug is there. In your nested ternary expression, I would be second guessing all over the place; what's the precedence? Hell, whenever I use ternary operators I still force myself to parenthesize the conditional, because while operator precedence in C/C++ is well defined, it's not well known.

Clarity is another reason I prefer type safe languages over dynamic ones for critical code. It allows me to be a little more "expressive" with my code where the reader coming along after-the-fact can be assured that everything works out type-wise.

Having worked on code that people's lives depended on, there are times when getting it wrong isn't an option. I can only feel for the software engineers of the 737 MAX going back over gobs of code and trying to dissect what went wrong and come up with a fix. They'd be heavily scrutinizing every line of code, and I can promise that a nested ternary would be very scrutinized. But, so would a deep chain of if, elsif, elsif, ... and recursive functions (which are often a _big_ no-no in mission-critical code). Although, I don't think anyone here effectively argue that recursion is something to be considered "clever".

It's all about a code reader being able to _prove_ to themselves that the code does _exactly_ what it looks like it does and nothing more.

I'm not a Rust fanboy, but - just like static type checking - the borrow checker adds one more level of surety to the reader that another thread isn't coming along and stomping over the data. And this is without needing the whole context of the code base. This is a _big_ deal.


That’s a nested if statement which stops being clear.

  return a > b ? 1 : a < b ? -1 : 0;  
  return a < b ? -1 : a == b ? 0 : 1;
  return a == b ? 0 : a < b ? 1 : -1;
  return a > b ? 1 : a == b ? 0 : -1;
  return a != b ? a > b ? -1 : 1 : 0;  
  return a != b ? a < b ? 1 : -1 : 0;  
  
Are all equally valid ways of writing that code. You can get used to a convention that makes it seem more clear, but reading it they are all equally easy to decode.


I completely agree. At my first professional programming job, I had to follow a style guide that forced me to code the verbose way. It drove me nuts and I found another job about 6 months later.

The less lines of code, the faster you can read a code base, apply fixes and have less bugs. Obviously you can take that too far and make code so dense no one can read it. It is finding a balance between the two. Comments can go a long way towards making complex code understandable within reason.


Thank you for taking up brother Ken's righteous crusade


I agree, but would go with:

    return a < b ? -1 : a > b;
This does the right thing, but removes the nested tertiary which to me is a big win.


What language? Those are different types.


I assumed C, and both values are of type int so they match.


Personally I prefer (cond ((< a b) -1) ((> a b) 1) (t 0))

Or, if that's too many parens for you:

(mcond (< a b) -1 (> a b) 1 0)

The definition of MCOND is four lines of code and can be found here:

https://github.com/rongarret/ergolib/blob/master/core/ergoba...


Parentheses would help make the one liner simpler but your basic point seems quite accurate.

In the end, what's simpler, spending 15 seconds looking at a one liner using well known language syntax, or digging through half a screen of if states, for loops (use iterators instead), for two examples.


I always found CL's `cold` macro very readable to use instead:

```

(cond ((< a b) 1)

      ((> a b) -1)

      (t 0))
```

edit 1: formatting


I find your example to be on the clever spectrum.

If anything, it should remind you that clarity depends on the context.


(a>b)-(a<b)


Why not something as simple as `return (a - b)` ?

It’s concise and simple to understand. Instead of returning 1 or -1 it returns a positive or negative value with I think is equally effective.


> return (a - b)

That will overflow if a & b are longs.


I strongly disagree that your example is better than Dave's. I would much prefer to see Dave's switch statement in a new-to-me codebase than your ternary.


I agree. I find that overly verbose code can be just as difficult to follow as code that's too clever.


Meanwhile I'll write (a>b)-(a<b), which is what Python suggests after having removed cmp


No, what Python suggests is just returning the value you sort on, or a tuple of the values to sort on.

cmp() is deprecated because there is seldom reason to use it anymore.

E.G:

Sorting words according to their number of letter is just:

    sorted(words, key=len)
You don't return 1, -1 or 0.

If you want to rank participants of a game in a dictionary of scores using names as keys and points as values, you would to:

    sorted(scores.items(), key=lambda score: score[1])
Again you don't return 1, -1 or 0, just the value that is significant. It makes it very, very simple to reason about.


Official docs: https://docs.python.org/3.0/whatsnew/3.0.html#ordering-compa...

> If you really need the cmp() functionality, you could use the expression (a > b) - (a < b) as the equivalent for cmp(a, b)


αν


I mainly use Python but that would still be unclear to me.

I think the ternary operator is the sweet spot between too verbose and too confusing.


That works nicely too, for those languages with "1-0" booleans. For languages without them (Java, C# come to mind), the ternary is the next-best way.


Your single line is terrible code.


Is your code really clear though? What are a and b? What types are they? What if they, or one of them aren’t that type? Why would you call the function? When would you call it?

I mean, I get that your example is directly related to the point you are making in verbosity, I don’t even necessarily disagree with you, but your example isn’t very clear to me.


One could make analogues to prose writing. The author's is in the style of xkcd's "Thing Explainer" where they deliberately only use 1000 words: https://xkcd.com/1133/

Yours is in the style of James Joyce where it's very dense...

the easiest to read is probably somewhere in between


> return a < b ? -1 : a > b ? 1 : 0;

Is this a joke? This comes off like an order of operations test.


> To be clear, I don’t mean to dismiss the work of a lone programmer toiling on programs without anyone to pair with or learn from. I’ve been that person many times in my career as a programmer, it’s not fun.

I'd say it's a lot of fun.


Definitely fun, but also slightly nerve-racking. A new project, script, or proof of concept left entirely up to you. The canvas is completely blank. Your initial code will probably be the seed for whatever this is for months or years to come. Remember all that trash you talked about the poor decisions everyone who came before you made? It's your turn. Don't screw it up.

Tangentially related for some of the younger devs: The predecessors on your project probably weren't idiots. It's possible, but they were likely working with a different set of requirements under a different scope. Even the code that it morphed into over time was probably a good enough decision for the constraints at the time.


It completely depends on the quality of your collaborators. Are they good developers, do they have somewhat compatible views, and are they reasonably friendly? I prefer working in a highly capable group.


Yeah, working with other good devs is fun too, but working alone is a special kind of fun. :-)


If you have collaborators you are not in that situation.


I’d say it’s in fact where the real fun happens


Working on personal projects, not having to conform to other people’s requirements, trying a bunch of cool hacks and getting to see how things really work…that’s the most fun part of programming!


The difference is the 'personal project' bit. If you have to do something, because it's for work and there's a deadline, then the fun hacks produce a general feeling of unease, uncertainty, and of not doing ones job properly.

At work myself i work mostly in isolation on codebases that have been in maintenance mode for years, where the original authors have typically either left the company or moved to another dept, and where there is almost no documentation. Everything is hacks and exploratory work, but it sure ain't fun. Adding relatively uninteresting small changes can take days or weeks.

Working on a broad and deep project as half of a pair is my preferred working mode so far in my career.


Side projects are probably the most fun that can be had with programming, not just for the freedom of requirements but also because you can build something you truly enjoy building.

I'm lucky to be writing fun code at work - most of the time -, but the side projects keep me sane for many reasons. Not having to deal with 'bad' code that I didn't write, is also a nice benefit. All the bad code in my personal projects is mine, so no one else to blame :D

EDIT: typos and delete possibly negative statement


Bs, nobody will read your code. 1 per 10 000 maybe. And even that 1 will be polite enough. Don't spread this bs - people shouldn't be afraid of shaming when they have some code to publish.


That's why I put the `/s` there. I wasn't being serious, and hope that most people got that. If not, my apologies. I constantly commit my toy projects and the few that got some viewers / comments were all polite indeed.

EDIT: I have deleted the original statement. After your comment it made me think that it might involuntarily scare people from sharing things in the open source world, which is not my intention.


Oddly enough I find the collaboration more interesting now. Not sure if that’s a getting older thing but I like managing the PR’s, ensuring code is of a high quality and serving the needs of the users.


I do both, and they have their own unique rewards. Writing personal projects is an exercise in self-improvement–it really doesn't matter all that much how your project turns out–while working on projects with others means you need to care about how other people and your project is improving. But in return you get to interact with people, show off your skills, receive and provide help…all useful skills in their own right.


Personal project that nobody cares about and doesn't have things like users, bosses, expectations, constraints, or deadlines, sure.

But the author said "in my career," which I took to mean during the course of ones paid employment.


I've never been happier working on a project than when I'm working with somebody that is a better programmer than me and willing to give advice and criticism.


I agree with the author maybe because for first four years of my software development career I was a lone developer. Maybe Dave was in similar situation.


I'm pretty sure we're not supposed to admit that, but I agree. Of course, if somebody asks me in person, I agree that team work makes the dream work and there is no I in team and we're all better together and the whole is greater than the sum of its parts. If there's a steady paycheck in it, I'll believe anything you tell me.


I agree with this sentiment, however, it does not continue to remain fun if that's the only development experience one has


Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: