
Never edit a method, always rewrite it? - pdq
https://dave.cheney.net/2017/11/30/never-edit-a-method-always-rewrite-it
======
JulianMorrison
It has been my experience that after a substantial period of being in real
use, methods embody tacit knowledge about the problem domain that isn't
necessarily evident from their public interface. Because initially they didn't
embody that, and it caused bugs, so people edited in fixes.

It has been my experience that rewrites from scratch typically lose all this
tacit knowledge and re-implement the original bugs.

~~~
DavidWoof
That's sort of the point of the suggestion, I think. If you _always_ rewrite
from scratch rather than edit, this sort of tacit knowledge won't get embedded
undocumented and untested into the middle of a function.

This is sort of the great contradiction of software development. Untested
legacy software is hard to work with. So should our focus be on how to work
with legacy software or how not to create it in the first place? There's
really no answer to that.

~~~
kibwen
One thing the OP doesn't address is, is the rewrite intended to be black-box
or not? If not, then every "rewrite" will just be someone copy-pasting the old
code with the new fixes, which seems completely pointless. And if it is a
black-box rewrite, then all that tacit knowledge will be lost, leading to
having the exact same bugs over and over and over and over again, forever.

I understand that this is all just a thought experiment, but there has to be a
better way of framing it that isn't so immediately farcical.

~~~
DavidWoof
> And if it is a black-box rewrite, then all that tacit knowledge will be
> lost,

This is a thought experiment on how to avoid that tacit knowledge, not how to
deal with it. Consider approaching every edit in the same way you'd approach
new code: is the function adequately tested, does the function have a single
responsibility, is the intent clear, etc.? The idea is that if you do this,
then you won't reach the point where you have a 200-line method with tons of
hidden business rules.

I agree there's probably a better way of framing this. Still, as a thought
experiment, it's worth thinking about when you can and/or should do this.

------
chx
A tangential thought but related: I would find this absolutely frightening. I
understand the motivation behind it but I always like to start from something
existing and editing it -- even if by the end nothing remains of the original.
To some, an empty editor window is infinite possibility but to me, it's a
"coders block", not sure how to phrase it. Infinite analysis paralysis
perhaps.

~~~
skrebbel
But this is different, because you already have:

\- the method's signature and spec

\- the changes in behavior you want

~~~
lallysingh
\- tests

\- history for file (and any bugs listed in there that had to get fixed more
than once)

~~~
criddell
The first thing I thought when I read the title is what that would do to the
history.

Looking at a diff and seeing a small modification to a method tells me more
than a method that has entirely changed with for loops changed to while loops,
indices replaced with iterators, different whitespace and brace placement,
etc...

~~~
s73ver_
That's where commit messages come in. Sadly, those can be just as poor as
inline comments for telling you why something was done.

------
allengeorge
The headline is misleading IMO. The author is proposing “Never rewrite a
method” _as a thought experiment_ to guide you to writing better code. Even
the original proponent walked back from that idea moments after he proposed
it.

~~~
taeric
Funny, a modern day waterfall method example.

~~~
TeMPOraL
Seems like Poe's law could be generalized into a heuristic about memetic
hazards - no matter how much you guide and warn people, any idea you speak or
put in writing can be taken in a way you did not intend. The chances increase
with the size of the audience.

------
adamnemecek
I've been doing something similar. I name the new method Method, rename the
old one as MethodOld and in Method, before the return value, I put an
assert(MethodOld(args) == return value). It's one of the things that when I
do, I appreciate the value of, but I'm not disciplined enough to do as much as
I would like to lol.

~~~
lloeki
For some hairy stuff that require live data you can also reverse the practice:
call both yet return the _old_ one, and gather data in production before
switching.

Obviously this does not work for side-effectful methods (beware of caches!).

~~~
dotancohen
I'm interested in what fields you would use this methodology. Machine
learning?

In most fields we have a sample of known input vectors, including edge cases,
that map to known outputs. A unit test is enough to test these methods with
different implementations.

~~~
karlding
An example of when you might use this is when you're cutting over to a new
service, and you want to validate that the new service behaves the same way as
the old one before transitioning all the traffic to the new service. In order
to build confidence in the new service, you can assert that the expected
output is the same, and raise alerts when there is an inconsistency. This
incremental rewrite approach allows you to slowly replace pieces of an
application with a new service and helps prevent regressions in behaviour.

Or consider when you're performing a database migration (on large databases)
with 0 downtime. Typically this involves something like:

1\. Dual Writing: Create 2 tables and write to both and keep them in sync (by
duplicating new data, and back-filling old data)

2\. Update read paths: Change all code to read from the new table, and
validate that the data being read is consistent with the old table. You can
use a library like Scientist [0] to validate that the reads are the same.

3\. Update write paths: Change all code to write to the new table (and raise
alerts if the old path is exercised)

4\. Deleting old data: Remove code and data that relies on the old data model

[0] [https://github.com/github/scientist](https://github.com/github/scientist)

~~~
dotancohen
I see, thank you.

------
atsjie
When writing functions often I end up deciding fairly quickly if the function
I am writing is intended to be reusable or not. If it is reusable I'll write
it as a utility function, helper, lib, ... whatever the project's convention
and language paradigm might be. Usually similar reusable functions already
exist within the project.

A significant portion of functions fall in another category; functions that
actually get some real work done. They are specific and not intended for reuse
at all.

Applying the "never edit a method, always rewrite it"-rule to those
helper/utility functions would just be painful. The public API you designed is
designed with a certain purpose and reuse intent. Improving it slightly to fix
an issue and forcing yourself to entirely rewrite it would just break things.
Most likely you'll end up writing lots of small copies of the original method
(how painful this exactly might be depends on the language you're using).

Applying that rewrite-rule to specific functions/methods makes slightly more
sense. Here the logic is more tightly bound by business rules/logic, which if
they change will quite often warrent a rethink. Particularly because business
likes to just slap a feature on top of everything else; which means for us
finding ways to keep everything sane in the codebase requires continuous
refactoring. Rewriting a function here does not intend to make the function
more reusable, it attempts to make the code more clear. Sometimes reusable
patterns emerge, but often they don't.

Obviously the above is a simplification of things. Often programmers are
obsessed with abstractions and design patterns (read too much GoF, Martin
Fowler & Uncle Bob); or don't bother with anything at all (script kiddies,
prototype developers, or any code written during a PhD...). The truth lies
somewhere in the middle.

~~~
taneq
I'd call it "bricks" vs. "mortar". Write your bricks to be reusable and
modular. Write your mortar to be simple but don't stress too much about it,
because it really only exists to glue your bricks together.

------
robbiep
The metaphor that biological systems undergo continuous renewal and so
therefore code should be rewritten not edited i think is actually off, code is
the dna and thus undergoes evolution not replacement

~~~
erikpukinskis
The DNA is rewritten from scratch each new generation.

It even gets rewritten over and over during the life of a single cell.

~~~
cdancette
It gets copied, not rewritten. This is the same with software that gets copied
to machines so that we can run it.

Here we're talking about modifying a specific function, not duplicating all
the code.

------
colemannugent
A big part of what this approach is trying to fight is function complexity. If
you really re-wrote the entirely of your functions everytime you went to edit
them I think it would end up slowing the programmer and the program down.

Programming is often a state of flow where you have a grand idea for how a
mechanism should act and then you task yourself with implementing that idea in
code. Sometimes large complicated functions are the best way to translate that
idea. The approach the author describes sounds like it would create lots of
small functions that don't really help you solve the problem at hand.

Good idea to keep in mind though. Everytime you go to edit a function and find
yourself dreading it, maybe it's time to rewrite that one.

------
everdev
TLDR; Cheney is asking "what if we always rewrote functions", not advocating a
position.

------
qznc
Devils advocate:

If you make this a strict requirement for your code base people write wrappers
all the time, which creates a huge mess:

    
    
       def newMethod(x):
           if x*2 == 42:
                return 36
           return oldMethod(x)
    

You cannot enforce the requirement anyway, because people will just copy-paste
the code and edit it under a new name.

You just cannot push this methodology unto developers.

~~~
MattHeard

        def foo_with_edge_case(x):
          if x == EDGE_CASE:
            return 36
          return foo(x)
    

seems clearer to me than stuffing the edge case handling into the foo method

~~~
thejosh
If only you could pattern match x in the function def, would remove the need
for this whole thing.

~~~
wruza
Aside from compile-time optimization, pattern matching is just this additional
if at runtime. It is questionable if pattern matches make code more readable
since when you see foo(42) call and def foo(42) is defined elsewhere, you can
spend some time on original function until realizing that it’s not what you’re
looking for. The same for type-based and operator overloading. It only makes
code beautifu^W hard to guess.

Though it does look nice for fib and fac.

------
js8
I had a similar idea some time ago, that all the code should be write only.
That is, you should always write new functions (and give them new names)
instead of trying to rewrite them (if the spec changes, this assumes they were
correct in the first place).

I think it's only really doable in purely functional language (like Haskell),
though. It sort of means versioning of individual functions, and also types.
It's very similar to rebinding values only as opposed to modification of
variables.

But naming is a problem. Maybe.. There are two kinds of names of types and
functions - intrinsic and extrinsic. Intrinsic name only describes what the
thing is (e.g. array.search()). Extrinsic name relates to the problem being
solved (e.g. product.findByPrice()). Maybe the functions (and types) that have
only extrinsic names should be versioned and short name should be assigned to
the latest version.

All in all, I think it's a concept worth researching.

~~~
gjjrfcbugxbhf
Why? Your revision history is already in version control.

~~~
js8
With VC, you can only call the latest version. With versioned functions, all
the old code would still call the old functions, until you would explicitly
refactor it (or type system would fail).

I guess there is a philosophical debate behind this - what's in a name? Should
a name of function refer to a specific body of code only, or all possible
function bodies, past and future? What did the caller of the function want?

You can only guarantee correctness if the former. But the latter gives you
more flexibility. I am not saying that this is the right answer.

~~~
rootlocus
Sounds similar to [https://thedailywtf.com/articles/best-of-2016-the-inner-
json...](https://thedailywtf.com/articles/best-of-2016-the-inner-json-effect)

Keeping all versions of the same method in the source files has a lot of
drawbacks:

1\. Readability. Autocomplete gives me 50 versions of that method. Which one
do I choose? The latest? Why do the rest need to exist?

2\. Code size. Can you imagine the compilation times? IDE indexing times?
Binary size?

3\. Consistency / Correctness. How do you know when each call should update
it's version. How do you increment all calls to the latest version?

Simplicity is the ultimate sophistication.

------
jstewartmobile
Coming from EE, I like the thinking behind this. Components are similar to
functions, they are tested to the nth-degree out of inventory, liability, and
other concerns, and everything goes along well enough.

What would really help for that style of development would be a sort of
_apropos_ feature for your own code where you could look up methods by
keywords rather than just identifiers. For larger projects, having people re-
write several variations of what is basically the same method can be a problem
even with mutable code. Perhaps Knuth's _literate programming_ would help?

edit: And I bring _apropos_ up because if you break things down into very
small functions, and you don't have that, you end up writing several
invariants of the same function because you can't find the one you're looking
for.

~~~
eropple
Literate programming, as a concept, is super interesting. But it has a
fundamental problem, when applied to software-as-an-industry, that I think is
difficult to solve: many, if not programmers, may be literate but are not
_literate enough_. If you go look at a Knuth example of literate programming,
there emerges a few fundamental capabilities.

One: the ability to look at code fresh, as if it were to someone wholly
unfamiliar with the module or function in question. And to do so without
leaping to the smarmy-tech-nerd "I understand it so this is easy" thing--we're
talking about a baseline level of empathy cultivated so as to be able to
return to the mindset of a novice in order to help them climb from there.

Two: the ability to explain, without the prior knowledge of that module
(unless you are to reference that module, which has the same rules), what it
does and why it matters. In words. Not "the code is the documentation", but
words.

Three: the ability to do both of the above engagingly enough that people don't
go take a nap instead of read it. You don't need to be able to write hot fire,
but you need to be able to write.

These are difficult things unless you have a decent grounding in the
humanities, and even on a place like HN, which self-selects for people who
_want to write things about stuff_ , you see pretty serious capability gaps on
the regular.

(To be clear, I wish it _was_ the answer, I just don't think it is. Not
without a fairly radical rethinking of how much humanities matter to software
development.)

~~~
jstewartmobile
True enough, though at this point I'd be grateful for _any_ kind of consistent
method-to-description binding.

Having it be compiler enforced would also be nice. Even if most of the
descriptions end up along the lines of "do some stuff with the string", at
least that's _something_.

------
brlewis
N.B. This is not advice! The headline reads like advice, but if you read the
article it's a "what if..." exploration to get you thinking.

------
nikanj
If only business requirements, specs and use cases didn't evolve.

For our Christmas campaign, calculateCostOfGadget() needs to account for the
compounding discount. Preferably yesterday, as we need to complete this
month's billing to make payroll.

------
andrewchambers
Usually when I want to simplify something I do a copying garbage collection of
code. Manually shifting the good stuff from the old version to the new
version, this means all code gets reviewed, and poor code gets left behind.

------
EGreg
I had a different rule, which I was advocating for PHP 5.3 back in the day:

Have functions take regular parameters and the last one will always be an
associative array of options.

This matches how functions evolve. You have some required parameters, then
introduce more but the old callers don't know about them so they're optional.

I was trying to argue that PHP could unify the function call syntax and array
definition syntax to encourage this. In other words the options would actually
be virtual, as the caller would just supply the optional named parameters at
the end of the function.

~~~
cillian64
This is basically the same as how many Python functions often have a long list
of arguments with defaults. When calling you can ignore them or set them as
you like using keyword arguments. I'm a big fan.

------
JepZ
Well, just ask a perl dev ;-)

[https://en.wikipedia.org/wiki/Perl#Criticism](https://en.wikipedia.org/wiki/Perl#Criticism)

~~~
mywittyname
Every old idea will be new again.

------
IncRnd
This is an interesting thought experiment.

Following the rewrite-not-edit method rule forces one to either sink or swim.
And the only maintainable way to swim is to write small focused orthogonal
methods that do one thing only. Otherwise, it is impossible at any scale to
follow rewrite-not-edit.

I like take on different perspectives like this. Doing so ensures one thinks
about how to design and what to code before simply diving in.

------
lazyjones
Perhaps the "Second system effect" would not apply if methods are kept small,
but is a method really a good unit of work for these kinds of ideas? Better
rewrite units, classes, whatever can be isolated/encapsulated in a meaningful
way.

------
swlkr
I’m not sure rewrite is the right word here. Always write a new version from
scratch, use a different namespace, and never delete old code is a better
idea. You can’t cause regressions if you never change old code that’s in use.

------
zitterbewegung
What if we did this (where methods are immutable) and also architected a
runtime to do this? Also we would use our source control to operate on
functions instead of files? All methods would have a UUID and a hash of the
contents .

------
mywittyname
Looks like businesses have finally caught on to developers' obsessions with
constant product rewrites and started saying "no."

So now we come up with this technique as a way to get our fix to rewrite
everything from scratch.

------
jnordwick
This is actually the APL way. You make the language so high level and dense
that even complex methods are one-liners. When you need to update it, you
rewrite it. APL people have been screaming this for years.

------
sharninder
I understand the sentiment behind this, but in practice this is going to be a
terrible idea, in my opinion. I can imagine the codebase littered with
method1, method2 kind of edits all over quickly.

------
maxekman
This is similar to what's happening to the functions in Ethereum contracts
where there is an actual cost of updating code.

------
sitkack
What if every time we hit a bug in a method, we split that method into two
methods?

------
tablet
It is similar to say to a writer:

"Never edit a paragraph, always rewrite it".

Looks weird.

------
jlebrech
how is that different to writing a test for the old method then writing the
new method from within the test then moving it to the old location when you're
finished.

------
GreaterFool
Saw the headline, first though "I'll bet this is about dynamically typed
languages", and lo and behold

> At a recent RubyConf...

EDIT: just wanted to add that when I code in dynamically typed languages I'm
inclined to do the same thing. Without rigidity provided by a strong type
system it's too easy to mess things up. That's why I prefer strongly typed
languages so I can benefit from the structure they enforce.

~~~
jtlisi
Dave Cheney is well known in the go community, and works at Heptio. I don't
think dynamic typing has much to do with this article.

~~~
GreaterFool
The author says Chad Fowler proposed this idea at RubyConf

~~~
mcphage
Yes, but the author of _this_ article, which is discussing the idea, was
written by Dave Cheney.

------
moocowtruck
i thought rich hickey already did this talk about accretion

