Stop Being an AI Babysitter: How to Use LLMs Without Losing Code Ownership

It was 4:15 PM on a Friday. The sprint was technically “over,” the beer fridge was calling, and you just needed to finish one last utility function. You asked the AI to “Write a robust function to parse nested JSON permissions and flatten them.”

The AI produced 40 lines of elegant-looking TypeScript. It uses .reduce(). Ternary operators. It looks… sophisticated. You copy, paste, push. CI passes because your test suite is basically just checking if the app starts without exploding.

Ten minutes later, the CTO is calling your personal phone.

That “elegant” code had a recursive loop triggered by a specific edge case — an empty array inside a null object. It didn’t just fail; it consumed 100% of CPU on your production instances, scaling your AWS bill to the moon in thirty minutes. When you look at the code again, you realize you don’t even understand what line 14 does.

You weren’t a developer that afternoon. You were a babysitter who fell asleep while the toddler played with a flamethrower.

This is the era of the AI Babysitter. If you want to move from coder to architect, it’s the first habit to kill.


The Ownership Lies We Tell Ourselves

Lie #1: “It’s just boilerplate.” Is it? Or is it the logic that determines how your data is structured? There is no such thing as “just boilerplate” when it lives in the hot path of your application.

Lie #2: “I’ll refactor it later.” No, you won’t. You’ll be assigned three more tickets on Monday, and that AI-generated mess becomes the “legacy foundation” of the next two years of technical debt.

Lie #3: “It passed the unit tests.” If the AI wrote the code and the tests, you haven’t verified anything. You’ve asked a pathological liar to provide its own character references.

The problem isn’t the AI. The problem is your ownership logic. If you can’t explain why every single character in that file exists, you don’t own the code. It owns you.


Phase 1: The “Just Accept It” Junior Approach

Imagine we need a function that calculates a dynamic pricing discount based on user tier, cart value, and a list of active promo codes. The Junior just accepts whatever the AI suggests first:

Prompt: Write a Java method double getPrice(double p, String t, List<String> codes) that calculates a discount. If the tier is GOLD, give 20%, if SILVER give 10%. If price > 100, add 5% more. If the list of codes contains “SAVE10”, add 10%. No comments.

public class PriceCalculator {
public static double getPrice(double p, String t, List<String> codes) {
double d = 0;
if ("GOLD".equals(t)) {
d = 0.2;
} else if ("SILVER".equals(t)) {
d = 0.1;
}
if (p > 100) {
d += 0.05;
}
if (codes != null) {
for (String code : codes) {
if ("SAVE10".equals(code)) {
d += 0.1;
}
}
}
return p - (p * d);
}
}

It’s “correct” in the narrowest academic sense. Single-letter variables. No type safety. Mutable state everywhere. This code screams “I don’t care about the person who has to read this in six months.” If you ship this, you’re a typist, not an engineer.

Phase 2: The Over-Engineered Mid-Level Trap

The mid-level dev has heard of “Clean Code” and “Design Patterns” and prompts the AI to use them. The result is a cathedral where a shed would do:

interface DiscountStrategy {
double calculate(double price);
}
class GoldDiscount implements DiscountStrategy {
@Override
public double calculate(double price) { return price * 0.2; }
}
class DiscountFactory {
public static DiscountStrategy getStrategy(String tier) {
if ("GOLD".equals(tier)) return new GoldDiscount();
if ("SILVER".equals(tier)) return new SilverDiscount();
return new NoDiscount();
}
}

You’ve taken a 10-line problem and turned it into a 5-file architecture. This is Resume-Driven Development. Five layers of indirection that don’t solve the core problem — they hide the logic. An architect looks at this and sees Complexity Debt.


The Director Mindset: How to Actually Use AI

To move from Coder to Architect, you need a Zero-Trust Policy on AI output. Treat it like a PR from a Junior developer who lied on their resume and is currently caffeinated on three energy drinks. Trust nothing by default.

Here’s how the same pricing problem looks when you direct the AI instead of babysitting it.

Step 1: Define the Contract First

Prompt: Define a Java enum for UserTier (GOLD, SILVER, BRONZE) and a record PricingContext that holds double basePrice, UserTier tier, and List<String> promoCodes. No logic yet.

public enum UserTier { GOLD, SILVER, BRONZE }
public record PricingContext(
double basePrice,
UserTier userTier,
List<String> promoCodes
) {}
public class PricingConfig {
public static final Map<UserTier, Double> TIER_DISCOUNTS = Map.of(
UserTier.GOLD, 0.20,
UserTier.SILVER, 0.10,
UserTier.BRONZE, 0.0
);
}

Step 2: Request Isolated, Pure Functions

Prompt: Write three static, pure functions in a PricingLogic class: calculateTierDiscount using a Map lookup, calculateBulkDiscount for prices over 100, and calculatePromoDiscount that reduces a list of codes against a static map. No side effects.

public class PricingCalculator {
private static final Map<String, Double> VALID_CODES = Map.of("SAVE10", 0.1);
public static double calculateTierDiscount(double price, UserTier tier) {
return price * PricingConfig.TIER_DISCOUNTS.getOrDefault(tier, 0.0);
}
public static double calculateBulkDiscount(double price) {
return price > 100 ? price * 0.05 : 0;
}
public static double calculatePromoDiscount(double price, List<String> codes) {
if (codes == null) return 0;
return codes.stream()
.mapToDouble(code -> price * VALID_CODES.getOrDefault(code, 0.0))
.sum();
}
}

Step 3: Write the Orchestrator with Edge Cases in Mind

Prompt: Create a PricingService with a method calculateFinalPrice(PricingContext context). Sum the three discount functions. Ensure the final price is never negative using Math.max. Guard against invalid base price.

public class PricingService {
public static double calculateFinalPrice(PricingContext context) {
double basePrice = context.basePrice();
UserTier userTier = context.userTier();
List<String> codes = context.promoCodes();
if (basePrice <= 0) return 0;
double totalDiscount = DoubleStream.of(
PricingCalculator.calculateTierDiscount(basePrice, userTier),
PricingCalculator.calculateBulkDiscount(basePrice),
PricingCalculator.calculatePromoDiscount(basePrice, codes)
).sum();
return Math.max(0, basePrice - totalDiscount);
}
}

Why does this version win?

  1. Immutability: We transform data, we don’t mutate variables.
  2. Testability: I can test calculateBulkDiscount in isolation without mocking a Factory.
  3. Readability: A human can read this in five seconds and know the business rules.
  4. AI-proof: Small, typed functions make it impossible for the model to hide a bug in a wall of text.

The Psychology: Why We Babysit

It’s called Cognitive Offloading. When the AI gives us an answer, our critical thinking literally dims down. We switch from Problem Solving Mode to Verification Mode. But verification is much harder than creation — it requires you to simulate the code mentally, looking for what isn’t there.

Time saved during code generation is paid back with interest during debugging. If you spend 10 seconds generating a function and 0 seconds auditing it, you haven’t saved time. You’ve taken out a high-interest loan from the Bank of Technical Debt.


On “But AI Models Keep Getting Better”

They won’t write perfect code. Because code is not the product. The product is a solved business problem. The AI doesn’t know your business. It doesn’t know your “Gold” tier users are migrating to a new system next month. It doesn’t know your database can’t handle a specific query pattern efficiently.

If you think your job is “writing code,” you’re replaceable. If you think your job is “solving problems using code as a tool,” you’re an architect. Architects don’t complain about the quality of their hammers. They just don’t let the hammer design the house.


Four Steps to Stop Babysitting and Start Directing

  1. Establish an AGENTS.md file. Create an AGENTS.md at your project root to encode your architectural guardrails, style constraints, and non-negotiables. It’s the only way to stop the LLM from treating your Java 21 codebase like a legacy JavaScript project.
  2. Define clear acceptance criteria before prompting. Never prompt without a “Definition of Done” that specifies exact inputs, outputs, and edge cases. If you haven’t defined the problem boundaries, you’re asking the AI to hallucinate a solution.
  3. Provide full context, not tunnel vision. Include the full context of impacted components and their dependencies. Precise instructions are the only thing between a 2-minute refactor and a 2-hour debugging session.
  4. Use Plan Mode first. Force the AI to scan all impacted components and produce a step-by-step execution plan before writing a single line of code. It’s significantly easier to veto a bad architectural decision in a bulleted list than in a 500-line diff.

The difference between a coder and an architect isn’t how much they know — it’s how much they own.

The AI is a tool, not a teammate. Stop letting it sit in the driver’s seat while you scroll your phone in the back. You’re the one who gets fired when the car hits the wall, not the algorithm.

Get your AGENTS.md in order. Stop accepting magic code. Use Plan Mode before committing to any refactor that will ruin your weekend.


Discover more from The Dev World – Sergio Lema

Subscribe to get the latest posts sent to your email.


Comments

Leave a comment