How I Work as an Engineer: Specifications, System Design, AI, and Verification
Over the years, the way I approach software engineering has changed.
Earlier in my career, a large part of the work was translating requirements into code, choosing the right framework, and figuring out how to implement a feature. Those skills still matter. But as systems become more interconnected and AI coding tools make implementation faster, I find that the most important engineering work increasingly happens before and after the code is written.
Before implementation, I need to understand the problem, define the constraints, and make architectural decisions.
After implementation, I need to establish that the system behaves correctly—not just in the happy path, but under retries, invalid inputs, partial failures, and changing requirements.
This is the workflow I try to follow.
1. Define the Problem Before Writing Code
I start by asking a few questions:
- What problem are we actually solving?
- What does success look like?
- What constraints already exist?
- Which decisions are still open?
- What can go wrong when this runs in production?
A requirement such as “process incoming emails” sounds straightforward. But implementation depends on details: Can the same email arrive more than once? What happens if processing fails halfway through? How do we prevent duplicate records? How will failures be detected and recovered?
These are not minor implementation details. They influence the system's design.
I try to turn ambiguous requirements into explicit behavior, acceptance criteria, and constraints before committing to an implementation. This gives both the developer and any AI coding tool a clearer target.
My principle: Ambiguity should be resolved deliberately, not hidden inside the code.
2. Think in Systems, Not Isolated Features
A feature rarely exists on its own. It interacts with databases, APIs, background workers, authentication, external services, and existing business rules.
I therefore consider the surrounding system before choosing an implementation.
That includes questions such as:
- Where should responsibility for this behavior live?
- Which component owns the data?
- What happens when a dependency is unavailable?
- Are operations safe to retry?
- How should the system behave when two requests arrive concurrently?
- What needs to be logged, measured, or alerted on?
I also try to avoid overengineering. Not every feature needs another abstraction, service, or architectural pattern. The goal is to choose the simplest design that satisfies the requirements without creating avoidable problems later.
Good architecture is not about maximizing sophistication. It is about making the important decisions explicit and keeping the system understandable as it evolves.
3. Treat Specifications as the Source of Truth
As projects grow, requirements can become scattered across conversations, tickets, documentation, code comments, and AI prompts.
That creates a subtle problem: different people—or different tools—can work from different assumptions.
I prefer to establish a canonical place for important specifications. Depending on the project, this might describe:
- System architecture and component responsibilities
- API contracts and data schemas
- Business rules and validation requirements
- Implementation constraints
- Acceptance criteria and testing expectations
When using AI coding tools, this becomes particularly valuable. Rather than repeatedly explaining the system from scratch, I can provide a shared specification that describes what must be built and the boundaries within which it must operate.
The specification does not need to document every line of code. It should capture the decisions that matter and remain useful as the implementation changes.
My working model is simple:
Specification = source of truth
Code = implementation
CI = enforcement
The specification defines intended behavior. The code realizes it. Automated checks help prevent the implementation from drifting away from that intent.
4. Use AI to Accelerate Implementation, Not Replace Engineering Judgment
AI coding tools have changed how I approach implementation. They can help explore design alternatives, generate initial code, explain unfamiliar code paths, draft tests, and investigate potential causes of bugs.
But faster implementation does not automatically produce a better system.
An AI tool can generate code that compiles while misunderstanding a business rule. It can follow a local pattern that is inconsistent with the wider architecture. It can also miss failure scenarios that become obvious only when the entire workflow is considered.
That is why I try to give AI tools a clear problem, explicit constraints, and access to the relevant specifications.
I use them to accelerate exploration and implementation while retaining responsibility for the decisions that shape the system.
The work then shifts toward asking better questions:
- Does this design satisfy the actual requirement?
- Are the assumptions correct?
- Does this introduce a new failure mode?
- Is the solution consistent with existing contracts?
- What evidence do we have that it works?
AI can reduce the effort required to produce a candidate solution. It does not remove the need to evaluate that solution.
5. Verify Behavior Instead of Trusting Confidence
Code review is important, but manual inspection alone is not enough.
Where possible, I want important expectations to be expressed as repeatable checks.
Depending on the system, that can include:
- Unit and integration tests
- Schema validation
- API and contract tests
- Database constraints
- Static analysis and type checking
- CI checks that block changes when required conditions fail
Consider an API that accepts a request and stores a record. A successful response in one test does not prove that the implementation handles malformed input, concurrent requests, duplicate submissions, or a database failure correctly.
Each of those scenarios represents a different expectation that should be considered and, where practical, tested.
This is especially important when code is generated or substantially modified with AI. Instead of asking only whether the implementation looks reasonable, I want independent evidence that it meets the intended behavior.
Not every requirement can be fully automated. Some architectural and product decisions still need human judgment. But the more important expectations we can make executable, the less we have to rely on memory and convention alone.
6. Design for Failure, Not Just the Happy Path
One of the most useful questions in engineering is: What happens if this operation fails halfway through?
A database write might succeed while a downstream request fails. A worker might process a message but crash before acknowledging it. A client might retry a request because the original response was lost, even though the operation succeeded.
These situations are normal in distributed systems.
They require deliberate decisions about transactions, idempotency, retry behavior, timeouts, observability, and recovery.
I try to consider those cases while designing the workflow rather than treating them as problems to solve only after deployment.
That does not mean anticipating every theoretically possible failure. It means identifying the realistic failure modes that could cause data inconsistency, duplicate side effects, security issues, or difficult operational incidents—and designing appropriate safeguards.
A feature is not production-ready simply because its happy path works.
7. Keep the System Understandable
Maintainability is more than clean formatting or a well-organized folder structure.
A maintainable system makes it possible for another engineer to understand why a decision was made, where a behavior belongs, and what might break if something changes.
I value clear boundaries, explicit contracts, meaningful names, focused abstractions, and documentation that explains decisions rather than restating the code.
I also try to avoid adding complexity without a clear reason. Abstractions should solve a real problem. Tests should protect meaningful behavior. Documentation should help someone make a decision or understand a constraint.
This matters even more when implementation is accelerated by AI. Generating more code is easy; maintaining a coherent system as that code accumulates is harder.
The objective is not merely to finish the current task. It is to leave the codebase in a state where the next change can be made safely.
8. Take Responsibility for the Result
Tools can generate code. Specifications can describe expected behavior. Automated checks can catch many classes of defects.
But none of these removes the engineer's responsibility for the outcome.
Someone still needs to decide whether the requirement makes sense, whether the architecture fits the problem, whether the tests cover the important risks, and whether the system is ready to operate in the real world.
That responsibility cannot be delegated to a successful build or a confident AI response.
For me, engineering means connecting the entire chain: understanding the problem, making design decisions, implementing the solution, verifying behavior, and considering what happens after deployment.
The Workflow in One View
The process I aim to follow is straightforward:
- Understand: The problem, constraints, and expected behavior.
- Design: The system boundaries, contracts, and failure handling.
- Specify: The important decisions in a shared source of truth.
- Implement: With the right combination of engineering effort and AI assistance.
- Verify: Through tests, validation, review, and automated checks.
- Operate: With appropriate observability, recovery, and maintainability.
In practice, these steps are iterative rather than strictly sequential. Testing may expose a missing requirement. Implementation may reveal an architectural constraint. Production feedback may require the specification to change.
The point is not to follow a rigid process. It is to make sure that speed does not come at the expense of clarity and reliability.
Final Thoughts
I don't measure engineering effectiveness by how quickly code can be produced in isolation.
I care about how clearly a problem is understood, how well the system is designed, how reliably the implementation behaves, and how safely the software can evolve.
AI has made parts of implementation faster. That makes sound specifications, system-level thinking, verification, and engineering judgment even more valuable—not less.
Specifications guide. Code implements. Automated checks enforce. Engineers remain accountable.
That is the approach I try to bring to the software I build.
Explore more about my engineering approach and projects:

Comments
Post a Comment