Code Runs on People
rachelbythebay.com
rachelbythebay.com
For example, I'd argue that using C/C++ at all is probably "too clever" - humans are good at simulating the happy path, but very bad at simulating all the possible different ways such code might actually be executed. But all too often people are seen as smart and rewarded for "using such an efficient low-level language".
Conversely, I'd argue that exposing a business constraint as an algebraic datatype that forces you to use it correctly reduces the cleverness required to understand the code.
But I'm sure there'll be someone here who thinks the exact opposite, and bringing "cleverness" into the conversation doesn't actually bring us any closer to agreement.
100% this. Code runs on people but people differ dramatically. A more compact solution that leans on some more advanced language features might be totally acceptable in a team of people fluent with the language. The same problem in a team of generalists working across different tech stacks day-to-day might benefit from a more verbose solution.
As with most things in software it depends on context.
If you have a rich vocabulary and so does your audience, you can explain an idea succinctly. Imagine a chain of function compositions in one line, very declarative. You grasp it near immediately or else its very difficult.
If you don't, you use simpler words and need more of them. Imagine a few nested for loops in something like Go that you need to execute in your head to realize what's going on, but anyone can figure it out given a little time.
People frequently talk past each other on this topic. It can be very frustrating.
On the opposite end of the spectrum, you could put Scala. There are already a few ways to use Scala as a language: Java with better types, OO all the way, functional all the way. And then there are ecosystems. Zio, cats, Akka. All of that means that for two people writing Scala code, the shared mental model is going to be small unless they use it the exact same way.
You can infer some things from that. Languages with big standard libraries that are somewhat good will allow people to share a bigger part of their mental model compared to languages with bad standard libraries (people will create replacements) or languages with very small standard libraries. Languages with a lot of features that are actually used will have a low amount of shared mental models, but also languages with not many features as people will reimplement things themselves.
C++ fans always claim this is possible, but can never say how to determine whether any given codebase follows their rules, or give examples of e.g. popular open-source libraries that follow that approach. So I've stopped believing in it, personally.
> This helps reduce what the author calls "complicated, Klein-bottle-wannabe tricks", labyrinthine Java classes of GC goodness where you don't know where the code begins/ends.
I find that claim extremely dubious. The problems of such code are very rarely due to not having clear directions on any local ownership relationship, and lifetimes are not actually visible in C++ in any case.
OK, so I've inherited a Single File App with multi-screen a multi screen main() function. I can tell what it's doing at a glance, except that there is a bug report, and something may be wrong.
First instinct "make the problem smaller". But how?
People who put effort into avoiding effort frequently succeed in moving effort.
That said, if you're stuck with C/C++, there are still many grades of complexity within that that can be tackled. Write boring code, instead of writing something complicated to save a few lines of code.
> “ If it takes an hour to figure out what's going on, well, that's an hour that wasn't spent doing something else more useful and interesting.”
What exactly is “doing something else more useful”?
As a software engineer your main job (yes I know talking to stakeholders and blah) is to write and to read code. If as a software developer you optimise for writing as little code as possible and not having to spend much time reading code either then what exactly are you working on? It’s great if you’re a solo entrepreneur who wants to optimise their own time, but for most devs reading code for an hour is some of the best use of their time in their daily job.
Minimizing code read/written _per unit of work_. The less code you have to work with for a particular task, the more tasks you can accomplish in a fixed time frame. I'd much rather read and understand three tasks in an hour than only one.
However, you might instead pack those five simple lines into a function that you give a good descriptive name. Now although the computational units are unchanged, the work now sometimes only costs one comprehension unit if the programmer working with the code understands at a sufficient-for-their-needs level what's going on from the function name/signature without needing to delve into implementation details. The difference between that and the terse clever line is that the terse clever line is still implementation whereas the function is descriptive abstraction.
This is of course very rough pretend-math and there are plenty of real-world cases where function names aren't descriptive, but hopefully conveys the idea of how you can reduce the code you need to read without needing to resort to cleverness.
On a documenting perspective, having the well named function contain five lines of simple operations will look better than the terse 1-in-5 line.
To me the catch is that the reader/reviewer will need to trust the function actually does what it is named after. Otherwise they'll need to go look at the 5 lines anyway, and it might be more costly to go navigate to the function and come back than reading the same content inline.
With that 5-in-1 terser (that's such an horrible compression ratio BTW) line, there is no need for blind trust. It might need more effort to understand, but you also don't have the burden to associate the name of the function with what it actually does (even aptly named functions will still have some gap with what they do or don't). I'd hold my head while decrypting that 5-for-1 bundle while cursing the world, but I'd also see that as a very pragmatic and reasonable choice if it relies only on standard libraries and don't abuse undefined or deprecated behaviors.
BTW I'd still go with a separate function containing the longer code if it helps for tests for instance. As everything and as you point out, real-world cases are always more complex and nuanced.
Is she ranting against "clever" code ?
Or is it about "wicked, nasty, complicated, Klein-bottle-wannabe tricks", which wouldn't fit a "clever" category IMO ?
But she mentions "how far they read into the spec", so these are language features she doesn't know about ?
Oh, but "Nobody will ever have more state about the code than the original author" so it's not about syntax or tricks, but code states and complexity ?
What is there to take from this rant aside from her not liking her coworkers' code each in every possible ways ? I am really not sure to get it.
My grief is that, given time constraints, I'll put in a list comprehension rather than an explicit loop, where appropriate.
Am elevating the task above n00b level? Sure. Welcome to the real world.
Problem is, I've seen people with years of experience put this crap in. When you show them how much better it can be, they don't tend to take it well. It implies they're not the "senior engineers" their resumes claim.
Senior is just a job title / pay grade, it does not mean someone is a good developer. Thankfully I've never had a label like junior/medior/senior attached to me, because while I have plenty of experience I don't think the job descriptions for senior developers applies to me. Probably impostor syndrome but still.
So, is that a "clever" solution that should be avoided because it's "clever" or is it a best practice because it actually solves real problems? Problems which have cost me hours of debugging that could have been avoided if I'd just used the "clever" solution. For me it's the latter.
In fact, speaking to the OP, it also encodes the original programmer's logic since without them, the new programmer has to know the 4 to 9 places they'd need to edit to add to the list where as with them they only need to know 1.
Maybe most uses of lexical macros could be done with hygienic macros / macro replacement bodies that are fully formed expressions (AST nodes), but not all of them.
For those macros, really the "hygienic" doesn't matter half as much as people pretend. Don't do complex macros where the "hygienic" is required. Don't generate code, write code. If you need to generate syntax to remove boilerplate, then sprinkle a few macros in. But to generate syntax, in general you'll need a lexical macro system.
Maybe you could clarify by posting a better version of what GGP suggested.
Just as an example: everyone would agree that react is more. Implied than jquery as it has more levels of indirections and abstractions, but it’s strong point has never been to show the cleverness of its authors but to solve state management issues and implement a serie of features otherwise not reachable from previous paradigms. Also for loops where more complex than gotos as they require state (invariant), but today for loops and other forms of loops are widely understood and teach during formal education.
New abstractions and indirections aren’t always necessary and when put in place they need additional documentation and training, but nothing of that was mention in the article.
I would add "including myself". It always happens when I go back to old code, even if it's my own code.
It's not that I don't understand my own code, or the lack of comments. But while coding (at least it happens to me) I have a global awareness state of what is going on, what's missing, what's failing, where I want to go, etc. You are plugged.
Losing and recovering that state is pretty hard and that's why (long) interrupts are so annoying.
A simpler language like C helps me recovering that state quickly. A mess with templates or 1000 levels of abstraction takes way longer.
dogma, in all circumstances, leads to intellectual laziness at best and malpractice as worst.
trying to express complicated ideas using simple constructs can result in complicated, verbose and very difficult to understand use of simple constructs. this is why we have complicated constructs, to simplify the expression of complicated ideas.