I keep fixing the same issues in AI-generated code — problems with ownership, boundaries, and duplication. Even with good prompts and design docs, the code still has them. The fixes are usually small, but they add up.
Ownership
The most common ownership problem is simple: the rule lands in whichever file the agent was already touching.
function assignTask(task, userId) {
if (task.status === "done" || task.status === "cancelled") {
throw new Error("Cannot assign closed task");
}
task.assigneeId = userId;
}
What makes a task assignable now lives in the caller. The next use gets a slightly different version. One check forgets Cancelled, another adds Archived, a third never sees the first rule at all.
The fix is to give the rule an owner — the abstraction that has the data and behavior it operates on.
task.assignTo(userId);
class Task {
assignTo(userId) {
if (this.status === "done" || this.status === "cancelled") {
throw new Error("Cannot assign closed task");
}
this.assigneeId = userId;
}
}
If code inspects an object’s state to decide what that object is allowed to do, that rule belongs inside the object.
Boundaries
One boundary failure is the method that does the whole workflow.
async function handleAssign(input) {
validate(input);
checkPermissions(input.userId);
const task = await loadTask(input.taskId);
task.assigneeId = input.assigneeId;
task.status = "in_progress";
await saveTask(task);
await sendNotification(input.assigneeId);
return toDto(task);
}
Validation, authorization, domain state change, persistence, notification, mapping: all in one method. It looks efficient in a diff. It also turns into the method nobody wants to touch later.
async function handleAssign(input) {
const task = await loadTask(input.taskId);
task.assignTo(input.assigneeId);
await saveTask(task);
await publishTaskAssigned(task, input.assigneeId);
return toDto(task);
}
I want that method to read like a table of contents.
Duplication
This one hides well because the names are different.
const overdueForReminder = invoice.dueDate < now && !invoice.paid;
const overdueForReport = invoice.dueDate < now;
Both mean overdue. One checks paid. The other does not. The task got solved twice instead of the concept being defined once.
invoice.isOverdue(now)
Give overdue one definition and every caller gets the same answer.
Conclusion
These are the edits I keep making:
- Move rules into the abstraction that owns them (solves ownership)
- Pull methods back to coordination (solves boundaries and ownership)
- Collapse duplicate concepts into one definition (solves duplication and ownership)
Most of the generated code is not obviously broken. It is just shaped by the nearest context more than by the system around it.