Hacker News new | past | comments | ask | show | jobs | submit login
Those Who Say Code Does Not Matter (acm.org)
266 points by swannodette on April 16, 2014 | hide | past | favorite | 265 comments



This is fundamentally what I call the Tower Defense[1] model, borrowed from my old manager here at LinkedIn, Rino Jose[2].

The Tower Defense model is an approach to software reliability that focuses on catching bugs at the lowest level, to avoid the inevitable combinatorial explosion in test coverage surface area. In other words, deal with problems like these at the language level, so there is NO NEED to deal with them at a higher process-level.

No one is disputing that processes, QA or Devops couldn't/shouldn't hypothetically catch these bugs before entering production. The problem of course is that they usually don't, because the lower level defenses are allowing too many bugs through, that they really shouldn't and the higher level processes become overwhelmed, fail, and allow bugs to cross the critical production threshold.

This means always giving higher priority to lower-level methods of reliability. For example,

* Language rules are more important than

* Unit tests are more important than

* Integration tests are more important than

* Code reviews are more important than

* QA is more important than

* Monitoring is more important than

* Bug reports

[1] http://en.wikipedia.org/wiki/Tower_defense

[2] https://www.linkedin.com/in/rinoj


I've found that the most reliable systems I've worked with have a defense-in-depth model, with reliability baked into the system architecture. That includes things like timeouts & retries for RPCs, replicas, graceful fallback, backup implementations, subsystem isolation, shut-off switches, etc. This is the Erlang/OTP model, and much of Google Search & cloud infrastructure is also built on similar principles.


Can't you make the argument that even in C/C++ they could have enforced different "Language rules" like forcing no warnings? When compiling with gcc you could use -Wunreachable-code. Then it is part of the process.

EDIT: with -Werror it will make this bug an error but not necessarily the class of bugs.


an unreachable code warning would have caught this particular defect, but it wouldn't have helped if the duplicated line were something other than a goto. I think the policy of always using curly braces for conditionals (even if only one line) in c like languages is a good one.


> an unreachable code warning would have caught this particular defect, but it wouldn't have helped if the duplicated line were something other than a goto.

In that case always using curly braces or using a language that requires an "end" statement after the conditionally executed code may not have helped either. Imagine the incorrectly repeated statement was "a = a + 1" or "error_mask ^= error_x" etc. Putting the erroneous line inside the conditional doesn't erase the error, it just modifies the conditions under which it executes. That's about as likely to hang you as save you.


Is there any language rule that can save you from incorrectly repeating an operation that is not idempotent?


gcc has not warned about unreachable code for years[1] now :(

clang -Wunreachable-code does, though.

[1]: http://gcc.gnu.org/ml/gcc-help/2011-05/msg00360.html


Wow, that's interesting! Thanks for that. I just saw it in a manpage, didn't realize it doesn't currently work. That's what I get for R-ingTFM :-D


Of course. I didn't want to muddie the issue, but I would consider compilation flags, transpilation (e.g., coffeescript) and linting (e.g., jshint, etc...) to reside between "Language rules" and "Unit tests."


Shouldn't code review be higher than unit tests?


Most definitely not. Human processes are always inferior to software enforcement. Code reviews are heavily dependent upon leadership, training, and quality control factors likes comprehensibility[1], mood, problem domain familiarity, etc...

[1] Ask a programmer to review 10 lines of code, he'll find 10 issues. Ask him to do 500 lines and he'll say it looks good. -- https://twitter.com/girayozil/status/306836785739210752


Unit tests aren't intrinsic or automatic. They're extra pieces of code that have to be written, debugged and maintained by human beings. They ought to be considered as little more than extremely limited, narrowly-scoped sanity checks; not definitive pass/fail gates for the code they're exercising.


Unit tests are also made by human processes :) The advantage is if they were made good that time they will give you nice regression coverage for free in the future.


Ideally your unit tests are run on each build, so you hit the tests locally, before you push your changes in for a code review.


This if you're testing spaghetti code or a monolith. In a system of low coupling and small highly cohesive components the cost of catching defects at the upper levels is not high enough to justify the cost of blindly using the tools below.


He's right, but he's disingenuous in saying that random line duplication can't cause catastrophic problems in Eiffel. This very specific bug can't happen in Eiffel, but the class of bug can (bug caused by bad merge or accidental, unnoticed line duplication).

If most code were idempotent, functional, immutable, etc -- then we'd start to get there, but usually randomly duplicating lines is going to be an issue unless it's always a syntax error.

I'd say clojure has more of a chance. (1) lots of immutable data and functional style (2) duplicating code lines is likely to result in unbalanced parens -- the unit of atomicity is the form, not a line. Many forms span lines in real code, and many lines contain partial forms (because of nesting).

Still there is plenty of clojure code that is line oriented (e.g. data declarations)


Clojure isn't particularly well suited to avoiding problems like this. I've written a lot of Clojure for work and for open source.

We need pure, typed FP langs like Haskell/Agda/Idris.

To boot, I'm having a much more enjoyable and relaxing time in a Haskell REPL than I was in my Clojure REPL.

Somebody I follow on Twitter just said something apropos:

"girl are you Clojure because when I'm with you I have a misplaced sense of [optimism] about my abilities until I enter the real world"


I don't think clojure (or lisp) was designed to avoid line-duplication errors. It's mostly an accident of it being not very line oriented.

I just randomly picked a function from core to illustrate this, but a lot of clojure code looks similar

     (defn filter
       "Returns a lazy sequence of the items in coll for which
       (pred item) returns true. pred must be free of side-effects."
       {:added "1.0"
        :static true}
       ([pred coll]
        (lazy-seq
         (when-let [s (seq coll)]
           (if (chunked-seq? s)
             (let [c (chunk-first s)
                   size (count c)
                   b (chunk-buffer size)]
               (dotimes [i size]
                   (when (pred (.nth c i))
                     (chunk-append b (.nth c i))))
               (chunk-cons (chunk b) (filter pred (chunk-rest s))))
             (let [f (first s) r (rest s)]
               (if (pred f)
                 (cons f (filter pred r))
                 (filter pred r))))))))
There are very few lines of this function that can be duplicated without causing a syntax error because parens will be unbalanced.

I see two:

In a let with more than two bindings, you could repeat the middle ones. In clojure, this is very likely to be idempotent. In this code, it's the

        size (count c)
In any function call with more than two arguments, if the middle ones are put on their own line, they could be repeated, like this in the final 'if'

        (cons f (filter pred r))
In many cases, you will fail the arity check (for example in this case). If not, the function should fail spectacularly if you run it.

So, I think it's accidentally less likely to have problems with bad merges and accidental edits (not designed to have that property)


Of course it's just as easy to duplicate a form as a line. When I edit Clojure code I use paredit so I'm editing the structure of the code instead of the text. Instead of accidentally "cut this line then paste twice" I could easily do "cut this form then paste it twice". Paredit will make sure I never have bad syntax but now I have the logically equivalent mistake.


Your analysis only holds if closing parentheses are all gathered on the same line as final expressions (which may not be true for some styles).


It is considered unidiomatic in every Lisp I am aware of to orphan parens. To some degree this is a stylistic concern, but it's a much more open-and-shut case than, say, C brace style.


I'm not thinking this through completely, but it seems resilient to a lot of styles.

Function calls (which is a lot of what clojure is) are an open parens to start and very likely not to have that close on the same line (because you are building a tree of subexpressions).

Wherever you put the close (bunched or one per line), if you don't put it on the line with the original open, it will be unbalanced in both spots (meaning the first line and the last line can't be duplicated without causing a syntax error).


True. But consider the form:

    (if (someExpr)
      (doTrueStuff)
    )
Then a duplication of the `doTrueStuff` line would lead to true stuff being done regardless of the truthiness of someExpr (as the third [optional] argument to `if` is the else branch).

This form is not entirely unheard of either. The overtone library for example assigns labels to its event handlers like such:

   (defn eventHandler ( ...
      stuff
   ) :: event_handler_label)


This is actually why I really like `cond` in Common Lisp (and other lisps and languages). You have to make explicit what should happen if your desired expression is true, and the only way to have an `else` clause is `(t ...)` so you have to intentionally create that last wildcard spot.


Even in Haskell you can fairly easily get non-reachable code:

  if a == a then
      ...
  else
      -- unreachable
Combine this with the unfortunate habit (which I'm also guilty of) of allowing variables to be called a' and you'll an easy-to-miss bug.


Given that types may define their own instances of Eq, the else clause might be reachable.


And the content of the first block may be unreachable all the time, depending on the implementation of Eq. But you probably have a number of issues to worry about if your implementation of Eq doesn't work for the identity case.


let nan = 0.0/0.0 in nan == nan


> but he's disingenuous in saying that random line duplication can't cause catastrophic problems in Eiffel.

I think you're referring to this part of the article:

  With such a modern language design, the Apple bug could not
  have arisen. A duplicated line is either:
    - A keyword such as end, immediately caught as a syntax
      error.
    - An actual instruction such as an assignment, whose
      duplication causes either no effect or an effect limited to
      the particular case covered by the branch, rather than
      catastrophically disrupting all cases, as in the Apple bug.
To be fair, he's not saying that it can't cause catastrophic problems in Eiffel. In fact, he's not strictly talking about Eiffel but about better constructed languages. He is saying that while it could be cotastrophic, that it wouldn't be nearly as bad as what happened here since it'd be contained to one branch of execution rather than exposed to all branches of execution.


I'm just saying that that distinction is no less catastrophic. It really depends on the line in question, but if a line has side-effects and isn't idempotent, it could be infinitely catastrophic to repeat it unless it causes a syntax error.

Even in the gotofail case, ALL cases were not affected, only the ones after the line. It would be more catastrophic higher in the function and less lower.

BTW, his code sample is NOT what the real bug was -- in the real bug it was more like this

     err = f1(cert) if (err) goto fail;
     err = f2(cert) if (err) goto fail;
     err = f3(cert) if (err) 
       goto fail;
       goto fail;
     err = f4(cert) if (err) goto fail;

     fail:
     cleanup();
     return err; // if this returns 0, the cert is good
The issue is that we jumped to fail with err set to 0 (no error), but we skipped the f4 check. The bug is only a problem if f4 would return an error (not ALL cases)


It seems to me the line of argument here boils down to "bad code can be bad". There's no language that can prevent code that is simply wrong from doing something wrong, not even the proof languages. Even "pure" code will happily generate the wrong value if you "map (+1) ." twice instead of once.

We should instead discuss the affordances the language has for correct and incorrect code. It is not that C is objectively "wrong" to permit an if statement to take an atomic statement, it is that it affords wrong behavior. And the reason I say it affords wrong behavior is no longer any theoretical argument in any direction, but direct, repeated, consistent practical experience from pretty much everybody who makes serious use of C... that is, it is the reality that trumps all theory.


OK, he oversimplified it a little bit: the error checks had side effects, which determined the return value of the enclosing routine.

His point still stands, I think: the code didn't do what was obviously intended, and should have been flagged by the main compiler/interpreter/parser, rather than a supplemental "lint" type tool.


That was a long way to go to insult C and brag about Eiffel. Ada doesn't have this problem either but nobody is jumping up and down saying how its the one true language. Back when I was looking at Phd topics (I ended up jumping into work instead) "provably correct" code was all the rage. Lots of folks at USC-ISI were looking into proving the code expressed the specification, and the resulting executable faithfully expressed the intent of the code. End to end correctness as it were.

What struck me about that work was that invariably there was some tool you ran over the specification and the code and it implemented some algorithm for doing the work. And yet if you went that far, then you should at least be willing to run something like lint(1) and had anyone at Apple run it, or made warnings fatal (always good practice), the repeated goto would never have escaped into the wild (useless code is useless, its a warn in both GCC and Clang, and always flagged by lint).

So is the challenge the language? Or the processes? I tend to favor the latter.


> So is the challenge the language? Or the processes?

Did you finish the article? This is exactly the false dichotomy the article was about.

It's the language AND the processes.


I finished the article.

It went on and on with its point but I didn't notice anything more illuminating than the rhetoric he was peppering into his arguments half-way through.

I think Chuck's argument still just trumps this guy: Language will always have problems. You always need good process no matter what language and you can find a tool to help you get over whatever "holes" some older language might have. No amount of cursing lazy-people-unwilling-to-enter-the-21st-century or referencing WWI will make the "you need ze one language zhat does zit all" argument that much better.


Process and tools will always have problems. You will always need a good language no matter what process or tools you use. No amount of touting of process will make the "you need ze one process zhat does zit all" argument that much better.

Believe me, I know there will never be a perfect language. But this idea that we only need process is tantamount to a claim that there will be a perfect process which will solve all the problems that a better language could solve. That won't happen.

Why would you choose a crappy language and a good process when you can have a good language AND a good process? Is there something about having a good language that prevents you from having a good process?


I think the challenge is to apply a rigorous expectation to the entire system of the software development lifecycle.

- Language chosen to reflect guarantees needed to minimize error (in all axes of analysis).

- Process chosen to meet quality and auditing standards

- Hiring and team selection done based on the kind of work needed

- Deployment system chosen to reflect the user's needs

etc. You've been around. :-)

Roughly, at each juncture, choose the thing that gives the best result for the least amount of work, given the tradeoffs involved. Engineer the thing. Language & tech stack are a big component of that - but not the whole thing.


I think you have to notice that software is an iterative rather than a one-time-only affair.

Basically, some given piece of software is going to be created once and inherently have many warts.

Just an inherently, the main effort will be spent tweaking the process over time.

The thing about programming language is that it can only be made once whereas Process, training and personnel can be constantly adjusted.


> The thing about programming language is that it can only be made once whereas Process, training and personnel can be constantly adjusted.

This is why I think that language is absolutely a much more important decision than almost any decision about your process. Choosing a poor process is fixable. Choosing a poor language is forever.


Language enforces process by design. Many processes are potentially good but philosophically undesirable.


Do you favor the latter because changing the language is not an option?


More it is a mindset that I favor. Which is that tools are not an excuse for not thinking.

I was doing a machining class where the instructor related stories that the machines with the most safeguards had the most accidents, he related it to people being less careful around them. There is a lot of research around accidents at NIH and elsewhere but I've not seen a really definitive study that took up the question of risk versus user awareness.

Having a process where you always run unit tests or lint Etc has been more reliable for me in terms of avoiding programming bugs escaping into production.


The instructor's observation seems to be an example of risk compensation:

https://en.wikipedia.org/wiki/Risk_compensation


There was an interesting article arguing that motorcycle helmets make motor cycling more dangerous. Sure, if you have an accident, a helmet is a win. But risk compensation indicates you might be less likely to have an accident when not wearing a helmet.


Excellent! Thanks for putting a name to it.


> That was a long way to go to insult C and brag about Eiffel.

Ruby does what Eiffel does also. And it got the idea from it.


Whenever "language doesn't matter" or "use the right tool for the job" is used in an argument it's quite often as a thought-terminating cliche used as a post-hoc justification for personal prejudices. I think almost everyone intuits that there is at least a partial ordering to language quality and safety, we just often disagree about how that ordering is defined.


"Use the right tool for the job" != "language doesn't matter".

There isn't one language to rule them all. If there were, we'd be able to stop discussing them. But sometimes you need explicit memory management, sometimes you need rapid development, sometimes you need high performance, and sometimes you need a strong library ecosystem. "Right tool for the job" means that getting the language correct is important, but that it's not always going to be the same language.


This "use the right tool for the job" reasoning is just as flawed, though.

Switching between programming languages when you need explicit memory management vs. garbage collection or high performance vs. rapid development incurs a huge cost in interoperability. Should an English speaking person switch to French when they want to discuss love, since that's the right tool for the job? Of course not. If one (natural) language has words or idioms that are useful, they get incorporated into the languages that don't have them.

As the field of programming languages matures, we most certainly will want to pick languages that can rule them all. Languages will be adaptable to support different tradeoffs in the various dimensions you mentioned, without making a bunch of arbitrary ad hoc changes to all of the other dimensions.

Whether there will actually be just one is another question. I'd guess "probably not" for the same reasons that there's not a single natural language. Probably we'll have fragmentation, instead. But it won't be because people are choosing "the right tool for the job" and it won't be a good thing.


There are idioms in spoken languages that do not translate to other languages. There may be a literal translation, but it doesn't make sense or are incredibly awkward in a different language. Programming languages share this behavior.


Most current ones do, but this will change as new languages gain the ability to express most of what the others can express.


There are a couple limitations on that. For instance, you cannot have a language that's both total and general recursive.


So have a total fragment in your general recursive language, or encode recursion in your total language. Either is preferable to using an FFI to combine markedly different languages.


That's fine, but you're beginning to talk about a lot of languages glued together instead of one "master language", I think.


So "use the right tool for the job" with the understanding that using multiple tools imposes a cost, and then take that cost into account when figuring out whether they are the right tools for the job.


I wasn't arguing that "use the right tool for job" is wrong. It's a tautology, after all. What's implied by most people using it in the context of programming languages (and what I believe michaelochurch intended to imply) is that there will never be a "best" programming language and that the best programming language to use will always depend on the problem you're solving. My point is that this is wrong. As languages mature, there will emerge a handful of winners, and they will be flexible enough to handle all of the variants mentioned with minimally painful interoperability.


While there are a few languages that are paradigm changing, in most cases, I would say that switching languages is more like switching between American English and British English, rather than English and French. When in America, it makes sense to use the right tool for the job (American English), and vice-versa, rather than trying to shoehorn what you normally use into a place it isn't well suited.


In many ways it's much worse than the difference between English and French. If you are fluent in both of those languages, you can read a book that's written with some paragraphs English and some French. Software written in two different languages can't be combined nearly so easily.


> Software written in two different languages can't be combined nearly so easily.

As far as a human reader goes, I don't see why not. In fact, Objective-C and Objective-C++ provide practical examples of how dissimilar languages can be intertwined without detriment to the reader. To add to that, when writing pseudocode, people often do mix metaphors from multiple languages.

Granted, having a machine understand that kind of code to be able to do something with it is more of a challenge without a strict set of rules, but that's a completely different matter.


PSA: Always put brackets after your conditionals (for languages where you can). You never know when a one-line conditional will become a ten-line, and you can get this sort of bug. It's not worth the two saved keystrokes now to have the NSA in your data later.

I think the most readable code has no shortcuts and no tricks. I'll take unambiguous over concise or 'beautiful' any day.


Anyone who has ventured into software from an engineering discipline takes it for granted that redundancy is necessary for reliable and robust systems. Another aspect of the engineering mindset that is relevant is the acceptance of burden of rigor in service of the end user.

In ('haute couture') software, the matter is up-ended. It is the 'comfort' of the developer that is a key consideration and not the reliability of the product. They don't wish to be "bored" with "boring" "boiler plate", etc.

I think the problem boils down to this: given the infancy of this field and the (still) primitive tooling available to support the mind-work, the field requires and attracts high percentile minds who are then tasked with building (again and again) what is in most cases glorified book keeping or plumbing systems. Naturally an outlet is required for self expression (which is a very basic need for high power minds) and that increasingly translates to need for 'bling' (thus the periodic migrations from the last "cool" stack to the new thing), and aesthetic 'elegance'.


I think that you are making a mistake in equating redundancy in engineering with redundancy in programming.

The point of redundancy in engineering is having multiple parts so that if one of them fails, the others can take over its burden. In programming this might be analagous to having multiple workers serving a queue of requests, for example.

The point of conciseness in programming is to provide consistency. You have a standard part that you can use the same way in many places. In engineering, it would be comparable to writing "use #7 widget" instead of drawing out the complete design for the widget in every place that you use it.


> In ('haute couture') software, the matter is up-ended. It is the 'comfort' of the developer that is a key consideration and not the reliability of the product. They don't wish to be "bored" with "boring" "boiler plate", etc.

The thing is, if the developer and/or code reviewer get bored reading boiler plate, they will pay less attention to the code, and will likely miss important things.


Have you met autoformat? Spend 5 minutes setting up a decent IDE to do this for you.


What? How does an IDE prevent a code reviewer from having to read through boilerplate?


There is a palpable disdain for any concept of discipline or engineering in this age of Lean. YAGNI, they cry! Just ship! To feed this culture of shipping-before-thinking, we're inundated with an endless stream of magical frameworks that will do nearly everything for us if we just trust them completely.


I agree with a chunk of this, especially the dislike for magical frameworks, but you're wrong to pick on Lean and YAGNI there.

YAGNI (You Ain't Gonna Need It) in specific comes from Extreme Programming, which is big on engineering discipline. E.g., its inclusion of Test-Driven Development and Pair Programming as practices. YAGNI isn't used there to reduce quality; it's used to reduce product complexity by only building features that are demonstrably necessary. Which, in my experience, helps with quality.

As to Lean, it's rooted in Lean Manufacturing, which comes from Toyota. An engineering-driven company, they used Lean techniques to reach quality levels way higher than competing car manufacturers, which gave them an enormous competitive advantage.

If people are using those terms to justify shipping crap, then they're doing it wrong.


>YAGNI (You Ain't Gonna Need It) in specific comes from Extreme Programming, which is big on engineering discipline

How is extreme programming even remotely related to engineering discipline? It is a bunch of randomly tossed together practices with no proven value or efficacy, pitched by people who were incapable of doing the job themselves, to teach other people how to do the job. YAGNI is controversial at best, and often creates far bigger problems that what it was intended to solve. And that is when it is combined with the rest of the rituals it was intended to work with. On its own it isn't even recommended by the people who made it up.

>As to Lean, it's rooted in Lean Manufacturing

The name is anyways, but that seems to be about it. Lean manufacturing was about eliminating things that don't add value for the customer. The lean fad in software development seems to be most often applied by people who can't even tell you who their customer might be, much less what adds value for them.

>If people are using those terms to justify shipping crap, then they're doing it wrong

If processes rarely work and any failure is dismissed as "you're doing it wrong", then your processes are worthless. This is "any failure can be attributed to you not following our religion correctly" thing is one of the major things that killed XP.


Regarding Extreme Programming: works for me. Sorry you couldn't make it work for you.

Regarding the Lean fad, yes, all fads in software are shallow and foolish. I'm not interested in taking responsibility for shallow idiots, so if you'd like to yell at somebody about that, seek elsewhere.


> I think the most readable code has no shortcuts and no tricks. I'll take unambiguous over concise or 'beautiful' any day.

Ambiguity and conciseness are orthogonal concepts. Mathematics and formal proofs are very unambiguous, yet they are concise. On the other hand assembly code is unambiguous yet very verbose. Code has to convey two things:

1. "what should be done" to humans

2. "how to do it" to machines

Striking a balance between these two is difficult since it depends on each individual coder and the programming language. That's why there are so many syntax bike shedding wars because what's readable to one isn't necessarily readable to another.


I disagree. Mathematics is often very ambiguous and formal proofs are often very long. The trick with informal mathematics is that the author and the reader (a) share a large amount of background and (b) tacitly assume that the text is talking about something interesting. So, given those things, you can usually resolve ambiguity by assuming that the proof is referring to the interesting part at hand instead of some boring detail.

The downside to that is the ambiguity sometimes causes bugs.

Formal proofs can be very long. It's an open problem today as to how to compress and decomplect them. It's extremely similar to the situation with programming.

I'm not sure that I intend to refute your argument at its core—clarity does not imply great verbosity in my mind—but instead to color your example a little more. As a perhaps more specific, powerful example one could consider "Proofs by the Book" of Erdös-style wonderfully short proofs. These usually demonstrate the interesting characteristic of having very high "compression".


i half disagree. even informal proofs are often very long, but while they may omit some steps omitted, i wouldn't say they're ambiguous. it's just that writing something for a human who can (and in fact will find it easier to) fill in some blanks themselves is different for writing something for a machine, which only understands "how to do" kinds of descriptions.

also, a tangent about Erdos: Erdos was really good at posing problems. if his proofs are concise, it could have something to do with the problems he chose to work on. i'm not saying there's no book, i'm just saying that the most perfect, concise proof of some facts might still turn out to be very long.


Certainly agree about Erdos/the Book. It's not an effort to prove that all elegant wonderful proofs are also concise, but instead that there exist elegant, wonderful, and concise proofs which are sometimes found.

I always think of human proofs as the words on the page plus a significant guiding practice and convention of mathematics. Some proofs require more of that than others. Internalizing these proofs is highly non-mechanical. But I don't think we disagree here.


Indeed, it is an easy mistake to claim that unambiguous code has to be verbose when all you've ever done is read and write verbose code. Mathematical proofs are hard to read, and take time to master, but are rewarding in the amount of information that can be conveyed in a short amount of information.


> Mathematics and formal proofs are very unambiguous, yet they are concise.

http://www.newscientist.com/article/dn25068-wikipediasize-ma...


It's amazing how following good coding standards can make bugs go away. If anyone disagrees, I would suggest reading Joel Spolsky's article "Making Wrong Code Look Wrong": http://www.joelonsoftware.com/articles/Wrong.html That article changed my perspective on coding standards.

To your PSA, I would like to add: Never use the '!' operator at the beginning of a conditional. It's too easy to miss when reviewing or changing code, especially next to certain characters:

    if(!llama) {
        ...
    }
This is a tiny bit more text, and so much safer for maintainers:

    if(llama == false) {
        ...
    }


Now you have the bug occur if someone forgets an equal since this is always true:

    if (llama = false)
So use Yoda conditionals instead:

    if (false == llama)
Is that really an improvement over the original? As I've stated in my sibling reply, you are decreasing human readability for machine readability.


I have seen code that uses the Yoda style, but I think it's a bit overkill. Any C/C++ compiler I've used in the last several years will complain if you do an assignment in a conditional. (Unfortunately I've also seen projects that spew out a slew of compiler warnings under normal circumstances, so YMMV.)


Maybe

    if (llama != true)
instead?

Or maybe you just use a not function:

    if (not(llama))


Sigh... I know of at least one coding standard that insists on not having bare Booleans in conditionals but instead code of this sort:

if (foo == true && bar == false) ...

and it's so prolix it drives me crazy (especially if combined with a naming convention for Booleans, so it's obvious from the name that the value in question is Boolean). I feel like I'm in an epistemology class. "If the truth-value of the proposition p is true...."

If ! is easy to overlook, what about other unary operators? Perhaps we should write (0 - x) instead of -x, (0xffff ^ x) instead of ~x, and x[0] instead of *x.


I've taken to trying to make the ! stand out more:

  if( ! llama) {
          ...
      }
I don't know that it's an improvement, and my personal jury is still out, but I currently hold that it's better than the risk of the single equals bug that another reply points out, or the Yoda conditional that is the suggested replacement.


I think the '!' is easy to spot when using a good monospaced font.


If you're having issues seeing the '!' you have a terrible font and/or syntax color theme.


I worked at a company a ways back that had a very strict rule about always putting braces on conditionals. This was because at one point an engineer changed a one-line conditional to a two-line conditional and forgot his braces. It was a fairly minor condition, and it slipped through testing. It ended up being responsible for a very quick $5M in lost revenue before it was caught and the code was rolled back.

Of course there are some better practices that would have caught it, but it was an easy mistake to make and an easy mistake to miss.

Another one I've come to enjoy is Java's insistence that I only provide booleans to my conditionals. It is a bit onerous to write "== NULL" a lot, but there have also been many times in C I accidentally forgot that the function I was calling did not return a boolean and so my test didn't do what I thought it would. For example, memcmp is not a true/false function but a negative/zero/positive function and so if you do not check "== 0" you can easily invert your logic.


> Always put brackets after your conditionals (for languages where you can)

That's the point. The programmer shouldn't have to remember to write code the Right Way, rather the language should be designed to avoid these ambiguities.


If you're using a language more complex than HQ9+ to accomplish your goal, there will always be a Wrong Way to do something and someone is going to find it. Granted, you can attempt to limit the number of Wrong Ways, but it will never be possible to eliminate them.


Consider problems as a Poisson distribution. If you eliminate problems, there will be infinitely many more, but they will be rarer and increase the overall stability of the system. Once you eliminate so many errors that the mean time between failures is greater than (say) 100 years, that's basically equivalent to having a bug free system since the system will almost certainly be retired before then.


That is a thought terminating statement. Using your logic it is alright to move towards a "more wrong" language for a task?


Well, yeah. But that kind of misses the point--that it would be far better if the design of the language were such that this kind of bug could not have occurred.


Sure but you could just as easily add this as a compiler flag to any language. For example, I think gofmt will auto-brace your conditionals and other such things. I'm all for interesting language design but this isn't a very powerful example.


Adding a compiler flag after the fact does nothing to change the volumes of code that either were written before it existed or without regard for it. Also, especially in the case of C++, have fun using that feature while including any third party headers that don't adhere to that convention.

From a language design perspective, part of the reason this is such a good example is precisely because there is so little to be gained by allowing the braces to be optional.


Paleographers have a whole catalog of scribal errors, which can be useful when trying to reconstruct a text from conflicting extant copies. Perhaps it would be helpful to compile such a list of common programming errors, and consider that list when designing a new language. It would include "scribal" errors like Apple's goto or = vs ==, and also low-level logical errors. It seems like this could make a fun paper for any CS grad students out there.


There are such lists, and compilers do generate warnings when you use a construct in it. Most of the problems is with people ignoring warnings.

But then, once in a while you have an example of bugs being inserted because the right construct was one that generated warnings. The Debian SSH bug is quite a clear example.


That's a good point. I'm thinking more about 10-20 categories of language-agnostic error rather than hundreds of language-specific errors. And there may still be scope for a paper that explicitly examines the paleography lists for inspiration. Yay multidisciplinary research! :-) But it's nice to think that such a paper might have applications not just in language design but also compiler warnings, which certainly seems more practical/realistic.


GCC has this, you turn it on by appending "-pedantic -Wall" to the compiler invocation. Sadly, many people don't know about it or care enough to use it.


"My pet language renders that problem impossible."

Um... OK.

"Therefore you should use my pet language rather than one written in 1968 for a PDP-11."

Not so fast.

First, when the language was written has nothing whatsoever to do with how useful it is today. (Cue the Lisp advocates.) It's just a gratuitous slam, and it comes off as being petty.

Second, even if Eiffel does completely prevent this class of problem, what about the reverse situation? What classes of problems does Eiffel allow that other languages prevent? (Don't bother claiming "None". That's not realistic. It just means that either you don't see the flaws, or you're a propagandist.)

It's about the best tool for the job. Now, it's fine to argue that another tool would have been better for that particular job, but "avoiding one particular class of bug" is nowhere near good enough.

One point for the original article, though: Code does matter. Choice of programming language matters. Code reviews matter. Testing matters. Code review policies matter. Develop training matters. Developer culture matters. It all matters.


Since he lumps in Java with C and C++ it's worth pointing out that this specific bug is not possible in Java since unreachable code is a compiler error. I assume the same for C#.

Also, many style guides such as Josh Bloch's highly influential 'Effective Java' recommend against the 2-line if statement without curly braces since it's known to be prone to this type of error. His argument that keywords are better than braces for ending blocks is weak.


In C# if this were nested between two Catch blocks (for handling different types of exceptions, per this example) then it would throw a compiler error.

If it were a switch statement, you'd get a warning for code that will never be executed.


I don't think that's quite his argument. His argument is that in Eiffel, the keywords are mandatory, whereas in C, the braces are optional (for a one-line body). And, in fact, mandatory braces would have prevented this particular issue.


> it's worth pointing out that this specific bug is not possible in Java since unreachable code is a compiler error

Not on my Java compiler (Android toolkit and Eclipse). I see it as a warning throughout the code base I work on. :-(


I use ADT as well, and IIRC there are certain kinds of unreachable code that won't compile:

  public int foo(int i) {
      return 2;
      
      return i;
  }


Use IntelliJ (Google's favorite Android IDE - they make a mod of it called Android Studio but the good stuff makes it back to IntelliJ)


I believe it is a warning in C# (have not used it in a while).


Well the ironic part is that the official eiffel compiler compiles the eiffel code down to C, which is then compiled again into assembly. So technically speaking eiffel still relies on C... Note that its not very optimized C either, its much slower than Java for most of the stuff I tried (with contracts disabled).

Said compiler also happens to be terribly buggy and unreliable: The author still teaches the CS "Introduction to programming" class at my university with this language and every year students struggle with the language and the obscure IDE. Also don't know anybody that ever wrote a line of Eiffel again after that class even though the idea with contracts is kind of interesting.

Summa summarum: Best language constructs don't help if your basic tools are broken and make it a pain to write in that language.


I really don't see how enforcing {} syntax on all conditionals is going to make us so much safer.

Yes, people make mistakes, but this is a pretty huge screw-up. If you are modifying an unbraced if-statement and aren't paying attention to scoping, then you are being woefully negligent at your job. Especially when you are working on cryptographic code used by millions of people to protect their most valuable information.

So let's say we force more red tape to make sure this doesn't happen. Those of us who pay attention to scoping probably won't mind too much, it's good practice to do this anyway.

But what about the mediocre programmer? He may decide that now his if/else if/else three-liner, when adding new lines for {}, should really just turn into a switch/case. And now he neglects a fall-through case, or adds an unconditional break; before important code. And we're right back where we started.

It doesn't matter how much we safeguard and dumb down languages. We can load our languages full of red-tape: extra braces, no jumping or breaking, no fall-throughs, always requiring explicit conversions, no pointers, no null types ... all we'll end up with is code that is much harder to read (and possibly write), while the mediocre programmers will find new and inventive ways to screw things up. It's just as likely to make them even more lax, and attract even less disciplined programmers into important development roles. You know, since it's presumed to be so much safer now.

The real problem is the amount of poor programmers out there, and the lack of repercussions for these sorts of things. A doctor that leaves a scalpel in a patient is (rightly) ruined for negligence. Do you think the "goto fail;" writer even received a written warning? Why not?

I'm not saying people can't make mistakes, but I think your pay scale and the importance of what you do should come with some actual responsibility for your errors. Just like in every other profession out there.

Yes, sometimes you can blame the tool. But there are also times when you need to blame the user.


Ruined for negligence - medieval thinking combined with the US legal system. Like that's going to help the patient whose body the scalpel was forgotten in.

But I like your example. Because in a well organised OR, the doctor cannot leave a scalpel inside the patient all by herself. The nurse would have to fail to properly count the instruments at the same time. BTW: all non-metallic objects have embedded pieces of metal that will show up on X-ray, so errors can at least be detected. Do they let plumbers operate on patients because it's now much safer? Not where I live, the laws are pretty strict with respect to who can practice medicine.

If you want safety, stop thinking in terms of blame and vengeance and design systems that avoid errors, and reduce their impact if they occur. This includes culture, processes and tools to protect against errors by those who do the work, and some regulation to stop management from putting employees in situations where they are likely to cause harm.

Those measures have made aviation safe, and medicine is catching up. Time for the software industry to mature.


> If you want safety, stop thinking in terms of blame and vengeance

But that's the thing, I don't think anyone is really doing that in the industry. It's not the solution to start blaming the programmer, but it's a part of it. The other part is like you said, better accountability. It should be every bit as concerning that the compiler didn't catch the dead code, that there was no code reviewer, that there was no static code analysis, that there was no test suite to ensure bad SSLs weren't passing validation, etc.

> Time for the software industry to mature.

Exactly! The way I see it now, there's no real accountability. We go, "Oh well, it's the fault of the language. It shouldn't have let me screw up. If only we had a new language without unbraced-ifs ... and it somehow caught on and replaced all 40-years of legacy C code. Whelp, back to business as usual."

I don't see unbraced-ifs as this great security flaw, and I don't see 'fixing' it as curing some endemic problem with language design that's going to lead us to not have bugs like this again. It's too reactionary.

It may not be best-practice to do this, but I'll admit there are times I want to add a quick one-liner check: "if(already_initialized) return;", and it's nice not having to put the extra braces there just because an Apple engineer once made a mistake.

For better or worse, the nature of technology is pragmatism, and not idealism. C let you use unbraced-ifs, and now it's the most used language in the world. We can argue about how this should change, but it's never going to. We can design a new language and maybe one day it'll overtake C. But until then, let's stop blaming our tools for things we should be taught on the first day we start using them.


Great comment! I agree with you, but a counterpoint is that punishment is one facet of the error-avoidance system design, based on the logic that people are more careful if they fear punishment. I think punishment is often overemphasized, but not completely worthless (as you seemed to, but perhaps didn't mean to, imply).


>Do they let plumbers operate on patients because it's now much safer? Not where I live, the laws are pretty strict with respect to who can practice medicine.

But there aren't laws that determine who can program...

>If you want safety, stop thinking in terms of blame and vengeance and design systems that avoid errors, and reduce their impact if they occur. This includes culture, processes and tools to protect against errors by those who do the work, and some regulation to stop management from putting employees in situations where they are likely to cause harm.

There obviously already are systems that are designed to avoid errors... Unit testing, static code analysis, automatic formatting, etc.

The author of the article said to throw all those out and put all of the safeguards into the language spec. OP is just saying that they don't belong there.


This is not an argument, just an attractive narrative for what boils down to a rejection of the possiblity of a better programming language reducing bug rates without negative side effects outweighing the benefits - I don't think you would find much agreement if you just stated your point explicitly, instead of painting a dystopian red tape programming language future... And you are obviously wrong, as proven by all the programmers today who don't even know what a buffer overflow is anymore, because a higher level language takes care of that for them once and for all.


A dead code analyzer would be even more effective than making a non-backward-compatible change in a 40-year old language. I'm not convinced enforced {} is such a compelling advantage, just because of one company's screw up. I'm not convinced they wouldn't have screwed it up in a different way, like putting the second goto outside of the brace scope entirely.

I'm not radically opposed to enforced {}, I am simply tired of developers blaming only their tools and never themselves.

> And you are obviously wrong, as proven by all the programmers today who don't even know what a buffer overflow is anymore, because a higher level language takes care of that for them once and for all.

Red tape decreases code readability to safeguard against basic due diligence. It is a burden to the developer on reading and working with the code.

Forced array bounds checking and garbage collection sacrifice significant performance to protect even further. It is a burden on scalability and battery life to the user. It some cases, it has the potential to reduce code verbosity.

And now we have new classes of bugs with dynamic type systems, with duck typing, with run-time errors, with not having to declare variables prior to use, with null pointer exceptions, with garbage collector stalls in real-time applications, and so forth. No language is ever going to be perfect. (Again, this is not me saying, "let's not try and make safer languages", that's not my point at all. My point is that you also have to encourage better diligence.)

It certainly serves a place for rapid application development, for hiring lower-paid and less-skilled developers (which is not a bad thing), and for working on applications that don't demand performance.

And yet performance still matters to many developers. For all of Java's professed safety, it's still below usage of C. And far below usage of the C family (C, C++, Obj-C.) It's still the #1 choice for operating system design, high-performance libraries, simulation, web servers, database engines, video codecs, and major studio games, among many other things.


The NTSB got rid of this thinking a long time ago when it comes to aviation accidents. If you think someone is "woefully negligent at their job" for modifying an unbraced if statement, how about pilots who fly their airliner into the ground? Yet aviation accident investigations focus not on blaming people who erred, but on how to avoid them knowing that humans make mistakes.

Instead of focusing on making ourselves feel better by talking about "a few bad apples" if we really want to avoiding things going bad in the future, we need to work with human behavior instead of against it.


Sometimes it really is a few bad apples, though -- it's a delicate balance.

Somewhere between "the pilot got onto the plane, intoxicated, and proceeded to fly it across the country" and "a major, unforeseeable control outage led to a midair collision between two aircraft following normal procedures", there exists a reasonably competent, responsible person, capable of performing their job duties as expected.

The key is being reasonable in what we expect from people. We can't all be superheroes, but people aren't mechanical robots with the wits of children, either.


But here's the thing - you don't weed out the bad apples by drastic punishment alone. You weed them out by designing your processes so they get tripped up, and then simply eject them from the profession.

And it's not the bad apples that are the problem. You spot a drunk captain easily. It's the mistakes that are the problem. The worst aircraft accident in history? Caused by one of the most experienced and responsible pilots in the industry. As a result of a large chain of failures along the way.

And these kind of problems are fixed by systematic change, not by punishment. Tenerife (the accident mentioned above) led to a whole slew of regulation changes. It led to an entirely new approach to leadership and decision making - crew resource management.

And that's what's keeping the airline industry a fairly safe business, not just imposing penalties.

It has nothing to do with "having the wits of children". It has everything to do with accepting that we all make mistakes. Sometimes silly ones. And that we need to put systems in place to prevent them before they happen.


I never meant to say there's no such thing as negligence. Of course there is.

But it's also true that the threat of punishment works poorly as a deterrent also in that case, because if you think you can get away with flying the plane intoxicated, you likely also think you won't get caught. And the same processes that catch honest mistakes also work to catch less innocent ones.


But would you say it makes more sense to put extra burdens on the majority of good apples to protect the few bad apples?

If we accept this as just an honest mistake, then can you name anything a programmer could do that you would consider just plain negligent? We are never going to be able to catch 100% of all flaws.

At least to me, I think that if the people writing sensitive code had a bit more "on the line", that they'd be more inclined to be careful. It may slow down output, it may cost more for development, but for really, really important stuff? I think that's worth the cost.


So you are OK with slowing down output by forcing people to be more careful out of fear, but not by putting in place a system that makes mistakes less likely?

There are lots of experience about how to write code with few defects, and rather than focusing on punishing people who screw up or some idea that you just hire awesome people, it focuses on a process where mistakes are caught: http://www.fastcompany.com/28121/they-write-right-stuff or, for a more detailed account, http://ntrs.nasa.gov/search.jsp?R=20110014946

The fundamental fact is: you can't change human nature, no matter how much you may wish you could. One day, the bad apple may be you, and thinking you are somehow immune to making mistakes is a very dangerous delusion.


>If you are modifying an unbraced if-statement and aren't paying attention to scoping, then you are being woefully negligent at your job.

No. Brains model the world based on sampled data from the senses. This internal model will frequently deviate from the real world so mistakes are inevitable. However, you can design programming languages and programming constructs in a way that makes brains either more or less susceptible to these kinds of mistakes. For example, if your programming language doesn't allow these types of compound statements, than the human brain will never make that kind of mistake.

I'm primarily programming in Java and JavaScript and I've learned to just put braces around every statement that warrants one. I got burned once or twice and I prefer the peace of mind that comes with removing an entire class of bugs at the cost of two extra characters.


Process is not immune to entropy.

It is very difficult to assure that even experts don't make mistakes: especially when they are running through the same process again and again. They will, at some point, always make a mistake. Even the worlds most competent doctor is at risk of leaving a scalpel in a patient.

It is possible to lower the risk of such an event occurring which is why we push for best known practices (like enforcing {} syntax on all conditionals (I do), regression testing, etc.) and bodies of knowledge.


Are you making the claim that language design has no effect on the number of bugs? That JSLint (for example) doesn't really reduce the number of bugs? If you're not saying that, then presumably JSLint / language design does reduce bugs, which is probably the main claim the article.

Nobody is saying that enforcing {} syntax etc. removes all, or even most bugs, but it removes a non-trivial amount of bugs while not making code particularly harder to read imo.


No, I'm more claiming that there's too much eagerness to blame ones tools. And that's very dangerous to me, because they blunt my tools in the name of safety.

So you have these advocates coming in, saying we should stop using goto, stop using pointers, stop allowing implicit type conversion, not allow return statements anywhere but the end of the function, rely only on garbage collection, and so on. I end up needing to jump through hoops in order to safeguard an amateur that doesn't know what he's doing.

This Apple bug was a failure on many levels. This was one of the most beginner-level bugs imaginable, on one of the most important libraries imaginable. Where was the programmer accountability? Where was the internal review process for this change? Where was the static code analysis to catch this block of unreachable code? Where was the security auditing suite to make sure the library was functioning as intended? Where was the corporate accountability? Blaming the tool for being sharp, to me, sounds like passing the blame entirely.

The {} enforcement is, solely by itself, very benign. It doesn't remove any language functionality or expressiveness, and it's a good thing to do anyway. Aside from requiring us to go back and patch up decades of old code, it's in a very rare category of easy wins. From it, you could easily say the same about always explicitly casting everything, so that there are no surprises. And put all your literals on the left-hand side, in case someone forgets an extra =.

if(!x) a = b * c + d;

if(0u == x) { a = reinterpret_cast<int>(b) * c + reinterpret_cast<int>(d); }

The code does exactly the same thing, yet the latter is going to have a real toll on code readability. My 1080p monitor is going to see a few less lines of code at once. I'm going to have to scroll a bit more. I'll have to filter out a few casts, flip a few compares in my head. But it adds up.

Yet what I'm most afraid of, is that I'm very hard-pressed to think of safety changes to C that won't remove any functionality, aside from the aforementioned. In fact, I start seeing odd things creep up, like Clang warning when switching on a boolean value. It apparently catches some odd mistake some guy made at some point. It was thought that nobody would ever switch on a boolean. Except that I did. (see my post history here if you want details on that.) I'm getting a bit tired of being grouped in with programmers like the goto fail guy. Sure I'm not perfect, but I'm also not making these kinds of trivial mistakes. I am not eager to go through hundreds of thousands of lines of code to add these changes.


Blaming tools doesn't mean that we can't also improve other areas, like problems with the process of programming (e.g. the things you mentioned). The thing is, it's easy to improve the tools, but difficult to improve organizations and processes. In real life I can simply add a linter to a software project without asking anyone and other team members are (or should be :) okay with it.

On the other hand, I can't just decide to get a security audit or corporate accountability, those things cost real money.

As for your critique of certain "linter rules", you might have a point. There's of course bad linter rules. It's not a good idea (in my opinion) to require explicit casts everywhere. But those are just specific critiques against specifict rules, not a critique about the general idea of using a linter / designing a language to be safer.

Updating legacy code to reflect new rules is a huge task, but you can start enforcing these rules only for the new code.


Do you really think that mistake was made by a beginner programmer? An amateur? That Apple has no review process?

Many things along the chain failed. Enforced braces would have prevented that chain failure. It's that simple.

> I'm getting a bit tired of being grouped in with programmers like the goto fail guy

Really? You never make mistakes? Or if you do, you just know they're not going to trigger a chain of failures?

Side note: What's with the "literals on the left side" obsession? That's an issue compilers started catching 20 years ago, provided you were willing to forego assignments in a conditional - another safety measure that most engineers are more than happy to accept.


>Sure I'm not perfect

But your reasoning assumes you are. You do make these mistakes. Everyone does. And languages that prevent them are not dulling your tools, they are putting handles on them. Insisting that you need to work with a blade with no handle is foolish. Claiming that people who use blades with handles are less capable than you is just plain absurd.


It should be noted that this might not have been a coder error, but a merge conflict. Nobody should be reprimanded in that case, since it's nobody's fault (except whoever decided to not test or lint this file, but he should have been punished for negligence even if it hadn't caused a bug.)


This is exactly what I thought when I first saw the Apple bug, and I was surprised that the author didn't mention this at all. He's blaming the programming language here, but I have yet to see a language where a single line accidentally duplicated or transposed by a merge tool couldn't cause the same problem.


Well, in Python, if you duplicate a return statement, then your code will recognize only the first statement. This, for example, will return 1:

  def func():
    return 1
    return 2
If the code from the goto fail example was written in Python so as to return an object when a condition was met, and it ended up with 2 return statements, then Python would have just returned the first one in the proper scope and moved on. Of course, this still depends on implementation in the code itself.


If we're talking about returns, then C/C++ would behave the same way. The second return is unreachable.

A duplicated line that isn't idempotent and that doesn't jump out of the current scope would be problematic.


Java (if it had gotos) would not have compiled this code because it doesn't allow unreachable code.


In the Apple SSL bug, a goto statement works much like a function call or a throw in Java. Here's a Java example that compiles without warning and has the same flaw:

    public static void foo() throws Exception {
        throw new Exception();
    }

    public static int bar() throws Exception {
        int x = 0;
        if(x == 1)
            foo();		
            foo();
        x++;
        return x;
    }


To the contrary, your list smartens the languages up. Your list describes a typical functional programming language, and I certainly would not say most such users are mediocre:

'extra braces (or no braces in favor of white space), no jumping or breaking (since everything is an expression), no fall-throughs (pattern matching is much more powerful than clumsy switches), always requiring explicit conversions (type correctness is worth it), no pointers, no null types (Option types are better) ...' The resulting code is very easy to write and read, although you must approach learning them with an empty cup.

Extra railings like contracts, contracts + prover, refinement types and dependent types enforce vigilance in ascending order of brutality. They do not leave room to be lax.

Programming languages are meant to counter the limits or deficiencies of human working memory and focus. Better languages aren't profitless trades, the cost of learning and using them must be less than the attendant rewards + cost of not using them.

Also, speaking of Doctors and attacking working memory constraints, checklists have been show to significantly reduce complications in surgeries. Type systems and good language design fall in the same category of aiding one in focusing on the bigger picture by alleviating WM of trivialities.


No one's claiming that requiring braces on conditionals is any kind of silver bullet. The Apple bug is just a concrete example of how a different language design decision (requiring braces) could have avoided this particular bug. The fact that bugs can also be introduced via an erroneously designed switch statement is a red herring.

And I also don't think anyone is claiming that the skill of the developers doesn't matter; of course it does. But the fact that no single change to any one part of the process (tools, developers, methodology, etc.) can prevent all bugs does not in any way negate the value of improving any one of those parts.


> The Apple bug is just a concrete example of how a different language design decision (requiring braces) could have avoided this particular bug.

I don't see how requiring braces would have necessarily avoided the Apple bug. Whoever was responsible for creating the extra "goto fail" might still have left it outside the scope of an if-statement.

    if (error_of_first_kind)
        { goto fail; }
    if (error_of_second_kind)
        { goto fail; }
    if (error_of_third_kind)
        { goto fail; }
    if (error_of_fourth_kind)
        { goto fail; }
    if (error_of_fifth_kind)
        { goto fail; }
        { goto fail; }
    if (error_of_sixth_kind)
        { goto fail; }
    The_truly_important_code_handling_non_erroneous_case
It all depends on how the extra line got there in the first place. Presumably there was a mistake made while editing the file. Braces don't necessarily eliminate those kinds of problems.


Assuming this is "C with required conditional braces", this would exhibit the same behavior as the original bug. Introducing a new scope with {} is valid (and regularly used) C.

The problem here is that, visually, this class of error doesn't stand out, because, when you scan the code, it just looks ok (albeit, ever so slightly less right in your example). And that brings us back to language design.

It's hard to have this type of bug and not bring up Python as an example, because Python behaves as your eye processes it.

  if error_of_fifth_kind:
      goto fail
      goto fail
Those two gotos are in the same block, because they are indented the same. People continue to freak out over significant spacing, but there is a lot of value in it.


Meyer argues that a language shouldn't have a separate block statement. Instead block structure should be integrated in the control flow statements. That would result in this code: (I'm keeping your indentation style)

  if error_of_fifth_kind
      then goto fail end
      then goto fail end
Which is simply a syntax error.


Interesting. This works because there's no explicit "begin" to go with the "end". I would normally consider this kind of asymmetry to be a language design flaw, but I suppose it does address this particular problem.

Of course, I think you could still end up with this pretty easily:

   if error_of_fifth_kind then
      goto fail
   end
      goto fail
   if error of sixth_kind then
      goto fail
   end


Well, Meyer doesn't let any chance to bash C/C++ go unused :) But a good syntax should minimize the chance that an accidental insertion/transposition/deletion of a character/word/line results in a valid program.

[A dramatic example of bad syntax comes from a FORTRAN, where a loop between the current line and the line with label 10 looks like this:

  DO 10 I=1,100
and this code:

  DO 10 I=1.100
declares a variable named "DO10I". Hopefully your compiler will warn that label 10 is unused.]


Or similarly:

   if error_5 {
      goto fail;
   }
   /* line deleted here */
      goto fail;
   }
   if error_7 {
      goto fail;
   }
Many ways to skin the same cat, but it boils down to following good practices that emphasize errors like this (presumably removing an error condition and leaving the goto).


Congratulations! You've constructed a strawman argument.

Sure, if somebody used a brace formatting rule like the one you've given, which is explicitly done in a way that wouldn't have avoided the issue, it still would've happened. However if somebody was to consistently place the statements on a line of their own as just about any real world brace formatting rule would, the issue would be avoided. Thus:

    if (error_of_first_kind) {
        goto fail;
    }
    /* ... */
    if (error_of_fifth_kind) {
        goto fail;
        goto fail;
    }
    if (error_of_sixth_kind) {
        goto fail;
    }
    The_truly_important_code_handling_non_erroneous_case
Is safe, as would be:

    if (error)
        {
        goto fail;
        }

    if (error)
      {
        goto fail;
      }
Or:

    if (error)
    {
        goto fail;
    }
The idea behind always using braces is to _avoid insertion errors_ when editing. If you need to jiggle the braces around to go from one statement more than one, you're doing it wrong.


Ah, a fair point! However, since we're talking about the hypothetical language what-C-might-have-been, I would suggest a further improvement: require braces for all conditionals /and/ require that the opening and closing braces shall not be on the same line.


That would really start to hurt readability. There are plenty of instances where the body is very similar save for a small change, and having it lined up in a grid makes it much easier to read. As a contrived example, you can look straight down to see the action values here:

if(condition_1) { action(318); }

if(condition_2) { action(219); }

if(condition_3) { action(142); }

In my opinion, the bigger risk is having a single-statement if have its payload on the next line.

if(x.open()) x.close(); //very difficult to add another statement accidentally here

if(x.open())

    x.close();  //much easier to do so here
But of course I wouldn't want to start treating whitespace as special in C.


Adding semantics to white space would be a huge change to the design of C (and most other languages).


Who on earth would ever format code like that?


> The fact that bugs can also be introduced via an erroneously designed switch statement is a red herring.

I really didn't think so. Nor did I think it was slippery slope. If our goal is to make a language that's safe against trivial mistakes like this, it's the logical next step. It's just as easy to forget a break; on a case statement as it is to forget to add braces around a multi-statement if block. So why not make it mandatory to indicate whether you want execution to stop or continue at the end of every case statement, in the name of good practice?

I'm also not saying that enforcing {} is really bad in and of itself. Just that the line of thinking is bad. There are so many ways to get burned programming in C, and this is one of the dumbest ways imaginable. If something like goto fail got through with no auditing or testing, it is indicative of much bigger problems than a minor language detail like single-statement if's.

This is the kind of bug that should only hit junior programmers in high school. And even if professionals accidentally mess it up, there should be methods in place for catching and fixing it long before it reaches production.


The answer is not "screw the safeguards, I know what I'm doing". The answer is language that

a) doesn't have different ways to do the same thing. (I.e. if with either a single statement or a compound statements)

b) Doesn't do things implicitly. I.e. if you want to fall through, you have to indicate that. If you want to break, you have to indicate that, too.

The idea of saying "it's only mediocre people" is nice, because it implies we're not mediocre. It's also wrong, because everybody makes mistakes. You want to prevent mistakes from being made in the first place, independent of skill level. That's why doctors have adopted checklists, for example. They certainly know all the things on there. And 99.99% of the time, they do just what's on the checklist.

But that one time when you're tired, and you forget one tiny step that can have catastrophic consequences, that checklist saves your bacon.

Unambiguity in your language is nothing but a syntactically enforced checklist.

After-the-fact punishment doesn't really help as much as you'd like to think. It merely leads to expending energy on cover-ups instead of making sure the mistake never happens again.


Why not just eliminate the sources of common errors altogether (by enforcing {}, per your example), especially when it can be done with a style-checking tool that runs automatically at build time?

The only reason to punish the programmer then would be if they blatantly ignore the style error. You could even do away with the punishment by making passing the style check a condition of code submission.

Similarly, I'm sure there are many examples in medicine where a source of errors was eliminated completely by just using a better tool for the job, though I can't think of any at the moment.

Edit: wording.


I don't agree with almost any of this.

To begin with, I think the reason you don't see this as a real language problem is because of status quo bias. Here's one way to think about it: your compiler (or interpreter or on-the-fly-IDE-underliner-thing) catches all sorts of syntax mistakes you make. You depend on that. But what if I pulled that all out from under you? Your compiler no longer checks that your function returns the type of thing it says it does in every case and your program just breaks horribly when it doesn't. What a terribly stupid thing for your language not to help you with! Well, as you'd say, you just need to be more vigilant, and it's a sign of mediocrity when a developer screws that up. We can extend that to literally every error-mitigation tool we have. In other words, just because you've steeled yourself against this class of error out of practical necessity doesn't mean it's a good idea to leave it around.

From the other direction, look at Haskell. I'm not a Haskell programmer, but my understanding is that its type system and pure functions eliminate entire classes of errors and makes reasoning about the behavior of your program easier. That's not programmers being lazy or incompetent. They want their tools to do more thinking for them so they can tackle higher-level problems their tools can't, rather than spending their cycles ritualistically ensuring they haven't made basic screwups, which humans are intrinsically bad at anyway. People certainly aren't attracted to that safety net because they lack the discipline and rigor for the rough-and-ready world of C. Sheesh.

There are all kinds of tradeoffs that go into language safety (and you mentioned one-- forcing {} for one-liners makes the code longer and potentially harder to read), but making your language less safe for the express purpose of making it less safe is just crazy. That's what you're doing when you keep around gotchas to keep out the noobs. Don't be the guy who thinks shaving with a straight razor makes him a real man. To push that further, if your measure of the quality of a programmer is how careful they are with syntax gotchas, I submit that you're doing it wrong. "Look at this beautifully designed, simple, elegant, fault-tolerant, highly maintainable module our new developer wrote! Oh, but she used an uncommented fallthrough. Fire her!" Being vigilant about little gotchas like this is, I suspect, uncorrelated with all the rest of the stuff you want in a programmer. That her module might be bad in spite of all that points exactly at the tools. If we fix the tools, now her code is awesome, but maybe you won't be able to lord your awesome never-mess-up-braces skills over her.

It's especially interesting that you brought up doctors. Because doctors make mistakes all the time (they only get sued for negligence, which isn't just any mistake with nasty consequences). To help with that, they've been steadily improving their tools: EMRs, protocols, checklists, decision trees, standardized equipment layouts, and so on. And it's really changed medicine.

So I don't believe the dichotomy you've set up is right. As applied to the "goto fail" bug, my guess is that the programmer who did it may well have been good, and just made the equivalent of a line-long typo. (Or maybe it was a merge error?) And next time you discover a baffling bug that, in the final analysis, should have been really obvious, I hope your peers cut you more slack then you cut this person.


> I think the reason you don't see this as a real language problem is because of status quo bias.

If we could go back and revise C before it became widespread, it'd probably be wise to do so. In a cross assembler and scripting language I built, I enforced {}.

> What a terribly stupid thing for your language not to help you with!

Agreed! Anything that is an error should be caught. I even think it'd be nice if the compiler alerted you to dead code, which would have caught this bug.

> We can extend that to literally every error-mitigation tool we have.

Or we can take a balanced approach. Recognize that C is a low-level language. That it has very compelling benefits, but that they come with tremendous risks. We can approach the language with reverence. We can fix its glaring bugs without neutering it of its power or terse expressiveness.

Some people can't handle it. And they should stick to higher-level languages, or at least stay away from backbone security libraries.

> From the other direction, look at Haskell.

I can't really evaluate it, as I've never come in contact with a single application written in the language. It's a wonderfully philosophic language, but real world usage indicates it is not at all practical.

> making your language less safe for the express purpose of making it less safe is just crazy

Yes it is.

> That's what you're doing when you keep around gotchas to keep out the noobs.

I am not suggesting we allow single-statement if's to bar beginners from using C. I am suggesting that if you want to program in the language, you should learn the rules. Preferably before you start working on a crypto library. I know there are countless insane, esoteric edge cases, especially if you go on to C++. But this is something I learned on the very first day I started programming in C.

How much expressivity and/or power should we take away from the language before we finally blame something on the programmer instead? I'm not saying "always blame the programmer", I am saying there has to be a point where you say, "maybe it's not C, maybe it's the author who is to blame here."

> Don't be the guy who thinks shaving with a straight razor makes him a real man.

I don't program in assembler :P (well, at least not on modern systems where C is an option.)

> Oh, but she used an uncommented fallthrough. Fire her!

It would be more, "Oh, but she forgot a break; statement, leading to an unintentional fallthrough that exposed millions of users' credit card details to MitM attacks. She didn't review her code, or run it through an analyzer, or run it through the test suite. Write her up. If it keeps happening, fire her."

I'm actually arguing in favor of allowing the uncommented fallthrough. Not saying it's best practice (if I wrote the language, case labels would default to breaking without an explicit fallthrough keyword), but a C programmer should damn well know when they see that code that it is going to fall through. Because that's how switch/case works.

> And next time you discover a baffling bug that, in the final analysis, should have been really obvious, I hope your peers cut you more slack then you cut this person.

I think it should scale based on the importance and consequences of your actions, or lack thereof. I screw up a lot in the little gaming apps I write. I also don't get paid for it in any way. If my screw-up brought down production ordering for a day, I'd expect to be disciplined for that.


I agree you should learn the rules, because that will make the software work better. But if a rule turns out to be easy to accidentally break, we should change the rules. (And we don't have to change the rules of C per se; we can just use a static code analyzer or something). The languages and compilers aren't static things to take as constants; they're just pieces of software. In terms of whose fault it is, I think it's not so much that anyone thinks the developer is completely free from blame in some abstract sense, just that if you want to fix the problem, you'd get more mileage out of fixing the tools than disciplining the coder. Blame is a complicated, squishy thing, so in a sense it's hard to argue about how to allocate it, but to the degree that you want to take concrete actions to prevent the issue here, the tools seem like the right choice here. I think that might be the core of our disagreement.

> I think it should scale based on the importance and consequences of your actions, or lack thereof.

I was worried you thought that. I'm not denying there's some truth to it; the bar for carefulness should indeed be higher when you're working on critical code. But it's tricky. Think of it from the perspective of someone whose full time job is working on an SSL library. First, you now have a huge downside risk, and have to be worried about every action you take. Are you rewarded proportionally or is it just that you just have a worse job than your carefree peers? Even if we do compensate these developers for that, we're now attracting risk tolerant people to work on risk intolerant code, which seems precisely backwards. Second, working with a constant fear that you're going to break things is exhausting and unsustainable. I don't know if you've done it or not, but I can tell you it's kinda brutal, and it's exactly why having a comprehensive set of unit tests makes for happier developers. Adding the stress of potential punishment for casual errors would make the working environment untenable, and if you could convince anyone to actually do the job, you would quickly burn them out. They might even produce more errors. For both those reasons, the way most organizations handle this is not by throwing lots of personal responsibility for errors on the developer, but by adding process: code reviews, extra testing, and of course, better (or at least additional) tools. It's the recognition that humans make mistakes and don't magically stop making them when the stakes are high.

Finally, it's not clear to me that being more careful would prevent this kind of slipup. Surely the developer in this case knows the relevant syntax rules. They just didn't notice. By their nature it's hard to notice things you don't notice, even if you walk around with a metal alarm bell telling you to notice things. I'm not sure it's really any less likely than it is in your games. (Aside: I'm tempted to look up the literature on error rates because I suspect this kind of thing is well studied.) So how would disciplining them help?

If there's one thing that just seems silly and broken on Apple's part, it's why they didn't use a static code analyzer to catch stuff like this, which to me is an easy, obvious step. I could certainly see the person responsible for that kind of thing being held personally responsible for this. Much more obvious than nailing the committer for a copy-paste bug.


"When people tell you that code does not matter or that language does not matter, just understand the comment for what it really means, "I am ashamed of the programming language and techniques I use but do not want to admit it so I prefer to blame problems on the rest of the world", and make the correct deduction: use a good programming language."

As emotionally satisfying as it can be to stick it to people we disagree with, I think we as an industry could do with a lot less of this black and white thinking.

Programming languages do not fall into a neat good/bad dichotomy. Tell me your favorite programming language and I will tell you three things that absolutely suck about it (even if I like it overall).

Yes, if C could do it all over again it would probably mandate that brace-less blocks go on the same line as the "if" (or are disallowed completely). So I agree with the author that certain features of programming languages can make it more or less error-prone.

But people still use C for a reason. That reason is that C has real advantages. If you really want to improve software engineering, then help the Rust guys out, but don't just tell C users to "use a good programming language."


His code example is:

if (error_of_fifth_kind)

    goto fail;

     goto fail;  
if (error_of_sixth_kind)

    goto fail;
The_truly_important_code_handling_non_erroneous_case

My question: If the "truly important code" is really that important, where are the unit tests to verify that it "handles" the "non erroneous case????"

Test. Your. Code.


Because Apple doesn't have a software testing culture. At least that's what I've been told by Apple engineers I know who used to work at Microsoft ("software testing is not the same respected discipline you're used to at Microsoft"), as well as when I interviewed with Apple and asked what kind of test team would be backing the product I would be working on (manual testing with no SDETs, apparently).

Take it as the quite anecdotal evidence it is, but to me it explains a lot.


I agree that it's totally shocking that Apple did not have an example of each kind of bad cert. Even a rudimentary unit test of this code would have caught this bug. I bet they do now.


But rudimentary testing of critical code is not part of Apple's corporate culture. They have released versions of the Finder with amateurish bugs that delete files[1] and versions of Mail that randomly delete messages[2]. Several versions of Mail on iPhone, a couple of iOS versions ago, send hundreds of copies of a message when emailing a link from Safari[3]. They've chosen to hoard over a hundred billion dollars in cash rather than hiring more competent engineers and enforcing quality control.

The blame for these fiascos, and for the goto fail bug, getting out the door lies not with the programmers, who can not avoid making mistakes, but the with the CEO and other management, who decide how to allocate resources.

[1]http://tomkarpik.com/articles/massive-data-loss-bug-in-leopa.... [2]http://discussions.apple.com/thread.jspa?messageID=12758081&.... [3]http://lee-phillips.org/iphoneUpgradeWarning-4-2-1/


The thing that kills me is that rudimentary unit testing of simple functions like this makes development faster. You have to run the code, right? It's easier to run it in a unit test than to set up an environment to run the result.

My pet peeve is code that is so broken that it has obviously never even been run.


> My pet peeve is code that is so broken that it has obviously never even been run.

Yeah...in my first support job I had to deal with a customer call that traced back to a install script for our company's (quite pricey, enterprise back-end) software dying with a syntax error.


Also: just because you can omit braces, doesn't mean you should. Stick to 1TBS and this kind of mistake shouldn't happen.


1TBS = One True Brace Style, had to look up that abbreviation [1]. Puts the `{` on the line with the `if`, `for`, `while` text and the `}` on its own line, and requires the braces even for one line blocks. (I knew the rest, just hadn't seen "1TBS" before.)

[1] http://en.wikipedia.org/wiki/Indent_style#Variant:_1TBS


Agreed, but the author's point (indirectly, perhaps) is that requiring braces is an example of something that should be part of the language, and the fact that it's not should be considered a defect of the language.


This mistake shouldn't happen?

Mistakes always happen. Even if I dedicated myself to using braces everywhere, my mistake might be that a) I put the braces in the wrong place, or b) I forgot to put the braces.


Shouldn't != couldn't. In Haskell and the MLs, for instance, certain classes of mistakes shouldn't happen because of the type system and pattern matching, but a single wildcard could throw that off.


To address only the "goto fail" example the author uses, I don't see how the proposed Eiffel solutions are conceptually any different than always using brackets in your C/C++ constructs. Brackets are the mathematical notation for a set, and having a set of instructions inside them makes perfect sense even if the set only has a single element.

Since

  if(condition){ instruction; }
instead of

  if(condition) instruction;
is already considered good practice, couldn't it also be enforced via compiler pragma?


This is one of my most loathed developments in "best practices" lately. I see it everywhere and it just adds noise, since it doesn't change the functionality of the code. It's like adding a comment on every flow control statement. A concrete example is try/catch, which forces the user to add superfluous curly brackets.

In fairness, I have hit the "goto" style of bug he talks about in switch commands, when I forgot to add a break statement. But I can't remember a single time in my programming career when I added a line to an if/for/while and broke something.

I think people are wasting their energies on subjective topics like telling people to use spaces instead of tabs, or putting { on the same line as the flow control statement, or even telling people how much whitespace to use between () and where.

What we really need is a standardized formatter for any language that works like Google's Go Format. Programmers should not be spending time refactoring code just for looks. If anyone knows of a utility that does this and is easily configurable for personal taste, I would sure appreciate it. Being able to convert /* */ comments to // and back again would be a plus.


Formatters are usually language-specific, and there are formatters for most popular languages. See eg. clang-format [1] for C/C++, your IDE's reformat command for Java [2][3], PythonTidy [4] or AutoPep8 [5] for Python, or jsbeautifier [6], JSPretty [7], or Aptana [8] for JS.

[1] http://clang.llvm.org/docs/ClangFormat.html

[2] http://help.eclipse.org/kepler/index.jsp?topic=%2Forg.eclips...

[3] https://www.jetbrains.com/idea/webhelp/reformatting-source-c...

[4] https://pypi.python.org/pypi/PythonTidy/1.22

[5] https://pypi.python.org/pypi/autopep8/

[6] http://jsbeautifier.org/

[7] http://www.jspretty.com/

[8] http://www.aptana.com/products/studio3


>I see it everywhere and it just adds noise, since it doesn't change the functionality of the code.

I'm confused. It sounds like you're saying

    if (condition) { stuff(); other_stuff(); }
is the same as

    if (condition) stuff(); other_stuff();
because the brackets don't change the functionality, but they do. I might be misreading what you're saying though.


What he was saying should be obvious, since there is actual code in the comment he was replying to, and its not what you wrote.


> Brackets are the mathematical notation for a set, and having a set of instructions inside them makes perfect sense even if the set only has a single element.

The thing inside brackets in an if statement is a list of instructions, not a set, so there's no real reason that brackets use in mathematical set notation makes them natural for this use.


Yeah. Especially since those brackets are completely unrelated to set theory. An instruction can appear twice in a list of instructions, but not in a set. Also sets are unordered. The reason for the curly bracket choice was that most keyboards had a key for that back then.


A "list of instructions" is simply a "set of instructions" with an order. I was making a conceptual point, not a mathematical one. If you want to be pedantic, the instructions inside the brackets ultimately get converted to opcodes and their numeric arguments, which do form a set.


> A "list of instructions" is simply a "set of instructions" with an order.

No, its not, since an element can be repeated in a list but not in a set. To represent a list of instructions as a set it needs to be something like a set of (position, instruction) pairs -- like lines of code in old-style BASIC, with mandatory line numbers.

> If you want to be pedantic, the instructions inside the brackets ultimately get converted to opcodes and their numeric arguments, which do form a set.

No, they still don't. The "opcodes and arguments" are just instructions in a different language, and are still a list; you can have the same combination of opcode and arguments more than once, and the order still matters.

(If you include the offset in memory at which each instruction will be loaded along with the opcode and arguments making it up, then you'd have position-instruction pairs, and you could represent it as a set. But that's not what you are writing, and the fact that there is an equivalent set representation for something you are writing as a list doesn't make set delimiters natural delimiters for the thing written as a list.)


You can enforce it at any level. But the deeper you do that, the more people will be protected.

What's the point of having branching construct that uses different syntax for exactly one instruction than for many instructions?

And if you are crazy like that then why not have:

  if(condition);
that does nothing? Oh. Wait. We have that in C, Java (not C# though). I guess consistency is not the be all and end all of language design.


>that does nothing? Oh.

Doesn't that depend on what you consider your condition? createSomething=function that creates something and returns true if correctly created and false otherwise

if(createSomething());

did that do nothing?

edit: perhaps nothing useful since we can't know the state returned by createSomething.


> Brackets are the mathematical notation for a set, and having a set of instructions inside them makes perfect sense even if the set only has a single element.

Then you consider that sets are unordered, and then the analogy doesn't make any sense any longer since the order matters for instructions.

Personally I think brackets are so important in C-like languages because they're such a pain for me to write on my keyboard.


It's about both!

From a programmer point of view, the ideal is a language that doesn't let you make simple mistakes like the goto fail; bug.

From an engineering point of view, it's having the processes in place to make sure that when such bugs inevitably happen, they don't end up in the final product.

The reality is having both of these things would be ideal.


Yes! It's about creating systems to reduce defects.

Processes are systems; language and its constructs are systems. Both may contain methods to control variation and improve quality.

The point is to control variation. Nothing more. Any complete system that connects what we intend, with we tell the computer to do, with what the computer actually does, is good. We need to move in that direction.

Blaming an individual is pointless. Never was there a more consistent or uncontrollable source of variation than the human. We need to surround ourselves with systems that enforce quality, and that do not let us choose to introduce defects.

For this to take place, we have to first and foremost understand that this is the goal. That quality comes from the system and not the individual. Until then we will see only human error translated to computer error continually and unstoppably.

http://en.wikipedia.org/wiki/W._Edwards_Deming had it.


As the article said. Disasters have more than one cause, almost without exception.

Then, quality minded people just know that they must correct ALL the causes, because they may lead to another disaster in another combination (and that includes dealing with human errors). People without the quality mindset often decide that it's enough to fix ANY of the causes, because that'll avoid a repetition of the disaster.


But the problem the author described in the article can be solved just by better programming habit- always add braces even for a one line condition statement.

Or just put the one line statement in the same line with the conditional statement such as: if(condition) statement; so when you try to add a line next time, you will notice it was a one line if statement.

But yeah, not explicitly requiring braces for one line condition statement can give us more succinct code but does requires better programming pratice.


Programmer's habits are possibly less reliable than requirements enforced by the compiler.


Exactly. And these things can be enforced by style checkers, so you don't even need to have good habits.


Style checkers should be exactly that: tools that check code for correct style. If you have "stylistic" choices that are so important that you disallow their usage altogether because they might lead to critical bugs, then what you're really saying is that the language design is broken.

In fact, you're saying that you think that certain parts of the language design are so broken you'd rather work with a subset of the language that doesn't include those features rather than risk having them be misused. This very much puts the choice of language in question.


Not necessarily. "This language is the best available tool for the job. It would be even better if..." That doesn't necessarily make it a bad choice. (It may in fact be a bad choice, but more data is needed.)


Yes -- I admit to saying all of those things. I am also saying, I have other concerns that are more important, so what can I do to mitigate the problems by my language's design?


> And these things can be enforced by style checkers

At which point you've got a subset of the primary language, and might (if it were available and appropriate) as well be using a language that already had these good style things as language features.


The cost to adopting a style checker (if one exists for your language) is very low.

Even if one doesn't exist, they aren't very complex -- e.g. Facebook thought it was appropriate to create one for C++

https://code.facebook.com/posts/729709347050548/under-the-ho...

The costs are more around build process and compile time. Whereas the costs for wholesale language migration include the risk of not being able to ship or hire.


This.


knocte, in case you were wondering why you got downvoted (though by the time you read this you may be back to 1 or higher, who knows), on this forum there's a bit of pressure to avoid the "this" and "+1" comments. Some with high enough karma will downvote these sorts of comments when they come across them. To avoid those downvotes, but still post something, minimally add a sentence or two explaining why you're in agreement. For example:

This. With Ada, for instance, there's SPARK which is a restricted version of Ada with additional constructs (in Ada comments) for formally verifying the program. Spark, then, has grown to the point where it's almost its own language (though Ada compilers can handle it because of how the annotations are placed in comments).


    if (error_of_fifth_kind)
        {goto fail;}
        {goto fail;}
How do braces save you?


That's why you put your opening curly-braces on the same line as the if-statement, like the good lord intended!

    if(expr){
        expr;
    }


You don't allow naked braces either. They have to be tied to control flow.


The "improving our tools is no silver bullet, so let's keep using what we already have" thinking that this guy is criticizing is an excellent example of the fallacy of gray (http://lesswrong.com/lw/mm/the_fallacy_of_gray/). Of course, using Haskell doesn't prevent all bugs, and PHP doesn't always cause disasters. But it's easy to see which of these is the darker shade of gray. And there is a point where switching to safer (but not perfectly safe) tools is the right thing to do.


A lot of the Computational Thinking movements seem to stress that "Computer Science is more than just computers!" And that's true, we have a lot more to offer! But at the same time, it's so misleading because so much of our really cool stuff is wrapped up in being able to program. I mean, CS is about solving problems at scale, and computers are how we best solve problems at scale. We can teach lots of carefully crafted things without them, but it's always going to ring a little false, and we won't get students more than an inch-deep into why we're truly cool.


Of course the programming language matters. The problem is, that there are many ways in which it matters, and some of those come at the expense of others.

A language may be more concise, leading to shorter code (which is good), but do so using tricks and "magic" that is hard to follow, which makes it, eventually less prone to analysis by others (which is bad). A language could be very declarative, thus clearly communicating intent (which is good), but do so with leaky abstractions that remove it away from the computer's view of things, and introduce subtle, and severe bugs that, in order to catch, require expertise in exactly how the language is implemented (which is bad).

So while there are certainly languages which are categorically better than others (at least for some domains), there is no consensus on the right path to choose among the "better languages", and they all take completely opposing approaches. I'd even say that most languages used in new code written today are among those "better languages". So while in theory the choice of the programming language matters a lot, in practice -- not so much (as long as you choose one of the many good languages). I don't think that we have a language (or several) that is that much better at preventing bugs as to offset any other advantages other languages may have.


> "If you want the branch to consist of an atomic instruction, you write that instruction by itself"

No, I don't, I generally use curly braces for those too. So for me, the solution would be throwing an compile error when those are missing. Is that really all it takes to make a language "modern"?

I also don't understand the jab at semicolons, which I like, nor do I see how getting rid of brackets around the condition is really a net saving when you have to write "then" or "loop" instead. Apart from being twice as many characters to type, words distract me (much) more than symbols, and now that I think of it, I wonder how programming would be like with symbols for reserved keywords like "if" and "function", so the only text you see anywhere in your code would be quoted strings or variable/function/class names...

Anyway, I think when talking about distractions, one should be careful with claiming to speak for everybody (at least unless you did a whole lot of studying test subjects or something).


Having worked on both line oriented and free-form languages over the last 30 years, there's less opportunity for foolishness when 1 line = 1 statement. (so long as you can continue a long line with an obvious convention such as a trailing backslash, ampersand or perhaps a comma)

Line oriented syntax need not be limiting: Ruby, for example, does a good job of treating statements such as "if", "while", "case" as expressions that can return values.

Also, there's only one correct way to indent with "END" type keywords: the body is indented to the right of the start/end lines, and the start (FOR/IF/...) and end (END/DONE/...) lines are aligned.

Now if we could just get an "AGAIN (parameters...)" keyword for tail call elimination, coupled with almost mandatory immutability, in more languages... :-)


Even though I already only put one statement in each line, and indent as if indentation mattered, I still like semicolons and curly braces... just to have the option to make the occasional very long freak line more readable without having to change characters because of whitespace changes.


This is terribly wrong.

Correctness is brought about by ALL of your tools in hand. These tools include unit testing, processes like continuous integration and code review, and so on, in addition to language features such as its syntax and static analysis capabilities.

The job of the programmer is to understand all of your tools and to then use them conscientiously and use them well. There is NO tool a programmer can't shoot themselves with. There's no prima facie perfect tool. And the combination of your tools is a better thing to evaluate anyway. A nail isn't universally useful. With a Phillip's head screwdriver things get a little better; but with a hammer, you'll start moving.

A good architecture and intelligent, disciplined execution is WAY WAY WAY more important than the specific tools we use. Arguments like this one are bike shedding.


Yet, some tools are objectively worse than others. And there is such thing as the right tool for the job.

You can't just push the problem into lack of discipline. People make mistakes, if you stubbornly ignore that fact, you'll get defective products.


Agree 100%. If there's one rule the history teaches us over and over again in every aspect of our lives, is that you should never count on human discipline. You build your systems to work in spite of, not thanks to, human behaviour.


I'm not pushing the problem to lack of discipline. People are certainly fallible.

That's why static analysis is so useful. But there are many other factors to consider -- and sometimes dynamic languages are the better choice.

You have to balance a lot of factors when solving problems. Human fallibility is just one factor in the problem, but it is one among MANY.


It's clearly true that having a language which forced an explicit ending to an if block would have prevented the goto fail bug.

But is there any actual evidence that code written in modern languages have fewer bugs overall? Or is it all, "let's focus on this one example"?

As another commenter mentioned, the goto fail bug would have been utterly trivially caught by any unit test that even just tested that it reached the non-error case in any way (you don't even need to test the correctness of the non-error code).

I would like to see data before I believe that "errors that would have been prevented by non-C semantics" constitutes a significant fraction of all bugs, or that they aren't just replaced by bugs unique to the semantics of whatever replacement language you're using.


I think it is also a matter of culture.

The modern language communities tend to be more open to static code analysers and unit tests than the C community, even though they have lint since the early days.


How about forbidding newlines in a single-statement if and requiring a newline after the first statement after the if?

So this is allowed:

  if (foo) bar();
  baz()
But this isn't:

  if (foo) bar(); baz()
And this isn't:

  if (foo) 
  bar(); 
  baz()
Edit: formatting


Yes, I was thinking along similar lines. If your coding standard strictly prevents known dangerous habits (and it should be for security related code).

Insisting on putting all if body statements in braces would have prevented the Apple bug.

A pre-processor checking for violations should be possible too.

The heart bleed one seems much less related to language and more about design. The flaw was not spotting that a user supplied value was passed in to a dangerous function. Explicitly denoting what is trusted and what is not could possibly be a feature of a language out there, I don't know, but it's certainly something that could be architected in an existing language.


The formatter ate your indentation. Is this what you meant?

So this is allowed:

    if (foo) bar();

    baz()
But this isn't:

    if (foo)
        bar();
    baz()
And this isn't:

    if (foo) bar(); baz()


Yep.


Put two spaces before your literal code blocks, your newlines are getting eaten up by the formatter.


Did so thanks!


Code doesn't matter, what matters is what it does.

How usable, secure, stable or fast it is are properties of how well it accomplishes it's task.

There's an amazing presentation by the author of Clojure called Simple Made Easy. Since I couldn't just link people to a 1 hour presentation, I made some notes on it :

http://daemon.co.za/2014/03/simple-and-easy-vocabulary-to-de...

The code that we write he calls a construct, and our application is what he calls the artifact.

We need to evaluate constructs based on the complexity they create in the artifacts.

Using C, for instance, affects the complexity of our applications by introducing many more opportunities for human error.


I absolutely believe that languages matter and are the best hope for improving code quality and productivity overall. We need better languages.

But the way we get there is not to pick some small but critical bug that could be avoided by an arbitrary style change and declare that a language which does not suffer that stylistic pitfall is superior. The new language may have much worse flaws. You're just playing language flaw whack-a-mole at that point.

If we want to improve we have to get a sense of what types of bugs are most common across all programs and reason from a logical standpoint about how they may be prevented. This will solve orders of magnitude more problems than fiddling around with the syntax to minimize the number of typos people make.


There are other reasons why choice of language matters. If you need a simple web app, you're probably writing it in PHP or Ruby instead of C. But you'll likely use C if you're interfacing with hardware. A lot of apps that need high concurrency use Erlang. If you can write a quick Python script that solves a problem before you can even get Eclipse to load, then why would you even bother trying to write the solution in Java?

Language errors aside, it's pretty obvious that at least in some cases, the choice of programming language does matter.


Some types of errors might be easier to make in one language versus another, but a language that through syntax eliminate the possibility of all errors, is of course a ridiculous notion. Cherry picking particular error types that are avoidable in the authors language of choice, does not prove anything.

The concept of why code does not matter, comes from development management literature. It's not a case of actually meaning "I'm ashamed of the language and the techniques I use". That's an awfully developer-centered point of view.

The influential factors of a successful software project are mainly the quality of the people involved. Next product scope and from there a huge drop down to development process, and finally technology, i.e language.

It's been statistically shown that barring a crazily bad technology choice (Visual Basic for the space shuttle kind of bad), language has very little influence on the success of a project.

That's of course not a nice thing to hear for a developer who's convinced his language of choice is the one true language. Regardless, it's well established knowledge, gained years and years ago through statistical analysis of thousands of projects.


On the surface, this reasoning makes sense. Unfortunately, human coders are the ones writing code. This means the code might have bugs in it, regardless of the language you choose. While it would be great to invent a new language n+1 whenever the blame for bad code can be directed at programming language n, it is not likely that you will find coders that are willing to repeatedly take that ride.


Just use curly braces all the time. There's nothing in C or C++ stopping you from using curly braces around single line statements. It's just that you CAN omit them.

But having been bitten by this issue early on, I started always using curly braces. I think it's the better way to write C/C++. Frankly I think those who omit them are just lazy.


Another thing that enabled this bug is that it was a language that allows misleading indentation.

Programmers indent all code. By making indentation to be not meanigful in your language you are ignoring large part of how programmers read and write code and allow for many misunderstandings between programmer and compiler.


Is there evidence that misleading indentation had a role in enabling the bug?

You can make a typo, you can duplicate a line, you can misindent a line, you can put a brace in the wrong spot. I believe different languages simply trade forms of potential mistakes for other forms. Ultimately no language will magically fix or make bad code known; it has to be detected somehow. Tests, static analysis, manual review, whatever.


You are right. It wasn't the case for this exact bug. The source of this bug was probably the the design of the code editor that had function that duplicates line bound to a single keyboard shortcut right next to shortcut for saving file that every programmer uses roughly 1000 times a day. (What for? What's wrong with Home, Home, Shift+Down, Ctrl+C, Ctrl+V, Ctrl+V? Do you really need to duplicate single line in place that much so you need single key combination?).

However language that doesn't ignore indentation would make such error benign as duplicating the line wouldn't place code in completely different level of AST.

All programmers indent. Why so many languages happily ignore that?


That's a great question.

I'm a huge fan of the Go compiler's general stubbornness. Have unused imports? Compile error. Have unreferenced vars? Compile error. Perhaps misleading indentation should be another compile error.

Pain the ass? You bet; it's a feature. Ignore the whiny kids who insist their 'flow' is broken by having to insert semicolons. Most software work is maintenance, not new code.


"All programmers indent. Why so many languages happily ignore that?"

I've encountered places where isolated breaks in indentation style radically increased readability. Unfortunately, I can't recall them well enough to reproduce here or know whether I'd now have a better solution. It surprised me at the time.


Two consecutive gotos wouldn't cause a bug if the indentation mattered, it would just be unreachable code.

The programmer is already communicating intention with code style (scope), you might as well design the language to use this signal.


So Python and COBOL are our only options?


Python doesn't allow misleading indentation in the specific case at issue, but does allow misleading indentation (e.g., inside list comprehensions split over multiple lines).


Haskell also features a white-space syntax.


It's optional though, and leaves too much room for ambiguity IMO, which I find annoying since the point of significant white-space is maintaining consistency.

These are all equivalent:

    main = do
      something

    main =
      do
        something

    main =
      do something
And obviously, you can also not use `do` notation.


[deleted]


You don't really know that. The brace could've ended up between the two lines and you have the same bug. Been there, done that.


Surely the key point is that most of us read indentation first, it doesn't matter whether you are witting C with semicolons and curlies or ruby with no semicolons and "end"s or lisp with parens, what the programmer really reads first is the indentation. Those other things are sometimes checked afterwards.

Therefore there are two reasonable courses of action to prevent this kind of problem:

  * use automatic code indentation
  * use a whitespace significant language
The second is absolutely the better choice. You may disagree but you are wrong. This is not a matter of taste, it is a matter of programming language syntax actually reflecting what the programmer reads.


That's just not the whole story. Significant whitespace can itself cause issues - for example, it can be really easy to accidentally indent to the wrong depth when merging or copying and pasting Python code.

Ultimately, well-formed indentation and whitespace is important, as you say. But whitespace significance is not a panacea.


Well, personally I don't like whitespace significance... But then, I wonder if forbidding spaces* at the beginning of the lines wouldn't correct all the problems.

* Yes, mandatory tabs. I know lots and lots of people will disagree, those lots and lots of people are wrong. Spaces are ambiguous, and have different semantics on different context. In a flexible language that does not make much difference, but when you want whitespace to have a specific meaning, it does.


As far as I can tell (and I may not be the best judge of that, because I've used a dozen languages over the past 25 years and still fail to see any really significant difference other than language philosophy as in OO, functional, etc), this is not about language quality but language safety.

Those two are not necessarily the same, and some of the most elegant languages aren't particularly safe. Conversely, unlike people like to claim based on hearsay and no longer existing features, a badly designed language like PHP is definitely not less safe than Python or Ruby.

By the standard for "good" set by the author, no dynamically typed language would make the cut.


    if( DoSomething() )
        goto fail;
    else if(DoSomethingElse())
        goto fail;
        goto fail;
    else if(DoSomethingOtherThanElse())
        goto fail;
You get a syntax error at the final else-if. A better way would probably be:

    int err = 0;
    if(!err)
        err = DoSomething();
    if(!err)
        err = DoSomethingElse();
    if(!err)
        err = DoSomethingOtherThanElse();

    if(err) goto fail;
    
I would prefer chainable commands that abstracts out the error checking though.

    err = Chain(DoSomething).Then(DoSomethingElse).Then(DoSomethingOtherThanElse);


My take home is this. If we are going to do everything in our power to make these systems work better, then choosing or developing a language that is intentionally designed to draw attention to common security mistakes or prevent them structurally is a damned good thing to look in to. We will also do everything else in our power, but we had bettered put choosing or making that language on the list.


Alternatively use a whitespace sensitive language. Defining how to handle tab vs space is irritating, but there are plenty of solutions to that.


But Eiffel can't prevent the bug of swapping the order of two statements accidentally; it must be a bad language!

Come on. Cherry picking a weak feature of a language to invalidate the whole language is just disingenuous. All languages have strength and weakness. One has to weigh the positives and the negatives and decide whether it would work.


I found the article interesting, but I wonder why he didn't also discuss using 'switch/case' which would surely have been more appropriate than a succession of IF statements.

Of course you can screw things up with switch/case too, but in my limited experience that usually involves a design flaw rather than just a typo.


The article's suggestion is: "make the correct deduction: use a good programming language."

It would be enlightening to hear what Mr. Meyer or anyone else thinks would fit that bill on the Apple's platforms. Until the article actually provides a real solution, the article isn't making a point at all.


Calling out Java for this issue is BS. Java doesn't allow unreachable code. If Java had a 'goto' instruction, that code wouldn't compile because the latter code was unreachable.

The article is poorly researched rahrah for Eiffel. Eiffel is a good language, but not for the reasons the author states.


What language is he implying to be "good"?

It's obvious that he thinks C, C++, C# and Java are bad (due to syntax). The world mostly runs on those, so I guess we're all doomed. But if they are so "bad", then what does he consider "good"? I read it, but must have missed that part.


He's well known as the designer of Eiffel.


Thanks. I did not know that.


Yet nobody is making a better c and we are stuck with performance vs safety.

Hit the nail on the head about if although I disagree about the fancy syntax examples - enforcing scopes is enough - every decent coding standard I've worked with forbids one line ifs... as does common sense acquired 15 years ago...


I think you wind up with problems no matter what the tooling - for instance, a language that required that every line be provably terminating would never suffer from infinite loops, but whether the project using said language would ever halt remains to be proven. :)


BTW, am I the only one who thinks that duplicating a line of code is 1) not all that common in the first place and 2) something that should FUCKING JUMP OUT AT YOU IN CODE INSPECTION LIKE A BLOCK OF ALL CAPS FUCKING FILLED WITH PROFANITY?

srsly, this was not a subtle hard-to-find error.


That font - tiny and grey - was by far the hardest to read on Android (increasing the minimum font size to 20 pt fixed it).

It's considered good practice to brace if/loop clauses (unless on the same line) for this very reason. Not enforced, though I expect lint picks it up.


> Often, you will be told that programming languages do not matter much. What actually matters more is not clear; maybe tools, maybe methodology, maybe process.

In this case it could easily have been caught if they had full test coverage, whatever language was used, so yes.


About the single and multiple expression if-statements, I couldn't agree more. Everyone says I'm an idiot always assuming multiple expressions (it is ugly), but in the end it is safer.


I wonder if Eiffel could have gotten a more widespread use, if Bertrand Meyer had gotten a major OS vendor behind it, instead of trying to cater for the enterprise.


I looked at Eiffel seriously in the late nineties. My issue was interoperability and libraries. A little later, he tried Eiffel.NET, but I had moved on by then.

Eiffel's main selling points (safer, purer OO) were eventually credibly implemented in Java and C#, and people who wanted something else, wanted something really different.

Not sure anyone really wanted Eiffel's extensive (and sane) multiple inheritance support. I certainly wanted pre/post conditions and thread-safety.


What attracted me to Eiffel, in the mid nineties, was the whole concept of design by contract, better OO than C++, compilation to native code, GC and belonging to the Pascal family of languages.

What putted me off was Eiffel Studio's price as university student, without knowing if the language would have any industry influence.


Clojure provides a nice equivalent of pre/post conditions in core.contracts, check it out perhaps.


Eiffel's contracts are really compelling, I wish there was a reasonable, non-hacky way to implement them in more mainstream languages.


I wouldn't say that clojure is mainstream, but their implementation is reasonable and makes sense in the clojure idiom (just a meta-data map on the function)


Turns out there's a sort of reasonable way to implement them in JavaScript, it's definitely hacky though - http://codemix.github.io/contractual/try.html


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

Search: