
Solid Principles: A Software Developer’s Framework to Robust, Maintainable Code - stemmlerjs
https://khalilstemmler.com/articles/solid-principles/solid-typescript/
======
risaacs99
The longer I've worked in software development, the less I'm convinced that
any of this matters, at all.

What has mattered more to me is choosing the right architecture, technologies
and dependencies. Understanding the fundamentals, especially at least a vague
understanding of what the computer is doing with the code I am writing. Basic
understanding of algorithms and techniques like state machines.

I've grown almost disdainful of things like SOLID principles, after taking
them more seriously in the past.

~~~
Jupe
So glad to see this stated publicly, and actually getting some positive
responses. 25+ years of software development and the best "pattern" I can
identify after dozens of projects is just one: simplicity.

If you can't follow the logic by just reading the code, then maintenance will
be slow and cumbersome. New development will eventually taper off as more and
more time is spent fixing issues. I've written production systems, still in
operation today decades later, that hold true to the simplicity principle. I
wouldn't do it any other way. But, the battle I frequently face is with
upstart devs who want to use some new pattern or principle because they read
that it was "better" than straight-up code. And yes, they can implement some
new feature quick, but 2 years down the road all hell breaks loose.

Some of my pet-peeve anti-patterns:

* "Plugin" systems only used by the primary development team. There's power in being able to follow the logic for any call stack by just reading the code; plugins make the flow much more challenging, and provide little value.

* Large (and complicated) run-time configuration IOC systems which make any debug session a dreadful process.

* Rules Engines. 'nuff said.

* Piles of abstractions, one atop the other, because teams had no time to find commonalities and/or sensible use-cases.

* 25+ new source code files to respond to a /heartbeat endpoint.

* Fixing a problem in 3 lines of code is not fixing anything if it complicates the debug process, forces a re-write of the unit test drivers, enables mysterious build failures or taxes the DevOps team.

And some of my favorite simplifying patterns:

* AOP for logging; but NOT (dear god) for business logic.

* 3 levels of abstraction, and no more. If you can't figure out a way to do it in 3 levels of abstraction, then something else is wrong.

* 3 systems of state: config (global), current request and persistence (database). If you need more than that, then something is wrong with the architecture. (Ok, compilers may not be able to get away with this, but most applications can, in my experience.)

* If you use a pattern (MVC, for example) apply it to the whole system. E.g. doing IOC only in the upstream API system when the rest of the code doesn't, just makes a mess.

~~~
jlj
Rules engines come up frequently in my domain area. What are some good
resources for managing either high volume or high complexity business rules?

Usually these start out as hard-coding, then evolve into a rules engine or
framework. Sometimes after the original devs leave, the rules engine turns
into a shiny black box that remaining devs are afraid to touch.

What are some anti-anti-patterns?

~~~
closeparen
Be deliberate and skeptical about how and why your rule definition language is
a better fit for the problem space than a general purpose programming
language. When you find yourself recreating a general purpose programming
language, stop. Just drop down to one. Or start with one. A very successful
rules engine at my employer is Python _minus_ features, as opposed to the
typical "config plus features until it becomes a shitty Python."

Realize that what you are doing is a programming language, and create as much
of the infrastructure for programming as you can for rule authors (version
control, code review, unit testing, incremental deploy, etc).

~~~
zaphirplane
What about security and safety. A user provided rule in a DSL has controller
access to the rest of the environment. While a user provided script in a
language will have a lot of security and safety issues, even if you trust the
user there is security in depth and safety of limiting the accidental damage

~~~
adrianN
Sandboxing the code is a solved problem, is it not? There are a number of
websites that run code for you somehow.

~~~
FabHK
It's a surprisingly tricky problem, btw, at least for some languages. Here's a
nice 2014 talk by Jessica McKellar: _Building and breaking a Python sandbox_
that gives insight into some pitfalls. Might be "solved" by now though, don't
know.

[https://www.youtube.com/watch?v=sL_syMmRkoU](https://www.youtube.com/watch?v=sL_syMmRkoU)

------
redact207
These principles are the first thing thrown out the window for new apps. I've
been developing for around 15 years and I've noticed the excitement of new
devs on new projects who'll rush out and just throw something together quickly
to solve the immediate problem. I've done this a lot too.

This gets you a long way very quickly, which is proof that none of these
principles really matter. Development continues along this path at a cracking
pace for 6 months until complexity spikes and your productivity is at a
crossroads. Do you take a step back, refactor, restructure so the code can be
built on with any degree of velocity?

Hell no! Everyone's expecting the same output as when you started, so now you
have to start copy/pasting everywhere trying to keep up, but your bug tickets
have started ticking in so you have to address those. But you don't have
tests, so now each bug fix raises two more bug tickets. Uh oh.

Better get more devs. Now you have people who don't understand the system
making changes. There's no real structure so they just squeeze code in where
they can, but progress is slow because it takes them five times the amount of
time to reason about what the system does and bugs are still going out. Must
be bad developers. Better hire a QA team to stop all that.

QA is reporting defects with each release candidate, and puts it back in the
dev queue. After a year each feature is taking months to go out. The business
just wants some simple changes, why is that so hard?

Enter the hotshot dev. They whip up a quick MVP with the latest
language/framework plus it's serverless! No SOLID principles but that doesn't
matter because the latest technology will fix everything. Better fire the old
team and start fresh.

~~~
alexvaut
Agreed 100%, I worked with legacy code at different stages: 1 to 2 years: very
little of structure but still velocity matters a lot, complexity is not very
high; 2 to 5 years: first customers. copy/paste designs and complexity arise;
5 to 10 years: I observed 2 different trends, software where refactoring
happened, product is still not in a very good shape but it is still ok and the
others, the garbage product with millions of lines: global variables,
If/then/else copy/paste design, doesn't scale, broken everytime for no obvious
reasons. It's going to take years to stabilise without too much churn. So
everytime I'm working on a project, I pledge managers, devs to allocate time
to refactor and follow those patterns, implement unit tests etc... everytime
at whatever stage, after sometime the team realizes the benefit. So yes,
without a doubt, good design patterns are keys to make our dev lifes as easy
as possible. And the SOLID ones are definitely my favourites.

~~~
p0nce
Thanks for saying this, it's especially strange to see so much people
disparage SOLID as if they have outgrown the concept because it was invented
some years ago.

------
SergeAx
I am surprised by a number of people here with a lot of relevant experience
and opinions like "to hell principles and patterns, just make your software
simple". Most of the software just cannot be made simple. I'll go further and
say: you cannot make life with simple software, because there are a lot of
players around who will take your simple software and make it into more
profitable complex software.

SOLID is essentially a way to make complex software simple to understand. Yes,
you have to write 5 classes to introduce new type of persisting entity, but
they are simple classes, mostly boilerplate. Complexity is not in number of
LOC or files, it's in business processes, their interdependencies, "when this
then that, except those" and so on.

With SOLID (coupled with DDD) your hiring process becomes quite simple: you
check basic knowledge of language, performance tricks and caveats (like n+1
problem), understanding of principles with examples and voila, you have new
team member. On the first day you will show him or her your domain dictionary,
give access to git and ticket system, and you will get good enough code to fix
bugs or introduce features in a matter of days. And it doesn't matter if your
code base is 10 years old, if you followed those principles yourself for all
that time.

~~~
mannykannot
This is how SOLID, or rather its most zealous proponents, go from reasonable
but limited capabilities to claims that cannot be sustained.

While small classes and methods can be (though are not necessarily) at least
superficially easy to understand, this does not guarantee that the system as a
whole is understandable. This is because a lot of the complexity is in how the
classes interact with one another. There are contracts and constraints that
have to be observed, that are inherently about more than one thing, and so
cannot be localized in one method.

This approach, applied to writing, would be to allow only kernel sentences
using common words. This is not going to be enough to make a complex topic
simple to understand, and, if applied zealously, is likely to have the
opposite effect.

Unnecessarily complex code is not a consequence of not following simple rules;
it is usually a consequence of trying to write simple code but not
understanding all the issues. There is nothing about SOLID that guarantees
that unnecessary complexity will be avoided. Your idea of turning programming
into something like painting-by-the-numbers will not work.

~~~
SergeAx
In SOLID there is S for "single responsibility", which in turn gives us
simplicity on a component (class/object/file, depends on language) level but
lead to complexity due to number of components. 100 tasks gives us 100
components, then software grows to 1000 and even 10000 tasks.

But then comes letter D for dependency inversion. This principle gives us back
simplicity due to abstracting away any set of those components as meta-
components like repositories, queues, services, factories and so on. This is
about integfaces, e.g. contracts, as you call them.

My zealotry comes from about 30 years of writing and SUPPORTING code. SOLID
code is harder to write, but much more easier to maintain. All those war
stories about "business needs that feature week ago" comes from unmaintainable
code, when you just cannot add new features by any other means except
spaghetti with epoxy glue and nuts and bolts all around.

Business wants to move fast and break things. Our mission as software
engineers is to create and maintain codebases solid enough (pun intended) to
sustain years of development, pivots, M&As and whatnot.

~~~
gdy
"But then comes letter D for dependency inversion. This principle gives us
back simplicity due to abstracting away any set of those components as meta-
components"

I honestly don't understand how this produces simplicity. Could you possibly
elaborate?

~~~
SergeAx
I'll try. Actually it's just what we did on my job this week.

For a long time we've had a single Cassandra database for write-performant
storage in our project. Let's say it was a storage for Foo objects, which we
have to write like thousands per second and read back aggregated stats like
total numbers, number per parent object, writes per second/minute/hour etc.

With time it's read performance degraded. Also from time to time the database
was diving into some internal service state (like index compacting or
something like that), which further affected performance. At the end Cassandra
cluster consisted of 30 hosts, which costs us about US$1000/day. So we decided
to cut it off and change it for MongoDB, where we have much more expertise on
the inside.

In our code we had a service class, let's call it FooService. All operations
with Foo objects (write, read, stats, delete) was made via that service
object, like FooService::addFoo(Foo, Bar, Baz). Any business logic class
wanting to deal with Foo objects just declares FooService as is dependency,
which is injected in runtime on calling business logic class constructor. So
when certain code path doesn't need Foo objects - system don't have to
initialise FooService at all.

FooService, in turn, dependes on FooRepository, which is a descendant of
abstract CassandraRepository, which has code to connect to cluster, send
requests, handle errors and so on. CassandraRepository is a descendant of
AbstractRepository with methods like getItemById, getItemsByIds and so on.

Overall LOC count in our project is about 400k, it is not very complex, but
complex enough to stuck with task like that if your architecture is not clean
enough. But in our case the task was super easy:

1.Spin up MongoDB cluster with just 7 hosts (master, 3 sharded replicas and 3
shadow replicas)

2.Inherit FooNewRepository from MongoRepository, point it on new cluster.

3.Inject FooNewRepository into FooService and start performing mutable
operations (create, update, delete) into both Mongo and Cassandra bound repos

4.Migrate historical data with script, which is using the same common codebase
(it took about 3 weeks actually)

5.Switch reads to FooNewRepository

6.Check that everyting is okay

7.Drop all Cassandra-related code from codebase and rename FooNewRepository to
just FooRepository

Because FooService declares it's repository dependencies as
AbstractRepository, ALL the new code except FooRepository wass looking EXACTLY
the same as before. The team noticed changes only due to our weekly all-hands,
where we discussing all such matters. All the coding took about 2 days net,
most of it was a migrating script, so with about US$750/day cost cut we
adjusted that work in a matter of days.

And now the cherry on a cake. When we tried to shutdown a Cassandra cluster,
we've got errors on another instance of our service, which used the same
database, but was forked about 6 months ago to temporarily serve another
country/language. We turned Cassandra back on, I checked out that fork,
cherry-picked only Cassandra->Mongo commits from master branch, run the
migration script (there were much less data for that country), and voila - all
tests passed like a charm, new code working now (we will migrate it to full
master branch later).

edit: formatting and grammar

~~~
gdy
Thank you, the design you described look natural to me, but I don't understand
how this reduces the complexity coming from having lots of objects.

If I understood you correctly, repository abstraction hides only one object -
a Cassandra or MongoDb repository.

Don't get me wrong, I'm not attacking your point (in fact, I think we most
likely would agree on almost everything), I just don't understand what you
mean.

------
cgrealy
Some constructive criticism:

    
    
        abstract class Employee {
        // This needs to be implemented
        abstract calculatePay (): number;
        // This needs to be implemented
        abstract reportHours (): number;
        // let's assume THIS is going to be the 
        // same algorithm for each employee- it can
        // be shared here.
        protected save (): Promise<any> {
            // common save algorithm
        }
        }
    
        class HR extends Employee {
        calculatePay (): number {
            // implement own algorithm
        }
        reportHours (): number {
            // implement own algorithm
        }
        }
    
        class Accounting extends Employee {
        calculatePay (): number {
            // implement own algorithm
        }
        reportHours (): number {
            // implement own algorithm
        }
    
        }
    
        class IT extends Employee {
        ...
        }
    

This is still a violation of SRP. IT does not care about the employees pay and
shouldn't be forced to implement it.

Modern OO adds an additional rule to SOLID: favour composition over
inheritance.

HR and IT should not extend from Employee. HR should be more like

    
    
        class HR {
        calculatePay (Employee e): number {
            // implement own algorithm
        }
        reportHours (Employee e): number {
            // implement own algorithm
        }
        }
    

The first question you should ask when deciding to use inheritance is: do I
really need inheritance here? Then, ask yourself again: Is <childClass> a
<parentClass>?

In most businesses, HR is a Function or maybe a Department. HR might _have_
Employees but HR itself is not an Employee.

~~~
Supermancho
Every employee needs to be paid, even if that's 0. The original class was
correct (a class is a namespace, then functionally dependent on language
implementation).

The implementation of calculatePay, is extensible as it stands.

    
    
        public Number calculatePay (Interface department)
    

You delegate to the class (even if the interface is optional)

    
    
        Number val = defaultPayscale();
        if(department) {
            val = department.getPay();
        }
        return val;
    

Because EVERYONE expects Employee to have a calculatePay of some sort, for
accounting software. The idea that you want to smear a concept across domains
for a misguided utopian design is over-engineering. Programmers have an
average intelligence, so aim for supporting a simpler mentality, regardless of
your beliefs.

~~~
cgrealy
My point is that IT doesn't (and shouldn't) need to know about employee pay.
Extending the Employee class to HR, Accounting and IT is a bad design.

You're violating ISP here. The Employee class can implement
IThingThatGetsPaid, but IThingThatHasNetworkAccess should know nothing about
pay.

The Employee itself should have the minimal amount of information it needs.
Prefer interacting with an object through a small API surface. It's not
"smearing a concept across domains", it's the exact opposite. It's confining a
concept to the appropriate domain.

The original design pollutes the Employee interface with multiple concerns.

~~~
jsymolon
> IT doesn't (and shouldn't) need to know about employee pay

Project Budgeting, is one concern. You may not need to know the exact cost,
but, having a hourly rate is useful.

"A,B & C 80 hrs a piece, how much for the budget ?" Don't know have to ask HR
for ....

------
maximus1983
The author uses the more complicated definitions of the SOLID principles.

These are much simpler to remember:

* Single responsibility principle - A class should have only a single responsibility, that is, only changes to one part of the software's specification should be able to affect the specification of the class.

* Open–closed principle - Software entities should be open for extension, but closed for modification.

* Liskov substitution principle - Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

* Interface segregation principle - Many client-specific interfaces are better than one general-purpose interface.

* Dependency inversion principle - One should depend upon abstractions, not concretions.

A lot of these principles also tie into other principles such as DRY (Do not
repeat yourself).

~~~
CodiePetersen
That is the cleanest clearest collection of solid definitions I have seen.

~~~
hasseio
They come directly from Robert Martin's 2000 paper, 'Design Principles and
Design Patterns'[1]

[1] -
[https://web.archive.org/web/20150906155800/http://www.object...](https://web.archive.org/web/20150906155800/http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf)

~~~
maximus1983
I stole them from Code Project :D

------
juliangamble
There is a fantastic talk by Kevlin Henney where he deconstructs the SOLID
principles as each being either misunderstood when they were written - or
frequently misunderstood after. It was done here:

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

I won't spoil it by writing all the points here - just some pearls.

* The Open/Closed principle is redundant because it is already covered by the Liskov substitution principle.

* Bob Martin's view of the Dependency Inversion principle is in fact the Single Responsibility principle.

He suggests 5 more principles - but I won't spoil it by listing them here. Go
see it!

------
hideo
My 2c to teams attempting to write SOLID code: Please make sure that the
entire team that touches the code base understands what makes it SOLID-
compliant (Don't like the term compliant, but it'll have to do). SOLID is more
a framework for communication between engineers than anything else.

For example, if one engineer thinks class X has a single responsibility, but
the name can be potentially interpreted differently, then unless she's on the
job and reviewing every commit the principle will soon get violated. However
if the team is open to having a healthy debate and iterating until they mostly
agree, then SOLID raises the team's understanding of their code and THUS the
quality of the codebase.

------
jonathanstrange
Here are two concrete problems that these general design articles never
answer:

1\. How to deal with global dependencies like logging, internationalization,
application "constants" (not always constants in the programming language),
preferences, ...? If you inject these as dependencies into every module or
package, then you end up with a convoluted mess of initialization parameters
and dependencies. If you make them globally accessible, re-usability is lost
almost totally. In both cases, the code depends on the preference system and
the particular values of preferences. (In a real-world application almost
every "constant" has to be parametrized as a preference.)

2\. Closely related to this, how do you deal with miscellaneous data that any
_real-world_ application needs, but that pollute and "impurify" your objects
and classes. Typical examples of this second category are binary data from
rich text and other potentially volatile and framework-dependent data---often,
but not necessarily only GUI-related. If your application is database-driven,
you have to store this data, which usually means you also have to store it in
a class or struct in your programming language. For example, you might not
just need to store an employee name, you might also need to store employee
name richtext data, employee name display flags and filter category
information, and so on. Trying to strictly separate model data from view-
related data basically just gets you twice as many classes and tables for a
little bit of gain in "purity." That is true even if you follow a strict MVC
pattern (which I usually do), and in the end you store all of the data in the
same database file anyway.

I'm currently using Go, but the same problems also came up in Racket for in
the past. I've never read any software design article that provided concrete
solutions to these problems. I wish those articles would stop discussing toy
examples about cars and employees and instead explain how real-world problems
are solved.

~~~
mdpopescu
1\. Decorators. See
[https://blog.ploeh.dk/2010/04/07/DependencyInjectionisLooseC...](https://blog.ploeh.dk/2010/04/07/DependencyInjectionisLooseCoupling/)

2\. Yes, you need a lot of model types (groups, layers). You need the view
models, the database models, the business logic models, and possibly others.
Sure, that's annoying at first because they're pretty much identical. That's
fine though, because in that case you can just use AutoMapper or something
similar.

I have never seen a project where the various models didn't change
independently in a short timeframe (weeks, months at the most).

------
zmmmmm
I have to confess, I have never been able to figure out what the Open Closed
principle means. I've never seen an explanation that made sense. Extension vs
modification? What's the difference? Is it just a very confusing way of trying
to express the idea of encapsulation?

I guess I am plagued by the knowledge that I have virtually never been able to
predict accurately what "extensions" will want to be done to my code in
advance. So much so that I consider attempting to do it now a fairly
pernicious form of over-engineering.

~~~
charlieflowers
Essentially, it means that you can extend something by writing _new_ code, in
a _new_ file, rather than modifying some code.

For example, a case statement cannot be modified without adding a new case.
But something that finds all objects which implement IFoo and uses them can be
modified by implementing a new implementer of IFoo. (That's not the only and
probably not the best example, but it is an example).

A lot of people don't realize that most (maybe all) of these principles
originated on C++ code bases way back, where changing one file in one tiny way
could cause massive number of files to need recompiling -- which at the time
was very painful due to slow compilers.

They have some good applicability, but I think they're massively over-sold.

~~~
zmmmmm
Thanks. That makes "open for extension" clear, but it still leaves me
wondering about "closed for modification". It is certainly a nice aspiration
that code doesn't _need_ to be modified to customize it if one can achieve
that. But failing that, code should be as modifiable as we can possibly make
it. Making it "closed for modification" sounds like a bad thing, like it will
be very hard to maintain.

~~~
dsego
This is my interpretation. Usually bad devs I've worked with will mistakenly
keep things DRY by reusing code in a bad way, for example modifying existing
classes to handle cases they were not meant to handle. The most primitive
example is reusing a function because it does 80% of some logic and then
adding some bool flags to make it do something a bit different. Instead one
should always abstract things and extract reusable bits, but only if it makes
sense. But more often than not, lazy or misguided devs will see a similar
piece of code and try to reuse it by modifying it to add new behavior, because
"copy/pasting is bad".

------
Izmaki
I believe you got the D wrong. Dependency Inversion is not just about slapping
an interface in between two components. It's about inverting the dependency.
Let me explain:

Picture that we have two components, a main component "Master" and a
subcomponent "Slave". What you seem to be saying is that we should have an
interface between the two so that Master uses an "ISlave" interface that the
Slave maintains (the developers of the subcomponent) instead of using Slave
directly. Dependency Inversion however dictates that you should invert this
dependency so that Master dictates which functionality Slave should implement.
That is, Master should have an ISlave interface (or an "IDelegation" or
similar new name) that Slave (!) is required to fulfil. There is a small but
very important difference between the two directions of the dependency arrow.
One of the main points is that with Dependency Inversion it is the main
component that is in charge of functionality instead of the main component
having to work with the functionality exposed by the subcomponent.

~~~
s188
Nice. That's a really useful way of looking it it. I'll definitely commit that
to memory.

------
UK-AL
Some of these code examples are not great.

Using inheritance when it's really not needed.

~~~
maxxxxx
This looks to me like the usual textbook examples of inheritance that usually
fall apart quickly when they get in touch with reality.

------
revskill
Code is not that matter. It's the database design. The understanding of domain
that reflects in your database design.

Another way speaking, given me a well designed SQL database, i could produce a
working software in one day, with bad code even.

Most of broken code is because of wrongly designed database, not the code
itself.

~~~
barbecue_sauce
For green field projects, specifically for commercial products, I've always
felt you should design the application interface first (wireframes, use cases,
dashboards, etc.), then design the ERD of a database that will support that
design, then do everything in between. Everything else is just moving data
around.

~~~
dsego
Haha, good luck, usually the project is "agile" and experimenting and pivoting
and "we'll know it when we see it" and only 7% of people are actually clicking
a button so let's redesign those screens and so on.

~~~
barbecue_sauce
You still need somewhere to start, though.

------
jondubois
For OOP, I aim for:

\- Separation of concerns between classes/modules. The responsibilities of a
class should be easy to explain to a non-developer. E.g. Ideally , every
class/module abstraction should be an entity discovered in the specification
(or easily inferred from it) and not invented from the developer's own mind.

\- Be very careful about how you name things. Try to stick to the spec/domain
terminology.

------
dirkg
Does SOLID align with functional programming?

I've come to think that IOC/DI should almost be an anti pattern as it depends
on interfaces and I've never seen anything use it that wasn't abstraction
hell.

Functional programming seems like a breath of fresh air, which is ironic since
it predates all this modern buzzword centric world and was around with LSIP.

------
planxty
I suspect the majority of people who claim to disdain SOLID principles are in
fact using them every day without realizing it.

Seems pretty disturbing that so many engineers are piling on to recommend
abandoning adherence to some really basic (and easy to understand and apply)
ideas about design. Glad I don't work with you! :-P

~~~
reallydontask
I think a lot of people have seen that SOLID principles sometimes actually
have the opposite effect of their intention, i.e. they make the code hard to
reason about and thus less maintainable and harder to extend.

There are quite a lot of articles out there with valid criticism of SOLID, you
might find that you don't disagree that much after all (or not). At the end of
the day, blind adherence to dogma will likely result in negative effects.

If strict adherence to SOLID principles works for you, then great. Not been
the case for me.

~~~
Izkata
> I think a lot of people have seen that SOLID principles sometimes actually
> have the opposite effect of their intention, i.e. they make the code hard to
> reason about and thus less maintainable and harder to extend.

For me, it's half that and half that the people most likely to promote it -
like in this blog post - are doing it badly.

For example, their very first one, the Single Responsibility Principle, isn't.
It's blind adherence to a code smell at the syntactic level, resulting in
subclasses that are closer to god objects than single-responsibility.

IMO, extracting "Payments" into its own class/module is the "right" day to do
it, whether or not the switch statement is kept or a method is created for
each Employee type.

------
cjfd
I am not a big fan of 'Solid'. Some of them are true but of the category
'water is wet'. I.e., the SRP principle. Others don't seem useful. The open-
closed principle, for instance. All software should be adaptable and we
generally don't know what requests are going to come from the field so we
cannot write parts of software to never be changed. Perhaps you think that

    
    
       bool is_prime(int n)
    

is never going to change but someday somebody is going to request that it can
report progress during its calculation. So really, you can never know.

Also, these things with dependency injection and interfaces. Please, only
introduce these complications when you can prove that you need them. The
software will be complicated enough when it is satisfying all its
requirements. It is unnecessary to invent complications for no reason.

~~~
croo
If SRP is akin to water is wet I'm working with newborns.

What is so trivial about what to do in a class? Deciding where to put what is
one of the hardest part of the job...

~~~
cjfd
What to do in a class may not be entirely trivial but also not that
complicated. What is trivial is noticing that a class is doing too many
things. The problem is actually doing something about it. This seems in many
case to be more of a discipline problem than it actually being that hard.

I also have seen that people are trying to do too much. When one sees a
problem many have the inclination to want to refactor everything at once and
also redesign an entire program. A more productive way is to do one small
refactoring that solves just one code quality issue at a time. Before you know
it you have done 5 or 10 small refactorings and things are starting to look
much better. The big refactoring is very likely to make as many things worse
as it makes better. The small refactoring has much more chance to have only or
mainly positive effects.

Sometimes one also has to take a step back and think about in what direction
to go with an application. That can be a bit hard, indeed. Even in that case
it is much better to do small steps in the right direction that one has
envisioned rather than some bing bang rewrite.

------
CodiePetersen
I must confess I am really bad at sticking to this. Sometimes I find myself
writing a clunky class or function that is doing too many things and I say to
my self "You're a dumb ass you are going to have to rewrite this." I just need
to practice holding my feet to the fire and doing it. There are some things
that I do regularly as habit, but occasionally I find myself in golden hour
mode ignoring all the good practices. But I suppose as long as the
refactor/rewrite follows good practice its probably not too much of an issue.
Although my one complaint is the very unhelpful acronym at times.

------
ideal_stingray
In the first example, isn’t it kind of a problem to have a bunch of different
functions for calculating pay? Seems like it would be easy for the accounting
department to end up with a different version than the HR department that
actually disburses the money, which would cause all kinds of hell when trying
to audit your accounts. If one team or department owns the function, you end
up with more interdepartmental communication if another team needs to change
it, but you won’t end up with Accounting and HR disagreeing on how much
employees should be paid.

------
hyperpallium
A program is a theory.

A theory can be evaluated: firstly, does it describe/predict the data (i.e.
does it work)? Secondly, is it parsimonious?

But the key issue with a program-as-theory is that you must understand the
theory to understand the program. Because of this, it may be much more
effective to shoehorn a problem into an ill-fitting theory, if that theory is
already well-known and well-understood.

A new, unfamiliar and strange theory must be _REAL GOOD_ \- and that
improvement must also be valuable in the applied context - for it to be worth
using. If so, it becomes a new standard theory.

------
codr7
There's just no way to win this game, whatever good ideas are thrown in will
be twisted, turned and corrupted into worse than what came before.

I once worked on the software side of a 500-employee electronics company that
had managed to ISO-certify their so called Agile development process.

Everything, and I do mean everything; was performed according to some ISO-
script. No deviation, no matter how meaningless the task. And we had more
useless meetings than anything I've seen before, it's just that they were
called stand ups and reviews.

Thanks, but no thanks.

------
mannberg
One alternative to learning these principles for solving problems that arise
when you wrap all your logic in objects, is to actually _not_ wrap all your
logic in objects.

~~~
s188
Amen

------
mxcrossb
It seems like 90% of this can be achieved for free using a language with duck
typing and named parameters. But of course we know codes written in those
languages are often not robust and inmaintanable. What is missing? Is it the
static typing? Or that all of these goals are implemented mindfully? Maybe the
key is in how you document the principles.

------
p0nce
This article completely changed my mind about SOLID and I realized how
underrated this simple mnemonic still is:
[https://www.gamedev.net/blogs/entry/2265481-oop-is-dead-
long...](https://www.gamedev.net/blogs/entry/2265481-oop-is-dead-long-live-
oop/)

