💻 Software Engineering Principles

Software is for people

Software is a means to an end

Building software is not the goal. The primary job of any software engineer is delivering value. The goal is solving problems for your customers. It's much cheaper if you can do that without building software.

You cannot deliver value without knowing who the people are, and what they want and need

You cannot build successful software until you know who will use it, and what they want and need.

Minimise cognitive load for others

The cognitive load is a good predictor of software quality. Reduce the cognitive load of the domain, tools, APIs, DevEx. Give others more space to think about the actually challenging parts of their problems.

Code is a tool

Build only what's core to your business

Don't build any part of your system that is not specific to your business. If you can buy it, or find an opensource tool, use that instead. A classic example of this is authz and authn.

The best code is no code

Code has an overhead of maintenance. Properly consider solutions that don't need tech. Coders gonna code.

Build less, not more

You learn way more while building the system than you could possible know when planning it.

Lean into this learning, build small first. Don't be tempted (or dragged) into building big.

Beware "not invented here"

Beware the "not invented here" syndrome. This is when engineers think a piece of tech is not suitable (or good) because they didn't have a hand in developing it. This can happen across companies, departments, teams, squads, etc.

If it's too early to criticise, it's too early to evangelise

If something is too early to be judged bad, it's also too early to be judged good. Beware those who say something is good, but is too 'new' for the criticism to be valid.

Tests are a tool

Tests show the presence of bugs, not the absence

Program testing can be used to show the presence of bugs, but never to show their absence

E. W. Dijkstra (1970), Notes On Structured Programming.

Don't put all your eggs in the same testing basket

Use a range of different testing types, don't rely on a single type.

unit, integration, system, synthetic, etc

Test outside-in

Test the external behaviour of the component. If it does what you want; you are done.

Testing inside-out focuses too much on the implementation, and not on the behaviour and API that consumers will use.

Clear is better than clever

Every layer of abstraction costs something

All problems in computer science can be solved by another level of indirection". This is often deliberately misquoted with "abstraction" substituted for "indirection". wikipediaopen in new window

Rule of three

It's OK to have two instances of the same code, but three instances should be extracted to avoid duplication. wikipediaopen in new window

Small duplication is better than small dependency

Some duplication in software code is often a godo thing. Using DRY (Do Not Repeat Yourself) as a blanket rule can constrain future development in unexpected ways.

Don't toggle the behaviour of a method with bool


It couples the methods, stops the methods from being independent. It's also inclear what the false or Abacus actually means.

Clean code: G15

Consider WTF's/minute

Write code that is clear, and obvious. Reduce the number of surprises.

Design away the error cases

Design the code so that it's not possible to have an error case. Instead of handling the error, structure the code so it's not possible for the error to have happened.

Variable names should not be sentences

Don't concat an entire sentence into a variable name. I see this happen in java code often.

final String concatinatingLotsOfWordsIntoAVariableNameDoesNotMakeItEasierToUnderstand = "";

Hope is not a strategy

Hedge against the bad outcomes

Hope ignores the information, and leaves the outcome to chance. Even a small increase in the probability of being right makes a big difference to the outcome.

Hedge against the bug happening, structure and build in a way that eliminates errors.

Murphy's Law

Anything that can happen, will eventually happen.

If you leave things to chance, sooner of later the chance of the 'bad' thing will happen.

Understand what is possible

If you don't understand the universe of what is possible, you cannot design a good solution.

Keep up with developer tools, tech innovations, cloud services. This can be time consuming, but is critically important.

Don't design a system to avoid a component you don't understand

Too often your fear of a component that you don't know influences how you design your system. Lean into what you don't know, and make it known. Don't seek solutions that avoid the unknown component, because it will come back to bite you.

Chesterton's fence

You can't change the system until you understand why it operates in a certain way. wikipediaopen in new window

Everything is a tradeoff

No matter what you do, you will have to trade one feature of the system against another. CAP principle is an example of this.

Know what trades you are making

Be conscious of the tradeoff you are making. You should be able to clearly explain why you are choosing one feature over another. If you cannot explain, you do not understand the tradeoff you are making.

Beware failure demand

Failure demand is demand caused by a failure to do something or do something right for the customer. Failure demand detracts from your ability to ship value. Delivering the wrong thing first time round, or not fixing the things after you've delivered it, will cause failure demand. Failure demand consumes the teams capacity by creating more work.

Know your positive and negative freedoms

Positive freedoms are the freedoms you give. The freedom to do something, in code, in a system, etc. Negative freedoms are the things you are rid of. Freedom you get by not needing to consider something.

Nothing is as permanent as a temporary solution

Don't accept bad trades because the solution is temporary. If the temporary solution improves the situation, it will rapidly be promoted to permanent. Your bad trades will then be permanent.

Systems evolve over time

Every system eventually sucks

There is no “right” architecture, you’ll never pay down all of your technical debt, you’ll never design the perfect interface, your tests will always be too slow.

Worry less about elegance and perfection, worry more about achieving your goals.

Every decision was a good decision at the time

No one sets out to make bad choices. No matter how bad something looks in hindsight, every decision was the best decision at the time (given the information, skills, and abilities).

Internalise the externalities

Systems will suck less, or less quickly, if the system can "sense". Expose the system and the developers to the 'externalities' (the cost or benefit of their decisions). Creating this feedback will help the system evolve, and suck less quickly.

Systems that don't evolve will die

If your system cannot evolve, it will either be a huge task to make capable of evolving, or it will get rewritten.

Build systems that support evolution

Don't bake policies or rules into the systems. Build the systems so that they are isolated from change, but also allow evolution.

Write code that's easy to delete

In the inevitable rewrite of your system, or when your system is evolving, you will appreciate it being easy to delete code. Make code that's easy to delete, don't couple it to other classes or functions.

Data is the most important part of the system

Data integrity is important

Don't leave data integrity to "hope". See hope is not a strategy. Anything that happens off the golden path should be handled to make sure it doesn't leave partial or dirty data. Dealing with this partial data later will be a really huge pain.

Model the data flows in a paper-based solution

Many mature business practices stem from paper-based solutions. Kanban worked on paper cards, ticketing systems worked on paper tickets, async queues worked on mail trucks and in/out boxes, 'cc' line in email stands for carbon-copy.

If you first model a system and it's dataflows using paper, you aren't distracted by the technical details of the solution. It's easier to see how the data will flow, and who will do what when you model the system using only paper. Then reflect that paper system in tech.

Good code stems from good data models

If you design and model your data well, good and understandable code will naturally fall out of that.

Models should be aggregatesopen in new window, with consistent aggregate boundaries.

Writing is a superpower

Write more. Being able to structure your thoughts into concise written form is a skill. Writing is an enormously scalable way of sharing information.

It's much harder to retro-fit documentation, than to write it up front

To add documentation to an old project or team is hard. To change the process so that team writes documentation as they go is easier.