Sandi Metz, author on object-oriented design, writes about abstraction, code evolution and Ruby, and is known for her books on code design.
HN commenters repeatedly cite "The Wrong Abstraction" as a classic and recommend her books on code design.
> I'm not sure why you think that quoting random HN users proves anything except personal opinions of specific people? These are fine, but other people have other opinions.
They support my argument so I included them. I don't see a practical difference between a "random HN user" and a random blog post or anything else. A good idea is a good idea, regardless of the medium.
> I don't oppose the "principles" you've quoted, but they would just as easily apply to e.g. Haskell
Possibly, I threw it together pretty quickly, so it's not very thought-out.
> (maybe except for the "there's only one way to do it" which however has never been true of any language, including Python).
It's not binary, but a spectrum. No language has just literally one way to express each concept. However, Python is definitely further towards the "one way to do it" end of the scale. Perhaps not as much these days as it used to, but still far more than for example Ruby or Perl.
> I feel like you missed my larger point. It's not terribly difficult to write code that you can understand line by line. It's incredibly hard to write a huge code base in a way that you can reason about many code paths simultaneously, however. That's where the abstractions start to make sense.
I thought I adressed that; everything I mentioned - explicit error returns, pure functions, no global state, and no metaprogramming - all show their true worth in large and/or unfamiliar codebases.
> I don't understand why people think…
> I thought I adressed that; everything I mentioned - explicit error returns, pure functions, no global state, and no metaprogramming - all show their true worth in large and/or unfamiliar codebases.
I agree with all of those. I try to avoid these things whenever I write code (in rare cases, there are valid reasons for using them, but I agree they are overused).
But that still leaves tons of room for different ways of writing code, and it's very hard to say which one of these is "obvious". Obvious to whom? Some people want to have large methods, so they can see everything at a glance, others prefer to have more smaller function so they can see the high-level picture before they see the details. Which one of them is right? I can't say - it depends on the person, on the problem, and on many other factors.
What is missing from your list is "don't use advanced language features". You can do all the things you mentioned and still use "advanced" language features. And the debate about Go is often about how (presumably) the absence of "complex" features (something which is IMHO a bit subjective) makes code more "obvious".
But you can have generics (or, more broadly, polymorphism), interfaces, higher order functions, proper algebraic data types, etc. and still uphold all the features you've quoted. That's what I meant with the Haskell comment (but supposedly the same would be true for e.g. Lisp).
Since you shared a talk with me (which I intend to watch, but I haven't had time…
This is discussed in detail in "The Wrong Abstraction" by Sandi Metz
https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
Quote follows:
----
The strength of the reaction made me realize just how widespread and intractable the "wrong abstraction" problem is. I started asking questions and came to see the following pattern:
1. Programmer A sees duplication.
2. Programmer A extracts duplication and gives it a name.
This creates a new abstraction. It could be a new method, or perhaps even a new class.
3. Programmer A replaces the duplication with the new abstraction.
Ah, the code is perfect. Programmer A trots happily away.
4. Time passes.
5. A new requirement appears for which the current abstraction is almost perfect.
6. Programmer B gets tasked to implement this requirement.
Programmer B feels honor-bound to retain the existing abstraction, but since isn't exactly the same for every case, they alter the code to take a parameter, and then add logic to conditionally do the right thing based on the value of that parameter. What was once a universal abstraction now behaves differently for different cases.
7. Another new requirement arrives.
Programmer X.
Another additional parameter.
Another new conditional.
Loop until code becomes incomprehensible.
8. You appear in the story about here, and your life takes a dramatic turn for the worse.
Existing code exerts a powerful influence. Its very presence argues that it is both correct and necessary. We know that…
I feel there are two separate things going on:
First, the question of deduplication a/k/a normalization. Deduplication is cheap, and it's "a two-way door, not a one-way turnstile:" It's cheap to re-duplicate when you find that one of the users of your de-duplicated function or whatever needs new behaviour.
Second, there is the question of the right versus wrong abstraction: If two different things use the same thing, that doesn't necessarily mean that they share the same abstraction conceptually. But of course, sometimes the duplication is a hint that there is some semantic commonality, and when you get this right, it's glorious, but if instead of de-duplicating we actually build the wrong abstraction, it can be extremely expensive to fix. That's more of a "one-way turnstile" problem.
To me, when I see code that could be "DRY'd up," I have to ask myself whether it represents an opportunity to write a better abstraction, but I treat that decision conservatively, because of the cost of getting it wrong. In many cases, I choose to de-duplicate the code without creating what might be "the wrong abstraction," and it is often less expensive and lower risk to start there and only later consider abstractioneering.
p.s. The tension between de-duplication and abstractioneering resembles the tension between is-a and has-a, often expressed by classical OO programmers as "inheritance" In those terms, I'm really describing my inclination as "Prefer composition to inheritance by…
I used to think that if something is done twice, it should be abstracted.
Now, I'm a bit more careful. There's a pretty famous quote by Sandi Mentz: "Duplication is far cheaper than the wrong abstraction."
https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction
Make sure that you're picking the write way to think about the task at hand, rather than blindly following DRY.
There are times when even a single instance of a code call would be made clearer with abstraction, and there are times where having the same piece of code duplicated multiple times (or duplicated with one piece changed) is far clearer than trying to abstract it.
This Reddit Comment also has an interesting take:
https://www.reddit.com/r/programming/comments/5txp5t/duplication_is_far_cheaper_than_the_wrong/ddq5ykf
> The main purpose of abstractions is not to remove or reduce duplication, and not even to make code "reusable"; it is to make semantic patterns and assumptions explicit and, if possible, first-class.
The further comments provide more discussion.
---
I agree with the rest of your comment that refactoring and code-cleanup should be done in pieces and that, as with everything, striking the right balance is key.
The short answer is: don't design for future use-cases.
Period.
Instead, only build what you need to solve the problems you have in-hand, but do so in such a way that your software and your systems are as easy to change as possible in the future.
Because you very, very rarely know what your future use-cases really are. Markets change over time, and your understanding of the market will also shift, both because some of your assumptions were wrong, and because you can never really have a total understanding of such a complex phenomenon.
Your business needs will change as well; your top priority from 2019 is likely very different than it is today.
That is why you build to embrace change, rather than barricade against it.
As to how, I'd start with Sandi Metz's 99 Bottles of OOP: https://www.sandimetz.com/99bottles
Learning to write readable code is also pretty important; Clean Code is a good starting point (https://amzn.to/3168z3A), but I'd be keen to know of any shorter books that cover the same materials.
Growing Object-Oriented Software Guided By Tests (GOOSGT) is a good read as well: https://amzn.to/3du1sEL