Skip to content

Latest commit

 

History

History
153 lines (81 loc) · 12.3 KB

File metadata and controls

153 lines (81 loc) · 12.3 KB

Engineering principles

Details

Our principles guide the way we work and interact with each other. They are based on the seven Lean principles as expressed in Lean Software Development: An Agile Toolkit by Mary Poppendieck and Tom Poppendieck.

1. Eliminate waste

Waste is anything that interferes with giving customers what they really value at the time and place where it will provide the most value. Here are some examples, listed against the seven types of waste identified by Lean.

Inventory — partially done work, e.g. plans and designs, code. Limit work in progress (WIP) and use a pull-based approach.

Inventory — unnecessary resources ARCHITECTURE-SUSTAINABILITY, e.g. server over-provisioning, complicated tools where simple ones would do. Adopt a "just enough, not just in case" mindset.

Overproduction — building unnecessary features. Start simple and basic, get feedback and iterate.

Overproduction — planning or designing in excessive detail. Use a "just enough, just in time" approach to both.

Overproduction — overengineering, i.e. building unwarranted levels of code perfection or quantities of tests. Be pragmatic and balance effort now with saved effort or risk in the future. Remember KISS and YAGNI and see the caveats in structured code.

Overproduction — reinventing the wheel. Solving the same problem repeatedly in an organisation. Make sure there are effective ways to share knowledge between teams to avoid this.

Overproduction — building when you could instead reuse or buy. ARCHITECTURE-REUSE Remember to consider all these alternatives.

Overproduction — premature optimisation for reusability. Before making something reusable, first make it usable. Prefer explicit logic to implicit. Excessively generic systems create accidental complexity. KISS and YAGNI and the caveats in structured code again.

Overproduction — premature optimisation, e.g. for performance or scale. Design with both in mind, but not excessively so. Defer optimisation until testing shows it is necessary. Start simple, test, iterate.

Extra Processing — due to changing requirements. Use just enough, just in time approach to understanding requirements and deliver small iterations in a build-measure-learn loop.

Extra Processing — due to delayed integration making merging / reconciliation harder. Use continuous integration with frequent merges.

Extra Processing — due to late testing. When testing is done after implementation, especially if long after, bugs become more time consuming to detect and fix. Use test driven development and continuous integration.

Hand-offs (“Transportation” and “Waiting” in Lean terminology) — excessive passing of work between individuals or teams. Develop multi-skilled individuals and cross-functional product teams.

Context switching — due to too much work in progress (WIP). Limit WIP explicitly. Focus on optimising lead time.

Context switching — due to poorly segregated work (e.g. project vs support). Identify different classes of work and create separation with time-slice or rota systems to avoid reactive work from destroying productivity.

Defects due to not understanding requirements properly or bugs leaking through. Use approaches described in the next section.

2. Build quality in

The following practices support the principle of building quality in.

Collaborative analysis and elaboration with the right people involved to examine a change from all angles to ensure requirements are fully understood at the point the work will be done.

Deliver incrementally. Establish build-measure-learn loops to keep the system simple and to ensure it meets evolving user needs.

Pair programming. Avoid quality issues by combining the skills and experience of two developers instead of one. Take advantage of navigator and driver roles. Also consider cross-discipline (e.g. dev-test) pairing.

Test automation. Use test-driven development: Write the tests hand in hand with the code it is testing to ensure code is easily testable and does just enough to meet the requirements.

Protect code quality to keep code easy to maintain.

Write less code. Treat code as a liability rather than an asset: the more code, the more there is to go wrong. Incremental delivery and test driven development both help keep the codebase small and simple.

Continuous integration. Build automated code quality checks and tests: security, accessibility etc. Use frequent code merges.

Minimise hand-offs to reduce knowledge gaps.

Automate any tedious, manual process or any process prone to human error.

Refactor often — expect to change existing code.

Maintain actively — prioritise fixing bugs and tech debt.

Keep it simple. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Special cases aren't special enough to break the rules. Although practicality beats purity. (Zen of Python.)

Prefer serverless. SERVICE-TOOLS Where not serverless use ephemeral and immutable infrastructure. Make it simple to guarantee predictable and reliable behaviour.

Bake in security. Understand it as a team. Consider it through every stage of delivery. Verify it automatically and continuously. Model risks. Use defence in depth. Segregate security domains (e.g. test/prod, public/PII). Minimise human contact with sensitive data.

Stay up to date. Automate patching/upgrades, dependency/image scanning and updating.

Bake in observability and reliability. SERVICE-RELIABILITY Understand requirements as a team, treated as unspoken user needs. Consider it through every stage of delivery. Verify it automatically and continuously. Practice incident management resolution using techniques like game days.

3. Create knowledge

Generate knowledge. Make time for activities which help us learn by doing, e.g. spikes, proof of concepts. Validation of technical design comes as the code is being written — expect the design to evolve. Encourage learning and experimentation.

Gather knowledge. Release a minimum feature set early for evaluation and feedback and continue to iterate and get feedback. Use continuous integration and live service monitoring and analytics to gain insight on code and system health.

Share knowledge. e.g. pair programming, code reviews, show and tell. Consider whether encoding knowledge in tools would assist other members of your team, for example building a VSCode extension to provide contextual information and tips for using a bespoke test harness.

Record knowledge. e.g. tests, self-documenting code, self-documenting system (e.g. OpenAPI), documentation — in that order.

Record decisions. Use the Any Decision Record template to document point-in-time decisions providing information like context, assumption, drivers, options and rationale to articulate the decision for the stakeholders and your future self. Always, consider and compare options as a decision made without that is not a decision.

4. Defer commitment

Make decisions small by breaking a big problem down.

Make decisions reversible whenever possible. e.g. prefer pay as you go to bulk buying, prefer cloud to physical infrastructure.

Decide at the last responsible moment for irreversible decisions.

Just enough, just in time — applied to all stages: research, analysis, product design, planning, technical design.

Recognise architecturally significant decisions that affect the structure, non-functional characteristics, dependencies, interfaces or construction techniques. A good architecture decision is one that helps guide development teams in making the right technical choices.

5. Deliver as fast as possible

We need to figure out how to deliver software as fast as possible. This reduces the cycle time for change, allows the business to reach the point of increased learning sooner and provides more immediate gratification and feedback for the customer. This also allows us to defer commitment as much as possible.

Eliminate waste (as above).

Deliver little and often. Prefer lots of small changes (with automation doing the heavy lifting) to fewer large changes

Start simple and iterate. Structure projects as distinct discovery, inception and build stages to allow commitment to be incrementally increased. Start with an MVP / steel thread / walking skeleton.

Sustainable pace. Work at a pace which is sustainable long term.

Choose the right tools for each job. SERVICE-TOOLS Balance autonomy and conformity: unnecessary proliferation of a wide variety of tools impacts overall effectiveness, but limiting too much promotes "least worst" choices. Note that "each job" does not have to mean a single toolset for a service: consider a polyglot approach for component-based services.

6. Respect for people

Communicate e.g. stand-ups, retrospectives, show and tell / demo.

Give and receive feedback. Give with honesty and respect; receive gratefully.

Encourage healthy debate. e.g. collaborative analysis and elaboration with the right people involved to examine a change from all angles, functionality/quality priority competition, collaborative design.

Foster a growth mindset. Invest in learning and individual development.

Default to self-organising and peer to peer whenever possible. Minimise hierarchy and bureaucracy.

Safe to fail. Individual mistakes should not have serious consequences. Build in guards and automate whenever practical.

No blame. When things go wrong, treat it as a learning opportunity for the team and organisation. Use blameless post mortems and Five Whys.

Use inclusive language. Avoid terms which cause hurt and offence, including if they have historically been considered industry-standard terms.

7. Optimise the whole

Visualise the work. Map the value stream with a Kanban board, identify bottlenecks.

Optimise the bottleneck. Use the theory of constraints.

Use feedback loops to avoid negative global effects from local optimisations.

Balance autonomy and conformity. Teams use the right tool for the job, within reason: unchecked proliferation of a wide variety of tools impacts the overall effectiveness of the organisation.