
My Favourite Diff - jwatzman
https://essays.jwatzman.org/essays/favourite-diff.html
======
kazinator
Recent GCC can diagnose the situation where a switch case falls through
without a /* fallthrough */ comment.[1]

C++17 has a comment-like language feature for demarcating desired fallthrough.

Fallthrough in case processing is often useful; it's just not such a great
default.

In the TXR Lisp tree-case[2] construct, any case which returns the : object
falls through to the next case.

This is used all over the place in the library.

\--

[2] [https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Warning-
Options...](https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Warning-
Options.html#index-Wimplicit-fallthrough)

[1] [https://www.nongnu.org/txr/txr-
manpage.html#N-03D834A5](https://www.nongnu.org/txr/txr-
manpage.html#N-03D834A5)

~~~
adrianmonk
> _not such a great default_

This is a tangent, but it reminds me of a personal policy of mine: if I'm
thinking about changing a default, I ask myself if there should be a default
at all.

The consequences of having the wrong default were serious enough that it even
came to my attention. So by changing the default, am I potentially exchanging
one set of problems for another?

Sometimes it would be better overall if the user is forced to explicitly state
which way they want it. Not always, of course, but I like to ask the question.

~~~
adrianmsmith
Right! I had a bug in a SQL statement to fetch tasks “order by priority” and
then execute them in that order. A stupid mistake, but I must have looked at
that code tens of times and I just couldn’t spot it. The bug was that that
statement found then executed the _lowest_ priority items first.

It’s not clear to me that an “order by” should have a default direction. I
don’t know why one direction is more likely to be used than the other. Without
a default, I would have been forced to think about the direction and wouldn’t
have had that bug.

~~~
Apanatshka
A similar example is in a "filtering" operation. I mostly have this in
functional programming where you pass a predicate to filter on. But what's the
perspective of the filter? Are you filtering poison from your water, or are
you sifting the water looking for gold nuggets. I prefer to use functions
remove and retain (respectively) to be more explicit.

~~~
kazinator
GNU Make has this naming problem too $(filter pat,list ...) and $(filter-out
pat,list ...).

------
letientai299
_Often my biggest contribution isn’t being the only one who is able to fix
something, but being the only one who realises something is fixable._

I feel relatable. At all the placed I've worked, people realize the problem,
sometime find the solution, but most of the time, they won't fix it, unless it
directly affect their code/project/product. As a result, technical debt keeps
bubble up and we have more and more issues.

Right now, the only thing I can do is just to keep fixing issues as I
encounter them. But that's for the code, I don't have any solution for the
"people" problem.

~~~
auxym
I have not worked in software for long (a year, a while ago) but my experience
with agile at that time was that even if you realize that you can and should
fix something, you still have do political representation to the scrum master,
team lead and product manager so that an issue (sorry, user story) is created
and gets prioritized (over features and other bugs) in a future sprint.

Going rogue and spending an afternoon fixing stuff without an approved US (to
charge time on) was not seen well.

~~~
julius_set
Unfortunately this is often the case at larger companies, unless you find a
really cool manager / PM to work under who just trusts you — note this can be
achieved over time.

I’m talking from personal experience of going rogue :)

------
xelxebar
This reminded me of Raymond Chen's blurb article on his blog, The Old New
Thing:

[https://devblogs.microsoft.com/oldnewthing/20110118-00/?p=11...](https://devblogs.microsoft.com/oldnewthing/20110118-00/?p=11733)

For whatever reason that heavily influenced me, and now I almost always submit
patches together with my bug reports. Sometimes its a whole lot more work, but
I feel like this practice pays dividends in the amount you learn as well as
the comraderie it fosters with the upstream devs.

------
MrStonedOne
One annoying thing about the war on case fallthrough at the language level is
the lack of imagination.

`continue` is a thing, compliments switch's existing `break` statement usage
and is overall a much better solution for explicitly specifying fallthrough
then returning special objects or c#'s abuse of goto.

~~~
Spivak
I think it's a pretty syntax idea but it means continue works opposite your
intuition inside switch statements compared to everywhere else.

I mean you could kinda argue that switch could conceptually mean something
like

    
    
        for matcher, block in cases:
            if matcher.matches(subject) or continued:
                eval block
    

but then continue still feels magic since it bypasses the test.

------
JadeNB
Since there's not much context here: the article, which I enjoyed, ends with
"Be the change you want to see in the world", and shows how the author
exemplified this by coding a fix to a persistent source of PHP bugs at $WORK.

~~~
thunderrabbit
And it doesn't show an actual diff.

------
aasasd
As a technical aside (I know it's not the main point, but still): with the
ubiquity of Git workflows employing pre-merge checking on the platform of
choice (e.g. Github or a CI tool), this is rather easily done via a rule in an
off-the-shelf linter. No `break` in a `case`? Can't merge it.

As a language-design aside, `switch` in general is stinky stuff. Not just with
the fall-through: it also violates the regular C-style syntax for no
particular reason, having a mini-syntax of its own instead.

But the most perverse thing I've seen done with `switch` is using it as `if`:

    
    
        switch (true) {
            case ($a == $b): ...
            case ($c == $d): ...
            case (itsFullMoonToday()): ...
            default: ...
        }

~~~
Someone
If that’s the most perverse you’ve seen, I guess you haven’t seen Duff’s
device, which mixes it with a _do…while_ loop (code copied from
[http://www.catb.org/~esr/jargon/html/D/Duffs-
device.html](http://www.catb.org/~esr/jargon/html/D/Duffs-device.html)):

    
    
       register n = (count + 7) / 8;      /* count > 0 assumed */
    
       switch (count % 8)
       {
       case 0:        do {  *to = *from++;
       case 7:              *to = *from++;
       case 6:              *to = *from++;
       case 5:              *to = *from++;
       case 4:              *to = *from++;
       case 3:              *to = *from++;
       case 2:              *to = *from++;
       case 1:              *to = *from++;
                          } while (--n > 0);
       }

~~~
tzs
I have no idea if this still works in modern C, but here's something kind of
similar I've seen used in C circa 1986:

    
    
      switch (foo)
      {
        case 1:
          ...code just for case 1...
          if (0)
          {
        case 2:
            ...code just for case 2...
          }
          ...code for both case 1 and case 2...
          break;
        case 3:
          ...
    
      }
    

used when two cases have a common tail, and you have sufficient space
constraints that you do not want to duplicate the common tail code, and you
have sufficient time constraints (and maybe space constraints on the stack)
that you don't want to put the common tail code in a subroutine and call it
from both cases.

~~~
fisherjeff
Oh my god. I’ve wondered about a way to do this but... this is just devious.

------
userbinator
On the contrary, I've heard of a story where a team decided to apply some new
tool to their codebase which warned about a missing break, inserted the break
so the warning disappeared, _" fixed" the failing tests that now resulted_,
and then upon the next release was subsequently screamed at by numerous
customers for the unwanted behaviour change.

 _but in a very high-level language like PHP, there’s really no reason for
switch to fall through at all_

I disagree. Switch fallthrough is very useful because it can reduce code
duplication, especially when the logic has a ladder-like structure. Trying to
impose increasingly draconian and such arbitrary rules is only going to lead
to a self-fulfilling-prophecy where all the intelligent developers will get
fed up and leave, and what's left are those which will continue to create tons
of bugs some other way instead.

~~~
nicbou
I don't think I would quit a company over a more stringent linter.

To me, this is the same attitude that leads to safety hazards in other
industries. "I don't need safety measures because -I- am not an idiot".

Even the smartest developers make dumb mistakes. If you can eliminate some of
those early with minimal friction, it's worth it. The earlier you catch a
mistake, the cheaper it is. Linter rules are less of a hassle than bugs in
production.

~~~
earthboundkid
The kind of person who complains about the linter is the exact reason your
company needs a linter.

------
evmar
I did a similar thing for Chrome back in the day, to have the compiler check
an `override` annotation:

[http://neugierig.org/software/chromium/notes/2011/01/clang.h...](http://neugierig.org/software/chromium/notes/2011/01/clang.html)

------
vortico
Just a reminder that in most compiled and JIT optimized languages with
switches, this

    
    
      switch (c) {
        case 4: return 30;
        case 5: return 70;
        default: return 0;
      }
    

compiles to the same instructions and therefore same performance as

    
    
      if (c == 4) {
        return 30;
      }
      else if (c == 5) {
        return 70;
      }
      else {
        return 0;
      }
    

As a side note, it's easier to see accidental fallthroughs if you write switch
statements like this.

    
    
      switch (c) {
        case 4: {
          return 30;
        } break;
        case 5: {
          return 70;
        } break;
        default: {
          return 0;
        } break;
    

But in my opinion, the `if` statement is more clear in both cases, and only
one level of indentation instead of 2.

~~~
enriquto
Are you sure? I'd guess that if the cases are an interval of consecutive
integers it may do a "jmp [c]" sometime.

~~~
KMag
You're correct. Many compilers will emit jump tables if the domain is dense
enough. Also, even if compiling to a bunch of conditional jumps, they'll
usually emit them to require O(log N) conditional jumps (equivalent to a
binary tree of nested ifs) instead of the O(N) linear arrangement suggested by
the GP.

~~~
vortico
Clang compiles both switch statements and `if` statements to the same tree
search code. E.g. [https://godbolt.org/z/7M_3PU](https://godbolt.org/z/7M_3PU)
My point is that the optimizations you're describing for switch statements are
also applied to a bunch of `if` blocks, at least in LLVM-based languages with
an LLVM version from the last several years.

~~~
KMag
Sure, I didn't mean to imply that compilers never (or commonly didn't) detect
that long if-chains could be optimized to jump tables, lookup tables, or if-
trees. I was just answering the more narrow question the GGP was asking: if
switch statements were always lowered to if-chains and never compiled to jump
tables.

~~~
vortico
Ah I see, gotcha.

------
joatmon-snoo
Google applies ErrorProne in the default javac toolchain to mitigate this for
Java builds:
[https://errorprone.info/bugpattern/FallThrough](https://errorprone.info/bugpattern/FallThrough)

------
dlbucci
Recently, my coworker basically asked if he should set a TTL on a password for
a job we were running. We said yeah, but our code would need changes to
accommodate it before it expired and we didn't have time to make them.

I joked that we'd make a JIRA ticket that we'd repeatedly deprioritize until
our code mysteriously broke in 6 months, but then my coworker actually made an
Outlook reminder for when we had a month left!

I thought that was really smart, so I guess I learned a lesson about actually
solving problems instead of just pointing them out like a smart ass.

------
CGamesPlay
What did you do in cases where the fall through was desired behavior? Did the
resulting code (replacing a fall through with a nested if or something?) look
better or worse as a result?

The typical way to handle this at companies that don't have a static-type-
checker department is to require a comment that says /* falls through */ at
the end of the switch. Why was such a simple solution inappropriate here?

~~~
mplewis
Because code reviews are more fallible than automatic linters, and OP had the
luxury of working somewhere that the noisy people didn't fight for an anti-
feature.

~~~
CGamesPlay
I wasn't talking about requiring it in code review though. The OP didn't make
an automatic linter that issued a warning when you did this implicitly, he
removed the ability to do it entirely. With a linter, you can add a comment,
like /* falls through */ to disable the lint warning. With the OP's change you
can... use a series of if-else blocks? The alternative isn't listed, which is
why I asked.

Sorry about your last company, though. Sounds like it was annoying to work
there!

~~~
jwatzman
OP here.

Explicitly-annotated fallthrough was still allowed; I codified a particular
annotation that the analysis tool understood and allowed. I removed _implicit_
fallthrough, but _explicit_ fallthrough can be quite useful sometimes.

------
l0b0
Some of my favourite diffs were related to linters:

\- Linting files different from origin/master in a pre-commit hook.

\- Linting everything in CI.

\- Making the linter rules stricter (mypy does a great job of having several
orthogonal strictness flags, so you can pick the set appropriate for your
familiarity with the tool).

\- Implementing project-specific lint checks.

------
ChrisMarshallNY
Cool story.

I use Swift. It does not have default switch fallthrough. After a couple of
versions, I learned about the fallthrough statement.

I remember being frustrated by it, at first, but, like so many Swift oddities,
it rapidly became second nature; thus, proving out that one of the goals of
Swift is to train developers to write code properly.

~~~
tcbasche
Go also has the same behaviour, with the fallthrough keyword. I find myself
using a switch so rarely anyway...

~~~
earthboundkid
Go has a fallthrough keyword, but you can also do case "a", "b": for simple
things where two cases are the exact same.

~~~
ChrisMarshallNY
Swift allows the same thing. In fact, Swift switch statements are powerful as
all git-go. You can do things like specify ranges and whatnot.

[https://littlegreenviper.com/miscellany/swiftwater/ranges-
an...](https://littlegreenviper.com/miscellany/swiftwater/ranges-and-
sequences/#switchranges)

[https://docs.swift.org/swift-
book/LanguageGuide/ControlFlow....](https://docs.swift.org/swift-
book/LanguageGuide/ControlFlow.html#ID129)

That said, I don't use them that often. They tend to take up a lot of real
estate on the screen, and introduce a fair bit of CC.

------
hkai
If the author discovered tslint/eslint he'd be surprised that this problem was
long solved in the JS world :)

~~~
muglug
I believe this happened before tslint or eslint existed.

------
unnouinceput
So many cases about this and that, pro and cons of switch fall-through, while
Pascal was King from beginning.

