Building Features the right way
KISS, Requirements, Architecture, Estimation – and AI Agents Done Properly
In a world where AI Can generate 500 lines of code in seconds, the real question isn't. How fast can we build? it's Are we building the right way?
I remember at my university days where people were fast but structure was chaos. Now with experience I know chaos doesn't scale.
This article breaks down my personal way to approach feature development with or without AI.
KISS = Keep it Simple, Stupid
To me KISS is one of the most violated principles in Software. Simple doesn't mean naive it means clear responsibilities, predictable behavior, easy to bug, easy to change, easy to code.
I still remember when I started with Python and my mentor used to always ask me to do multiple solutions for one single problem. Then we will elaborate and discuss what proposal was the more aligned with KISS.
Now complexity compounds simplicity compounds too but in your favor. To me bad engineering is when you are away from simple and understandable solutions, over-abstracted patterns, 8 layers for a simple feature and "future-proofing" imaginary problems.
While using KISS as a base I have being able to solve today's problems, design for clarity, make it readable and optimize when data demands it.
I wanted to talked about KISS because is in my DNA now and I try to always used as a first line of thought. Now let's talk more about the process.
The Process I Follow
Requirements
Sometimes you fail even before writing the code. Requirements with experience can be vague and sometimes we feel confident about understanding based on experience.
But the true is that a proper feature requirement should answer:
- What problem are you solving?
- Who is affected?
- What is the expected outcome?
- What are the constraints?
- How will we measure success?
Let's do an example using iRent:
Feature: Add collaborator task checker
- Problem: Collaborators manually check task asignation based on period of time.
- Users: Collaboratos iRent team.
- Outcome: Task assignation notification automatically.
- Constraints: Must handle multiple notifications to collaborators at the same time.
- Success metric: Multiple notifications to multiple collaborators in periods of 1 hour.
Clear. Measurable. Concrete.
Estimation
We have the requirements and is time to Estimate. Estimation is not guessing hours it's breaking uncertainty into components. Modern estimation flows do the following:
- Break feature into technical units
- Identify unknowns
- Asses integration risk
- Account for testing & deployment
Instead of "This feels like 6 hours" do this:
- Schema change: 1h
- Context and logic: 3h
- LiveView UI: 3h
- Edge cases & validation: 2h
- Testing: 2h
- Deployment & QA: 1h
Total 12h realistic estimate.
One crucial part when estimating that shows engineering maturity is admitting overhead.
Design
I personally love design for features. A good design can make coding so much easier. Clarity here is important and saves time and effort. Design shouldn't be taken lightly due to his impact on the final result.
Start with a simple question. Where does this feature live?
- Context?
- Domain layer?
- External integration?
- Separate process?
This questions are vital to define the boundaries of our feature. The next part is to model the domain. As we use elixir in elixir terms:
- what structs?
- what changesets?
- what events?
- what processes?
Good design matters and domain modeling matters more. Not UI, not Endpoints, Domain first.
Modern Structure of a Feature
Now we mentioned a notification system for iRent (one of our apps). Is important to lay a clean structure:
lib/app/notifications/
-- notification.ex
-- notification_service.ex
-- notification_supervisor.ex (if async)Inside Phoenix:
- Context handles ochestration.
- LiveView handles presentation.
- Domain module handles business rules.
- Background task isolated.
No logic templates, no db calls where we render (LiveView) no random functions in controllers (if we actually need one). There's a clear separation and we keep it simple.
Now the agent
At this point we have the everything we need. There is a big difference by following this path than going into the agent and prompting:
"Build me a notification system"
That's not engineering that's gambling...
This is my approach to use an agent (claude code) and is quite simple after doing the engineer part the architecture part first.
- You design first (All the steps we did above)
- Then you delegate.
The prompt needs to be specific. Scoped. Directed. Never allow the agent to design everything for you because AI doesn't know your constraints, it doesn't know your production history, it doesn't know your performance requirements. Clarity is a most here.
"Generate Ecto schema for this model". "Generate test for this context"...
AI accelerates code production but correctness still matters. Architecture still matters. Responsibility still matters. if a System crashes at scale no one blames the prod or the agent. They blame the engineer.
The real modern stack will make you:
- Think clearly.
- Design minimally.
- Structure intentionally.
- Estimate realistically.
- Implement carefully.
- Use AI as a force multiplier.
- Review like a senior.
- Deploy with observability.
This is how professionals are working this is how we work. The different between generating a lot of code and a lot or meaningful code is up to the engineer who build the system.
Save this article use it as an accordion with time it will make sense and the process will make you think outside of the box and close to the simple reality.
At the end we need to be clever as Neo was to manipulate the matrix. I'm Morpheus and remember "Free your mind".
Jorge Baez, Founder of Foxlabs Developers.