
Confessions of an Abstraction Hater - rumcajz
http://250bpm.com/blog:144
======
crazygringo
I personally wouldn't even call the author's given example abstraction, it's
just splitting code into functions.

Splitting code into functions is mostly an aesthetic and "reading
comprehension" choice so you get shorter, well-named functions -- as well as
not too many variables to keep track of in a single scope.

Abstraction, in contrast, takes two similar but different functions (or other
structures) and turns them into a single one that uses some kind of parameter
instead.

And the easiest rule I've ever found is to abstract whenever you've duplicated
3 lines of nearly-identical code, and also whenever you've written a single
line of nearly-identical code 3 times.

If it hasn't been 3 times or 3 lines, it's not worth it.

And _never_ abstract in advance (the author's "You can imagine a case where
somewhene would like to call it from elsewhere") unless you _know_ it will
need to be called.

(Side note: also be extra-careful about abstracting stuff in business logic --
there's extra value in having code map 1:1 to specs or processes even if it
winds up being redundant, for ease of maintainability later.)

~~~
darkerside
I've found that it's much more difficult to unwind an abstraction than create
one. And having similar, even identical, code in multiple places isn't
necessarily a good reason to build an abstraction. The question to ask is not,
are these similar, but will they act and change in the same ways (to your best
knowledge) both now and in the future?

~~~
jtdev
I agree with this comment wholeheartedly. Too often DRY is blindly applied to
every little piece of code... in ways that violate SRP and lead to a tightly
coupled mess of code. It seems that DRY is the most easily understood of the
SOLID concepts, and is therefore, unfortunately, over applied by many
developers without an understanding the downstream affects.

~~~
layer8
Nitpick: DRY isn't directly a SOLID concept.

I find SPOT (Single Point Of Truth) to be more to the point than DRY, because
it is explicitly about semantics and not about mere syntactic repetition.

~~~
JamesBarney
How have I never seen SPOT. I've read about the idea many times over the last
ten years, but weirdly enough I've never seen it put it into an acronym. I
feel like rules of thumb in software never quite make it until they're an
acronym.

I mean honestly the SOLID advice is pretty terrible, but is so often repeated
because it has such a great acronym.

~~~
acje
What is suboptimal about SOLID? I’m about to buy into it so I’d like to know.

~~~
JamesBarney
Bout to see a movie with my wife so here's just a short description for now.

SRP. Advice everyone can agree on, but not very actionable. I've seen a dev
make a change to conform to SRP while another dev argued the change was bad
because it violated SRP.

Open/closed principle if you look at it's origin this is just terrible advice.
So everyone tortures this advice until it says something different

Liskov substitution principle - sure, but as a top 5 most important idea in
oop

Interface segregation principle - sure

Dependencies inversion principle - I don't think abstractions are always
better than concrete representations. In fact a lot of times they're worse.
Humans are trouble at learning abstractions. So bad in fact they can learn
abstractions from the concrete so much easier than from abstractions. And this
is a huge cost to readability, and it's harder to get correct.

------
barefoot
Good naming, often a causality in older codebases, helps this a lot.

Abstraction hurts when things are abstracted and named poorly but helps a
massive amount with readability when done well.

If I’m looking at a (software) method to cook a meal show me the high level
salient features of that process in some centralized place. Don’t show me the
angle of the knife blade or the exact movements used to chop the tomatoes. It
should be (simplified in many ways):

PreheatOven(temperature:400);

ChopVegetables();

CombineIngredients()

Etc...

Sadly the problem is this basic level of software abstraction is preserved in
much enterprise software but ChopVegetables upon closer inspection - and after
drilling down into other methods - will also purchase missing ingredients and
if you don’t have the money to buy them will also sell your items including
the already preheated oven you need to cook the food.

~~~
gilbetron
I love when developers make food analogies, they almost always show their
ignorance of cooking, and sometimes of software development. After 30+ years
of software development, I always read the actual code to find out how
software works. "CombineIngredients" is useless and honestly quite horrific.
Instead of "simplifying" it is obfuscating. The details of cooking are in the
function. I don't need to see "PreheatOven" at the same level as
"CombineIngredients". One of my favorite posts on this subject is:
[http://number-
none.com/blow/john_carmack_on_inlined_code.htm...](http://number-
none.com/blow/john_carmack_on_inlined_code.html)

Tucking away code in a function serves a purpose, and that purpose is not to
make the highest level function hold no detail. Like cooking, software is
about the details, so don't hide them from me. I can't stand the "Russian
Doll" approach of a function that just calls another function that just calls
another function that just ... Nothing improves the understandability of 10
lines of code than spreading it across a 10 functions often in different files
/s

Great abstractions exist, but very few abstractions are great.

~~~
Bekwnn
In my experience Unreal Engine 4 suffers from this. It's a fantastic game
engine and in many ways its code is really great, but on many of the occasions
when I've had to trace call stacks in its source, it's a mess of long function
call chains marred with branching conditions and extremely similar sounding
function names.

I think it's a good example of how code can look clean but actually be a bit
of a mess to work with. In contrast at my job I've been making heavy use of
structs and functions scoped solely to cpp files. The result has been in my
experience somewhat messy-looking code which actually has very low cognitive
overhead and is very easy to change.

Sometimes long functions are a viable answer. It all depends on whether it
actually makes sense to tear things out, otherwise that function called in
only one place you pulled out is just polluting the scope. There are other
ways to handle it: local function, placing the chunk of code in its own scope
limiter, and possibly comments documenting and breaking down the steps.

------
BinaryIdiot
I have a strong suspicion that heavy handed enforcement of DRY (Don't Repeat
Yourself) has lead to large codebases containing far too many abstractions in
a foolish attempt to _literally_ never repeat yourself.

DRY can be a useful guideline but please don't enforce absolute, strict
adherence. Sometimes a little duplication is okay and helps avoid complex,
inflexible abstractions.

~~~
B-Con
This is one of the idioms of Go I liked. "It's OK to repeat the same 3 lines
of code."

My thought process has always been: If there is a common piece of
functionality to be shared, then refactor. If it _just happens_ to be the
same, maybe leave it.

~~~
lelf
> _It 's OK to repeat the same 3 lines of code_

… 2–3 times. Not ok to repeat them 1000 times.

------
magnetic
I'm not sure what this has to do with abstractions. What I call abstraction is
a cognitive pattern that allows me to model a specific entity with a semantic
that foregoes any implementation specific details.

Example of abstraction: file descriptors. I can open()/read()/write()/close()
[to/from] a file descriptor and I can model and understand the semantics of it
without knowing what's "behind" the file descriptor. Is it a pipe? Is it a
network socket? Is it a file on the drive? a partition? a whole drive? a
logical volume across many drives? perhaps encrypted? I don't care! That's
what the abstraction brings to me: not needing to care.

Not needing to care reduces the cognitive load on me, which means it reduces
complexity. Since our propensity to make mistakes increases very quickly with
complexity, keeping it low is key to success.

What the article describes instead is breaking down code into individual
functions, and the common trap associated with imaginary requirements ("You
can imagine a case where someone would like to call it from elsewhere").

~~~
mlthoughts2018
> “Not needing to care reduces the cognitive load on me, which means it
> reduces complexity. Since our propensity to make mistakes increases very
> quickly with complexity, keeping it low is key to success.”

I actually have come to disagree strongly with this beyond a very, very tiny
amount of basic attribute encapsulation, over my career.

Inevitably, 99% of your time becomes dealing with the headache of how the
abstraction hides the underlying details at the next reductive step down the
chain. You quickly realize that the “win” you get from being able to express a
program more concisely in terms of a layer of intermediate abstractions is
pretty meaningless, because the number one thing you and all future code
readers will need is immediate visibility into how properties of the
implementation result in certain resource usage, running time, etc. The number
one day to day activity will be performing surgery at some layer below the top
line abstraction, to such a degree that the abstraction is nothing more than a
nuisance.

This is also one reason why a functional style tends to work so well. You
separate data encapsulation (in dumb no-method, no-inheritance record types)
from instruction encapsulation (just functions), and the abstraction does not
have much effect on visibility of the primitives it relies on from the next
lower reductive layer of components.

------
josteink
The author’s views on abstractions and developers in general seems absolutist
and overly simplistic to me.

Everything in moderation is good rule.

In general I find a lack of abstractions a sign of lacking maturity, but so do
I for overuse of abstractions (“defensively abstracting”).

Getting the balance right takes experience, and you gain that experience by
making both of the above mistakes, and typically in that order.

The problem with abstractions is that you don’t really notice good or working
abstractions. You only notice the bad ones.

Thus the false developer-meme that abstractions themselves are a bad thing.

~~~
rossdavidh
I agree, but in my own experience at a dozen different shops, insufficient
abstraction is a mistake that a first-year developer makes, and excessive
abstraction is a mistake made by nearly every developer with more than a year
of experience. Thus, the team leaders and senior developers can find and catch
many of the errors due to insufficient abstraction, but the errors of
excessive abstraction tend to accumulate.

------
shaan7
OTOH, its equally important to stress on this section of the post- "Not that
we can do without abstraction. We need it to be able to write code at all"

In the last 4-5 years I encountered a lot of such "hate" against abstraction
and without knowing I _STOPPED_ writing abstractions. None at all. I feared
writing abstractions because people advertised them as evil. The years have
gone by, but the codebase needs to be maintained. There are often days when I
have to spend shitload of time doing something simple just because I never
created abstractions where I should have.

So yeah, my fellow programmers, please do not scare off young programmers from
using abstractions. They might write the wrong abstractions sometimes, but
hey, how else do you learn if not from mistakes?

~~~
Aeolun
We had a ton of endpoints without any form of abstractions. Need to do
something on all endpoints? Have fun copy/pasting into 80 source files.

------
darawk
I half agree with this. The problem is: what's the alternative? The reason we
have abstraction is because not abstracting _also_ has a cost. Consider
encountering a legacy codebase with _no_ abstractions. Yes, perhaps each unit
of code is more digestible, but there's _so many more units_. The codebase is
10x as large, because there's no abstraction. Is it really easier to
comprehend that way?

As in everything else, it boils down to an issue of balance.

~~~
hliyan
I think refactoring large blocks of code into functions named after their
purposes is good abstraction. But introducing whole new constructs (Proxy,
Adapter, Bridge, Factory, Repository) should only be done when it makes the
code simpler than the function-only alternative.

~~~
fdw
Some time ago, I would have agreed with you about Adapters, Factories and so
on. But now, I'm not so sure.

My team inherited a code base that wasn't really legacy - maybe two years old
- with a very simple business logic: Collect data from one REST service, save
it in a "cache" database and answer to requests from another one. Seems
completely simple, and the code wasn't awful, either. However, there was no
abstraction, no layers, nothing: The REST controllers knew exactly what the DB
looked like and contained a large part of the collection logic. There was a
client for the collection, but it was very low level and exposed everything.
Additional domain logic was smeared over five to ten levels of hierarchy.

In the end, each apparantely simple feature we had to add took days instead of
hours because we had to touch so much code. The principle of locality was
completely absent. In the end, we rewrote large parts of it so that you don't
have to change the REST controller anymore if the DB schema changes.

My personal lesson was that you should always have some abstraction, even if
the service's task is so simple. Things will change, and you must be prepared
for that. If every part of your code knows all the other parts, you're doing
it wrong. Have clear interfaces and boundaries (and models) so that changes
can be localized. Of course, that doesn't mean that you should implement
something like:
[https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...](https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition)
.

------
skrebbel
I find that Casey Muratori's "Compression Oriented Programming" helps strike a
perfect balance between too much abstraction and too little of it:

[https://caseymuratori.com/blog_0015](https://caseymuratori.com/blog_0015)

------
3xblah
It would seem useful to retain a copy of the original code in the "simpleton"
format.

This is because it is easier to "refactor" simple into complex than to
refactor complex into simple.

In the simple format the work is accessible to more people.

In the more complex formats, it becomes less accessible.

Perhaps it is added cleverness, abstraction and complexity that creates a sort
of "irreversability" (cannot return to simple) that motivates people to
"reinvent the wheel", ignore past work and totally rewrite things from
scratch.

What they really want when they do that (methinks) is a "simpler" format to
work from, not "simpleton" but not too far from it. Goldilocks levels of
cleverness and abstraction.

Similarly, perhaps that unmanageable, irreversible complexity is also what
compels people to _add more abstraction layers_ in attempts to "simplify".

In other words, at some point the level of complexity becomes too great for
someone to unravel. All they can do is add to it.

~~~
tempodox
I think you're on to something. I compress this to “quantity is easier to
manage than complexity”. The simpler version will commonly be “more” in terms
of LoC but, as you say, easier to grasp and to work with when done well. I
found (the right form of) laziness a good counterforce. I don't want to wrack
my brain with an over-engineered abstraction, but I also don't want to squirt
the same 15 lines of code all over the codebase. When the criterion for
correctness changes, I'll have to edit those 15 lines everywhere, and I'm
guaranteed to forget at least one instance.

------
ken
> Imagine that the requirements are that your program does A, B, C, D and E,
> in that order.

I find that every year I write software, the less my software looks like "do
step A, then B, then ...". It's always becoming more functional and
declarative. I'm not sure there's _any_ function in my entire program that has
to do 5 high-level calls in order like that.

Without hearing what A/B/C/D/E actually are, this sounds almost straw-man-ish,
or perhaps architecture-astronaut-ish.

~~~
draw_down
That’s a shame, I prefer reading very simple code myself.

~~~
ken
What's a shame? Functional and declarative code _is_ simpler.

------
svat
Relevant recent discussion:

•
[https://news.ycombinator.com/item?id=18959636](https://news.ycombinator.com/item?id=18959636)
(John Carmack on Inlined Code (2014))

• This comment, which contains a useful example:
[https://news.ycombinator.com/item?id=18832382](https://news.ycombinator.com/item?id=18832382)

Please let me know of any further discussion on the topic. (I'm also reminded
of Ousterhout's advice in _A Philosophy of Software Design_ that “classes
should be ‘thick’” — see talk
[https://www.youtube.com/watch?v=bmSAYlu0NcY](https://www.youtube.com/watch?v=bmSAYlu0NcY)
or (unrelated) slides:
[https://platformlab.stanford.edu/Seminar%20Talks/retreat-201...](https://platformlab.stanford.edu/Seminar%20Talks/retreat-2017/John%20Ousterhout.pdf)
)

One thing I see people miss in this discussion is that there's a tradeoff
between having an individual function be understandable (shorter functions are
easier to understand) having the entire codebase or interrelationships between
functions be easier to understand (fewer functions are easier to understand).
When there's an example presented like in the post, it's often presented as an
example with a single function, and of course four short functions may each be
easier to understand than a single function, but you've also added three nodes
to the “graph” (of functions and calls between them) and now you need to study
whether these functions are called from anywhere. To make up for this one may
start introducing classes/objects to indicate “private”/“public” functions,
and so on. (Some languages allow nested functions, which can help.)

Most of all I'd say reading very different styles of code, and seeing how
people achieve great things in unusual ways (e.g. with lots of abstraction, or
with very little abstraction), can be an illuminating experience (see my
comment in this thread about reading Knuth's programs).

~~~
dahart
You might like this related comment
[https://news.ycombinator.com/item?id=13571159](https://news.ycombinator.com/item?id=13571159)

~~~
svat
Thanks for sharing; that's an interesting discussion.

------
tempodox
As with everything, it's a question of the right balance. Which balance is the
right one under what circumstances is a matter of experience. And nobody can
make your experience for you, each one of us has to do it for themselves. So
never be afraid to make your own mistakes (and learn from them). It is, after
all, the fastest way to gather experience.

------
mruts
What would piss me off to no end in this Scala codebase I used to work on was
the creation of traits that were then only extended by one object:

trait Foo { val x = 1 }

object FooObj extends Foo

So pointless and unnecessary. And before anyone says this is just planning for
the future, the future can go fuck itself.

~~~
dtech
There's two valid use cases for this:

1\. Testing. The trait makes it easier to mock a dependency. In this case the
second implementation is in the testing codebase.

2\. Quick overview and contract.

If you have a `trait DoThing { def firstThis() def thenThis() }` it very
clearly shows what the trait is meant to do with what contract, even if the
actual implementation is 500 LOC with 15 private methods.

~~~
xchaotic
It is this way for large projects. You write your function, the interface is
tested, but the whole project becomes a big bloated mess.

I don't think there's any better way to write large projects.

Instead consider loosely coupled smaller projects.

Perhaps this only moves the complexity elsewhere and you have a dependency
hell, but arguably the same can be said for versioning parts of code.

------
devin
"Hell is someone else's abstraction." -Anonymous

~~~
dvh
My own code written six months ago looks like someone else's code.

------
ilitirit
This is simply an issue of naming, ensuring people fully understand the
problem, and having the appropriate level of granularity (not necessarily
abstraction).

Consider these within the context of the A->B->C->D example he gave:

    
    
        PutLeftLegForward();
    
        PutRightLegForward();
    
    

vs

    
    
        // Wrapper that utilizes the above functions
        Walk(numberOfSteps);

------
hliyan
The very first reader comment at the bottom of the article is (almost
unintentionally) hilarious:

    
    
      Why not [A, B, C, D, E].map(do) ?

~~~
dtech
But it's so elegant! Just make sure they all implement a `interface DoAble {
void doIt() }` and we can make a generic do!

~~~
tempodox
Exactly! And all the dynamic contextualization and dependencies of each `doIt`
are gathered by the standard Clairvoyance module.

However, this is still incredibly verbose compared to the pinnacle of elegance
discovered as early as the 1960s:

    
    
      TRANSFORM THE CURRENT OBJECT INTO THE DESIRED OBJECT.
    

None of those modern wimp languages has reached that level of terseness yet.

~~~
myWindoonn
Have a cookie. You can have another one if you source your concept and cite
the prior work; your handwave is useless against a search engine, which turns
up this phrase only in your posts on this site.

'When someone says "I want a programming language in which I need only say
what I wish done," give him a lollipop.' ~ Perlis

[0] [http://www.cs.yale.edu/homes/perlis-
alan/quotes.html](http://www.cs.yale.edu/homes/perlis-alan/quotes.html)

------
amvalo
Re-labeling and re-arranging actions in a sequence seems like a pretty shallow
example of "abstraction."

~~~
rumcajz
OTOH, if I used generics or such people would argue about merits of generics
and ignore the main point.

------
alexfringes
Spent some time (slightly) re-abstracting a bunch of code yesterday. I started
that particular project trying to follow some of the anti-abstraction gospel
that came up on HN in the past months. Expecting new people to join the effort
sometime in the future, it seemed to make sense. But certain things just ended
up being spread out too broadly over time. I worried a newcomer would miss
some related thing that I purposefully left unrelated.

Having read this thread, I really hope that this kind of (comparatively
shallow) architectural problem is something AI can help with sooner rather
than later. Or at least the onboarding process to a new codebase could be
tackled by some smarter tools / better IDEs. Seems like there’d be some real
value in that.

------
throwaway415415
This is in line with the other day's post on gameloops and inlining functions
from carmack.

IMO one should always strive for simplicity and clarity. And if it is critical
code that needs to be secure then there is no excuse for unecessary
abstraction.

~~~
zoul
Good abstraction is what gives you the simplicity, clarity and security.
Abstraction is great to talk about things precisely.

~~~
Tervis
Exactly. IMHO abstractions on the problem-domain are good. But then there are
these kind of meta-level abstractions, like factories, proxies etc., which are
usually just making matters complicated.

------
Ace17
IMHO the "casual" reader should be able to understand the body of one function
in isolation ; i.e, she shouldn't need to to dig through other functions
(callers/callees).

This is exactly why all functions should be kept small, and thus it might be
preferable to extract small functions.

Proper function naming is critical here, but it works : when was the last time
you needed to look at printf's source code to understand your own code?

Same thing for max/fopen/malloc/atoi/scanf/sqrt ... if it works for standard
library's functions, we can make it work for our own functions.

~~~
danmaz74
Standard library functions are well understood because they are shared between
all projects using a language. Their naming doesn't even need to be
descriptive, because people will learn them regardless.

But functions which only exist in your project are a completely different
beast. It is often very difficult to capture what they do in the name, even
when you try to - especially for the higher level functions, which do a whole
lot of things (by calling other functions). And, even if the name was
originally good, it could become misleading when new use cases are added later
- I found this problem very often when working on legacy codebases.

Using lots of small functions isn't a panacea, and is completely different
from functions in the standard library.

~~~
speedplane
A good anecdote about this: I once wrote a file_lock function for a company
that took about 15 minutes to complete. The function name was something like
"file_lock". But then, it had to be modified to work on Mac and Windows. Then
again, had to double re-entrance from the same thread. Again, it had to be
modified to lock files for network files, FTP files, you name it.

I worked on that initially tiny function on and off for two years. Anyone just
reading the function title and comments along the way could easily assume it
did things that it didn't.

------
tluyben2
First of all I agree with the top (currently) commenter that the examples the
author gives are not really abstractions, at least not of the kind I thought
this would be about.

Anyway, my thoughts on the subject:

Ofcourse the problems with abstractions is when they leak; when you read an
unfamiliar codebase for which you do (not yet) understand the abstractions and
reasons behind them, you brain will be leaking cases all around them. There
are ways people try to prevent that; 4gl, xml/json ‘definitions’, DSLs etc but
unless you actually deliver the ‘runtime’ or libraries (classes (sealed etc in
C#) and frameworks) as binary, your new colleagues will look under the hood
and probably cry until either they get it or find it is actual crap.

In the end it never really works if you go broad (let too many people look
under the hood) because people want to/have to break out of the abstraction
and start to abuse the leaks. And then the result will become an absolute
mess.

I did a lot of successful projects with very high level abstractions but they
all rot when another team takes over. That is normal for most code imho
though; I measure success in decades not years and I have stable ancient code
running in many companies/online which use the abstractions and the shielding
to keep those transactions from leaking for all those years. Yes, it limits
what you can do with it, but if that is a given and the business case holds up
and management is not swayed by ‘but I read now we need to use something
called React/Redux’ then there is a lot of mileage to be gotten out of
effective abstractions.

------
Kaali
Abstractions need proper names and meaning. In this example the "abstracted"
version would be a lot simpler if the names of the functions would be 'doB()',
'doC()' etc. and it is still hard to make sense of as A, B, C, D, E as an
example doesn't really have an obvious meaning. Just an order. But if the main
function describes a process that must happen in a specific order, then by all
means keep the code in the single function if it makes it more clear.

~~~
hliyan
Agreed. I personally find it very difficult when abstractions are named after
the design pattern rather than the purpose. E.g

    
    
      class B
      import com.acme.b.BRepository
      class BImpl extends B
      BFactory.getInstance(STANDARD_B)
      b.process(); // this is all we want, doB()

------
shdh
My favorite abstractions:

~ == home directory . == present working directory .. == parent directory

------
apo
Names are abstractions. If there's an itch to apply some abstractions on a
project, begin with good, very carefully considered names for functions,
variables, and classes.

The main difference between the projects that make me want to pull my hair out
and the ones I can actually understand and immediately begin working with is
simple: good projects use good names for things.

------
nwmcsween
This is why I think a language with refinement types (and maybe dependent
types) as well as an effect system would be great as more could be inferred
from the function signature. You would still need to go down the rabbit hole
of abstractions a bit but at least you know what you're getting into and if
the abstraction is useless.

------
ummonk
This isn't code abstraction, just splitting up long functions. Obviously if
you name your split functions "foo" and "bar" then it hampers legibility. But
if you name them "doC" and "doBCD" then you can read them fine.

------
matchagaucho
As far as first impressions go, I would _much_ rather see all the noun
entities in a system abstracted into classes; and verbs implemented as
methods.

Well abstracted, fluent code is self describing.

    
    
      new Invoice().withParam(param).generate().deliver( person );

------
allenu
The author doesn't go into enough detail about the nature of the blocks of
code (how big, what they do) as that will most certainly have an effect on the
decision to break it up into functions.

I think if A, B, C, D, and E are mutating functions then yes, it may be
sillier to break them up if they are all contribute to a core, almost atomic
mutation. However, if they were functional, breaking them up may actually make
sense and be more readable.

    
    
      let a = A(x)
      let b = B(a, y)
      let c = C(b)
      let d = D(c)
      let e = E(d, z)
    

Then you can unit test each function and read each one in isolation to
understand what transformation is being done on the data.

~~~
rumcajz
Cautionary tale:
[https://news.ycombinator.com/item?id=18832382](https://news.ycombinator.com/item?id=18832382)

------
rb808
That abstraction is trivial. The real problems are the unintelligible
ManagerConfigContextInjectorProtoFactory style classes that seem to
proliferate in big corporate projects.

------
toomim
I wrote an academic paper on the "Costs of Abstraction" in 2004 -- I recommend
interested parties check it out!

[https://www.microsoft.com/en-us/research/wp-
content/uploads/...](https://www.microsoft.com/en-us/research/wp-
content/uploads/2016/02/toomim-linked-editing.pdf)

------
scegit
This reminds me of Laravel their codebase sometimes feels like a maze even
with a very decent IDE. Too many abstractions, and facades makes it even
worse.

Edit: Baklava code is a good term for it

[https://www.johndcook.com/blog/2009/07/27/baklav-
code/](https://www.johndcook.com/blog/2009/07/27/baklav-code/)

------
api
Abstractions obey the same hierarchy as relationships: great abstraction > no
abstraction > bad abstraction.

------
sorokod
About abstractions, even the best non leaky abstraction may begin to leak over
time as the reality it originally ( and successfully ) captured has moved on.

This is just a price of doing business, abstractions should be maintained like
anything else.

------
Nursie
This isn't even abstraction, it's just encapsulation of code into functions.

If you're fundamentally against that, I pity anyone that has to attempt to
read and understand anything any bigger than a toy example.

------
mbrodersen
ALL programming languages are abstractions. That's why we need
compilers/interpreters to make the source code have an impact on the world.

------
grumblar
It's hilarious that so many people are pointing out that this argument against
abstraction would not be convincing if the author had used good concrete
function names.

------
luord
Looks more like indirection than abstraction to me, but then again someone
could say that abstractions are just indirections one likes.

------
svat
Recently, I've been reading the TeX program by Donald Knuth, and some related
programs. One the one hand, they are _intended_ to be read (written using the
“literate programming” style he developed), so they ought to be easier to read
than typical programs (and maybe they are, compared to circa 1980 Pascal
programs of similar size). On the other hand, I… struggle.

In these programs, Knuth uses very few abstractions. I think there are two
reasons for this:

* These programs were written in the “standard” Pascal as of 1980, a language which had many limitations (see Kernighan's essay on the topic) — and on top of that, Knuth wrote these programs to be as portable and memory-efficient as possible, across the many (often poor) Pascal compilers that were available to typical users at typical computer installations.

* To some extent, I suspect it is just Knuth's style and preference. As a programmer who started his career writing assembly programs in his own style (a story from 1960: [http://ed-thelen.org/comp-hist/B5000-AlgolRWaychoff.html#7](http://ed-thelen.org/comp-hist/B5000-AlgolRWaychoff.html#7)), even when given a supposedly “high-level” language, he probably thinks very close to the machine. Even as a mathematician (a profession often believed to be fond of abstraction), his style is characterized less by abstraction-building and more by deep attention to detail and “just do it”: he calculates constants to 40 decimal places; he does not stop at analyzing Big-O complexity (despite having popularized the notation) but finds the actual constant factor; he does not analyze on an abstract model of computation but writes assembly programs for a specially-designed mythical computer and analyzes them; and so on.

Nevertheless, his literate programming seems to be a way of solving the
problems for which abstraction is often used as a solution (comprehensibility,
etc), without introducing abstraction (and paying its cost): you can have
functions that are hundreds of lines long, but presented in understandable
(dozen-line or so) chunks.

Here's an example from something I was reading today. Unfortunately to make
sense of it you need to be somewhat familiar with the conventions of Pascal,
of WEB, and the current place in the program, and turn off your instinctive
horror of non-monospaced code and the weird (for today) indentation. (Some
background here:
[https://shreevatsa.github.io/tex/program/pooltype/](https://shreevatsa.github.io/tex/program/pooltype/)
and
[https://shreevatsa.github.io/tex/program/tangle/](https://shreevatsa.github.io/tex/program/tangle/))
Anyway, the example is this: see sections 53 onwards:
[https://shreevatsa.github.io/tex/program/tangle/tangle-6](https://shreevatsa.github.io/tex/program/tangle/tangle-6)
— here you have a single function `id_lookup` that is hundreds of lines long
and has very complex case analysis and code paths, but chopped up in a way
that makes it less forbidding to understand.

I think though that programmers today have a strong cultural bias in favour of
abstraction, so they tend to have a strong negative reaction to code like
this.

~~~
microtherion
Literate programming is ALL ABOUT abstraction! Every named section represents
an abstraction. E.g. look at section 53 in the code you link to:

    
    
        begin l←id_loc−id_ﬁrst; { compute the length }
          <Compute the hash code h 54>;
          <Compute the name location p 55>;
          if (p = name_ptr)∨(t = normal) then
            <Update the tables and check for possible errors 57>;
          id_lookup←p;
        end;
    

What are the references to 54, 55, and 57, if not abstractions?

I agree that Knuth style literate programming uses a quite different
abstraction style than programmers are used to, and it's difficult to adopt
and do well. But it IS using abstractions, and very extensively so.

~~~
svat
Thanks for reading. I understand what you mean, and I agree with you that what
you identified is in some sense what literate programming is all about. I
think debating whether that's precisely what is meant and is discussed as
“abstraction” probably gets into motte-and-bailey
([https://slatestarcodex.com/2014/11/03/all-in-all-another-
bri...](https://slatestarcodex.com/2014/11/03/all-in-all-another-brick-in-the-
motte/?comments=false)) territory: by defining “abstraction” sufficiently
narrowly or sufficiently broadly, nearly anything being discussed in this
thread can be said to be abstraction or not. For example, while here you're
pointing out that giving a name to a bunch of consecutive lines of code is
indeed abstraction (and I'm inclined to agree with you), currently the top
comment on this thread argues that even splitting code into functions isn't
“real” abstraction
([https://news.ycombinator.com/item?id=19011659](https://news.ycombinator.com/item?id=19011659)).

So instead of trying to define “abstraction”, let's try to discuss the
tradeoffs: I think what we're discussing is abstractions that are “formal” and
“strong” versus those that are “lightweight” and “flimsy”. Look at the
original example in the post:

    
    
        void main() {
            // Do A.
            ...
            // Do B.
            ...
            // Do C.
            ...
            // Do D.
            ...
            // Do E.
            ...
        }
    

One could argue that here too there is “abstraction”, via the comments: they
identify and document some related lines of code as doing something. When we
move to the rewritten example:

    
    
        void bar() {
            // Do C.
            ...
        }
    
        void foo() {
            // Do B.
            ...
            bar();
            // Do D.
            ...
        }
    
        void main() {
            // Do A.
            ...
            foo();
            // Do E.
            ...
        }
    

what's changed is that these abstractions have become more formal, more solid:
grouping code into a function (I just left a comment about this:
[https://news.ycombinator.com/item?id=19012979](https://news.ycombinator.com/item?id=19012979))
(instead of just with a comment) is a form of abstraction provided by the
language, with stronger guarantees, such as that foo() cannot use variables
that occur internal to bar().

Literate programming is somewhere between the two. The sectioning it provides
is not a language-level construct; it's just textual (macro) substitution, and
if you wrote something like that in C++ with #defines, you'd probably get
yelled at for doing something dangerous instead of the abstractions (like
functions) that the language provides. The literate-programming sections are
very “flimsy”: code in one section can and does use variables defined in
another (note all the function's variables are defined in section 53, and the
function also uses global variables like `buffer` and `hash`); there's a “goto
found” in section 56 where the label “found” is defined in section 55.

Abstraction, beyond just naming/chunking, involves bringing into existence
some new entity. The more “formal” this new entity is, the more you have to
worry about things like: its place in, and relationship with, the rest of the
system (e.g. what are all the function's callers); its interface/boundaries
(under what circumstances does it make sense to call this function); how much
of a clear meaning it has, and so on. With just a named section, there's less
of that. The same things that make the latter kind of abstraction “flimsy”
also make it have less cost.

~~~
microtherion
I agree that literate programming chunks can be very leaky abstractions, and
it's a good point that LP systems provide no guarantees whatsoever about
interfaces. But for precisely that reason, LP chunking sometimes is useful in
situations where procedural abstraction is limited: In algorithms where the
various sections are connected with very wide interfaces. If all you have are
procedures, you either pass around a big struct, or an enormous number of
procedure arguments. In object oriented programming, the big struct is
implicit in the self/this pointer. LP gives another alternative.

~~~
svat
Right, I agree; that was exactly my point too. :-) Maybe I worded it poorly
initially: The original post was about the cost of "heavy" abstractions
(functions), instead of something lightweight like comments and naming. (In
searching for "the cost of abstraction", I just now found another post by the
author: [http://250bpm.com/blog:86](http://250bpm.com/blog:86)) I agreed with
this, and gave literate programming and the programs of DEK as an example (an
alternative to “formal” abstraction): a prominent programmer finding ways to
achieve things with only chunking/“lightweight abstraction”.

(BTW, the programs I mentioned also eschew other sorts of abstractions like
abstract data types, e.g. all memory is laid out in huge arrays and manually
managed — it seems to work for him. But that may have been a constraint of the
language for all I know.)

------
gitgud
A quote I heard recently:

 _Any problem can be solved with another layer of abstraction..._

~~~
dtech
This sounds like a modification of David Wheeler's quote:

    
    
        There is no problem in computer science that can't be solved using another level of indirection.

~~~
szemet
And we also have the modification of Rob Pike:

 _" There's nothing in computing that can't be broken by another level of
indirection."_

[https://twitter.com/rob_pike/status/48790422931243008](https://twitter.com/rob_pike/status/48790422931243008)

