I've found the when teaching, I sometimes work on an example program too much, producing what I think is elegant and compact code, but that the students find hard to understand. I suspect that the same may be true when I am collaborating with others on a program. There can be value in writing code in a straightforward, easy to comprehend style.
"Compact code" is not orthogonal to "less code." It is said that you don't truly understand the problem you're trying to solve until you've implemented it a few (3?) times. Once you begin to understand the problem, you can often find places that required no code: either an existing API solved that problem and you weren't aware; or perhaps you found a more 'pure' solution and you can remove much of your code. This does not mean that you need to write compact, unintelligible code.
Also consider: "In anything at all, perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away, when a body has been stripped down to its nakedness."
Lately in my programming career, I find myself simplifying code, distilling it to solve the problem at hand, then clarifying the code (with good variable names, explicit comparisons to NULL/nil, fully demarcated if/else, small well-named functions, etc) so that future me can grasp it faster. This has the added benefit of pleasant peer review and getting new devs acquainted with the code.
The clearest case of this I've seen was Norvig's Udacity CS212 class: in the third week there were despairing and even angry posts to the forum about his code to generate strings from regular expressions. (Here's a version of it, with small changes plus a regex parser, since Udacity started requiring login: https://github.com/darius/parson/blob/master/eg_regex.py)
But some people have also told me that was a great learning experience -- they spent many hours understanding this half page of code, and felt they'd really grown once they'd mastered it.
My experience has been similar. A good coding style for teaching, when the reader doesn't yet recognise the building blocks of a language or their idiomatic usage, is often very different to a good coding style for professional use, when the reader can be assumed to understand the concepts and idioms already.
This affects more than just beginning programmers. Many best practices we teach programmers today help insiders manage a project but hinder understanding in newcomers to the project (even if they're familiar with the language, libraries, etc.). In a strange new project straight-line code is usually easier to follow than lots of indirection and abstractions. Comments are of limited value because most comments explain local features, but fail to put them in a global context. Build systems that automate a lot of work in our specialized industrial-strength setup turn out to be brittle on someone's laptop when running for the first time.
You raise interesting points, though I don't think this one is obviously true:
In a strange new project straight-line code is usually easier to follow than lots of indirection and abstractions.
I would argue that indirection and abstraction can always be harmful to code readability if the amount of complexity they hide is less than the added complexity from using them. For example, if your abstractions are leaky and you use them to break a long algorithm down into a hierarchy of very short functions, a reader probably still often needs to look through the implementation to figure out what is really happening, but now they have to follow several levels of indirection to find that information.
I would also argue that if you choose levels of abstraction that really do hide a lot of complexity most of the time, this can be helpful for beginners learning a new system as well. For example, this can happen when the abstractions in question have intuitive meanings, such as representing real world objects or other recognisable concepts from the problem domain you're working with. It can also happen when the abstractions represent common patterns of behaviour in some reasonably clean and concise form, such as navigating a data structure in a particular way.
In short, while I agree with you that straight-line code can be clearer than lots of indirection and abstractions, I think that is often because of the poor choices of the latter rather than the experience level of the reader with that particular project. If they were very new, they'd have to learn what the key concepts and common patterns of behaviour were anyway, and once they do understand those ideas, good code using them should be easier to follow than code written using more primitive concepts.
I agree with this phrasing! The extra context I'd add to your statements is that poor choices of abstraction are overwhelmingly more common than good ones, and that insiders learn to live with them. The combination of getting used to them and to having lots of reasons to maintain status quo (compatibility considerations, minimizing chances of merge conflicts, etc.) means that in practice their radar is often off when it comes to gauging the quality of an abstraction.
To create good abstractions, constantly look for opportunities to "hallway usability test" [1] them with newcomers.
The extra context I'd add to your statements is that poor choices of abstraction are overwhelmingly more common than good ones, and that insiders learn to live with them.
There's definitely a tendency for that to happen, I agree. If nothing else, we often don't know what good abstractions will look like yet in the early days of a project.
Even if we do, the most useful abstractions might change as the project evolves. Fortunately, the kinds of abstractions that tend to be most useful for hiding a lot of complexity also tend to be quite stable, in the sense that while they might be extended or generalised as the project develops, it is rarely necessary to severely break backward compatibility.
In an ideal world, we could definitely have more natural tools for things like refactoring, source control and diffs/merges, though, to lower the kinds of barriers you mentioned and promote willingness to refine the design and keep it clean as a project develops.
I think you have interesting ideas and laudable goals. It's nice to see some promotion of good coding style that goes beyond basics like how to choose good names (which are also important, of course, but well covered by existing material). It's also nice to see some recognition that what may be good for working with code in the small may not always be good for working with code in the large, which I think is not as well understood as it could be in our industry, to our cost.
Curiously, while I agree with many of the goals and much of your analysis of the problems, my own search for solutions has gone in almost the opposite direction. I tend to emphasize good modular design and clearly specified interfaces more as a result and to rely on ad-hoc examples and test suites less. It's not that I have a problem with the latter or don't use them, just that I don't find them sufficiently general and unambiguous on their own. So instead, my own thinking has often been about how we might improve the way we define interfaces and then connect modules using them, including evolution and being able to migrate code using a module from one interface to another efficiently and reliably if and when that becomes useful.
It's fascinating, and perhaps a little scary, that despite being interested in similar areas and having reached some similar conclusions about problems that it would be helpful to solve, we can still look in such different places for those solutions. I suppose that's a sign of how "young" our understanding of how to build software still is and how much we still have to figure out. Perhaps your project will bring some of those answers, and I wish you luck with it.