Engineering approach
The principles I apply when designing, changing and operating software systems.
- Section
- Get started
- Status
- Published
- Updated
- 2026-08-28
- Read
- 3 min
These are the working principles behind the technical decisions described throughout this site. They are not a methodology — they are the defaults I fall back on when a decision is ambiguous, which is most of the time.
Understand the system before changing it#
Most production defects are the result of a correct change applied to a misunderstood system. Before modifying behaviour I read the surrounding code, trace the actual runtime path, and confirm my model against a real device or a real trace rather than against documentation.
This applies at every level: a Fragment lifecycle callback, a Gradle task
graph, or the way a vendor HAL reports state. The cost of an hour spent reading
is consistently lower than the cost of a change that has to be reverted.
Note
The reverse-engineering work described in Reverse Engineering is the same discipline applied to systems whose source I do not control.
See also
- Performance — Startup time, jank, memory and battery: measurement first, optimization second.
- Debugging — Systematic diagnosis of crashes, ANRs, native faults and field-only issues.
Reduce friction#
Engineering throughput is usually limited by friction rather than by difficulty: a slow build, a flaky test suite, an undocumented release step, a manual environment setup. Friction compounds, and it is rarely anyone's explicit responsibility.
I treat measurable friction as a defect class of its own:
- Build and CI runtimes are tracked and budgeted, not left to drift.
- Repeated manual work is automated once it happens for the third time.
- Setup steps are encoded in scripts or convention plugins, not in wiki pages.
- Flaky tests are quarantined and fixed rather than re-run.
Design for evolution#
Requirements change faster than architectures do, so the useful question is not "is this the right design" but "how expensive is it to change this design later".
In practice that means clear module boundaries, dependencies pointing inward toward stable abstractions, and behaviour that is testable without the framework. It also means resisting abstraction that is not yet paid for — an interface with one implementation is a cost, not a design.
Prefer explicit state#
Implicit state is the most common source of defects that reproduce only in the field: mutable singletons, lifecycle-dependent side effects, and values that exist in two places at once.
I model state explicitly — a single owner, a defined set of valid values, and transitions that can be enumerated. On Android this typically means unidirectional data flow, immutable UI state objects, and side effects confined to well-defined scopes.
A useful test
If you cannot describe the current state of a screen as a single value, the screen has more states than the team believes it has.
Deliver end-to-end#
A feature is not delivered when the code is merged. It is delivered when it is released, observable in production, and understood by the people who support it.
I take ownership across that whole path: implementation, tests, pipeline changes, rollout, monitoring, and the follow-up when the metrics say something unexpected. Working end-to-end also produces better designs, because the constraints of release and support feed back into the code.
Improve the surrounding engineering environment#
Individual output matters far less than the throughput of the team. Wherever I work, I spend a consistent share of time on the environment itself: pipelines, tooling, review practice, documentation, and mentoring.
The trade-off
Infrastructure work is easy to over-invest in. I scope it by the friction it removes and stop when the return flattens — tooling that nobody but its author uses is a liability.
Summary#
The six principles and the observable signal that each one is actually being applied rather than merely stated.
| Principle | Practical signal |
|---|---|
| Understand first | Traces and source read before the first commit |
| Reduce friction | Build, CI and setup time tracked as a metric |
| Design for evolution | Change cost, not design purity, drives structure |
| Explicit state | One owner per piece of state, enumerable transitions |
| Deliver end-to-end | Ownership from design through post-release monitoring |
| Improve the environment | A measurable share of time spent on team throughput |