
Write code that is easy to delete, not easy to extend (2016) - pcr910303
https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to
======
flohofwoe
The unfortunate side effect of this (very good) advice is that all code that's
easy to delete will eventually be replaced with code that's hard to delete
(and thus will eventually be impossible to delete in order to be replaced with
something better).

~~~
hedora
Many people argue systemd is an example of code that’s easy to delete being
replaced with code that’s hard to delete.

They’ve deleted init, the dns client, dhcpd, the whole xdm family, various
small open desktop protocols, kernel-level file permissions enforcement on
certain device files, rsyslog, countless shell scripts for running background
tasks via ssh, and I’m sure hundreds, if not thousands, of other well-
modularized programs. None of the collateral damage is in subsystems related
to init. Instead, it is subsystems that worked well, but that were easy to
delete.

One the other side of the coin, look at all the effort people are spending to
rip systemd out. Multiple Linux distributions exist solely to contain the
damage it’s doing.

It’s unclear if gnome will even survive the war if systemd loses.

It’s also wasting the time of end users, so the damage can greatly exceed the
total resources put into building Linux distributions.

A few days ago, I ran an “apt-get fullupgade” on my headless Raspberry Pi, and
some systemd subsystem wedged during the upgrade. Now networking is broken. I
want to use this raspberry pi in an embedded I2C application that run for last
decades. So, I need to find an operating system that:

(a) doesn’t use systemd - fool me once, shame on you, fool me twice, well this
is well past the second time.

(b) runs on raspberry pi

(c) has userspace tools to work with the i2c bus on the pi

(d) has a working upgrade path.

This is a huge pain, and it’s all to delete one software package that I don’t
even care about, and that is irrelevant to the use case for this machine.

/rant

~~~
teddyh
> _some systemd subsystem wedged during the upgrade. Now networking is
> broken._

All software has problems. Without specific details, blaming your problem on
systemd specifically seems just as unreasonable as blaming “Linux” or even
“Unix”. In fact, in past years during the old OS wars, there were many such
rants, blaming “Unix”. (See for instance _The Unix-Haters Handbook_.) Become a
curmudgeon, and idolize the past, at your own peril.

I recommend, instead, to live in the present, to use currently normal
software, and to fix every problem as it appears. Ceasing to upgrade
permanently (possibly by moving to an obviously dead-end fork) is _never_ a
sensible option in the long run.

~~~
mumblemumble
Don't bring the _Unix-Haters Handbook_ into this. A fun, self-consciously
curmudgeonly romp like that has about as much in common with the systemd
debate as P.J. O'Rourke does with Bill O'Reilly.

~~~
teddyh
I don’t know who any of those people are.

------
kqr
This was part of the initial idea of extension. David Parnas' 1970's paper
that popularised the term was called _Designing for the Ease of Extension and
Contraction_ where "ease of contraction" refers to _subsettability_ , i.e.
removing parts of the code without having to change other parts.

These original papers are well worth a read!

~~~
Jtsummers
Thanks for the reference. I've read some of his writing, but had not seen this
one. I've submitted it here [0].

[0]
[https://news.ycombinator.com/item?id=23917627](https://news.ycombinator.com/item?id=23917627)

------
smileypete
I think that one possible problem with the 'O' in SOLID (open closed
principle) is spending excess time second guessing future modifications to the
code. The examples are always clear cut, but in the real world it sometimes
doesn't work out that way.

OTOH I'd say that the 'L' is worthwhile (Liskov substitution principle) as
inheritance can be abused as a kind of 'version control' for functionality,
and LSP helps guard against that.

~~~
Uberphallus
Some time ago I worked exposing an API that, given that it was constantly
evolving and different partners adapted to it at different paces, we had to
support a wide range of versions of it, which was basically what you say as
version control.

I'm not gonna say it was pretty, actually from a design perspective it was
disgusting, but from the perspective of handling a dozen versions of the same
code in the same application, it was certainly very sane.

For example, at some point we supported versions 2.0, 3.0, 4.0, 4.1, 4.2, 4.3
and the freshly new 5.0. They inherited this way: 2.0 -> 3.0 -> 4.0 -> 4.1 ->
4.2 -> 4.3 -> 5.0, easy enough.

One day decide to deprecate version 2.0.

What happens is we consolidate 2.0 code in 3.0:

1\. If a method calls super, copy + paste from 2.0 its place. 2\. If it's
overridden, there's nothing to do. 3\. Run unit, regression tests, on the
remainder of the versions and fix anything that might have been missed. 4\.
Delete 2.0 code.

The end.

If we need to fix a bug introduced in 4.1, you fix it there and it gets fixed
automatically for the rest of the higher versions.

~~~
smileypete
Sounds reasonable, I should really have said 'informal version control' where
it's used to get stuff out the door and piles on technical debt as well :-o

------
amai
The author has a followup:

"Write code that’s easy to delete, and easy to debug too."

[https://programmingisterrible.com/post/173883533613/code-
to-...](https://programmingisterrible.com/post/173883533613/code-to-debug)

------
kmorgh
This guy has a talk too with the same kind of tone about the whole industry.

Had a good laugh when I was starting out.

[https://youtu.be/AUYPnxv0yss](https://youtu.be/AUYPnxv0yss)

~~~
earthscienceman
Here's his github too, if anyone's interested. Fairly prolific to say the
least:

[https://github.com/tef](https://github.com/tef)

~~~
edelans
most of the commits are empty

[https://github.com/tef/0.0/commit/59575ac7d888c60eb043fe3b3c...](https://github.com/tef/0.0/commit/59575ac7d888c60eb043fe3b3c4a7f3057a31165)

~~~
mariusmg
Yeah, that's the joke....

------
onnnon
This reminds me of a talk Greg Young gave called "The Art of Destroying
Software".

[https://vimeo.com/108441214](https://vimeo.com/108441214)

~~~
hew
One of my all time favorite talks in software.

------
dkarl
Microservices tip for this: Don't put code into a shared library without
careful consideration of the consequences. Even if it is used in multiple
services, consider duplicating it instead, and compare the risks and trade-
offs compared to putting it in a shared library.

Common scenario:

New developer: "I'm done refactoring the implementation of the BingBong class
in the shared library. It was a lot of work to change and test all the
frobnicateXxxx methods!"

Manager: "Wait, I thought none of our services do any frobnification?"

The old hands discuss: "They don't. Not anymore." "Are we sure? Perhaps the
Foo service we haven't touched since last year?" "No, the Foo service never
did." "Let's search the repos. Maybe we can delete some of these methods."
"This is weird. We have seven different frobnicate methods, and we never
needed to support that many different kinds of frobnification." "Of course.
It's such a pain to roll out a new major version of a library, you don't want
to change anything existing." "Do you remember when the rendering service was
stuck on version 3 of the auth library? It was because we removed a method
from the auth library and rolled out 4.0 without it. Turns out it was being
used on a feature branch in the rendering service that got merged right before
the release." "I remember. I'm the one who had to roll out version 4.1 the
next day with exactly the same code as version 3.7."

There's a simple way of understanding how stories like this happen, and a
simple conclusion: Once functionality is added to a shared library, changing
it requires much more care. Therefore, shared code is much more expensive to
maintain than non-shared code. This is why it makes sense to ask a question
like, "Which is cheaper in the long run, maintaining three non-shared copies
of this code, or maintaining one shared copy?"

~~~
butisaidsudo
Yup, I'm a big believer in applying the rule of three (
[https://en.wikipedia.org/wiki/Rule_of_three_(computer_progra...](https://en.wikipedia.org/wiki/Rule_of_three_\(computer_programming\))
) for moving code into a library as well. I find if I create an API with just
one or two examples in mind, it often doesn't turn out to be as general as I
thought it would be. By the time I've done something three times, I have a
much better idea of what the different use cases will be.

------
slooonz
I wholeheartedly agree with "don't immediately jump on the modularize and
abstract everything right away".

I think that "modularization and abstraction is always and uniformly good" is
one of the big lies of our profession. It’s easy to see how it’s attractive :
programming is intellectual work, and displaying capacity of abstraction is
rewarding. I was extremely enthusiastic about that stuff when I was young,
too. Then I got to school to get a CS degree, and it only reinforced it.

Now that I have some years of experience, let me talk to you about my last
job. It’s a web app like most of us (I think) are doing. Backend in node.
Frontend in react. The client was extremely unhappy about the delays and cost
of their current contractor (weeks and 4-digit invoices for simple features),
they wanted us to in charge of the app.

As soon as I received the source code, I looked at the backend part. It was
incredibly clean for the standards of my younger self and CS teachers. Each
class in his own file. You had one routes/_index.ts file that just included
routes/users/_index.ts for /users, routes/blog/_index.ts for /blog, and so on
(recursively: you would have routes/blog/comments/_index.ts too). routes/*.ts
would be just that, declaring routes. Code was cleanly separated and
implemented in controllers (controllers/users/_index.ts, and so one). Of
course the controller didn't directly used the ORM, there was intermediate DAO
classes to do that.

Very clean. Also, changing the slightest things required to go through 5-6
files. May god save your soul if you wanted to change more complex stuff.

I discarded everything. Most of the code now lies in /app.ts, which contains
all routes. Each route is directly implemented in this file (no separate
controllers) and directly calls the ORM (no DAO), except one which does
complicated stuff and has his own implementation file.

Loccount on my "ugly, not modular" backend, which, incidentally, has many more
features (that the client wanted for months but the previous contractor
couldn’t implement for a reasonable price/time). And ~1/3rd of those lines are
just some CSS assets moved from the frontend to the backend because we needed
them for the mailing list:

all SLOC=2284 (100.00%) LLOC=0 in 13 files

Loccount on the "very clean" backend of the previous contractor:

all SLOC=12507 (100.00%) LLOC=0 in 262 files

Now, don’t get me wrong. Abstraction and modularization are not uniformly and
always bad. But they have to make sense. Don’t write and organize a 2k LOC
project the same way you would write and organize a 100k LOC project — or
you’ll end up with a 500% overhead in LOC that WILL translate to a 500% higher
burden of maintenance.

I just wish school taught me that, or that my younger self could have been
clever enough to see it by himself.

~~~
kqr
> Also, changing the slightest things required to go through 5-6 files.

That is a sign it was incorrectly modularised. Good modularisation means high
cohesion and low coupling. Having to make changes in multiple files means the
opposite.

Don't rack on modularisation (great concept!) when your only experiences are
of bad implementations of it. (Which is not surprising, because most people do
get it wrong.)

Maybe the takeaway is "better not do it at all than do it incorrectly."

More concretely, the code you describe seems to have suffered from the awful
common implementation of the model-view-controller pattern, where module
boundaries are based on technical implementation details (routes are their own
module, pages their own, controllers their own, etc.) This looks "clean" when
you are inexperienced, but it is contrary to all good modularisation.

Module boundaries should be based on business domain concepts, not
implementation details.

~~~
gcpwnd
That sounds like myriads of programmers have done it wrong for decades. Can
you elaborate on this and maybe add a few sources?

~~~
BurningFrog
If you have to change in 5 places to change one "thing", the code is probably
not DRY.

~~~
ajuc
You have 5 places in code where you draw a blue rectangle:

    
    
        drawRectangle("blue", x0, y0, x1, y1);
    

Do you refactor them into drawBlueRectangle(x0, y0, x1, y1)?

It seems you removed the duplication but you didn't. Because if you now have
the requirement to draw red rectangles instead you surely won't leave it as

    
    
        def drawBlueRectangle(x0, y0, x1, y1):
            drawRectangle("red", x0, y0, x1, y1)
    

So instead of changing 5 places you now have to change the invocation in 5
places and implementation in 1 place.

You can argue it's because you named it wrongly, it should be
"drawWhateverThingTheyHaveInCommonRectangle" instead, for example
drawHighlightingRectangle. And you'll be right.

But you don't know if they will have that thing in common forever. And
splitting the code is harder than refactoring it, so it often leads to code
like this:

    
    
         def drawHighlightingRectangle(someDecidingFactor1, someDecidingFactor2, x0, y0, x1, y1):
             if someDecidingFactor == something && someDecidingFactor2 != somethingElse:
                drawRectangle("red", x0, y0, x1, y1)
             elif someDecidingFactor2 == somethingElse:
                drawRectangle("blue", x0, y0, x1, y1)
    

This code may seems ok, but it tends to grow business logic inside, and you
don't immediately know what combinations of deciding factors are actually
possible without looking at all the invocations. So either you look at all the
invocations before implementing your change (and then the refactor didn't
actually save you any work - what does it matter if you look at code and
change it vs just look at code and change stuff elsewhere), or you ignore the
invocations and add your change in isolation (probably writing code that is
redundant and overcomplicated because you handle cases that cannot happen).

This is obviously oversimplified example, but I've done these exact mistakes
several times :)

~~~
im3w1l
I think you should ask yourselves _why_ you are drawing blue rectangles. What
is their purpose? Do they just happen to be blue? Or are they blue because
they have they all "do the same job"? Maybe you should have

drawInlineHelpBox(x0, y0, x1, y1)

or

drawEnergyShield(x0, y0, x1, y1)

We can see from the language that unlike your example this is a true
abstraction. You went from color parameter to a specific color. It's both
phrased in terms of colors.

But here we go from color to ui elements. The terminology is completely
different.

    
    
         def drawEnergyShield(shield_capacity, incoming_dps, x0, y0, x1, y1):
             hue = incoming_dps / shield_capacity
             alpha = shield_capacity / 200
             drawRectangle(hsva_color(hue, 1, 1, alpha), x0, y0, x1, y1)
    

And I think you should embrace having business logic in there.

------
dang
Discussed at the time:
[https://news.ycombinator.com/item?id=11093733](https://news.ycombinator.com/item?id=11093733)

------
staycoolboy
Fun read, but does anyone actually change their coding practices based on
high-level essays like this? HN seems to be filled with a highly opinionated
bunch, and I question the point of these musings.

~~~
ben0x539
I find value in essays like that even if they don't make me write completely
different code tomorrow. Having design consideration clearly laid out and
discussed is really helpful, imo. (Of course that varies with how effective
the essay is at conveying its points and how relevant the points are to me to
begin with.)

Maybe I've been thinking about similar things myself but never got around to
thinking it all the way through or formulating actionable conclusions. Then
reading the essay is just like having bits and pieces fall into place in my
mind and having disorganized thoughts finally make sense, like a shortcut
through the whole mental bureaucracy of forming opinions.

Maybe I've been having a disagreement with coworkers about something the essay
discusses. Then reading the essay may open up a new perspective on the issue,
or introduce terminology that helps us discuss things more clearly, or at
least validate that we're discussing something meaningful that other people
have also had to think about.

Maybe I've already been doing exactly what the hypothetical essay advocates
for! But I've always felt kind of bad about it because I couldn't convince
myself that I'm doing it for a good reason, and now the essay is making me
feel better about myself.

Maybe the essay is completely wrong, but it's wrong enough that it provokes an
expert into writing an enlightening comment on the details on why and how it's
wrong, and how to be less wrong.

More people should write more essays, even if they don't end up as seminal
blogposts that are cited for decades to come.

~~~
grugagag
Perhaps you are using your intuition. I do let it guide me without
overanalizing every decision but more like feeling it. Programming is also an
art and intuition can play a bigger role in it. Engineering is the organized
form but built on pre-existent ideas enforcing helpful boundaries.

I think when faced with too many slices in decision-making and decision
considerations as you put it, it could become overwhelming especially if one
is not completely sold on a certain ideology/opinion to fight for. Thats where
intuition can be a good guide. However, intuition can do only as much, it
needs to feed off at times with some external input such as this type of
opinionated articles.

------
marijn
Apropos, the author is currently looking for work:
[http://tef.computer/](http://tef.computer/)

------
agumonkey
This is the essence of a variable. (physical) engineers probably know that
more tangibly than coders.

------
Paddy3118
The articles structure makes it difficult to comprehend.

------
devchris10
A Philosophy of Software Design - John Ousterhout

------
stef-13013
Very funny :)

------
underdeserver
Missing (2016).

~~~
dang
Added. Thanks!

------
tkfu
I have to confess, I was a solid halfway through before I realized it was
satire.

~~~
noir_lord
You missed the Sarte quote about programming in C at the top then.

It's at least good satire, it's so close to the real kind of article it apes.

~~~
miceeatnicerice
Not satire so much as an ironic dialectic - a progress through a series of
(decreasingly?) daft ideas

~~~
ben0x539
Which ones are daft?

