đź’» 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". wikipedia
Rule of three
It's OK to have two instances of the same code, but three instances should be extracted to avoid duplication. wikipedia
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
someMethod(false)
someMethod(Enum.Abacus)
2
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. wikipedia
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 aggregates, 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.