This is from C.A.R Hoare's "Prospects for a better programming language" (1972) [0]:
> It is on production runs that the security is most required, since it is the results of production runs that will actually be trusted as the basis of actions such as expenditure of money and perhaps even lives. The strategy now recommended to many programmers is equivalent to that of a sailor who wears a lifejacket during his training on dry land but takes it off when he is sailing his boat on the sea. It is small wonder that computers acquire a bad reputation when programmed in accordance with this common policy.
It is also quoted by Donald Knuth in "Structured programming with goto statements" (1974) [1] ( which incidentally is also the source of the quote about premature optimization):
> He [Tony Hoare] points out quite correctly that the current practice of compiling subscript range checks into the machine code while a program is being tested, then suppressing the check during production runs, is like a sailor who wears his life preserver while training on land but leaves it behind when he sails!
How would errdefer work in the general union setting?
Having errors as a first class construct in the language allows things like errdefer to be very simple and easy to use. It looks needlessly specialised at first, but I think it’s actually a really good design.
> Clean code / readable code / whatever you want to call it is often at odds with performance.
I disagree; or rather, I'd put it the other way around by saying that often you can get both clean code (by some metric) and reasonable performance. The patterns in e.g. the book by Robert Martin doesn't give you either, though.
> And for most enterprise projects it just doesn't matter.
It matters for the users. I use software that is slow for no good reason, and I'd like to live in a world where this is not the case.
> At some point in my programmer career I figured out that optimizing for human comprehension, a.k.a. "clean code", is a valid goal.
Nobody is arguing against comprehensible code. Caseys video is not about clean code in general, but "Clean Code" as presented e.g. in the book by Robert Martin, which contains a bunch of code I would not classify as "clean" by any metric.
> But as others pointed out, he focus on squeezing every bit of performance in the context of real-time video game logic
He doesn't, though. The "Clean Code, Horrible Performance" video is an excerpt from his course called "Performance-Aware Programming", where he explicitly says many times that the course isn't at all about "optimization", but merely being "performance aware". This isn't highlighted in the video though, so getting the full context is difficult.
> The problem with this saying is that many people wrongly interpret it as “early optimization is the root of all evil.” In fact, writing fast software from the start can be hugely beneficial.
I sat down and read the original paper a few years back with the same intent as the author here, and afterwards I listed a couple of quotes that I found interesting [0]. It didn't really get any traction [1], but maybe someone here will find it interesting.
> This is perhaps too big a burden on a user. Some might say they rather not have the compositionality unless it's guaranteed to be correct.
I think this is it. Or rather, I think to some people, myself included, compositionality should imply correctness. If you /can/ compose things, but the result is wrong, can you really say that you've got composition?
How useful it is really to be able to plug in your own types in other peoples libraries if you have to trace through the execution of that library, figure out which libraries they transitively use, to ensure that all of your instantiations are sound? How do you even test this properly?
It's a really hard problem, and from what I can tell, Julia gives you no tools to deal with this.
[0] is probably relevant here, although I'm not sure I share the positive outlook.
That's not what this is, and if you'd bothered to look for more than a second, you'd seen that too. All the claims listed are very concrete claims that are possible to falsify, unlike "Thing good!", and the research (which, admittedly isn't directly linked, so it might be hard to find what research they're referring to) shows that this is either not true, or that the research is inconclusive. It is important not to take these statements for granted if they cannot be shown to be true; that's simply cargo-culting.
The fact that they're all cubic isn't really the notable part of the runtime of computing the different decompositions, because the constants involved are really different. In practice, a common reason for computing many of these decompositions is to solve a linear system `Ax=b`, because with the decomposition in hand it is really easy to solve the whole system (using e.g. backsubstitution). For instance, with C++s Eigen, look at the 100x100 column of [1], and we can see that there's orders of magnitude difference between the fast and slow approaches. THey're all still cubic, sure, but we're talking 168x difference here.
(of course, it's not so clear cut, since robustness varies, not all methods are applicable, and the benchmark is for solving the system, not computing the decomposition, but overall, knowledge of which decomposition is fast and which is not is absolutely crucial to practitioners)
Why do you think people should unconditionally accept code into their own codebase? Your wording also suggests that you think the maintainers are somehow leading you on to doing more work. Have you considered how much work doing proper review is?
Good post, and a good list. Here's a mind dump on some of the items:
Right off the bat:
> Well, I'm certainly not a world-class expert, but I have put my 10,000 hours of deliberate practice into programming.
Okay, I don't know the authors exact history here, but I seriously doubt that they've had anything that's even close to 10k hours of deliberate practice of programming. Why? Because programmers basically /never/ do any deliberate practice. We don't have anything that's even close to what a piano player does when they practice fingersetting, chord transition, scales, or the like.
I'm not even sure what this would look like, but I suspect that it is fundamentally different. Most musical instruments have a physical or mechanical side to them that is completely disjoint from the "musical" part of it.
For instance knowing how to play a cool solo a guitar (meaning knowing which notes to pick and how long to hold them for), and being able to have your left hand fingers in the right positions in the right times and your right hand picking the right strings has almost nothing to do with each other.
The way it usually works (I'm an amateur player who hasn't played in years, so maybe some professionals can take over this analogy) is that you practice very slowly and ramp up quicker and quicker untill "magically" your muscle memory takes over and it all kind of just happens.
The result is that you don't really pick each indivitual note anymore, you kinda just play that part (when practicing you've decomposed the whole piece into small parts), and each part is atomic to you. Starting in the middle of a part doesn't quite work.
The only programming analogy I can think of (from the top of my head) is how effortless it feels to write `for (int i = 0; i < n; i++)`, or something like it. But we only spend a negligible time on actually writing out the lines of code when programming, so while being able to effortlessly write out a bunch of standard `for` loops doesn't really push the needle in any way.
For a performing musicial, of course, this is very different, because the /have/ to play it right! Imagine having to write all your `for` loops in sync with the beat of music.
> 4. Syntactic sugar is usually bad.
It's hard to really get exactly what this means, because if taken literally it is obviously not true. Pretty much any loop or control flow structure is just syntax over goto, but I assume the author doesn't really think we should manually write out the gotos. Having recognizable patterns in a codebase is good. Cramming in some esoteric syntax quirk of your language into your code because it technically fits is not good.
> 5. Simple is hard.
Again, difficult to get something concrete out of this since it reads as a zen mantra. If they mean that finding a simple solution to a difficult problem is hard then I agree. If they mean that a system being simple means it is hard to use then I disagree.
> 7. Know the internals of the most used ones like git and bash (I can get out of the most gnarly git rebase or merge).
Knowing the internals of e.g. `bash` is too far for me. I'm happy if I even knowing the surface of it! I guess they mean that cargo culting these tools usually doesn't lead you anywhere good and that invesing the time into them will pay off, as opposed to "cookbooking" it, like with git in that xkcd.
> 9. Only learn from the best. So when I was learning Go, I read the standard library.
I think this is important, but it is very difficult for programmers to do this,
> 10. If it looks ugly, it is most likely a terrible mistake.
I don't think this is good advice because it suggests that all code should "look nice" and if it doesn't it's just wrong. In my experience the number one metric (if you can even call it that) one should be concerned about is flexibility. If your code is "rigid" and difficult to change it really doesn't matter a whole lot how nice it looks, because, presumably, you're not really sure exactly what you're doing, and the code you write will probably not last very long in the codebase. It's hard to put a number on this, but I'm pretty sure most of the code I write does not last a month. All the time I'd spend worrying about whether this code was "ugly" is effectively wasted, because in a month it will not exist any more. Make code easy to replace.
> 11. If you have to write a comment that isn't a docstring, it should probably be refactored.
I also don't agree with this. I think it's completely reasonable to have comments in function bodies expalining what this part of the function does. See Jon Blow's related blog post[0]. Docstrings are for users of a function, but you might also want comments for the people reading the function body. Sometimes this can be avoided by writing Good Code, sometimes not.
> 12. If you don't understand how your program runs in production, you don't understand the program itself.
Very true; I'd phrase it as "understanding the problem", in Mike Acton lingo [1]. If you don't understand The Problem, then you don't understand what your program, which is (a part of) the solution to The Problem, does either.
> 14, 15, 16
This is good advice, and I imagine it's difficult for beginners (or novices) to make sense of what to do here; some circles advocate for always pulling in dependencies, others for always inlining 3rd party trees in your own codebase. I actually think this is one of the parts of programming as a field that has the potential for some kind of a paradigm shift. I'm not convinced that in 25 years we'll be still trying to write generic "libraries" for others to use, and other people will download semvers of libraries to ensure compatibility. There has to be a better way.
> 17. Know when to break the rules. For rules like "don't repeat yourself,"
Thank you! DRY is probably the worst of the n-letter programming mantras. DO repeat yourself! Do whatever you need to quickly get a better understanding of your problem!
> 18. Organizing your code into modules, packages, and functions is important. Knowing where API boundaries will materialize is an art.
This is also very true. Given an API, filling in the function bodies is often close to trivial, and coming up with the API in the first place is indeed an art.
> An extremely simple example is cross compiling. In Rust, usize is dependent on the architecture you're compiling for. A very simple "just compile and run the program, get the answer, and use it" implementation of compile-time execution will produce a usize of the size of the host, not the target.
I get that there are non-obvious problems here, and as you say, this problem specifically has an easy fix, but I'd just like to note how Zig does this:
Modulo the `.{` weirdness, this probably looks familiar. By default, this prints out 8 and 8 on my system, but if I cross-compile to a 32-bit target, it prints 4 and 4.
> It's just that, when you start applying this super rigorously, you end up in weird places. How can you trust any behavior in a language without a specification?
I think this is a social issue for me; if rustc decides one day to change its behavior under my feet it feels like it's my fault for not having written proper Rust in the first place (even if the behavior wasn't properly defined in the first place), but if there's crazy things going on in the compiler due to bugs (that we didn't find, because proofs are hard), then that's not really my fault, in a sense. And of course, if my CPU decides to run my program wrong, that can't really be blamed on me. The end result in these three cases are all the same: the program didn't run as expected, but the blame (I don't want to point fingers, but this is the best word I could come up with) is different, and the probability of this happening is vastly different. I've never hit a CPU bug, but in the little unsafe Rust code I have written, I've had behavior change with a compiler update, which I'm sure is because I hit UB.
And for what it's worth, I would greatly prefer Rust having a proper spec, even if that would increase turnaround time for the language evolution, just to ensure that everyone really is on the same page with respect to what the language really should and shouldn't do. I realize that Rust would rather be careful and make sure that the decisions made are the right ones. I think it's a fair trade-off, but I'm not sure I would have made it, if it were up to me.
> It is on production runs that the security is most required, since it is the results of production runs that will actually be trusted as the basis of actions such as expenditure of money and perhaps even lives. The strategy now recommended to many programmers is equivalent to that of a sailor who wears a lifejacket during his training on dry land but takes it off when he is sailing his boat on the sea. It is small wonder that computers acquire a bad reputation when programmed in accordance with this common policy.
It is also quoted by Donald Knuth in "Structured programming with goto statements" (1974) [1] ( which incidentally is also the source of the quote about premature optimization):
> He [Tony Hoare] points out quite correctly that the current practice of compiling subscript range checks into the machine code while a program is being tested, then suppressing the check during production runs, is like a sailor who wears his life preserver while training on land but leaves it behind when he sails!
[0]: https://ora.ox.ac.uk/objects/uuid:dff9483b-e72f-4599-bf90-76... p. 341
[1]: https://dl.acm.org/doi/pdf/10.1145/356635.356640 p. 269