Hacker News new | ask | show | jobs
by mumblemumble 680 days ago
I wish code like this still felt normal to me, but over the past ~10 years it seems that many people have come to value brevity over explicitness.

I strongly prefer the explicitness, at least for important code like this. More than once in my career I've encountered situations where I couldn't figure out if the current behavior of a piece of code was intentional or accidental because it involved logic that did things like consolidating different conditions and omitting comments explaining their business context and meaning.

That's a somewhat dangerous practice IMO because it creates code that's resistant to change. Or at least resistant to being changed by anyone who isn't its author, though for most practical purposes that's a distinction without a difference. Unnecessarily creating Chesterton's fences is anti-maintainability.

6 comments

> I strongly prefer the explicitness

I have a rule for my teams: "Don't write clever code".

I try to constantly reinforce that we don't write code for ourselves, we write it for the next person.

We should be doing everything in our power to decrease their cognitive load.

I try to envision the person that comes after me (who may be me in months or years!) and imagine that they are having a Bad Day and they have to make changes to my code.

Good code is clear, and tells a story. A story that's easy to follow, and easy to drill into.

Not to knock elixir unfairly, but I think that's the basis of my mental block with that language. It seems to be designed from the ground up to violate that rule. Everything is elixir is clever. Very clever. Too clever for me.

>I try to constantly reinforce that we don't write code for ourselves, we write it for the next person.

Heck, it does't even need to be another person. Even me 10 months from now who may have forgotten some context around some code will appreciate boring code.

Can you give some examples of how elixir is too clever? It’s been a breath of explicit fresh air for me…
I know you can do these sorts of funky exercises [1] with pretty much any language, but elixir breaks my brain for some reason.

[1] https://evuez.net/posts/cursed-elixir.html

That is a good rule. If you want to write clever code, do some code golf. For everything else I heavily prefer explicitness. Smart software architecture hides the verbosity if you do not have business with a specific and most likely specialized piece of code.

Even with ugly code, you should think about refactoring if this particular piece did run successfully for several years and there are no security related issues.

There are several languages the violate this principle. Brainfuck is probably one of the most prominent. It is of course not to be taken seriously. Overall alleged "elegance" of some code parts is rather annoying if you really need to understand and adapt it.

Your Elixir feedback is strange. I find it very explicit. Can you give some examples what you find clever / implicit about it?
I think it's super subjective, and I'm sure it's just my mental block from 30 years of C style languages.

I have trouble with the equals sign being "pattern matching". There are other syntax things like that, where it seems like too much is being done in odd (to me) ways that are hard to grok.

I know a lot of people love it, and I really did try, but for whatever reason the syntax just doesn't work for me.

You are kind of backtracking here because I don't see anything about implicitness. I mean okay you can't get used to the syntax and you don't want to -- not something I'd deem a serious reason to drop a language but it's fair enough and it's obviously your right so cool.

But Elixir is anything but implicit. If anything, people periodically raise a stink about wanting some magic in there, and we the wider community just reject them.

Sure, people find different things complex.

For example, I can't make heads or tails of rxjs, and honestly have zero motivation to do so.

Other people see it and find it intuitive.

Shrug.

Agreed. Explicitness and comments are very useful in understanding the intended functionality and logic, whether or not the code actually implements that intent correctly (an in providing that intent, they can help identify bugs earlier than they would be identified otherwise).
But comments go out of date, and the compiler doesn’t check them against the implementation. To document + enforce the intended functionality, use tests.
Outdated context is miles better than no context in my experience. As long as the comment isn't intentionally misleading, it always helps a ton in piecing together what happened, even if the comment is factually wrong when it was written, because I can tell how the original author was mistaken and why it led to the current code.
Outdated comments are great, because it means you probably have a bug right there. If the comment didn't get updated, the code change probably didn't look at all the context and missed things.

Pretty sure I'm guilty of that pretty often.

Looking at someone else's code, how would you know which was out of date, the code or the comment?
Does it matter? If the comment doesn't match the code, there's a bug (in the comment or the code). Either way you need to spend time to understand the context and figure out the correct thing, not trusting either.
Looking at the commit history is a great start. Especially if your team actually empowers people to reject code reviews when the commit messages are unclear or insufficiently detailed.
git blame will show when lines in a file were last changed, and the commit that changed them
Comments (should) explain the "why" not the "what". The "why" doesn't go out of date, even if the "what" does.
Generalizations like that are theoretical, and don't always align with reality. There's nothing wrong with comments summarizing the "what", and in fact doing so is a good thing because it can describe anything from the intention of the code to the business logic. "This function merges array X with array Y, converting all the keys to lowercase because they will be used later by foo()."

The "why" can go out of date, e.g. "do X before Y because [specific thing in Y] is dependent on [specific thing in X]". If you rewrite Y to no longer be dependent on X, the comment is now out of date.

The reality is that any comment can go out of date at any time if the code it describes changes enough. But that's really no excuse for failure to maintain comments. Sure, in reality code is messy and inconsistently written, not even counting comments. Comments are an essential parts of your codebase, and while they are used exclusively by humans, that doesn't mean they are any less worthy of being updated and cultivated.

The why can also go out of date. Maybe not as frequently? I don't have a great intuition for the ratio, but it is certainly more often than never.
I dunno, the "why" for me is "why are we doing this, and doing it this way?". If that changes, but somehow the comment isn't changed, that would feel really strange. It's not just tweaking a few lines, it's rewriting the whole routine. If all the code changed but not the comment, that would have to be deliberate, and definitely picked up in code review.

Though, obviously, accidents happen, etc. But then that also happens with tests and everything else. I have definitely seen out-of-date tests in code bases, where the test is no longer relevant but still maintained.

So I actually find this helpful because if the why doesn't match the what (code), I know to look back at the history of changes and see why there is a mismatch. This is honestly a great signal that something might have gone sideways in the past while I'm trying to triage a bug or whatever. So even if the comments are out of date, they're still helpful, because I know to go look at why they're out of sync.
tests -> verify intended functionality implementation (the how is right).

comments -> why intended functionality was implemented that specific way (marketing wanted X because of Y, so we had to do it like Z with a bit of A).

> But comments go out of date

Just like updating the tests when code is changed, update the comment when the code is changed.

Comments go out of date because of bad developers.

The same people who do the bare minimum for tests not to explode. But won’t add a new test case for the new branches they just introduced.

The same people who will mangle the code base introducing bizarre dependencies or copy paste the same piece of code rather than refactor.

People who fail to handle errors correctly. My favorite: by wrapping code in a if statement without an else. (else? Get a weird error without logs! Miles away from the call site!)

People who don’t validate inputs.

People who don’t give a monkey about adding context to errors making the thing impossible to debug in prod when they explode.

People who are too lazy or in incompetent to do their job properly and will always jump at the opportunity to save 5 minutes now but waste 5 hours of everybody else’s time later. Because of course these people can’t fix their own bugs!

And of course these are the people who make comments go out of date. I’ve seen them implement a thing literally the line below a TODO or FIXME comment and not delete the line.

Comments going out of date is a shit excuse for not writing comments as far as I’m concerned.

The fact that some people are incompetent should not drive engineering decisions. You should always assume a minimal level of competency.

> Comments going out of date is a shit excuse for not writing comments as far as I’m concerned.

I agree.

> Comments go out of date because of bad developers

I disagree.

Comments can also go out of date because

- developer is having a really shit time atm and their head is not in the game (bad looking after people management)

- developer is on a one day a week contract and doesn’t have the time in their contract to write big header comments explaining nuances (bad strategy)

- developer thought it looked obvious to them but it’s not obvious at review time (developer is being classic human)

- developer is getting pushed to deliver the thing now now now (bad workload management)

Most of those are the result of some decision made by someone who was not the developer (they’re all real cases). And they are the “non-code blockers” that good managers solve for us, so we can focus on getting good stuff done.

I’ve been where it seems like you are at. Blaming others for being bad didn’t help me. I had to lower my expectations of others, keeping my expectations for myself. Then get on about teaching willing people how they could be better. Might be making a few assumptions/projecting a bit there, but that’s my experience with “bad developers”.

Being any type of “leader” is lonely. Whether that’s an official role assigned to you or not. Or if it’s just a skill level thing. No one can quite match up to the expectations or understand what we see and why. But explaining it goes a long way with the right ones.

> Just like updating the tests when code is changed, update the comment when the code is changed.

Well, yeah. But the point is that tests can be run in a pipeline that can fail if the tests fail. Comments going out of date has to get caught by a human, and humans make mistakes.

> humans make mistakes

All software is built by humans in some way. All software has mistakes.

Perfection is an impossible goal.

Yeah but there's a fundamental difference between something like tests that can be checked automatically and comments, that have to be checked manually. Because of this, it can be assumed that comments will eventually go out of date.
> To document + enforce the intended functionality, use tests.

Tests go out of date

Tests increase the maintainince burden

The compiler does not ensure code is tested

Tests get duplicated

Mēh! Tests matter, and testing is very important. Good judgment is required

Just like comments.

Writing code requires professional care at every step. The compiler helps of course see, but being professional is more than writing code that compiles

It involves documents too. And tests. Not too many (tests or documents) but not too few

Undocumented code is an enormous burden to maintain (I am eyebrows deep in such a project now). It is not enough to just write code and tests, documents including inline comments, are crucial

Note in Rust if you include comments with code that will run as tests but be inline in your main code instead of having to find the relevant test function to confirm functionality.

https://doc.rust-lang.org/rustdoc/write-documentation/docume...

Go has something similar: functions marked as examples that are both run as tests and shown and run as examples in the documentation (https://go.dev/blog/examples)
Interesting. I don't know when that was implemented in Rust but clearly Go has had it for a long time, since that post is dated 2015.

While things like syntax are important, languages adding tooling like this (along with stuff like package managers) is so important to the continued evolution of the craft of software development.

The first developer I ever worked with was very explicit, and I learned some important lessons from him. He had created a system in PHP that controlled printers and it was very explicit. He didn't know what a function was, his code had no functions. It was a 5000 line script that would run on a Windows timer, top to bottom. In some places the control structures were nested 17 deep, I counted; an if-statement inside an if-statement inside a while-loop inside a for-loop inside an if-statement inside a for-loop inside an if-statement, etc, 17 deep. The cyclomatic complexity was in the thousands.

I never could understand that code. I know it wasn't brief, does that mean it was explicit?

I think the truth is brevity and explicitness are orthogonal. Let me ask you this: can code be both brief and explicit? What would that look like?

I’m not sure brevity and explicitness are totally orthogonal, explicit sort of implies spelling things out in a longer way. That doesn’t mean that something that is long is thorough, however.

I like the tradeoff the Swiftlang project talks much about: brevity vs clarity (because explicit doesn’t necessarily mean clear, either, as your example shows). I think those are more orthogonal concerns, both important to think about, for different reasons that may often compete.

Every time some code reviewer comes into my PR and says something along the lines of "you know you can just write it this way" where "this way" means obfuscating the code because "clever" and "shorter," I die a little on the inside. This is from experienced devs who should know better. At one point I wrote a comment write above a section I knew would be targeted by this kind of thinking explaining it must be written this way for clarity. Sometimes that works.
I only recommend that if the more explicit code is less idiomatic. For example, if someone appends to a list in a Python loop where a list comprehension would express the same thing plainly, I’ll suggest it. That’s it.

Otherwise, please optimize for writing stuff I can understand at 2AM when things have broken.

In one of my first positions out of undergrad we had a few devs on the team that got overly caught up in stuff like this. They were no doubt smart people, but their egos got in the way of things way too often. I'm not kidding - we'd get caught up on an if statement curly bracket for a ticket and there would be an argument for 30 minutes to an hour over whether the curly bracket should be there or not. These arguments would go into full blown dissertations, evolving into tangents of BSD coding style or Doom code. Keep in mind this was on a very well known automotive software platform with 20k bugs and counting open.

There is definitely a time and a place. Those were extremely frustrating times and a great learning experience for a young dev.

I’m not a dev, but I manage them. On one team they were spending many hours on code golf and nothing was being built. I pushed the devs to passing testing=PR accepted.

In your opinion, what problems might come from removing opinionated code reviews? Why do some reviewers gravitate toward “Here’s how I would have written it?”

Often, different approaches can be used to solve a given requirement. So debate is needed.

But it could be that your team was just divided on approach and style.

They were struggling because they were trying to work out their differences through PR comments. That will be frustrating for everyone. Somebody went and got the PR working "the other way," and now the reviewer is trying to get the author to change the PR to "their way". If it goes on long enough, your devs will head for "the highway"...

If you just mandate "test pass = pr accepted," it will unblock your team short term, but in the long term, the large system will gravitate into many tiny, fiercely defended fiefs, each with different styles. Maintenance will be slow. Debugging will be complex. Wide-reaching refactors will be prone to blockage.

Fix the coding by having the team spend time "not coding." Establish a proposal and design process where an author needs to get team buy-in before they can implement. Establish an accepted standard on coding style. Do wide reviews on critical features and highlight issues where flow needs to go from one end of the system to the other and there are weird boundaries. Do the RCA (root cause analysis) and ask the "five whys". Look for patterns of issues and address them soon rather than pushing them to the back of the backlog. Prefer many small incremental changes over few large changes.

I think the biggest problem that can come of it is lack of standards.

Each dev has their own way of writing things, their own little language. To them it is perfect. And it all works, passes tests. But, if you let them do this then your code becomes sloppy. Styles go in and out. Like reading a book where every other paragraph is written by a different person, and none of them talked to each other. Sometimes you get PascalCase, sometimes camel_case, sometimes snakeCase. Sometimes booleans are real bools, sometimes they're "Y" "N" (yes, real) or sometimes they're ints. Sometimes functions take in a lot of arguments, sometimes they take in a struct with options. Sometimes missing data is nullable, other times it's an empty string, othertimes its "N/A". And on and on.

You can enforce a lot of this through automatic linters, but not all. You require a set standard and the PR procedure can enforce the standard.

I used to have opinions but I don't care anymore. After a spelunking into all manner of code bases I just match the style and move on. Verbose or terse, comments at the top or inline or nor none at all, 100 or 5 line functions, etc.

I treat it like a card game; follow what's led.

I agree to a point, but I would separate explicit code from excessive commenting. Explicit code is good because it lets you explain to the reader what you're actually trying to do. Excessive comments (or even comments in general) is less so because compiler cannot check them for correctness, if someone simply forgets to update a comment or writes it incorrectly then the only thing to potentially catch it is a code review.
Do you consider this example as "excessive commenting"?
I haven't looked close enough at it to really know for sure. I'm not saying it's _always_ bad, comments are helpful, but the problem is that unlike code they are not required to actually match reality.

In this case, I see several `if`s with no corresponding `else` even when the `if` section does not throw/return at the end, and that's largely my point. If the "space shuttle code" requirement is not actually rigorously followed, then why go on at length about it? And if it really is that important, then the comment about it is not good enough.

Rather than a comment about it that can be ignored, they should set up a static analyzer to enforce it at build time. That way you're forced to follow the convention and not relying on code reviewers that probably don't even see that comment during their review.