Hacker News

Principle of Least Astonishment(wiki.c2.com)

98 pointsgeerlingguy posted 2 months ago50 Comments
threatofrain said 2 months ago:

Yukihiro Matsumoto, creator of Ruby:

> Everyone has an individual background. Someone may come from Python, someone else may come from Perl, and they may be surprised by different aspects of the language. Then they come up to me and say, 'I was surprised by this feature of the language, so Ruby violates the principle of least surprise.'

> Wait. Wait. The principle of least surprise is not for you only. The principle of least surprise means principle of least my surprise. And it means the principle of least surprise after you learn Ruby very well. For example, I was a C++ programmer before I started designing Ruby. I programmed in C++ exclusively for two or three years. And after two years of C++ programming, it still surprises me.

edbrown23 said 2 months ago:

For what it's worth, I don't think ruby achieves this goal either. I've been programming full time in Ruby for nearly three years and things like this [1] still surprise me. Ruby feels less surprising than C++, sure, but that's a pretty low bar to clear.

[1] https://makandracards.com/makandra/46939-ruby-a-small-summar...

loopz said 2 months ago:

Principles of least surprise (or astonishment), is one of cornerstones of Ruby. However, the language itself is dynamic, so take the end result with a pinch of salt: The language's OO syntax is beautifully simple, incredibly easy to structure into classes/mixins and have tons of useful libraries, to write code with. Not always so much for reading code, refactoring or discerning what REALLY goes on in a Rails application..

gregkerzhner said 2 months ago:

I think there is a great middle line for a language that is easy to work with, and provides enough safety to be maintainable for a long time. Environments like C++ and enterprise Java miss this middle line by being too cumbersome. However, Ruby equally misses this middle line, just in the other direction - it's too simple and dynamic leading to lack of maintainability in the long run. The best solution will be somewhere in between.

skrebbel said 2 months ago:

In my opinion, Kotlin, C#, and TypeScript all come pretty close.

jolmg said 2 months ago:

My pet-peeve in ruby:

  '//'.split('/') # => [] instead of ['', '', '']
  '/'.split('/') # => [] instead of ['', '']
  ''.split('/') # => [] instead of ['']

  '//'.split('/', -1) # => ['', '', '']
  '/'.split('/', -1) # => ['', '']
  ''.split('/', -1) # => [] instead of ['']
In comparison:

  "".split("/") # => [""] in javascript
  "".split("/") # => [""] in python
drewcoo said 2 months ago:

But if "instead of" means "I expected," doesn't that also mean that Javascript and Python don't do what you expected?

ithkuil said 2 months ago:

JS & python both have:

'//'.split('/') => [ '', '', '' ] '/'.split('/') => [ '', '' ] '',split('/') => [ '' ]

so they indeed produce what parent expected.

simplify said 2 months ago:

To be fair, blocks, procs, and methods are fairly fundamental to Ruby. After a bit of study, they stop being surprising. I'm sure there are other examples, though.

afarrell said 2 months ago:

I think the responses of "Well, it doesn't astonish me" and "principle least my surprise" point to something deep about how to write code for other humans:

As a field, we would benefit greatly from learning more cognitive science.

A core suggestion of John Osterhout's book A Philosophy of Software Design is to have well-named modules with 'thin' interfaces which accomplish a reasonably-deep set of responsibilities. But...why?

Working Memory -- Humans have limited Working Memory.

If you give a function:

- A clear name.

- Few simple parameters.

- No side-effects not in the name.

- No GOTOs or exceptions.

then a human can glance at it, put the name and parameters into working memory, and carry on.

But if you split up a namable responsibility across multiple files, then a human reading the codebase to understand data flow would need many more open editor windows or to hold much more info in working memory.

enraged_camel said 2 months ago:

This is why I am a fan of long function names, like openUpgradeDowngradeSubscriptionDialog(). Even though they add a bit of verbosity to the code, I can immediately tell what they do without having to scroll to the function definition, and there is zero astonishment when they are called from elsewhere. To a lesser extent, same with variable names: speaking for myself (and perhaps only myself), I like long variable names -- as the saying goes, code is read many more times than it is written, so saving a few keystrokes just to shorten a variable name tends to carry a high cost that is paid down the line.

jasonpeacock said 2 months ago:

You're describing self-documenting code. Well written code shouldn't require any "what" comments, only "why" comments.

Too often you see code with a generically-named function/variable, and a comment next to it describing what it does. Why not just name the function/variable correctly?!

There's a strong cargo-cult idea that "real" programmers use short/abbreviated names, and yet there's absolutely no value to using "idx" over "index", or a more appropriately named "last_name_index".

jbay808 said 2 months ago:

Naming is always hard, but sometimes the "what" requires a full paragraph, and even very long variable names shouldn't exceed 80 characters all on their own.

torque_aux_motor_braking_only_safety_threshold_nm is a good variable name, but I wouldn't want to let them get much longer than that.

Ididntdothis said 2 months ago:

Totally agree. At least don’t use generic names like “Config”. Call it “”DatabaseConfig” that has some meaning.

jameson said 2 months ago:

In JavaScript, "0 < -1 < 2" evaluates to true.

The coercion rules were a big surprise to me when I first started to work in JS. Sometimes codes were obvious but the underlying interpretation is a surprise call.

skrebbel said 2 months ago:

0 < -1 < 2

To be fair, is there any mainstream language other than Python where that expression makes sense at all?

And even Python had to make explicit, syntactical support for it, and IMO it's a pretty non-obvious language hack. I mean, there's an invisible AND in there, that's pretty astonishing to me at least :-)

xeeeeeeeeeeenu said 2 months ago:
ghettoimp said 2 months ago:

Works beautifully in Common Lisp. Of course, it is not mainstream.

? (< 0 1 2)


? (< 0 -1 2)


jdminhbg said 2 months ago:

In Clojure:

   user=> (< 0 1 2)

   user=> (< 0 -1 2)
said 2 months ago:
benatkin said 2 months ago:

It's a big ask, to have a binary operator and ternary operator that use the same symbol.

Doxin said 2 months ago:

It's honestly some fairly trivial AST-twiddling to support that feature. the compiler can simply detect cases where the left-hand-side of a comparison is /also/ a comparison and change the AST to make it two comparisons combined with an AND statement.

That said I find comparison chaining rather confusing and unexpected so I make a point of avoiding its use. Typing it out as two comparisons hardly saves any effort in any case.

benatkin said 2 months ago:

I know, but it isn't a very useful feature, so why bother?

geerlingguy said 2 months ago:

Reminds me of https://www.destroyallsoftware.com/talks/wat

Javascript is convenient for many use cases, but it definitely violates POLA on many levels, in many situations!

said 2 months ago:
saagarjha said 2 months ago:

And it does in C as well!

r00fus said 2 months ago:

So does this mean that JS does satisfy the principle of least astonishment for an experienced c-coder (something important when it was developed in the early 2000s)?

In what language (other than a RPN based language like Lisp) would this expect to return something meaningful?

saagarjha said 2 months ago:

Yes, you could say that. It preserves the order of operations and “reverse-truthiness” (i.e. boolean-to-integer) conversions from C.

dingo_bat said 2 months ago:

> So does this mean that JS does satisfy the principle of least astonishment for an experienced c-coder

No, this means don't use weird ambiguous syntax even if it is legal in the language.

projektfu said 2 months ago:

There is one in Excel VBA (I assume the Behavior is still the same) where IIRC “Sort” called from code leaves its criteria in the dialog box and if those parameters are omitted on a second sort, the previous ones are used unless cleared. This can make previously working code stop working when someone works on an unrelated part of the app.

reallydontask said 2 months ago:

I once had an argument about usage of exceptions for flow control and I mentioned this principle.

The developer replied, mostly tongue in cheek to be fair:

Well, it doesn't astonish me

ArnoVW said 2 months ago:
reallydontask said 2 months ago:

That's one of the arguments I used. We used C# and he actually did some noddy perf tests the conclusion of which where:

It's slower but only adds 0.01 s as compared to the a version not using exceptions. This was about 3 orders of magnitude slower (probably more under load would be my guess)

+1 for all the extra work to prove to himself that he had taken the right approach.

-10 For refusing to listen, ignoring data and cherry picking articles on C about exception handling (Of course they're not going to use exceptions, they don't exist)

Doxin said 2 months ago:

That seems to me to be an implementation detail though.

There's no real reason you can't have a compiler that converts exception-throwing-code into error-code-returning-code. That'd basically cost you the performance of a couple if-statements per function call return, which doesn't seem too excessive to me.

Of course most languages don't implement exceptions this way and I'm sure there's a good reason for it but you can still choose -- as a language implementor -- to have cheap exceptions.

fallingfrog said 2 months ago:

I think the time I’ve been most astonished recently by an api was asp.net. They map http parameters onto function parameters by name, using reflection. One of the first things you’ll read in any introductory programming textbook is that function parameter names don’t matter; only the type and order matters. When the light went off and I figured out what they had done, I swore out loud at my desk..

einpoklum said 2 months ago:

I often use this principle when explaining my code review objections: "I am slightly astonished that this code does X." If someone asks me if it's really astonishing, or what I mean by that, I explain the principle to them.

mason55 said 2 months ago:

Do you find that it just pushes the argument "up the stack"? And now instead of the discussion being about the code you're arguing about which outcome would be less astonishing? It seems like it would move the conversation from concrete to abstract and subjective. If the person replies "this is how I would expect it to work" you can't really argue with that, because maybe that is what they'd expect, even if that's not what you think a generic person would expect.

Also, in my experience, one of the major holes that mediocre devs have is that they struggle to look at things from a different perspective (either the perspective of a user or the perspective of another developer). This principle is fully based on putting yourself in someone else's shoes and thinking about what they would expect, which is exactly what these devs struggle with.

zhengyi13 said 2 months ago:

I think that's the point, and the value of the discussion (hopefully not "argument").

In my imagined best case result, you and your coworker are now having a conversation about your backgrounds and perspectives to try to understand where the mismatch is that led to your astonishment. One possible outcome there is that you establish a new, common basis for understanding, and less future astonishment thereby.

Ididntdothis said 2 months ago:

To be honest the word “astonished” comes across a little weird in a code review. I think it’s better to say “I don’t understand” or ask “why is it this way”?”

said 2 months ago:
einpoklum said 2 months ago:

> the word “astonished” comes across a little weird

... this is what gives me an opportunity to describe the principle :-)

and for those who know it - they already know what I mean.

Waterluvian said 2 months ago:

I wish languages more clearly distinguished between functions that produce values and functions that have effects (and ones that do both, but discourage that).

saagarjha said 2 months ago:

I like how this ends up on the topic of efficient encoding of an x86 instruction that zeroes the accumulator.

juped said 2 months ago:

xor reg, reg is the best because it's such an ancient idiom that processors recognize it as a zeroing idiom, enabling some processor-level optimizations.

saagarjha said 2 months ago:

Now you need to decide whether to use opcode 0x31 or 0x33…

juped said 2 months ago:

Which one weighs less?

saagarjha said 2 months ago:

0x33 I think, by a minuscule amount, since it has fewer “on” bits?

stcredzero said 2 months ago:

Coding to impress your coworkers often results in a prioritization of most astonishment.

eeZah7Ux said 2 months ago:

Same for CV-driven work: the adoption of modern "devops" tools is an endless bad surprise.

hinkley said 2 months ago:

Perhaps one of the more important consequences of egoless programming.