Those Who Say Code Does Not Matter
cacm.acm.org
cacm.acm.org
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
EDIT: with -Werror it will make this bug an error but not necessarily the class of bugs.
clang -Wunreachable-code does, though.
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.
[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
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)
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.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)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.
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.
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 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)
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).
(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) 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.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.
- 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.
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.
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.
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.
Did you finish the article? This is exactly the false dichotomy the article was about.
It's the language AND the processes.
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.
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?
Ruby does what Eiffel does also. And it got the idea from it.
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.
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.
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.
I think the most readable code has no shortcuts and no tricks. I'll take unambiguous over concise or 'beautiful' any day.
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.
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'.
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.
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 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.
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.
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.
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.
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) {
...
} 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. if (llama != true)
instead?Or maybe you just use a not function:
if (not(llama)) 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.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.
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.
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".
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.
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.
http://www.newscientist.com/article/dn25068-wikipediasize-ma...
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.
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.
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.
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.
If it were a switch statement, you'd get a warning for code that will never be executed.
Not on my Java compiler (Android toolkit and Eclipse). I see it as a warning throughout the code base I work on. :-(
public int foo(int i) {
return 2;
return i;
}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.
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.
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.A duplicated line that isn't idempotent and that doesn't jump out of the current scope would be problematic.
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;
}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.
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.
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.
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.
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 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.
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.
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.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. if error_of_fifth_kind
then goto fail end
then goto fail end
Which is simply a syntax error.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[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.] 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).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.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.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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
'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.
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.
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.
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 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.
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.
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."
if (error_of_fifth_kind)
goto fail;
goto fail;
if (error_of_sixth_kind) goto fail;
The_truly_important_code_handling_non_erroneous_caseMy 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.
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/
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.
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.
Take it as the quite anecdotal evidence it is, but to me it explains a lot.
Since
if(condition){ instruction; }
instead of if(condition) instruction;
is already considered good practice, couldn't it also be enforced via compiler pragma?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.
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.)
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.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.
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.
[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
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.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.
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.
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.
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.
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.
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.
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).
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.
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.
if (error_of_fifth_kind)
{goto fail;}
{goto fail;}
How do braces save you? if(expr){
expr;
}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.
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).
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... :-)
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.
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.
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.
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.
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.
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: formattingInsisting 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.
So this is allowed:
if (foo) bar();
baz()
But this isn't: if (foo)
bar();
baz()
And this isn't: if (foo) bar(); baz()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.
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.
Language errors aside, it's pretty obvious that at least in some cases, the choice of programming language does matter.
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.
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.
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.
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.
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?
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.
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.
The programmer is already communicating intention with code style (scope), you might as well design the language to use this signal.
These are all equivalent:
main = do
something
main =
do
something
main =
do something
And obviously, you can also not use `do` notation.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.Ultimately, well-formed indentation and whitespace is important, as you say. But whitespace significance is not a panacea.
* 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.
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);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.
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.
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.
The article is poorly researched rahrah for Eiffel. Eiffel is a good language, but not for the reasons the author states.
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.
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...
srsly, this was not a subtle hard-to-find error.
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.
In this case it could easily have been caught if they had full test coverage, whatever language was used, so yes.
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 putted me off was Eiffel Studio's price as university student, without knowing if the language would have any industry influence.
If you were writing OpenSSL in it, and you decided to use system calls then you'd be in the same boat. If you OO modeled it with classes, then you might be more safe. Not sure what it does if you model a bunch of bytes as an array, allocate it and don't manually wipe.
What do you mean by timing attacks? If you mean timing the string comparison on a login attempt, then no way -- no language forces you not to compare byte-by-byte, right? I doubt they even discourage it.
Where I'm currently at, my manager wanted code coverage but needed someone to do it. Since I'm just that guy, I setup the infrastructure for iOS. Android, you're such a pain in the ass to setup for (rooted device, or the dog-slow emulator, and it still doesn't give me results) I'm surprised anyone does code coverage for Android. I can't remember the name of the python tool, but holy smokes it's slick and painless.
So tooling quality varies, which might have a lot to do with the lack of coverage testing. I long for the days of the coverage tool Tim Paterson wrote (yeah, that Tim Paterson). No profiling, it would inject the coverage DLL at runtime. Then, as you used the program, it would log coverage on the fly. So program running on one monitor, coverage log on the other, and start clicking. Can't remember the name of it (it's no longer sold by Paterson Technologies), but damn it was slick.
In my project, that could could have never gotten into production, because the tools would have stopped us, even without a single unit tests.
And hopefully nobody else either.
Beware the "experienced" developer who skips braces "because it's more elegant" or "because it's unnecessary typing".