
The Software Engineering Rule of 3 - tim_sw
https://erikbern.com/amp/2017/08/29/the-software-engineering-rule-of-3.html
======
franciscop
Since no one has stated it, this looks like a rip-off of
[https://blog.codinghorror.com/rule-of-
three/](https://blog.codinghorror.com/rule-of-three/) (which comes from
[https://www.amazon.com/exec/obidos/ASIN/0321117425](https://www.amazon.com/exec/obidos/ASIN/0321117425)
).

Of course there are many other places such as the mentioned C2:
[http://wiki.c2.com/?RuleOfThree](http://wiki.c2.com/?RuleOfThree) , my point
is just that the Erik authoritatively says he _is postulating it_ , but it's
already all around the internet.

~~~
erikbern
Author here. It's slightly embarrassing that this turns out to be an old idea
– it wasn't my intent to rip it off. I'm fairly sure I must have seen it a
long time ago and then forgot about its origin. In retrospect I probably
should have googled it.

~~~
franciscop
I was just thinking about this last week because of that old CodingHorror post
so I was quite surprised to see it here as a new idea. I was actually just
expecting a link or two at the bottom of the article.

Also, I have misused rip-off here; I didn't mean to say you _defrauded
/cheated/stole_ anything, just that it's an old idea already going around. I
apologize for incorrectly using this verb and accusing you of _ripping off_
this idea.

------
kelnos
I think the real solution here is just in learning more about your problem
domain and teaching yourself to think ahead better. To borrow the example from
the article, if you know that you're working with a small domain and auth will
only differ in the parameter names and URLs, then it's ok to generalize like
in the first example.

If you want to be able to build something that works with any bank ever, then
you need to think from a higher level: you have a login operation that creates
a session, and then a fetch-statement operation (that takes the session as a
parameter), and so on, and those are your general building blocks that make up
your interface.

No, you may not get it perfectly correct, and you may need to rethink and
refactor at some point if you come across something different from what you
could imagine. But that's not really a big deal, and you shouldn't be so
afraid of refactoring that your go-to strategy is to always copy and paste
similar code everywhere.

The awareness of this sort of thing perhaps isn't inherent; it comes with
experience, but I don't think the take-away is to shy away from general
functions. I've found that it makes things easier in the long run to think in
terms of high-level operations and interfaces that you implement. Clean APIs
aren't just for customers; you should architect your own "internal" code in
the same way as if you intended to make it a public interface that random
people could use. There's certainly a pitfall in trying to make things _too_
generic, but after a while you develop an intuition around finding where the
best balance lies.

~~~
_pmf_
More specific: being too generic can be solved by downcasting in consumers
without affecting the module boundary; being too specific cannot be solved
without changing inter-module interfaces.

Having a generic callback interface exec(Map(object,object)) is ugly, but
better than changing 76 upstream and downstream modules' interfaces with code
ownership and release cycles across different vendors/organizations.

------
gilgoomesh
FYI, you can remove the "amp" from the path:

[https://erikbern.com/2017/08/29/the-software-engineering-
rul...](https://erikbern.com/2017/08/29/the-software-engineering-rule-
of-3.html)

to get site that's a little easier to read in a desktop browser.

~~~
nicky0
Ah, interesting. I actually rather liked the AMP version on my desktop
browser, with the full width and the giant pictures. Found it strangely
compelling and I assumed is was a neat design touch.

~~~
wry_discontent
I had the exact opposite reaction. One of the things I miss from Firefox when
I use Chrome is the reader feature. It strips away styles and formats all the
text in a thin single column so I can actually read it. It's most useful for
me in dealing with sites that have interesting content and horrible styles
(looking at you, academic folks)

------
jbb67
Even a rule of 2 would be helpful in a lot of code I've worked on.

It seems like nobody wants to build a function to do the thing thats needed,
they have to build an abstraction or a framework.

------
asplake
The C2 wiki has a Rule of Three page:
[http://wiki.c2.com/?RuleOfThree](http://wiki.c2.com/?RuleOfThree)

------
magnushiie
While I kind of agree with the rule of 3, in the example given, I would just
rename BaseScraper to ScraperWithFormLogin when encountering the 3rd instance
and not derive the 3rd from anything (or create an abstract BaseScraper) -
there's still a high likelyhood there will be another scarper with form login.

~~~
ptr
Why not just pull it out into a function, "loginWithForm(session, 'user',
'password')"? Why is it interesting on a class level that the scraper is using
a form based authentication method? What if a page is switching between two
authentication methods just for laughs?

~~~
razorunreal
I think you've hit it on the head. Mostly you shouldn't be trying to codify
what a scraper is, you should be collecting little utilities that are useful
for building scrapers. The more lego-like the better.

------
bpicolo
> The problem is we’re overfitting massively to a pattern here

Seems like this will be true regardless of the number of units we're breaking
down at. There can always be a new outlier later, and trying to predict future
use cases is usually a losing battle. It's not like you can't refactor yet
again later.

------
JoachimS
So this is the second system syndrome x2? ;-)

[https://en.wikipedia.org/wiki/Second-
system_effect](https://en.wikipedia.org/wiki/Second-system_effect)

------
contravariant
This just seems like a combination of choosing the wrong abstraction and
applying the DRY principle in a way that doesn't actually lead to less or
clearer code.

In this case almost none of the lines contain any superfluous information. At
best you could try to simplify things slightly by writing it as follows:

    
    
        class ChaseScraper:
            def __init__(self, username, password):
                self.credentials = {'username': username,
                                    'password': password }
    
            def scrape(self):
                session = requests.Session()
                sessions.get('https://chase.com/rest/login.aspx', data = credentials)
                sessions.get('https://chase.com/rest/download_current_statement.aspx')
    
        class CitibankScraper:
            def __init__(self, username, password):
                self.credentials = {'user': username,
                                    'pass': password }
    
            def scrape(self):
                session = requests.Session()
                sessions.get('https://citibank.com/cgi-bin/login.pl', data = credentials)
                sessions.get('https://citibank.com/cgi-bin/download-stmt.pl')

------
zinckiwi
Same goes double/triple/multiplier-of-your-choice in the UI layer. While back
end code _usually_ has process and rules behind it, so many things in the UI
are:

\- actually identical, but by coincidence

\- perceptually identical, but not

\- perceptually identical, but technically unrelated

\- arbitrarily different in special cases

~~~
wry_discontent
We have this issue with our code quality tool telling me that I have identical
html in some places. It's not at all helpful, but I want my jsx files included
in the figures for quality.

------
aslakhellesoy
Also related: [https://www.sandimetz.com/blog/2016/1/20/the-wrong-
abstracti...](https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction)

------
sidmkp96
The problem with this approach is, if there are 10 such different cases, which
can be made a one-liner, the dev would instead make those 10 cases to 20,
while waiting for 3rd to come in either of them. Instead of being afraid of
future, do some abstraction. If a 3rd case is massively different, then based
on the timeline either modify the abstraction, or create a new-functionality.

------
blago
Agree with respect to code organization but I can't help but feel the the
number three is somewhat arbitrary and it nudges the reader in the wrong
direction.

Instead of waiting until the third, I evaluate by asking myself if the
duplication is accidental, how likely is it for the next instance to be
different, and what will any extra arguments look like. If it doesn't feel
right I leave the repetition.

------
gfiorav
I only really agree with #3.

#1 – It should be swift and easy to extract code, if not that's a different
problem.

#2 – You need to be patient and research ahead. You don't really have an
option if you're in a big project. You get used to it.

~~~
Cthulhu_
Code that is swift and easy to extract makes doing it a low-cost operation;
just because you can do it quick and fast doesn't mean you should, is the
point made in the article.

~~~
gfiorav
Fair enough. I just never allow for duplicate code. I don't wait for duplicate
code to be present on three clases, I attack the problem the first time.

------
AnimalMuppet
I've used a modified version of this. I run a process by hand. I run it a
second time by hand. The third time I automate it. By then, I've seen enough
to know what I'm actually trying to do.

------
reacweb
My 3 rules of sotware engineering are: 1 - KISS (keep simple and stupid) 2 -
DRY (do not repeat yourself except to avoid violating rule 1) 3 - there are
only 2 rules

------
tashian
@erikbern nice post. Just a quick typo I noticed: in your example code for the
CitibankScraper, the class name is still ChaseScraper.

------
lwhalen
And here I was hoping it would be a reiteration of "Fast, cheap, and good:
pick any two".

------
rjcc
Premature optimization is the root of all evil in programming. \- Donald Knuth
1974

------
bluedino
So what's the third, ideal pattern to use here?

------
amelius
A better rule is to wait until you have all the requirements.

~~~
toddkazakov
You'll never have all requirements

~~~
_pmf_
Well, I wait until I have 90 percent, and the remaining 90 percent will
usually manifest after delivering the release candidate.

------
wiz21c
funny, until this this very moment, this post had exactly 3 comments.

