It lacks coherence and overview and structure. I'm sure you'd agree that taking related "and thens" and wrapping them up in a procedure would be helpful. The next step after that is coming up with more informative relations than "and then".
A description like
"I went off the platform and then on the train and then I looked at the nearest seat and then I saw it was occupied and then I looked at the next seat and then I saw that was occupied too snd then I repeated that and then I saw an empty seat and then I walked toward it and then I sat down and then ..."
is begging for structure by wrapping up into procedures
"I boarded the train, and then I located the closest free seat, and then I sat down in it ..."
with the obvious definitions. That can in turn can be further improved to
"After boarding the train I sat down in the first free seat ..."
where the relations between the things are more useful than "and then".
Just how I program after 10 years of practice. Tables and procedures mostly :-). No considerable developments in programming languages needed as far as I'm concerned.
How about a language that lets you put your procedures in tables?
I often wish for a more rich way to layout a complex 2-level decision tree. Dividing it up into sub-functions/classes scatters the logic, while putting it all together is too heavy. However a 2D decision table would often be perfect.
I remember reading someone’s proposal for 2D decision tables in code quite a while ago maybe 15 years.
I’ve just tracked it down. Here’s something about it by the same author Roedy Green (author of How to Write Unmaintainable Code)
http://mindprod.com/project/scid.html (look for the words ‘decision table’)
Any language that lets you put anything in tables lets you put procedures in tables. Most languages do not offer reflection for their own syntactic elements (probably for good reason), however if you need that you might want to make your own language anyways...
# now call the reqd. function via string s read from somewhere: keyboard, file, etc. ...
t[s]() # error handling omitted
Kidding apart:
>Dividing it up into sub-functions/classes scatters the logic
What is the issue with scattering the logic? if you break up your problem/solution into different logical units, with good judgement as to the points of breakup (i.e. coupling, cohesion, etc.), then what is the issue? Not clear.
Well, no. Each individual piece in the child-like code might be easy to understand, but you lose the forest for the trees. You, as the reader, have to reconstruct how all the simple (simplistic?) pieces fit together to accomplish, well, whatever the code is supposed to do. Compared to over-abstracted code, you've just shifted the difficulty from understanding an explicit structure created by the programmer to mentally extracting the implicit structure of the code.
A good point of comparison is technical writing. Think back to the best textbooks you read: they didn't have the flourishes and sophistication of literary writing, but they still had a significantly richer structure and organization than, say, a children's book. They needed this richer structure to get their ideas across effectively. You can't write a coherent textbook in the style of Where's Spot?
The same goes for code. You can be too clever, sure, but you can also not be clever enough. On a scale from "Salman Rushdie" to "Clifford the Big Red Dog", you don't want to be at either extreme.
Is this hard? Yes! Writing readable code is a skill unto itself, distinct from writing working code. People don't always agree on what is and isn't readable, but that's true for writing too. Still a worthy goal, just one that doesn't lend itself to simple rules. You just have to build up the right skills from experience.
Haha, great example—that's exactly what I want from my bash scripts too :). Just a simple list of commands.
Salman Rushdie style is reserved for libraries that make a massive impact. Learning abstractions like that is like learning a new part of the language so it has to really pay off. Rare but not impossible.
The Haskell lens[1] library is a great example. It's almost too clever for its own good and learning it is like learning a new programming language, but it is such an improvement for the entire codebase it's worth it. I use it widely at work and it pays for its own difficulty almost immediately. (It's also important that it can't achieve some of its core functionality without the "clever" things it does.)
The documentation could use a bit of work though :/.
I've always thought Haskell was later James Joyce clever :)
I'm not sure if it is really appropriate for any code to be more than Robert Frost clever, sometimes think that should be clever enough for anyone. But this is only a periodic opinion.
on edit: I guess that means I'm saying no language needs to be more clever than Python.
We don't talk about c-groups and compressed files with metadata, we talk about containers and images. We don't talk about individual machines running a number of highly specialized daemons with configuration files, cryptographic keys, and networks-in-networks, we talk about clusters and pods and gateways.
Notwithstanding that much of the code which has built up these sweeping generalizations is built of a tangle of red dogs, the abstractions are very high level and mean we rarely have to discuss infrastructure at a very low level.
Of course, there's a twist to that as well, that hearkens back to an earlier statement: the understanding have been limited to an extremely high level as well. The number of people who can troubleshoot a container cluster is small, and growing smaller. We've intentionally created cliff notes of "Salman Rushdie" novels and believe that's all the populace needs.
This style has quite high cognitive load, how many 'and then's can you hold in you short term memory until you loose the context completely?
I believe the the art of programming is actually writing the code in a way that transitions the reader through context changes keeping the amount of context necessary to understand what's going on to a minimum.
I strongly agree. I usually express this as "minimizing complexity", but I like the "guiding through context changes" phrasing.
One of the best things about humans is how easy it is for us to work at multiple levels of abstraction simultaneously, as long as we're guided to and between them properly. Good code - hell, good engineering - exploits this to create things which otherwise wouldn't fit in a person's head.
Or, how little can you make the reader have to think and still be able to really understand the code? There's a sweet spot there, where either simpler (or more simplistic) or more complicated is a net loss to the reader.
The problem is, the location of the sweet spot depends on the reader (or at least, on their familiarity with the idioms used...)
It's ok for short code/stories, but how many pages of that would you read? There's often a point where there just are better structural choices than the crude linear approach -- hence, OOP, FP, RP and everything else.
Programming is specialized work (I'd call it a discipline, but then we'd have to argue whether it's more an engineering discipline or art discipline). You have to gain knowledge and experience to understand it.
A bridge blueprint is not understandable for all. It is understandable for all who took time and effort to gain education necessary to understand it.
(The motivation between this comment is to counter the increasingly noticeable sentiment in our industry that everything needs to be dumbed down to the lowest common denominator; it's an understandable sentiment if you're selling something and want the biggest market, but it goes against what's needed to build great things.)
The thing is, people have been building bridges for thousands of years. What a bridge is and what it does changes very slowly. Almost all the specialised knowledge that bridge builders have is about _how_ to build a good bridge.
Software is not like that. We may be experts in programming languages, algorithms and data structures, but the things we create and the problems we solve keep changing all the time.
We're not usually experts in these problem domains and in some cases there are no such experts at all. So our code needs to be a lot more descriptive and written for readers that may not be very familiar with the problem at hand.
We are more like lawyers supporting law makers or even like law makers themselves.
We think bridges are a very well explored domain. Then something like the Tacoma Narrows incident happens, and we realize how much of a fallacy that really is.
Bridges are unique to their locations and their scale: what works over a creek will not work over a ravine. What works for a pedestrian will not work for a car. What works for a car will not work for a train. What works for a train will not work for a marching army.
The Tacoma bridge collapse has a wikipedia entry exactly because bridge building is a very well explored problem and bridges don't usually collapse.
I don't doubt that each bridge comes with unique challenges. But the purpose, user interface and constraints of bridges have remained stable enough for long enough to allow specialisation. That is not the case in many areas of software development.
>everything needs to be dumbed down to the lowest common denominator
That kind of ought to be the default for most code. However, it's the natural tendency of code to become abstruse and unnecessarily complicated.
Since market forces will often tend to exploit rather than reward the work done by programmers to "dumb their code down" and inadvertently reward "clever magic understood by a limited number of experts" there's more of an incentive to amplify this effect than to work against it - especially where money is involved.
I'm a bit conflicted because on the one hand I don't want to help developer compensation get ground down by billionaires or other business owners with an entitlement complex who consider developers to be "spoiled brats", but I do prefer working with clean, straightforward code.
There are times when you simply have hard problems.
For example, take code generation (I actually have in mind a SQL query generator, but a compiler back end would do just as well). The problem is already abstract - the input is code, you're naturally writing code that manipulates code, like you would with macros or reflection. The problem is complex: you can generate simple code, but it won't perform well. There's irreducible complexity here that cannot be simplified away.
You'd also like the code generation itself to be fast. That puts a limit on how much you can break it up into parts that can be understood individually and in isolation. Optimization works in opposition to abstraction because the optimal often requires steps that span multiple abstraction layers, and often requires multiple instances of the specific over few instances of the general.
Personally, I think the two biggest reasons code becomes unnecessarily complicated these days are (a) testing and (b) local modifications. Unit testing in particular encourages over-parametrization so that dependencies can be replaced for the purposes of testing; while normal software maintenance under commercial pressure leads to local modification because nobody has time to understand the whole. People instead make conservative local changes by adding parameters, extra if-statements, local lookup maps, etc.
I've found elegant solutions are often on the other side of a hill from over-engineering. You write specific solutions, then you climb the a hill of abstraction as you add layers, indirections, parameters etc., until you reach a summit, where you can see the whole, and can then start boiling things back down again, only retaining abstraction where it's actually necessary, or perhaps replacing multiple abstractions with a single more powerful abstraction (I've found this to happen a lot with monads; another one is converting control flow into data flow).
>There are times when you simply have hard problems.
>
>For example, take code generation (I actually have in mind a SQL query generator, but a compiler back end would do just as well). The problem is already abstract - the input is code, you're naturally writing code that manipulates code, like you would with macros or reflection. The problem is complex: you can generate simple code, but it won't perform well. There's irreducible complexity here that cannot be simplified away.
Yeah sometimes you do, and that is exactly the kind of problem that is irreducibly complex, but I think new kinds of problems like this don't tend to crop up in the wild very often and when they do they tend to show up in subtle and non-obvious ways.
The problem you've described is far from a new problem - it's the same problem space that is covered by ORMs. Furthermore, if I were working on a team where a developer has uttered the words "I've created my own ORM" (or something to that effect), my face has probably already landed in my palms.
The rest of what you wrote I'm in vigorous agreement with though- especially the parts about unit testing, local modifications and "the other side of the hill". Seen all of that.
The problem is, the converse is not always true. Problems that are very easy to formulate ("find the k shortest paths from my house to my office") might require very sophisticated, non-obvious, non-simple code to be solved in a non-naive way.
We all know that sometimes problems are easy to state and hard to solve. To my mind the problem you describe sounds quite possibly impossible to do better than brute force, so any code at all that solved it would be to a certain extent insightful (but the simpler, the better - and truly great code for that problem would be code that let me understand how an easier solution was possible).
I highly disagree with this, though I like your statement on principal.
Greatness is highly dependent on context. I think your statement on greatness would apply to code that helps you learn and think.
This is, however, NOT the kind of code I'd want to see at work. Great code in a business setting is simple, easy to understand, and has absolutely no subtlety.
If I was looking for a literary example, great code in a business setting would be like the writing style you'd see in a newspaper: written for easy consumption by the greatest number of people possible.
If you write in a child's language, you will only be able to write children's books.
To add to p2t2p's comment, procedural programming doesn't offer very powerful high-level abstractions. OOP on the other hand is so powerful that it's easy to do wrong.
A description like
"I went off the platform and then on the train and then I looked at the nearest seat and then I saw it was occupied and then I looked at the next seat and then I saw that was occupied too snd then I repeated that and then I saw an empty seat and then I walked toward it and then I sat down and then ..."
is begging for structure by wrapping up into procedures
"I boarded the train, and then I located the closest free seat, and then I sat down in it ..."
with the obvious definitions. That can in turn can be further improved to
"After boarding the train I sat down in the first free seat ..."
where the relations between the things are more useful than "and then".