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?
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.
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.
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.
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
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.]
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.
> 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.
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.
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.