
Ask HN: How do you write code so it's easy to change? - mtm7
I work as a web developer in the startup industry. I&#x27;ve heard many times that code should be extensible – that it should be easy for engineers to make changes to the system in the future, especially as a product finds market fit. What tips would you give other engineers on writing easy-to-change code?
======
cle
You're going to get a bunch of extreme platitudes in response to this
question.

Like "DRY", "YAGNI", SOLID, short vs. long functions, how to test, minimizing
vs. maximizing reuse, static vs. dynamic typing, cyclomatic complexity,
interfaces, functional programming, immutability, etc. You should definitely
be familiar with these principles and their tradeoffs.

My advice though would be to take them all as suggestions, not absolutes, and
to look at your specific requirements of your project, what you know about it
and your customers (how it's likely to evolve over time, how it has already
evolved, how your company will evolve, who the engineers are, etc.), and then
find & adjust the principles that apply. There are no silver bullets here, and
which techniques are successful depends very much on the context in which
they're used. Work backwards from your team, your situation, and your
customers.

~~~
mikekchar
I agree with this, but I'd like to add one more thing. No matter what you do,
if you find that your code is hard to change, then change it so that it is
less hard. Next time you visit it, change it again. Keep doing this until it
is very easy to change the code. Write enough test support in your code that
you can do this kind of work safely.

------
BurningFrog
Only write code that's absolutely needed for what you're building.

It's very tempting to make it "extensible" by overgeneralizing. What you get
then is overly complex code, most of which is never used.

Cut out everything that isn't currently used, and you have a small system
where every line pays for its keep.

~~~
tunesmith
Agreed - and note that this principle is in tension with the
junior/intermediate (heck, even senior) urge to make things "easy to change"
by just making everything abstract and configurable. ("See, now if we want to
do it this other way in the future, we only have to flip this bit!")

I still haven't found a pithy way to describe the limitations of this
principle. Obviously, frameworks are helpful and also fail this principle, so
even this principle has exceptions. But are you going to be so arrogant to
believe your abstract extensible change will be adopted by all your fellow
teammates in the future?

~~~
solidasparagus
I think the core problem is that it assumes you know what part of the software
will need to be changed. But you often don't so you end up with code that is
too rigid in places that you need to be flexible and too flexible (i.e.
complex) in places that don't need it.

------
jrockway
Less code, loose coupling. Do one thing and do it well. Keep the external API
simple.

It's always easier to change a small thing instead of a large thing. So make a
small thing, and only make it a big thing if you absolutely have to.

I think my favorite recent HN example of this was that article where Discord
rewrote their "unread message count" service in Rust. They were able to make a
huge change to a critical section slowly and incrementally because it was
loosely coupled. The programming language they chose didn't matter, because it
could just be changed. The implementation didn't matter because the consuming
code didn't know about any implementation details. The change happened and all
the other teams had to care about was that there weren't weird latency spikes
anymore.

~~~
nikofeyn
> Less code, loose coupling. Do one thing and do it well. Keep the external
> API simple.

> It's always easier to change a small thing instead of a large thing. So make
> a small thing, and only make it a big thing if you absolutely have to.

this is along the lines of what i was going to suggest. i prefer to code
everything such that it does just one thing that is appropriately at the level
or context it sits at.

a function should just do one thing and have reliable outputs based upon
inputs. a class should represent one thing. class methods should do one
action. modules should collect functionality that does a thing. processes and
servers should do a thing. and so one. if you build your system out of things
only doing the thing they say they do, then it becomes clear when they're
being changed in incorrect ways.

------
nybblesio
Separate _policy_ from _mechanism_. [1] I usually reframe this as separating
the _what_ from the _how_. Back in the 80s and early 90s people would commonly
call this "data driven design" (this is distinct from "data-oriented design"
which lives more in the _how_ ).

One area you can quickly see this in action is with user interfaces. So many
modern applications construct their UI tediously by coding each component and
all of the aggregations by hand. If you take a step back, it's usually very
easy to see that the configuration of the UI is just data. Why not just build
one layout "engine" and feed it data to generate your UI? The engine part can
use whatever hotness you like, but you only have to write that code once. The
configuration of the UI (the policy) is what changes frequently (the business
requirements); not the mechanism underneath. (This doesn't mean you won't have
to change the mechanism, you will. It just requires change at a much slower
pace and the changes are bound within the context of the mechanism itself.)

This approach separates layers of a system which have different change vectors
and velocities. The mechanism tends to have a much lower rate of change and
it's where we programmers like to live. Build tools for "the business" \--
whatever that means in your context -- so they can maintain as much of the
policy as possible.

If you think through designs like this; however, you will realize that our
modern fascination with massive "scaling" of teams is no longer attractive.
This may explain why it has fallen out of favor.

[1]
[https://en.wikipedia.org/wiki/Separation_of_mechanism_and_po...](https://en.wikipedia.org/wiki/Separation_of_mechanism_and_policy)

------
contingencies
Documentation (document the "why"). Loose coupling (this goes for databases,
programming libraries, languages and paradigms, and operating systems as well
as function interfaces). Design data structures first. Make everything a
program. Make every program a filter.

 _Those who don 't understand Unix are condemned to reinvent it, poorly_. -
Henry Spencer

Many architecture tips here:
[https://github.com/globalcitizen/taoup](https://github.com/globalcitizen/taoup)

------
munificent
The answer to your question is essentially the entire field of software
architecture and to some degree even software engineering itself.

Thus there aren't many simple always-applicable tips. Every tip requires
experience to know when you apply it. Any tip that doesn't require that
experience ends up not accomplishing much.

Keeping things simple is good. But sometimes you do need some abstractions,
scaffolding, and structure that sacrifices simplicity but enables the code to
grow.

Tests are good. But sometimes code can be so heavily tested that changing the
tests themselves is a large maintainence burden when the requirements change.

Abstraction and encapsulation are good. But both add complexity and raise the
amount of code someone has to load into their head to understand what's
actually happening.

Documentation is good. But writing good docs itself an artform, and docs that
aren't maintained well can cause more harm than good.

The best advice I can give is to not seek out "tips". Just work with the best
engineers you can find, write code, reflect on it, and trust that your
experience and intuition will grow over time. There is no shortcut to mastery.

------
ezekg
Be as explicit as possible. Use guard clauses. Don't couple things for the
sake of less code. Very, very often, repeating yourself is OK.

------
btilly
Factor out logical units early and often.

DO NOT try to generalize and abstract until the third time that you are
writing something. (Having written it for other companies counts.) Until the
third time your guesses about the right generalizations and abstractions
generally work out badly.

Focus on simple over clever. I cannot count how many abstract systems that I
have seen which were designed to achieve some general purpose reusability.
They almost never actually achieve the asked for dream and almost always
become a barrier to comprehension by other developers.

FOLLOW ALL STANDARDS IN YOUR ORGANIZATION. Code review. Unit tests.
Formatting. Naming conventions. Doing what you think is the perfect thing is
almost always worse than staying consistent with other developers.

------
gregkerzhner
Lots of comments here on keeping this as simple as possible and not over-
architecting. This is a good value, but its equally important to add
architecture and abstractions before its too late. Software projects become
tangled spaghetti messes slowly, over time, by adding "just one more thing" to
existing modules and functions until they are large and impossible to fully
understand. My simple rule of thumb is this - no file should be longer than
300 lines of code and no function should be longer than 15 to 20 lines of
code. If your file or function is longer than these guidelines, its trying to
do too many things and its time to break it down.

------
ssalka
I used to focus on making my code easy go extend. I still enjoy that style in
personal projects, but on large teams, I find it is better to make your code
easy to delete.

------
docteurklein
Write code for use not for reuse. Write code that is easy to remove, not code
that is easy to change.

------
dyingkneepad
Assume the code is going to be changed by first semester CS students. Dumb it
down as much as possible. Don't do any clever tricks. Make the patches as
small as possible so they can be (i) easily reviewed and (ii) allow easier
bisecting.

Example: if a computation requires some intermediate value, create a new
variable with a proper name and assign the intermediate value to it, instead
of just inlining it in a bigger statement or assigning it to "res". The
compiler knows how to deal with temporary variables like these in an optimal
way.

~~~
technofiend
This and include conversational comments that explain in plain terms what
problems you're solving and why.

------
Olreich
Most of the responses focus on how you meet the current requirements. However,
the most important thing to know is not actually about current requirements,
but future ones.

Sure you can assume that all the requirements will change, but then you’ll
need to make a system so flexible it becomes a nightmare to do anything (just
look at JIRA or other software like it, with 100 configuration options for
every piece). These systems require vast resources to maintain because there
is so much code to make it flexible.

You can also assume the requirements won’t change at all, tightly following
the spec and optimizing all the edge cases so you have a pristine black box no
one can touch.

Finding the happy medium involves knowing which assumptions are likely to
change, and which aren’t. Usually, languages won’t change often, so you can
rely on most of those features. Certain well-maintained libraries and APIs
won’t change much, so those can usually be baked-in too. The business logic
around things like pricing, users, analytics, data visualization, and data
processing are likely to have changes. My favorite one for web apps with
databases is search. The filters very often change and grow, so getting a
highly flexible search working is usually a big win for not too much effort.

Note, I’ve never said always. If you can get knowledge about the business
needs you’re supporting, you’ll have a much better idea of which things need
flexibility, and which things don’t. The longer the roadmap on a project, the
more wins you can get when architecting systems too, as you can set yourself
up for easier implementation of future features.

------
slifin
Make it easy to delete conceptually

It should be removable in a way that does not break other systems and it
should be easy to identify when "it" ends and when something else begins

------
simion314
My strategy is make small functions that do one thing then combine this small
functions to create larger ones. When a new requirement is added to change the
large functions you probably have to change just one or a few of the small
functions but many of the smaller functions are unchanged. Also when this new
requirements are added you can reuse the smaller functions, most issues I had
to fix is understanding code that evolved over time and ended up to be a giant
function that does too many things , with many branches.

Also make the code easy to read and understand, have good naming convention
for the small functions and variables, add comments if you have to do
something unusual (like workarounds for weird quirks or bugs) so it makes
sense 3 months later when you read the code again and you forgot most of it.

------
Rainymood
Whole books have been written about this. I use the following rules from [1]
myself. I like them because they definitely put some constraints on my coding
which I like. It forces me to think beforehand and gives me (a junior
developer) some guidance which I desperately need:

* Write functions/units that are <= 15 LoC.

* Create units with <4 branching points

* Write functions/units with <5 parameters

These guidelines force you to write maintainable code. Always open for
discussion. Note that these lessons are lifted straight from [1].

[1] [https://www.softwareimprovementgroup.com/resources/ebook-
bui...](https://www.softwareimprovementgroup.com/resources/ebook-building-
maintainable-software/)

------
grecy
I'd say write the least amount of code you can.

While you're adding a feature it's always tempting to go down the "what if"
route and make some kind of crazy interface that is extensible and able to
accept plugins and all that. The problem is you're likely making it extensible
in ways that turn out to be useless down the road.

Just code the least amount you have to in order to achieve the current goal.
That way down the road it's easy to delete what you did, or code up some
changes to it. I personally think the worst thing you can do is write code
that is all things to all possible future features.

~~~
oweiler
This is probably the most valuable advice. Less code has less bugs, is easier
to read, test and document.

------
joddystreet
This is how we do the API -

\- Database models should contain all the possible mechanisms/methods for data
I/O

\- Data Transformation utilities/ helper functions that do one job well, that
is - sort, filter, validation, whatever \-- -- If an API is using (more on API
below), utils become immutable code

\- Finally the API level \-- -- treat the API code as immutable, once shipped
\-- -- The code should look like a pipeline of functions \-- -- -- eg:
serialize(...) validate(...) filter(...) db(...) \-- -- -- need not be chained
\-- -- new API version, new router, new routing logic

\- Review API with an intent to extract out any possible util methods. final
API methods should look like a series of data transformation functions

\- Overtime the utils would become extremely messy, but the APIs would be very
clean

\------------------------------------------------

The layer running business logic would be the most interacted with layer, if
you can keep it clean, then you can keep frustration low in your fellow devs.
A lot of people can work on the API level, irrespective of their experience.

Utils and Models should be managed by more experienced devs. Rate of change in
Utils and Models is quite low as compared to the rate of change in the API
levels.

\------------------------------------------------

Not sure, I guess I am writing something very trivial and obvious.

------
jmilloy
In codebases I've worked on, the biggest culprits that make maintenance and
modification difficult are

\- functions that do several different things

\- tight coupling

\- implicit behavior

\- too much indirection

\- too many thin custom wrappers

Often several of these come together when an inexperienced developer factors
out everything they can (over-eager DRY) but still tries to keep a few highly
specific things "easy". One thing I see often is modules or classes that wrap
a standard library, _adding_ code essentially _removing_ features in the
interest of "ease-of-use".

I think you can write better code if you write code in multiple passes: only
write what you need _right now_ , and use the code as-is between each pass.
With that approach, you'll gain some real intuition about what is easy to read
and modify, which is much more helpful than someone's list of tips.

That said, I've found that writing simple explicit functions that take
arguments and return a result can get you pretty far. Same for class
constructors and methods, API endpoints, etc. Try to avoid writing
"convenience" functions or classes when just using the library directly with
some arguments would suffice. In other words, boilerplate probably isn't as
bad as you think, and the indirection/implicit behavior/tight coupling is
often worse than the boilerplate.

------
austincheney
Here is my personal checklist:

* If in a loosely typed language go way out of your way to ensure conventions are in place to identify things by data type. TypeScript has completely changed how I look at JavaScript particularly its use of interfaces for defining objects and data structures.

* Limit your use of dependencies as much as possible. In my current personal project I am down to 1 dependency. Dependencies will weigh a project down and prevent nimble shifts of direction. This also includes frameworks and tools.

* Write good documentation and keep it up to date. Automate the authoring of documentation where you can so that it stays up to date with less manual intervention.

* Isolate concerns as much as possible so that a feature can be deleted and replaced with as little effort as possible.

* Keep things simple. This requires lots of extra effort both in planning and refactoring. When there are fewer pieces and everything is as uniform or consistent change requires far less effort and leaves far less dead code in its place.

* Don't abstract things for convenience or easiness. Abstractions are incredibly helpful when they result in simplicity (fewer pieces and fewer ways of doing things). Absolutely don't use abstractions as a means of being clever or for vanity reasons. Then you just have extra code and the larger a project gets the harder it is test and change.

* When other people want to impose their opinions on you push back until they come back with test automation and/or strong provable evidence of a better way of doing things. If they still try to impose their opinions on you, and this will happen, kindly tell them to go fuck themselves. So much of the stupidity in programming comes from baseless irrational subjectivity.

* Test automation. Its not about how you test, but about what you test. There is a lot of nonsense out there about how to test. In my current personal project I have automation in place for code validation, compile checks, command tests in the terminal, service tests. Soon, as I figure it out, I will be adding test automation for user interaction and user data storage. However you are able to execute all the kinds of tests you need just do it. The goal is to promise your software can deliver a feature and prove it with test automation and then add more tests later to cover for edge cases you did not consider.

------
mzanchi
Keep a document of your design decisions. It's very tempting to hack away to
get things done, but human memory is short and feeble: you will forget why
stuff was coded the way it was faster then you think.

I usually keep a Google Docs page open where, as I write the code, I update
the documentation. It keeps things consistent and flexible, and much easier to
go back and refactor.

~~~
tucaz
I think this is key when you are working with code you don't touch every day.

On code you touch every day one can easily make changes since everything is
fresh in one's mind.

When you are working with a code base that you touch once a week or even for a
few hours/minutes every day having notes about what you did, why and perceived
next steps at the time is crucial and VERY useful.

Another strategy I found useful is to try and be consistent with your choices
even if they are not fantastic.

For example: all your tables are named like tbl_entityNam. Even if prefixing
every table with 'tbl' is a bad idea, the consistency in keeping it will be
useful later when you need to work with that code.

------
thdrdt
Easy to change code is code you understand a year later.

This includes:

Easy to understand variable and function names. IDEs make autocompletion
possible so don't fear long names. MyEasyToUnderstandFunction() is better than
MyETUF().

Function names should describe what they do. CreateOrder() is better than
NewOrder().

Make sure you don't use global/public state in any object or component. Public
state is one of the biggest sources of failure and bugs. If you can, don't use
it.

KISS: Keep It Simple Superstar!

But maybe the most important (for me): use separation of concern and context.
This always helped me a lot. For example: if an email must be sent when a new
order is created you can put the email code directly after the order creation
code. This will ofcourse work and might even be valid code. But the component
that creates the order should not be bothered with how emails must be sent. If
you separate this, it will help you a lot to create maintainable code.

------
DanielBMarkham
Maybe a better question is this: how do you write code that does the most
possible to prevent a maintainer from screwing it up?

It sounds like the same thing, but acknowledging that mistakes happen no
matter what and trying to focus on preventing them is something much more
tractable that trying to come up with the perfect thing that can be understood
right away.

For instance, I used to be a fan of long variable names that say exactly what
they do. I still am, but only in certain situations. In fact, I'd argue that
using very descriptive variable names in the wrong situations can actually
make the code less maintainable, not more.

This takes us naturally to a discussion about what works, why it works, and
under which circumstances it might not work (or actually hurt), which is
probably a much more productive conversation than something along the lines of
"how do you guys think the best way to code is?"

~~~
renox
> Maybe a better question is this: how do you write code that does the most
> possible to prevent a maintainer from screwing it up?

By writing tests, that's the only way.. I remember recently writing the test
two weeks after I added the feature, the feature was _already broken_!

The difficulty is that maintainers are very creative in their way to break
your code so it's difficult to write 'thorough' tests.

~~~
DanielBMarkham
Yeah. That would be my answer if I had to pick just one. Tests should give you
immediate feedback if you're wrong. They might not tell you exactly what to
do. I think there's a bit more to the story than just tests, or I wouldn't
have been so elliptical in my comment.

------
Jugurtha
The first thing we did when we started building our ml platform was to go with
a plugin architecture. All applications are simply plugins. Data connectors
are plugins.

We do consulting work and have to build custom solutions for our clients. Once
they were satisfied with the models, they'd ask for an application to use
these models: user management, data uploads, etc.

We used to build a custom solution for each client. It was very hard to change
these solutions when client's demands changed, and it was almost impossible to
reuse that solution with another client.

We took advantage of a decrease in activity to build a platform where things
would be plugins. The time series forecasting application is a plugin, with
endpoints you can tap into with authorization tokens.

The recommender system is a plugin, the notebook application is a plugin, etc.

Now our notebooks are "Published" in one click, which generates an AppBook a
business person can interact with by changing the parameters. The runs are
tracked (params, metrics, and models) in case domain experts tweak parameters
that result in a better model than we have. The models can be deployed to
endpoints you can invoke with generated tokens.

The major benefit is that when a new member joined the team, they had to
understand the whole code base to be able to contribute or add something. Now?
They don't have to understand anything, only the application they're building.
If they want to render it on the sidebar, they just have to add a
`sidebar.html` with the proper icon and route. If they want to show it in our
appstore, they just have to add a file with the proper icon, and our platform
handles the rest. It will discover the application, load it, etc.

An administrator can even load an application as a zip file. The platform will
consume it, install its dependencies, load it, and activate it.

We want the product to be easy to add to and modify its behavior with config,
instead of changing the code and we work to do just that.

------
ben509
Beyond having good testing, don't try to write it to be easy to change, write
it to be good code.

You should try to express problems elegantly, and that will help make some
changes easy. That's nice when that happens, and it does happen quite a bit.
But many changes will still not be straightforward.

Generally, write the code to do what it's meant to do, and when it needs to
change, rip it out and rewrite it.

It's shockingly easy to write something that's concrete and later realize "I
need two of these" and then quickly factor out the common functionality.

Especially, don't kludge things. The problem with "easy to change" is it means
"easy to kludge just one more feature on here." That won't remain easy to
change. Refactor aggressively and your code still won't be easy to change, but
it won't become a kludgey mess that's hard to change, either.

------
loopz
Start with the absolute most simplistic structure, or no structure. Starting
from "Hello world" is OK! (ie. starting in a new programming language)

Keep in mind you can always start over, especially if you componentize your
work!

As you prototype, or just design by drawing on whiteboard, learn your domain
and gain ideas what would improve your initial lack of design.

Iterate and refine when cost effective to do so, not sooner. If still on
whiteboard, you could benefit from implementing rapid no-code solutions before
diving into any code at all!

Resist committing to structure, unless your experience and insights into the
domain require it and you see clear benefits from doing so. Deconstruct
structure you find bring unnecessary complexity, as well as build on structure
when doing so clearly brings leverage.

Care for your code.

------
coziestSoup
Writing code always involves starting with assumptions about how the code is
going to be used. Extensible code is really just code that includes in its
design assumptions that a part of it might need to be switched out or
accommodate something more in the future.

Sometimes its easy to predict how your code may need to be extended if you
give it a bit of thought. In those cases, it might make sense to allow for
easy extensibility. This is pretty rare though. Most times, you can't really
predict what will be required of the code in the future. In those cases I
prefer to bake as few assumptions into the code as possible, so when someone
else is extending it in the future, they spend as little time understanding
your code.

------
matt_s
Simple things go a long way. An easy one is abstraction.

If you are writing an interface to another system of any kind (DB, API, etc.),
abstract that interface one layer from other parts of your app. Then when that
other thing changes you are less impacted.

An example: if your organization has multiple apps, it might sound easy to
just let app #2 have direct DB access to app #1's DB. Take a little time and
build a simple API in app #1 to get at the data, then when you change column
names, etc. as long as your API layer doesn't change app #2 won't require
changes. You don't have to build a fully RESTful API to all-the-things if it
is just one table. Iterate.

------
Nican
My usual go to is every time I write an interface, I think to myself: "How
easy would it be to explain this module to another developer?"

For every additional change, I look at the module as a whole again, and keep
thinking "If we got a new hire, how easy would be for the person to understand
this in the bigger picture?"

Understanding is all about mindset. If part of the code starts to do too much,
then other developers are likely to make incorrect assumptions of the logic
behind that piece of work. The small mistakes slowly compounds over time, and
if not taken care of, the whole project becomes un-maintainable.

------
sush1612
Write however you want to write, choose whatever best practices available, any
design pattern, design principal. Just add lots of comments, as many comments
as possible which talks about what each line is suppose to do. If you are
using a pattern, write at the start that this is x pattern used for this
scenario. if you are branching the code, which scenario each branch will take
care of.

At the end, if you comments and logs are complete and self explanatory, any
one can come in and change the code or debug the code.

------
mattsfrey
Keep code as simple as possible, shun abstractions until the refactor stage,
adopt a language / toolchain that enforces a modular / functional discipline.
EDIT: also, reading the comments here - absolutely shun DRY, be very explicit
with code and don't try to couple anything until its absolutely clear that is
the way to go. This in my opinion is the true mark of somebody experienced
with codebases that grow unmanageable vs the less initiated -- the latter are
always trying to reduce code by coupling things into abstractions, the former
wait until it absolutely makes sense.

------
EastLondonCoder
The biggest bang for the buck for me has been using functional techniques
where immutability is perhaps the most important thing. A good way to get
started is to make a project with react/redux.

------
bjourne
Keep it short and succinct.

------
jressey
Think of your tests as a consumer of the software you create. You will know
it's easy to test if your component does not take much setup. If you have to
create all kinds of different objects to get a test to pass, you've built a
coupled component.

Said another way: Making an easy to understand interface to your software
should be your top priority. Make the interfaces within your public interface
as easy to understand, etc., all the way down.

Easy to test: Easy to understand. That makes it easy to change.

------
newscracker
Extensible doesn’t mean the code itself gets changed often. It would help to
read up on SOLID [1] design principles and books on software development.
Actually implementing SOLID would come by reading good code, getting mentored
and from experience.

[1]:
[https://en.m.wikipedia.org/wiki/SOLID](https://en.m.wikipedia.org/wiki/SOLID)

------
jcutrell
\- Don’t write code until you need it. This results in less code, which is
directly correlated with ease of change. \- If you write lots of separated
classes but then use them directly inside other classes, you’ve defeated the
point. When you depend on something, pass it in instead of directly
referencing, if possible. \- keep methods less than 5 lines, classes less than
100 lines.

------
HackOfAllTrades
Just one tip: Comment your Intent, not the code.

Anyone can read the code and see what it does.

No one can read your mind to figure out what you intended it to do.

------
boltzmannbrain
"Write code that is easy to delete"

[https://programmingisterrible.com/post/139222674273/write-
co...](https://programmingisterrible.com/post/139222674273/write-code-that-is-
easy-to-delete-not-easy-to)

------
gitgud
Here's a couple of mantras to write clean code:

\- Code is written once and read _at least_ once, have empathy for the
readers...

\- If code cannot be understood, it cannot be changed safely...

\- Less dependencies on other code, makes code easy to understand...

\- The smaller the _context_ is, the easier it is to understand the code...

------
jgwil2
Keep files small and use the file system to your advantage: it's much easier
to organize information in a tree than in a string. Keep code for components
together (e.g. no "controllers" folder). Write twice as much documentation as
you think you need.

------
xupybd
Keep it simple and don't allow any technical debt to build up. I've worked in
run down legacy systems and in clean ones. The run down ones are impossible to
change without breaking. The clean ones are just as easy as a green fields
project.

------
ativzzz
If this were an easy answer, software devs would not be paid as much. Read
some books about the topic, then write a lot of code, then change a lot of
that code, and learn from your experiences regarding what was easier or harder
to change.

------
RedBeetDeadpool
To add to the list - I just watched this great video on composition vs
inheritance:
[https://www.youtube.com/watch?v=wfMtDGfHWpA](https://www.youtube.com/watch?v=wfMtDGfHWpA)

------
deepaksurti
Separate interface from implementation. [1]

[1]
[https://en.wikipedia.org/wiki/Interface_(computing)#Software...](https://en.wikipedia.org/wiki/Interface_\(computing\)#Software_interfaces)

------
codekilla
In a few words?....'Don't write the code in the first place'. Yes, follow best
practices of modules etc, but in general write a lot less code. This is harder
than it sounds, and often requires multiple rewrites.

------
taherchhabra
I would suggest reading Head first design pattern, apart from design patterns
the book also explains how 'change' is inevitable in software development and
how to design the software extensible and modular

------
pier25
In very simple terms make small pieces of untangled code. Think in terms of
small lego blocks that connect and can be replaced and modified independently
instead of tangled webs of logic.

------
ykevinator
Comments and empathy for others and your future self. That means minimalism
and lots of comments to remind yourself what you were thinking when you wrote
it, who calls it, why, etc.

------
crimsonalucard
Your asking about modular code where modules can be changed or swapped.

The functional paradigm is the most modular style I've seen. Modularity is a
side effect of immuteability.

------
airbreather
If you can, use state machines, they provide a level of effective
encapsulation otherwise hard to beat.

This menas ability to change specific behaviours wih very limited side
effects.

~~~
renox
In theory yes, in practice I'm not so sure: I've seen state machine
implementations which were ten times more code than the few if/else/for loop
needed.

Sure you can draw a pretty state machine diagram, but is the code really
easier to read and maintain? I'm not sure.

------
jiveturkey
TDD is the key.

By using TDD methodology (structure your code to support it, not necessarily
obey the principle head to toe), your code will be 80% of the way there.

------
peterhi
Write shallow code, code that is the simplest solution for the problem

If the word "architect" springs to mind then you are doing it wrong

------
stevens32
Make code easy to change by changing it often, thus forcing you to either
structure it so that it's easy to change, or die

------
Wildgoose
Optimise code for Deletion.

If it's easy to delete, then it's easy to replace.

And if it is easy to delete then it should be naturally loosely coupled.

------
rolph
try to anticipate where the code may have to change, and with what frequency.
For example how likely is it that a modification to handle new file
specification will be required, or how often would you have to integrate new
authentication methods. how often would you need to give things a facelift to
keep up with marketing trends.

------
sergioisidoro
A lot of people say "You could write this X/Y block in one line" or "You can
simplify this".

A lot of the times they don't mean simplify, they mean compact code.

I write verbose code, as explicit as possible. Sometimes it looks like it was
written by someone just learning to program. And that's on purpose to make the
code as easy to understand and change as possible.

TL:DR: Be skeptical about code "simplification" and don't fall on using fancy
language features when if/else conditions do the job.

------
throwawayForMe2
Encapsulate the concept that varies.

------
hprotagonist
high test coverage and functional purity as much as possible.

don't get trapped by being "too DRY"

------
lincpa
Keep code Simple and Unified.

Keep data and logic separate, data-driven development.

Adhere to the "standardized data interface specification" and use pipeline-
functions to manipulate data from the initial state to the final state.

Pipeline-functions conforming to standardized data interfaces are easy to
change, replace, and insert extensions.

In the web model, It can be easily replaced as long as the components that
conform to the http data specification. For example, Clojure web application
model:

\- product standard (data interface): the req-map and resp-map of the ring

\- warehouse: the ring

\- workshop: the pipe functions on both sides of the C/S, and Raw materials
(hash-map) are transferred to each other through warehouses through
interactions.

[https://github.com/linpengcheng/PurefunctionPipelineDataflow](https://github.com/linpengcheng/PurefunctionPipelineDataflow)

------
PaulHoule
Don't repeat yourself.

There is an objective way to tell if design A is better than design B, and
that is to measure how hard it is to make changes. You have to build that into
your culture and not optimize for something else, or do things because
somebody thinks that is the way to do it.

A big problem in current architectures is that what seems to be a small change
(say, add a "cell phone" field next do a "home phone" and "office phone")
field often requires a number of little changes in widely separated code:

* a database schema change * changes to database queries * changes to a back-end API * changes to a front-end Javascript app * changes to an iOS mobile app * changes to an Android mobile app

Conventional system design means that what looks like one change to management
is actually at least six changes, which might affect that many source code
repositories, etc.

An ideal design would let you create a feature by writing all of your code in
one place. Of course this would require a big change in how applications are
structured and built, but it could lead to a durable advantage. See

[https://en.wikipedia.org/wiki/Software_product_line](https://en.wikipedia.org/wiki/Software_product_line)
[https://en.wikipedia.org/wiki/Feature-
oriented_programming](https://en.wikipedia.org/wiki/Feature-
oriented_programming) [https://en.wikipedia.org/wiki/Low-
code_development_platform](https://en.wikipedia.org/wiki/Low-
code_development_platform)

The biggest barrier to the above I think is that when you talk to a team they
always have shiny objects that they can't live without. Maybe they think
functional programming is great (yeah, you can handle errors with monads, but
you can drop errors on the floor just as easily as you can with exceptions,
return codes, etc.) or that immutability solves everything, JDK 13 is the
best, whatever. So people are thinking about everything except the thing that
management will care about which is long-term sustained productive
development.

~~~
Supermancho
> Don't repeat yourself.

And also do repeat yourself. Consistency (patterns) are a factor in reducing
errors.

