Hacker News new | ask | show | jobs
by nrjdhsbsid 3436 days ago
I always work with a few developers that complain about my long variable names and aversion to certain shortcuts like ternary operators.

They don't understand that unclear code is probably the number one cause of technical debt. Nobody wants to waste time trying to understand it so they start to attach workarounds and it just keeps getting worse.

Some of their code is so "clever" that I've refactored the line with 7 method calls just to understand what the hell is going on.

Lambdas and fluent syntax make me quiver with fear. In the wrong hands they let you do unspeakable things

4 comments

> aversion to certain shortcuts like ternary operators.

> They don't understand that unclear code is probably the number one cause of technical debt.

At the same time, verbosity can have an obfuscation quality all of its own. For simple assignment, I find a ternary operator very clear and concise, and much preferable to a 5-9 line (depending on style) if/else for a simple assignment. It also might keep you from using the single statement version of if/else if your language supports it, and that's probably justification in itself given how many problems that's caused in the past.

Specifically, I think:

    usefulMetric = wantComplexCalc ? complexCalc(foo)
                                   : simpleCalc(foo);
is preferable to:

    if ( wantComplexCalc ) {
        usefulMetric = complexCalc(foo)
    } else {
        usefulMetric = simpleCalc(foo)
    }
even if only because it doesn't obscure intent with what is essentially boilerplate.
OP is about Scala, where you write directly what you mean, without special operators:

    val usefulMetric = if (wantComplexCalc) {
      complexCalc(foo)
    } else {
      simpleCalc(foo)
    }
Well, that's only "directly what you mean, without special operators" if you come from a C style procedural background and have already internalized all the special operators you've included there, such as parenthesis and braces. Sure, that's most people, but that doesn't mean they aren't operators.
I'd rather have Python's way of doing it, which plays with the order for the sake of readability:

    useful_metric = complex_calc(foo) if want_complex_calc else simple_calc(foo)
Ruby has something similar, and I can't stand it. I think the conditional is the most important item in the phrase, and it's shoved off to the right. If you lead with the conditional, it becomes immediately apparent that the assignment is predicated on the result of a branch.
Ruby (and Python) likely get that from Perl, which has post conditionals, but with specific qualities to prevent them from too much abuse, and which also prevents them from being used in the way presented here (which is why I didn't trot them out earlier, as much as I was tempted by the "you write what you mean" line). The limitations are that there is no else branch, and it only applies to a single statement, so you can't have a block executed with a post conditional. It leads to usage like so:

    die "Invalid param: please enter a positive number" unless $param1 > 0;
    
    $param2 = 0 unless defined $param2;

    return undef if $param1 and not $param2;
    
    my $foo = 1 if $bar; # This unfortunately creates a closure around $foo and is a big source of bugs.
As much flak as Perl gets, quite a lot of thought went into making it flow similar to how people think and talk (which is no surprise if you know Larry Wall is a linguist by training). There were some missteps, but it was very early in this area, so that's expected.
Perl hung onto too many of its warts for too long to stand a chance of competing with Ruby and Python. Only recently has Perl5 introduced real function parameters instead of unrolling @. Flattened lists are another one but the worst is having to specify "use 5.020;" if I'm using Perl 5.20. They've even carried this "tradition" into Perl6 where you have to specify "use v6;" at the top of EVERY damned script. That's progress? Prefixing every variable with "my" is another one which found its way into Perl6. Why can't an advanced language have default lexical scope?
Agreed, plus obviously your "if" statement doesn't do the assignment to usefulMetric. One more way the ternary wins (along with functional languages that use "if"s as expressions).
a "final" declaration (in Java) would have pointed that out through a compilation error ;)
D'oh! Fixed. Thanks. :)
Unfortunately, ternary operators eventually end up like this due to refactoring blindness:

  usefulMetric = wantComplexCalc ? 
                   (complexity > 40 ?
                     superComplexCalc(foo) :
                     regularComplexCalc(foo)) :
                   simpleCalc(foo);
At some point you have to rely on policy and not language constraints. I submit that no language is constrained enough to protect against refactoring stupidity while also being flexible enough to be useful to the average programmer on the average project. If not ternary if, it will be something else. So, do you throw out every alternative method to accomplish the same thing, or do you put policies in place to keep the code sane, such as "no chained ternary operators are allowed" ?
In all honesty, I prefer having rules that have no special-case 'unless' issues. It's too much effort/trouble to remember all the cases where things don't work. I'm a good engineer but a terrible compiler.

I believe part of learning a new library/framework/language is to limit yourself to a certain subset of the API offered. After working with Ruby (the language) and Javascript (the ecosystem), I feel like that's the only way to preserve your sanity and productivity. I don't need to know 4 different ways of creating a lambda in Ruby, selecting 1 that can express the other 4 is good enough.

---

In this case, the rule would be no ternary operators, since they work well unless you nest them or unless you make them long/complicated.

Other examples -

You don't need to wrap if conditions unless you have a multi-line body:

  if (myCondition)
    x = 42;
    y = 23;
Early returns simplify short circuiting logic unless your function becomes too long:

  if (myVariableAtBeginningOfFunction) {
    return true;
  }
  ...
  // 2 screens later
  ...
  if (x == 42) {
    return false;  // why am I not getting false?!
  }
Using a variable as a conditional in javascript to test against undefined works well unless the value can be falsy:

  if (person.isStudent) {
    showSchool();
  }

  if (person.age) {
    showBirthCertificate(); // what if age is 0?
  }
> the rule would be no ternary operators

Why not "no nested ternary operators"?

> You don't need to wrap if conditions unless you have a multi-line body

Why not "keep unwrapped if conditions on a single line"?

> Early returns simplify short circuiting logic unless your function becomes too long

Why not "keep functions short"?

> Using a variable as a conditional in javascript to test against undefined works well unless the value can be falsy

Why not "only use conditionals on boolean values"?

I'm not saying your rules are right or wrong, I actually follow a couple of them myself, but your wording implies that other people are simply not following rules, or their rules have a lot of nuances and special cases, but the reality is more likely that their rules are different.

Ultimately we all make different connections and form different patterns in our head. As long as a team can agree on a code style, within a few months everyone starts developing the same cognitive patterns.

It's because it's too easy to lose some of these nuances during refactoring/development blindness. I'd go so far as to say it's inevitable.

If you come into a 3 year old codebase and during the first 2 weeks you need to add extra functionality to a 30-line function with an early return, are you going to refactor the early return? Or are you going to extend it into a 32-line function? What about the new hire after you?

Alternatively, your team has decided to embrace the "only use conditionals on boolean values" philosophy. You're working with a section of code that reads `if (myVar)`. It's been 3 hours, and you don't understand why the code's not working. Suddenly, you realize that at some point `myVar` was refactored from a non-nullable boolean to a nullable number, and someone missed changing this.

And the biggest offender yet - code that is grouped within a file into 'logical sections'. I've never seen this work out. What is a logical grouping for you is a confusing pairing for me. Or maybe it's that I can't immediately grok all 2000 lines of a file I've never seen before, and know where to place the method. This madness around code location is one of the quickest ways to code rot.

---

The perplexing thing to me is that these situations are completely preventable.

If you don't use early returns, scenario 1 won't happen.

Scenario 2 won't happen if you use real comparisons e.g. `if (person.age !== undefined)` (Similarly, `if (person.age != null)` breaks when null and undefined start meaning different things...)

And lastly, a canonical alphabetical/visibility ordering for methods in a file of any length is unambiguous. I don't care what the order is, as long as there is a canonical order.

---

I understand that other teams have their own rules. It's no trouble at all to adjust to things that are purely syntactic differences. But when the rules that are chosen hide lurking semantic pitfalls...I don't know why you'd risk shooting yourself in the foot.

A lot of my strong feelings on code style come from the book Code Complete. I highly recommend that to everyone who hasn't read it. It's filled with examples of confusing/broken code you might inherit, and teaches you how to avoid creating it yourself.

Edit: looks like we hit the HN thread depth limit. Happy to continue this over Twitter, check my profile.

That's just laziness on part of the refactorer. At that point, you need to use an outer if-else statement. Ternary operators are confusing when nested.
what about:

  usefulMetric = wantComplexCalc == false ? simpleCalc(foo)
               : complexity <= 40         ? regularComplexCalc(foo)
               : /* else */                 superComplexCacl(foo)
               ;
Doesn't look much better.
IMHO it's a wrong approach. Every programming language, just like the spoken ones, has it's common shortcuts and idioms. The fact that they're commonly accepted and used is what makes them easy to understand. Your brain learns to recognize them quickly, often much quicker then the long version. With newbies and programmers who switched from other languages problem is that their brain is just not yet trained to do that efficiently. Instead of investing some time into getting used to the peculiarities of the language that they use, they then try to avoid them as "complicated". By lowering a bar too low, and avoiding using these patterns all together, you encourage people to never train their brains to recognize them effortlessly. And by definition of common patterns, they're, well, common, and they'll keep running into them all of the time. Also keep in mind that you're probably bothering others, more skilful ones, with unnecessarily verbose code which is to them harder to quickly scan through.

I'm not saying that one should go crazy with one-liners or uncommon patterns, but things like ternary operators used with reasonably short expressions in a single line of code are totally valid and should be readable to any average dev out there.

Great comment.

Particularly

> Also keep in mind that you're probably bothering others, more skilful ones, with unnecessarily verbose code which is to them harder to quickly scan through.

I stopped contributing to one Powershell repository because author thought that ps is hard and he wanted Get-Process. I put a "i am the greates babysiter meme" in PR and that was considered very disrespectful

Particulary

> Also keep in mind that you're probably bothering others, more skilful ones, with unnecessarily verbose code which is to them harder to quickly scan through.

> I put a "i am the greates babysiter meme" in PR and that was considered very disrespectful

Not sure I can think of too many situations where it would be otherwise.

In general context yes, but if you tell your top contributor that besides doing full day job work for entire year for free (while main author also has commercial offering) he should also babysit "dumb" users (making entire job not fun) and you get the tip multiple times that such behavior will alienate him from the project, you can be sure there is a way better approach to project management. Since I left it, the PRs and issues that nobody looks at started to pile up (I kept both at almost 0) which is extremely important given that project relies on constant PRs and reports by the community.

Here is the meme:

http://content.randomenthusiasm.com/d4ZEVg4VB.jpg

Not exactly profane I would say but what do I know ...

Not profane, but condescending nonetheless and not appropriate in a professional/code review setting.
Its FOSS setting, not a professional setting. Being a jerk to people that do excelent stuff for your project for free is far from appropriate in any setting on the other hand.
>I put a "i am the greates babysiter meme" in PR and that was considered very disrespectful

Sounds like it was disrespectful too.

Your underlying assumption that everyone working on the code will be skilled is wrong in any large team. It's not like it takes a lot longer to read a 3 line if statement than a ternary operator.

Terse code isn't much faster to read, the difference between a 300 and 400 line file isn't significant.

Your attitude of "he isn't 1337 enough to understand my code" is the logic that leads to no comments and horrible to maintain code in the first place

> It's not like it takes a lot longer to read a 3 line if statement than a ternary operator.

It doesn't take a lot longer to sit down and understand how ternary operators work, either. C'mon, it's not differential equations, it's just a notation, and a fairly simple one. It might take a newbie slightly more time at first to understand the logic. We've all been there once, you stop and stare at it for 15 minutes, but after a few times of deciphering it you get used to it. It's not about being 1337 (I surely hope that it's not what's considered elite this days), it's about learning new stuff and any averagely intelligent person can do it. Honestly, would you really hire someone who is not capable to (in a reasonable amount of time) teach him/herself how to read a ternary operator? What programming would that person be capable of doing in future?

One person's clever is another person's clear and vice versa. These conversations are pointless as there is no objective truth on code clarity
This is not really as subjective as you think. Research in software engineering shows that certain structures are more prone to errors than others. We know that higher cyclomatic complexity leads to more bugs, more statements lead to more bugs, and certain usage patterns lead to more bugs.

Smart people can disagree, undoubtedly, but there's a reason why GOTO is considered to be brain cancer and pattern matching is generally considered to be great.

Sure but I'm talking about things like "break things down into lots of small functions to make things more readable!" vs "keeping code together makes it easier to read!" or "make things verbose so it is easier to read!" vs "conciseness makes code easier to read!"

On a lot of those I know what makes code easier for me to read. It's not the same as some of my coworkers. Based on some of your phrasing I suspect we'd agree on a lot of them, fwiw

When the metric used is "easier to read" it becomes far too subjective IMO, things you mentioned are similar but not quite the same

In my experience, very experienced programmers end up converging towards very similar idioms: terse expressions for common patterns, clarity when the domain is complex through verboseness if necessary, and just keeping things as simple as possible unless there's evidence that complexity will reduce technical debt in the future.

I don't really see highly competent devs doing the whole J2EE architecture astronautics anymore, nor using single-char variable names. There's a tendency to write things concisely when simple, and then moving the complexity away to some other place, stashed in its own function, when it reaches a certain mental threshold of complexity. There's a tendency to use the best features languages have to offer, maximizing simplicity through orthogonal features and repeated idioms, while discarding unnecessary cruft; one of the marks of junior devs is their desire to try to fit problems into new idioms just to test out language features or strange design patterns.

Given that human intelligence is fluid but the variance just isn't that high (after all, we all have a similar amount of working memory), common design practices emerge out of this understanding for our limits for reasoning about problems. Exceptions abound in extremely technical and complicated problems (just check out non-trivial linear algebra code or bit-flipping, low-level device drivers), but the most part it just sticks out how things are made to look simple within a finite range of tradeoffs. This has been my experience in my domain of expertise; look at most current web development frameworks and they respect common patterns even in radically different languages that are really about the essence of the request-response cycle, not made-up constructs of additional complexity or a restating of the problem.

> I don't really see highly competent devs doing the whole J2EE architecture astronautics anymore

Yes we do, because the customers and their in-house architects decide how it is going to be, not us.

> nor using single-char variable names

My pet hate.

We had a policy at my last place that any SQL joins alias the table name with a single character alias. The rule resulted in the most insanely confusing stored procedures I've ever seen. Whoever came up with that is a complete idiot
It's a judgment call where small functiosn are more readable than cohesive code.

If the 'idea' of the code isn't easily broken down into abstractions, even mentally speaking, then small functions will just obscure what's actually going on by pointing out all the implementation details that are wound together.

I think the point is that there's a wide range of styles that are readable, but the condition for readability is also related to the skills of the coder to clarify. And that range is ample but also finite.
> but there's a reason why GOTO is considered to be brain cancer and pattern matching is generally considered to be great.

Look at any large C codebase and you will see plenty of goto statements to manage resource cleanup. The problem is using goto in place of structured control flow like loops and if/else. Statements like yours make it seem like there is a conceptual problem with a jump.

Sure there are some cases in C where you want a particular control flow that the language doesn't allow, and goto is the best solution in those cases.

But all the examples of this that I've seen are still structured it's just that the language isn't able to express that structure. The most common examples are jumping out of nested loops (better solved by allowing named loops so you can use break/continue) or jumping immediately to error handling logic (better solved by exceptions, or even just simplistic try/throw/catch).

I do think there's a conceptual problem with arbitrary jumps.

That's why I'm a proponent of code ownership. We should be coding to each others interfaces instead of constantly poking around in the same shared codebase. It just leads to pointless re-writes and low quality - a tragedy of the commons.

https://www.visualstudio.com/en-us/articles/devopsmsft/code-...

Unfortunately sole code ownership increases the bus factor.
Why? The code is still there even when a person leaves.
Code is really hard to work with without the theory that surrounds it, and is alive in the people who worked on it.

Peter Naur - Programming As Theory Building http://pages.cs.wisc.edu/~remzi/Naur.pdf

Legacy shitcode is legacy shitcode. If it's owned by someone at least we can push for a sane interface, which is better than the abandoned communal messes I encounter in the real world.
I'm not sure if this is true.

Scala provides a whole new level of ability for people to write code that can be meaningless to others.

It really is quite different.

And that sort of scala code is extremely meaningful to other folks, which captures my point. I see scala code all the time that would give me an instant headache but there are people who would find that more readable. To each their own, the is to work with people who are at least somewhat aligned to your sensibilities.
Fair enough - but I'll offer this:

+ Any decent developer can read decent code in Java or whatever normal language and get along just fine.

+ Only a few people can deal with Scala - and even fewer if there's a log of specific project Scala weirdness used in a particular program.

So sure - among a narrower set of 'Scala friendly' developers, and possibly within that even narrower set of people familiar with the 'Scala weirdness' of a particular project - those people can 'get along fine'.

The problem is that this can be a pretty narrow set of people.

Scala would have to represent a pretty big advantage to propose it's general weirdness as something to bother with.

I don't think it does - hence the 'de-adoption' of various entities.

My gut tells me it's past the threshold - the 'extra power' offered Scala just isn't quite worth it's weirdness for most things, and so most devs won't learn it ... and so then it becomes less valuable from a business perspective.

It's possible we may have it peak Scala.

We'll see I guess.

That's not what you said. You said others, not most people. The most important aspect is who you surround yourself with. For instance the scala folks at Verizon basically live in the zone you're talking about and it is fine for them even though half the time it makes no sense to me
> They don't understand that unclear code is probably the number one cause of technical debt. Nobody wants to waste time trying to understand it so they start to attach workarounds and it just keeps getting worse.

True, but once get into the length of variable names in iOS and Android development, you're in a whole new territory. 38 character variables have no place in life.

And extreme's like:

outputImageProviderFromBufferWithPixelFormat:pixelsWide:pixelsHigh:baseAddress:bytesPerRow:releaseCallback:releaseContext:colorSpace:shouldColorMatch

This is 149 character.

The thing is that, shorter names do not help in this case either...