I like the idea that different readers of the same code base could opt for differing levels of explicitness when it comes to operator precedence. One thing that working on the project helped demonstrate for me is that adding parens around _every_ subexpression is _way_ too noisy. So, you need to draw the line somewhere. But for me, I prefer drawing that line on the noisier side.
I prefer the noisier side as well. When adding parens to indicate/force precedence, I will sometimes split my expressions into multiple lines, with indentation, as an aid to legibility, e.g. instead of ( ( a + b ) / ( ( c - d ) / e ) ) I might write:
(
( a + b )
/
(
( c - d )
/
e
)
)
I've found this not only quite legible, but also fairly easy to edit because you can easily match parens visually.I feel like if you just remove the spaces around the parens themselves, it's the most clear: ((a + b) / ((c - d) / e))
Nowhere I have worked would pass a code review for what you have proposed.
To answer your earlier question about object definitions, in cases where the objects are small, I do think more concise (single line) notation can be more readable. An example, though not a great one because it's data more than it's code:
items: [
{ type: "a", quantity: "12", price: 123.45 },
{ type: "b", quantity: "7", price: 456.78 },
{ type: "c", quantity: "3", price: 9.00 },
]
If the number of properties exceeds say 3, or the names of them are complex, I would lean toward the longer form.Another example: do you write { foo: bar, baz: bat, bing: bang } or:
{
foo: bar,
baz: bat,
bing: bang
}
If you use the latter method, why wouldn't you write your expressions the same way? Obviously not the ultra simple ones like a + b, but the longer ones.Incidentally, I used to write code with no spaces between parens, which is what you suggested: ((a + b) / ((c - d) / e)), but eventually found it much easier to always put spaces around parens: ( ( a + b ) / ( ( c - d ) / e ) ). I would say this is similar to the indent-with-spaces-vs-tabs argument, which will rage forever.
The thing for me is my text editor visually matches the braces, so the second option makes that harder.
One bug that appeared is bitwise-AND followed by a comparison with no parentheses grouping the bitwise-AND operation. Obviously developers thought the bitwise-AND had higher precedence than the comparison. They were wrong. They obviously didn't test the code either.
Javascript code doesn't twiddle bits as often as C/C++ code so this bug doesn't appear as often.
For text editor support of this problem, what may work is to show that an expression with higher precedence IS evaluated first before the expression using an operator lower on the operator precedence table.
There was a suggestion in the Vim mailing list years ago to use background color to show the nesting depth of a block.
One could use background color or a font attribute, say underline or font-size, to indicate the evaluation order of a non-parenthesized multi-operator expression.
HN posts only support italics for font formatting so I'll use that in my example - hmmm, looks like I can't use block formatting in addition - just imagine the code indented in a fixed-width font
====
if (x & 3 == 1) { // do something }
===
I’ve taken naming them as variables instead. Definitely more verbose but more readability.
const aIsB = a === b;
… aIsB ?? c … return a === b ?? c;
The intent was const bOrDefault = b ?? c;
return a === bOrDefault;
You posted the actual (buggy) evaluation. I agree that its better if that is what you intend.aIsB is guaranteed to not be null or undefined so the ?? is a no-op. You just did a comparison so it's either true or false.
I was just pointing out that this is a case prettier wouldn't do anything. The expression parses differently and adding or removing parentheses to any subexpression will change its meaning.
That by manually doing the transform in your example that you would realize your cody was a no-op or that the intended version was what I posted.
a=(b+c)
Because you know it's not necessary. But we're human and sometimes make mistakes when we're sure we didn't.That's also why we allow for some misunderstanding, (rather than (writing (using) (sentence trees))).
On a related note, I occasionally write expressions that both assign and test, e.g. if ( ( a = b ) == c ) and in those cases I parenthesize liberally and always write a comment that indicates yes, I intend to make an assignment in the middle of a test for equality.
while((buf = resultofstreamingoperation() != null) {
}
The ` != null ` is redundant here but you can imagine where it'd be required. if ((error = do_a_thing())) {
// some error happened, good thing we saved the error code
}
Given the standard "zero is success" paradigm, handling errors is very succinct. a = b
if ( a == c ) ...
The repetition is even worse if you replace 'a' or 'b' with a more complex expression: a[foo/bar] = b[baz/bat]
if (a[foo/bar] == c) ...
Oof indeed! Just rewrite it as: if ( ( a[foo/bar] = b[baz/bat] ) == c ) ... // Yes, assign and test!In a code review I would definitely reject this.
What I'm getting at is one person's "easy to parse" is another person's "difficult to parse", and there may be no objective answer which makes one any better than the other.
I would think to myself if I was writing one “have I made a mistake somewhere that means I have to use this?”
I don’t think it’s necessarily about “can I parse this”, it’s much more about will all the devs on the team be able to parse this.
To me, code is all about communication - to myself, but also to an audience I haven’t met with varying skill, knowledge and context levels.
I’ve been bitten too many times with code that only one or two people can work on.
I'm not sure where you get that idea. Repetition anywhere is usually a code smell.
> You write code once
And then you update it when you add new features or the requirements change or you fix bugs, etc. Having to change two symbols is more error prone than changing one. And having to parse more code is harder than parsing less code.
The assign-and-test pattern is common in several languages (e.g. C), and adding a comment that explains the logic should remove all doubt as to what is happening and why, so I see it as a win/win.
In any case, there is a trade-off between terseness and legibility, and while I usually favor more verbose code, I tend to draw the line at needless repetition. But that's my personal preference, and everybody draws that line in different places.
I'd rather parse three straightforward copies with minor differences than one chunk of dry spaghetti. And in assign & test case, splitting them makes debugging much easier.
key1 = foo/bar
key2 = baz/bat
a[key1] = b[key2]
if (a[key1] == c)
I'm sure a coder who understands the context can come up with better names than key1 and key2.You're also splitting what is intended to be essentially an atomic operation into multiple steps, which can be good if you want to analyze and tweak them in the future, but it's now no longer clear where the process begins and ends in relation to the code that comes before and after: you have to add more comments, or split the whole thing out into its own function.
I'm not saying your code is bad or wrong, just that there are downsides to any solution (including mine), and ultimately everybody has to pick whichever has the fewest negatives for their particular project.
Here's one example that the rule caught: https://github.com/captbaritone/vscode/blob/ab86e0229d6b4d0c...
I've been writing JS for over 10 years now, and I'm not sure I would have caught that in code review.
Huh, now I'm curious if my IDE would highlight this with a warning... will have to check!
You may be able to chalk it up to some “senior” reviewer who asks a junior developer to remove the parens because “they aren’t needed”, and the junior developer doesn’t know how to argue their case.
Left to right is fine. So is right to left. Just pick one and stick with it.
“But PEMDAS!” Ok, YOU can use parens.
I'll never understand why programmers don't simply put parens where they want the expression to be evaluated, rather than relying on their (sometimes incorrect) assumption about operator precedence. I want to chalk this up to hubris, but it's probably just laziness.