Hacker News new | past | comments | ask | show | jobs | submit login

I once asked my college professor about operator precedence in C. He had been writing C code in industry for decades.

"I have no idea" he told me.

He said many programmers try to make their code as short and pretty as possible, as if there was some kind of character limit. Instead of falling into that trap, he just used parentheses. And the order of operations never was an issue.




I agree with your prof, and have a peeve with linters that complain about unnecessary parens. They may be unnecessary, but they can sure be helpful for those of us who don't have the precedence tables fully embedded in our neural code scanners.


Even when I have no issues with the precedence order, I often add parens anyway because it helps with mental grouping at a glance, instead of having to scan and mentally parse the line.


A wise practice.

As a young egotist I would often omit parens in complicated C expressions. I did this intentionally and in a very self-satisfied way - writing multi-line conditionals and lining them up neatly without parens with a metaphorical flourish of my pen.

Then one day, chasing a hard-to-find bug, I realised it had happened because I'd mixed up the precedence of && and || in a long conditional. I was an idiot. Since then I've made a point of reminding myself that I know nothing and that there's nothing to be gained from pretending I do, and putting parens in everywhere.

Sometimes, even now, I get those grandiose moments when I think the code I'm writing cannot possibly go wrong. Those are the moments that call for a bit of fresh air and an extra unit test or two.


That's a great observation. Passing on that expertise is what wizened veterans can do to move our capabilities along and not let learning go to waste. I've heard that a big reason for sometimes struggling software engineering quality in countries and companies is places where the only way to grow is to become a manager - you need people to stay in technical paths so they can pass on that learned knowledge.


I heard an aphorism once I've tried to live by since:

It is twice as hard to debug code as it is to write it. If you write code as clever as you can make it, you will never be able to debug it.


Ironically enough, given the article, this is attributed to Brian Kernighan.


I'll try to remember the attribution as well as the aphorism in future, thanks :-)



Exactly this. I add parenthesis not for computer but for myself. Mostly because it is easier to comprehend. I think the reason for this is that I learned precedence order something like 25 years ago in fifth grade maths and still do it to this day.


If I were writing a linter I'd do the exact opposite: complain about a lack of parens if it might be confusing. I don't know if a / b * c is (a/b) * c or a / (b*c). Don't tell me, no matter how many times I'm told I'll forget 5 minutes later.


> If I were writing a linter I'd do the exact opposite: complain about a lack of parens if it might be confusing.

Last I checked this behavior was still on the roadmap for Zig. It would be a compile error to not clarify such expressions with parenthesis.


Exactly. Linters often complain about the amount of whitespace as if readability was important, but then complain about "too much" readability of the operator precedence. Except one can cause a programming error while the other can't.


Also they help if you're working in more than one language any given year, as there are differences in precedence rules between programming languages.


Scheme and Common Lisp have significant differences, but their precedence rules are identical.


Is there? I've never noticed precedence differences, and I would find that crazy!


This article describes one: Swift/Go/Ruby/Python differ from C++/Java/JS/C#/PHP on & vs && vs ==.


PHP famously has the wrong associativity for the ternary operator.


Gofmt will add and remove spaces to imply order of operations.


I have a peeve with linters in general. The useEffect dependencies lint rule for React is one the worst I’ve seen - “fixing” it changes the behaviour of your program. They are horribly opinionated and often leave you with less readable code. Bah


“Instead of falling into that trap, he just used parentheses.”

That’s my conclusion too. Especially when I deal with several languages at the same time I don’t want to spend time on thinking about these details. Makes my code a little more verbose but I think it adds clarity.


But where do you stop?

Some languages may have boolean types, where others evaluate non-boolean types as true/false. So do you say "if myflag" or "if myflag=true" to make sure it's valid?

And some languages don't have short circuit operators, which I find annoying, but have learned to work around. Should I then write C or whatever that way, when I do have short circuit and/or?


I try to be reasonable. There is probably no hard rule (a lot of people seem to want that) but you have to see where things are causing problems and then addressing them.


> But where do you stop?

The moment you catch yourself adding parentheses to other people's code to be able to understand it, and then having to "git checkout --patch" to flip it back the way it was.


If (myFlag===true) ...


How often I wanted to apply the same to contracts and would wish to see it in legal texts as well. Parenthesis (and lists) would make legalese so much more readable and remove ambiguities. Still wondering why they do not use these tools.


I'm completely ignorant in the legal texts/contracts area, but they don't use parenthesis and lists? That seems hard to believe - or is it that they just don't use it to indicate precedence and order?


Canons of legal construction exist because of ambiguity in human language.

Here are a few that illustrate common imprecision in language.[0]

Conjunctive/Disjunctive Canon. And joins a conjunctive list, or a disjunctive list—but with negatives, plurals, and various specific wordings there are nuances.

Last-Antecedent Canon. A pronoun, relative pronoun, or demonstrative adjective generally refers to the nearest reasonable antecedent.

Series-Qualifier Canon. When there is a straightforward, parallel construction that involves all nouns or verbs in a series, a prepositive or postpositive modifier normally applies to the entire series.

Nearest-Reasonable-Referent Canon. When the syntax involves something other than a parallel series of nouns or verbs, a prepositive or postpositive modifier normally applies only to the nearest reasonable referent.

Proviso Canon. A proviso conditions the principal matter that it qualifies—almost always the matter immediately preceding.

General/Specific Canon. If there is a conflict between a general provision and a specific provision, the specific provision prevails (generalia specialibus non derogant).

——-

[0] https://www.law.uh.edu/faculty/adjunct/dstevenson/2018Spring...


How would one parse "if A and B or C"? You'd want to add parentheses "(A and B) or C"; or "A and (B or C)". For a simple case, a comma might suffice, but more conditions can get difficult to express unambiguously in plain language.


As a programmer that used to work with lawyers, they don't seem to get the idea of precedence/parentheses in the context of boolean logic.


They do to some degree. But it could be a lot better.


shush it makes work for other Lawyers


Lawyers depend on the ambiguities.


> "I have no idea" he told me.

Indeed, this is the pattern most industry programmers follow: never rely on operator precedence and always uses parentheses to disambiguate where things aren't glaringly obvious.

That makes your code both more robust and maintainable.


Even better, use more interim variables.

You'll thank me when you're printf debugging.


Also when using a normal debugger and setting breakpoints on those extra lines :-)


and thank you again when reading stack traces

it's never fun to track down a bug to a line that is doing 8 things.


You should write your code with consideration for the next person tasked with maintaining it. If for some reason you can't manage that (?!?), I suggest coding with consideration for _yourself_, six months from now, still slightly drunk at 2am when the page comes through...


I do the same. Except I use Common Lisp.

Joke aside, I use parentheses liberally. Even if I know operator precedence it saves me from errors when I edit the code and another person reading it might not know precedence rules perfectly.


I once knew the C precedence chart pretty well. Now, about all I remember is that times and divide are higher than plus and minus.


One would have assumed it was the same as Fortran


If you didn't learn it in 5th grade math, I don't want to see it in a code review.


I remember only one huristic: unary operators have higher precedence than others.


Oh yeah? What is the correct interpretation of

    *a.b
Is it

    (*a).b
Or

    *(a.b)
Your heuristic says it should be the former. Is it? What about this one:

    *a->b


The mental precedence list I have is:

* Name operators (e.g., a::b, a.b, a->b)

* a[b], a(b) expressions

* Unary suffix operators (C doesn't have these, but Rust's ? operator applies)

* Unary prefix operators

* Arithmetic operators, following normal mathematical precedence rules (i.e., a + b / c is a + (b / c), not (a + b) / c). Note that I don't have any mental model of how <<, &, |, ^ compare to each other or the normal {,/,%}; {+,-} rank.

Comparison operators

* Short-circuit operators (&&, ||)

* Ternary operator (?:)--and this one is right-associative.

* Assignment operators

This list I think is fairly objective, although C and some of its children "erroneously" place bitwise {&,|,^} below comparison operators instead of above them. The difference between suffix and prefix unary operators is somewhat debatable, but it actually does make sense if you think of array access and function call expressions as unary suffix operators instead of binary operators.


Those show why I called it a heuristic. With some code similar to yours, I would probably use parentheses unless they are frequent enough to deserve some brain cells.


I certainly always always always used parentheses in situations like these back in the day. More because it was the idiom I learned early, rather than a conscious decision to be risk averse or professional.


PEMDAS is pretty easy to remember too. Though for programming the E isn't usually relevant unless it's python.


Is it PEMDAS or PEDMAS ?


Luckily, the M and D commute.

(...Over the reals. Your mileage may vary in less exact types. The Surgeon General recommends avoiding division in production code. Regulations vary by state.)


you can still run into problems with chains like 2×3÷4×5

    (2×3)÷(4×5) = 6÷20 = 0.3
    2×(3÷4)×5 = 10×0.75 = 7.5
    ((2×3)÷4)×5 = 7.5


Either/or.

That's kind of one problem with it, it's less PEMDAS and more P, E, MD, AS. Multiplication and division don't have precedence over each other and neither does addition and subtraction. Both of those go from left to right.


It's BEDMAS. B for brackets (which is what parentheses are called in Canada; see also square brackets, curly brackets, and angle brackets).


> "I have no idea" he told me.

So, in other words, he didn't properly understand quite a bit of other people's code that does not follow full parenthesization.


As I was reading the article I was thinking this isn't an issue if you just use parentheses.


Obviously yes, but that is question-begging. How does the novice programmer know that it is a good practice to use parentheses? x + y == z is correct, so it seems reasonable to conclude that x & y == z is also correct, particularly when the compiler does not complain about it.


How does the novice programmer know about order of operations? Maybe they are blissfully unaware of both features, but if someone is being taught one feature, they should be taught both. In my case, I'm self-taught and just applied what I learned in basic algebra about parens and boolean logic.


"I have no idea" he told me.

With an answer like that he would no doubt flunk the modern interviewing process:

"We had a guy come in, tons of experience, aced all of our coding tests... but when we asked him about operator precedence in C, he just shrugged and said 'I have no idea'. So we had a to let him go, for his lack of strong CS fundamentals."

Said one 25 year-old SSE to another in the follow-up.


Interviewing skills are a thing like everything else. A good candidate should jump to the opportunity to explain why she can't trust even herself to do it right (let alone other people, including her future self!). Brining (or making up plausible) examples from experience, etc.

Yes, sometimes it's silly, but there is the harsh reality of how interviewing is executed, especially by those companies who want to base the assessment mostly on the judgement of peers as you said.

If you're really senior, in most cases, you can't expect that the majority of people in the company you're applying to is going to be as senior as you. You'll need to do a lot of convincing and explaining of things that might be obvious to you even after you join, on a daily basis. As with everything, the interview can be a good place to show you can do it.


Knowing the precedence and knowing it well is necessary so that you can read code quickly and accurately. Not so that you can write code using the minimum number of parentheses.

Sometimes, even when you have the power to add parentheses to existing code and merge the commit, you still have to know what the unmodified code is doing: just so that you're sure your readability improvement is not changing the behavior, for one thing!

You can be looking up precedence tables all the time, or adding prints to test things empirically run-time.


As with everything, the interview can be a good place to show you can do it.

Until the moment where you get flushed for some dumb random thing or another. Then the rest all goes down the drain.




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

Search: