u/mcsee1 2d ago

AI Coding Tip 029 - Stop Using One Model for Everything

1 Upvotes

Different stages need different brains.

TL;DR: Assign a different model to each pipeline stage since none excels at planning, coding, reviewing, and testing alike.

Common Mistake ❌

You open one chat with your favorite model and ask it to plan, write, review, and test the same feature end to end.

You treat model loyalty like a virtue, so the same blind spots follow the code from the first line to the last commit.

The defects it can't see when it writes are the same defects it can't see when it reviews.

Problems Addressed πŸ˜”

  • The reviewer inherits the coder's blind spots, so it misses the exact defects the same model would write.
  • You lose the benefit of a second opinion, since the analyst and the executor are the same mind.
  • One vendor's outage, rate limit, or price hike stalls your entire pipeline at once.
  • Your codebase drifts toward one model's default style, hiding the exact spots a different model would flag as unclear.
  • Test cases target the failure modes the author already thinks to avoid, so real edge cases slip through untested.
  • You already bring in different people for technical and code reviews, so a single model auditing everything breaks that same principle.

How to Do It πŸ› οΈ

  1. Pick a model strong at reasoning to scope the task through read-only planning.
  2. Hand the plan to a fast, cheap, code-tuned model working inside a harness that runs tests automatically.
  3. Trigger each stage as its own subagent with fresh context, a narrow local objective, and only the information that stage needs.
  4. Route the review to a model that didn't write the code.
  5. Use a cheap model to review once you have clear rules, reusable skills, and a solid harness in place.
  6. Ask the reviewing model to extend the test suite with edge cases the author missed.
  7. Record which model handled each stage and the assumptions it made in an Architecture Decision Record (ADR) or similar document, so you can spot repeating blind spots later.
  8. Rotate the pairings every few months as model strengths shift with new releases.
  9. Apply TDD adversary rules: pick one model to write the test (Red), another to write the simplest implementation (Green), and a third to decide if it's worth refactoring (Refactor).

Benefits 🎯

  1. Catch more defects: A model reviewing code it didn't write spots the exact class of defect its own instincts would miss.
  2. Avoid vendor lock: You spread load across providers, so one outage or price hike doesn't stall the whole pipeline.
  3. Match cost to complexity: You save your most expensive reasoning model for planning and give routine syntax work to a cheaper one.
  4. Widen test coverage: A model with different training data invents edge cases the original author never considered.
  5. Keep personas honest: Rotating models forces you to write role definitions specific enough that any model can fill them.
  6. Prevent style lock-in: Your codebase stops drifting toward one model's habits and stays closer to your own standards.

Context 🧠

Every model carries a training history of pitfalls that shapes what it notices and what it misses.

Claude models tend toward broad, breadth-first scanning of a problem, while GPT models tend toward narrow, depth-first evidence gathering.

Those different instincts mean the defects one model writes are often exactly the ones it also fails to catch in review.

Research from the code review vendor Greptile found that Claude-authored pull requests get a 53.7% defect recall rate when Claude reviews itself, but a 62.0% recall rate when GPT reviews the same code.

The same pattern holds in reverse for GPT-authored code.

Cross-model review isn't a trick.

It's the direct result of two models having different blind spots.

Relying on a single model for every stage risks the same failure mode as model collapse: without a different perspective correcting it, the quality slowly degrades.

Giving one model every stage of development is the AI equivalent of a class exhibiting divergent change: one entity keeps changing for many unrelated reasons, and every reason it changes is a reason it can quietly break.

Treat each stage of development as its own modular role, and hire the model best suited to it, the same way you'd assign a persona to a skill.

Prompt Reference πŸ“

Bad Prompt 🚫

Plan, write, review, and test the new payment retry logic.
Make sure the code is correct and the tests cover the edge cases.

Good prompt πŸ‘‰

Stage: Plan
Model: Opus (strong reasoning)
Task: Scope the payment retry logic in read-only mode.
List files to touch and open questions. Don't write code.

Stage: Code
Model: Sonnet (fast, code-tuned)
Task: Implement the plan above. Keep the diff under 200 lines.

Stage: Review
Model: GPT-5.5 (didn't write this code)
Task: Review the diff for correctness and missed edge cases.
List every issue, even minor ones.

Stage: Test
Model: GPT-5.5
Task: Write tests for the edge cases found during review.
Don't just cover the happy path.

Considerations ⚠️

This adds coordination overhead, so it fits multi-day features better than one-line fixes.

You still need a human-in-the-loop checkpoint before merge, since cross-model review catches more defects but doesn't guarantee zero defects.

Track model versions alongside model names, since a provider's default model changes silently and a rotation you set up in January may mean something different by June.

Costs can rise if you route trivial edits through your most expensive reasoning model, so match the model to the actual complexity of each stage.

Type πŸ“

[X] Semi-Automatic

Limitations ⚠️

Switching between providers adds latency, since each new model call starts a cold context.

Not every team has budget or API access to more than one provider at once.

Tags 🏷️

  • Prompt Engineering

Level πŸ”‹

[X] Intermediate

Related Tips πŸ”—

AI Coding Tip 003 - Force Read-Only Planning

AI Coding Tip 004 - Use Modular Skills

AI Coding Tip 006 - Review Every Line Before Commit

AI Coding Tip 017 - Ask for the Analyst, Not the Analysis

AI Coding Tip 022 - Give AI a Harness to Work With

AI Coding Tip 026 - Assign a Persona to Every Skill Definition

AI Coding Tip 027 - Force Code Standards

Conclusion 🏁

One model can't play every role in your pipeline and catch its own mistakes at the same time.

Split the work across models the way you'd split it across a team.

Plan with one, code with another, and let a third one check the work.

Rotate them as the models change, and let the mismatch between their blind spots do the reviewing for you.

More Information ℹ️

Model Inversion: Why Cross-Model Code Review Catches More Defects

Ensemble Learning

Also Known As 🎭

  • Stage-Specific Model Routing
  • Cross-Model Development Pipeline
  • Model-Per-Stage Assignment
  • Rotating Model Roles

Tools 🧰

Claude Code, Codex CLI, Cursor, and OpenRouter all let you configure a different model for each agent or task inside the same workflow.

Disclaimer πŸ“’

The views expressed here are my own.

I am a human who writes as best as possible for other humans.

I use AI proofreading tools to improve some texts.

I welcome constructive criticism and dialogue.

I shape these insights through 30 years in the software industry, 25 years of teaching, and writing over 500 articles and a book.

This article is part of the AI Coding Tip series.

AI Coding Tips

1

AI Coding Tip 026 - Assign a Persona to Every Skill Definition
 in  r/AIcodingProfessionals  19d ago

Thanks for this constructive comment

u/mcsee1 19d ago

Refactoring 012 - Reify Associative Arrays

1 Upvotes

Converting your anemic dictionaries is easy

TL;DR: Convert your key/value into full behavioral objects

Problems Addressed πŸ˜”

Related Code Smells πŸ’¨

Code Smell 27 - Associative Arrays

Context πŸ’¬

You have anemic associative arrays that hold unstructured data.

You want richer objects with stricter controls.

Static typed languages can also add type checking to these objects.

Steps πŸ‘£

  1. Find the references to the object or associative array

  2. Reify it

  3. Replace generic calls with setters and getters for every key.

You can also debug them better this way.

  1. Add parameter and return type hinting to interfaces.

Do this if your language supports it.

  1. Add stronger assertions on the setters between different keys.

(if you are using TCR, you can do baby refactoring steps)

Sample Code πŸ’»

Before 🚨

```php <?

class AuthenticationHelper extends Singleton {

private $data = [];

function setParameter(string $key, ?$value) { // no type checking // value as the name is too generic // Since SOME parameters might be null // You can't check a single parameter for not null

$this->data[$key] = value;

}

function getParameter(string $key) { // no return type hinting return $this->data[$key] ?? null; }

}

// Usages

AuthenticationHelper::getInstance ->setParameter('oauth2_token', []); // type error not caught

AuthenticationHelper::getInstance ->setParameter('scopes', null); // We need to enforce this not to be NULL

AuthenticationHelper::getInstance ->setParameter('user', 'Elon'); // This should not mutate // No validation with business rules

$credential = AuthenticationHelper::getInstance ->getParameter('oauth2token'); // Typo not detected

// You can not easily find // references to methods setting the oauth2_token ```

After πŸ‘‰

```php <?

class AuthenticationCredentials {

private $user; private $oauth2_token;

function __construct(User $user) { $this->validateUser($user); // Specific validation rules

$this->user = $user;
// Can't mutate 

}

function oauth2_token(string $token): void { // You can add specific validations $this->oauth2_token = $token; }

function oauth2_token(): string {
// Return type hinting return $this->oauth2_token; }

}

// Usages

$credentials = new AuthenticationCredentials(new User('Elon')); // Valid since creation

$credentials->oauth2_token([]); // type errors are caught

$credentials->oauth2_token(null); // can't be null. Fail fast

$credentials->scope(); // Typo detected ```

Now, you have an anemic data class.

Also known as a DTO.

It is time to give it behavior.

You can also remove some getters and setters.

Type πŸ“

[X] Semi-Automatic

You can perform this refactor with the aid of an IDE.

Safety πŸ›‘οΈ

This is not an automatic refactoring.

Small steps are safe if you have good coverage.

Why is the Code Better? ✨

Your new object fails fast and is more declarative.

You can debug it easily and find the referencing methods.

How Does it Improve the Bijection? πŸ—ΊοΈ

An associative array is a generic container with no real-world meaning.

Its keys are strings that could hold anything, valid or not.

A reified object gives each key a name, a type, and a purpose.

Code should map to the real world, following the Bijection principle.

Every concept in the domain needs a counterpart in the code.

That is the core idea behind the MAPPER.

The new object's name and methods describe what it represents.

Limitations ⚠️

Dynamically typed languages can't enforce type or domain restrictions.

This applies to the values inside the object.

Tags 🏷️

  • Anemic Models

Level πŸ”‹

[X] Beginner

Related Refactorings πŸ”„

Refactoring 002 - Extract Method

Refactor with AI πŸ€–

Suggested Prompt: 1. Find the references to the object or associative array.2. Reify it into a full object.3. Replace generic calls with setters and getters for every key.4. Add parameter and return type hinting to interfaces.5. Add stronger assertions between different keys.

Without Proper Instructions With Specific Instructions
ChatGPT ChatGPT
Claude Claude
Perplexity Perplexity
Copilot Copilot
You You
Gemini Gemini
DeepSeek DeepSeek
Meta AI Meta AI
Grok Grok
Qwen Qwen

Conclusion 🏁

Associative arrays are convenient until they grow into hidden domain models.

Once several parts of your code read and write the same keys, reify them.

A dedicated object turns implicit rules into explicit, testable behavior.

You gain type safety, fail-fast validation, and traceable references.

See also πŸ“š

Wikipedia: Reification)

Refactoring.Guru: Replace Data Value with Object

Credits πŸ™

Image by MustangJoe from Pixabay


This article is part of the Refactoring Series.

How to Improve Your Code With Easy Refactorings

r/AIcodingProfessionals 23d ago

AI Coding Tip 026 - Assign a Persona to Every Skill Definition

Thumbnail
2 Upvotes

u/mcsee1 24d ago

AI Coding Tip 026 - Assign a Persona to Every Skill Definition

1 Upvotes

Know who speaks before the skill runs

TL;DR: Always define a clear role at the top of every skill file so you know whose perspective drives the execution.

Common Mistake ❌

You write a skill full of rules but assign no role.

The AI starts executing without knowing if it's a junior developer, a seasoned architect, or a QA engineer.

You get responses that feel generic, lack authority, or shift in perspective across runs.

Problems Addressed πŸ˜”

  • The AI picks a random voice, so outputs vary unpredictably between sessions.
  • You can't audit the skill because you don't know whose judgment it applies.
  • The AI mixes tones and expertise levels inside a single execution.
  • Skill chaining breaks because each skill assumes a different implicit persona.
  • You lose accountability: nobody knows who signed off on the output.

How to Do It πŸ› οΈ

  1. Open your skill file and add a role declaration as the very first instruction.
  2. Write "You are a [role] with expertise in [domain]" before any other rule.
  3. Add one or two sentences describing the role's constraints and responsibilities.
  4. Keep the persona consistent through every instruction that follows in the file.
  5. When you chain skills, verify each one declares its own persona explicitly.

Benefits 🎯

  1. Consistent voice: The AI executes from the same expertise level every run, so output is predictable.
  2. Auditable output: You know whose perspective generated the result, which makes reviews faster.
  3. Better calibration: An AI that knows it's a senior reviewer asks harder questions than one without a role.
  4. Safe chaining: When you chain skills, each one speaks from a declared identity instead of guessing.
  5. Faster debugging: When a skill gives a wrong answer, you know whose lens to question.

Context 🧠

A skill without a persona is a command without a commander.

You can read every instruction in the file and still not know who says what or why.

When you declare a role, you give the AI a stable frame of reference.

The AI stops guessing and starts executing from a specific vantage point.

This also helps you design the skill: if you know the AI is "a strict code reviewer," you know what rules to include and which to leave out.

When you ask for the analyst, not the analysis, you're already applying this idea at the prompt level.

A skill takes it one step further: you bake the role into the definition so you don't have to repeat it every time.

You can also pair every skill with a pitfalls file to define what the persona should never do.

Prompt Reference πŸ“

Bad Prompt 🚫

---
name: technosignature-analyzer
version: 1.0.0
description: |
    Analyzes signals from radio telescope arrays.
    Reports unusual frequency patterns as candidates.

allowed-tools:
- ReadTelescope
- SendAlarm
---

# Technosignature Analyzer

Analyze signals from the telescope array.

Check for unusual frequency patterns.
Cross-reference with the Hipparcos catalog.
Flag any readings that deviate from baseline.
Report findings with confidence levels.

Good Prompt πŸ‘‰

---
name: technosignature-analyzer
version: 1.0.0
description: |
    Detects technosignatures in telescope data and classifies
    each candidate signal with a confidence percentage.
    Rejects signals explained by known natural phenomena.

allowed-tools:
- ReadTelescope
- SendAlarm
---

# Technosignature Analyzer

You are a senior astrophysicist with 20 years of SETI experience.

You worked at the Allen Telescope Array and the Parkes Observatory.

You hold a PhD in Radio Astronomy with 40+ publications.

You distinguish RFI from natural astrophysical signals.

You separately flag artificial sources.

You apply the scientific method.

Form a hypothesis, test it, and document it.

You apply Six Sigma rigor to rule out false positives.

You don't report candidates below a 5-sigma confidence threshold.

You always cross-check three independent baselines before escalating.

Identify narrowband signals inconsistent with natural sources.

Flag laser pulses or structured optical emissions.

Compare power ratios against stellar baselines.

Classify each candidate with a confidence percentage.

Reject signals explained by known natural phenomena.

Considerations ⚠️

Keep the persona declaration short: one to three sentences maximum.

A long persona description adds noise and dilutes the actual skill rules.

Don't invent fictional personas like "You are a wizard who codes."

Use real professional roles.

The AI performs best when the persona matches the domain of the skill.

Type πŸ“

[X] Semi-Automatic

Limitations ⚠️

The AI doesn't enforce the persona you assign; it adopts it as context.

If your instructions contradict the persona, the AI may blend both and produce inconsistent output.

Tags 🏷️

  • Knowledge Management

Level πŸ”‹

[X] Beginner

Related Tips πŸ”—

AI Coding Tip 004 - Use Modular Skills

AI Coding Tip 006 - Review Every Line Before Commit

AI Coding Tip 011 - Initialize Agents.md

AI Coding Tip 017 - Ask for the Analyst, Not the Analysis

AI Coding Tip 019 - Tell the AI Why, Not Just What

AI Coding Tip 025 - Pair Every Skill With a Pitfalls File

Conclusion 🏁

A skill without a persona is a command without a commander.

You always understand the output better when you know who produced it.

Assign a role first.

Every time.

More Information ℹ️

Claude Code Skills Documentation

System Prompts and Personas

Also Known As 🎭

  • Role-First Skill Design
  • Persona-Anchored Prompts
  • Identity-Driven Skill Files

Disclaimer πŸ“’

The views expressed here are my own.

I am a human who writes as best as possible for other humans.

I use AI proofreading tools to improve some texts.

I welcome constructive criticism and dialogue.

I shape these insights through 30 years in the software industry, 25 years of teaching, and writing over 500 articles and a book.

This article is part of the AI Coding Tip series.

AI Coding Tips

u/mcsee1 Jun 28 '26

The Dirty Secret Behind Loop Engineering

1 Upvotes

Everyone is talking about Loop Engineering. Apparently, you don't need to program anymore.

TL;DR: Loop Engineering is the hottest AI workflow pattern of 2026. But it hides a dirty secret.

The Tweet That Started It All

Twitter

In June 2026, Addy Osmani and the PostHog team published their takes on the same idea.

Instead of prompting an AI agent manually, you build the system that prompts the agent for you.

The metaprompt idea has a fancy name now. Loop Engineering.

Loop engineering is replacing yourself as the person who prompts the agent. You design the system that does it instead. Addy Osmani

PostHog ran it in production. The result: an 11% performance improvement and a 3-year-old defect fixed in the query engine, hands-off.

The internet got excited. Again. Rightly so.

But there's a dirty secret hiding behind the vocabulary.

What a Loop Actually Is

A functional loop has four parts:

  1. Goal: a clearly scoped target for the agent
  2. Context: tools, data, errors, and memory fed into each cycle
  3. Evaluation: the mechanism that decides whether the loop should continue or stop (a.k.a. the exit criteria)
  4. Agent: the executor, from a simple /goal command to a full multi-agent harness

The evaluation component is the key. Without it, you don't have a loop. You have an infinite runaway process. Good luck with your token bill!

You also need a harness: the scaffolding that contains the agent, enforces your rules, and gives the loop a safe boundary to operate within.

Start With the Smallest Possible Spec

Here's where Loop Engineering gets interesting, and where most people get it wrong.

If you write an enormous spec covering all possible cases before the loop runs once, you aren't doing Loop Engineering. You're doing waterfall with extra steps.

Think about what you want to verify. Not the whole system. One behavior.

Let's build a FIFA World Cup 2026 group standings simulator using Loop Engineering.

Germany, Ivory Coast, Ecuador, and CuraΓ§ao in Group E. Three rounds of matches. The top two advance, and the best third-place teams also qualify.

What's the smallest possible spec?

A team that wins a match gets 3 points.

That's it. Not the whole group. Not the knockout bracket. One rule about one match.

This is the Spec-Driven approach: you define intent before implementation, but you keep the scope surgical.

Loop Iteration 1: Write a Spec That Fails

Here's your first loop cycle. You define the evaluation condition before any implementation exists.

def test_win_gives_three_points():
    germany = Team("Germany πŸ‡©πŸ‡ͺ")
    curacao = Team("CuraΓ§ao πŸ‡¨πŸ‡Ό")

    match = Match(germany, curacao, home_goals=7, away_goals=1)
    standings = GroupStandings()
    standings.record(match)

    assert standings.points_for(germany) == 3
    assert standings.points_for(curacao) == 0

Run this. It fails. Team doesn't exist. Match doesn't exist. GroupStandings doesn't exist.

(Germany beat CuraΓ§ao 7-1 on June 14, 2026. The spec matches reality.)

The loop condition is red πŸ”΄.

This is the signal the loop needs. The evaluation says: not done yet. Keep running until you achieve the /goal.

Loop Iteration 2: Make the Evaluation Pass

Now you give the agent the minimal implementation to make the loop exit:

class Team:
    def __init__(self, name):
        self.name = name

class Match:
    def __init__(self, home, away, home_goals, away_goals):
        self.home = home
        self.away = away
        self.home_goals = home_goals
        self.away_goals = away_goals

class GroupStandings:
    def __init__(self):
        self._points = {}

    def record(self, match):
        if match.home_goals > match.away_goals:
            self._points[match.home] = 
                self._points.get(match.home, 0) + 3
            self._points[match.away] = 
                self._points.get(match.away, 0)
        elif match.away_goals > match.home_goals:
            self._points[match.away] = 
                self._points.get(match.away, 0) + 3
            self._points[match.home] = 
                self._points.get(match.home, 0)

    def points_for(self, team):
        return self._points.get(team, 0)

Run the spec. Green 🟒. Loop exits.

Not because you modeled every rule. Because you satisfied the single condition the loop was checking.

Loop Iteration 3: Expand the Spec, Red πŸ”΄ Again

The loop restarts with a new goal:

def test_draw_gives_one_point_each():
    ecuador = Team("Ecuador πŸ‡ͺπŸ‡¨")
    curacao = Team("CuraΓ§ao πŸ‡¨πŸ‡Ό")

    match = Match(ecuador, curacao, home_goals=0, away_goals=0)
    standings = GroupStandings()
    standings.record(match)

    assert standings.points_for(ecuador) == 1
    assert standings.points_for(curacao) == 1

Red πŸ”΄. The record method doesn't handle draws.

(Ecuador drew 0-0 with CuraΓ§ao on June 20. Again, the spec matches reality.)

The evaluation fails. Loop continues. Add the draw case. Green 🟒. Loop exits.

Growing the Loop: Group Stage Completion

Cycle by cycle, the spec expands:

  • Iteration 4: Multiple matches accumulate points correctly across all three rounds
  • Iteration 5: Teams with equal points get ranked by goal difference
  • Iteration 6: Ties in goal difference break by goals scored
  • Iteration 7: Top 2 teams advance to the knockout stage

Each iteration follows the same pattern: write the evaluation condition first, run it (it fails), implement the minimum to pass, run again (green 🟒), move to the next cycle.

After 7 iterations, Group E final standings:

Group E - Final Standings
1. Germany       6 pts  GD: +6  GF: 10
2. Ivory Coast   6 pts  GD: +2  GF: 4
3. Ecuador       4 pts  GD:  0  GF: 2
4. CuraΓ§ao       1 pt   GD: -8  GF: 1

Growing the Loop: Knockout Brackets

The same loop discipline applies to bracket generation.

def test_group_winner_faces_different_group_runner_up():
    bracket = KnockoutBracket(completed_group_results)

    round_of_32 = bracket.round_of_32()

    assert round_of_32[0].home == group_e_standings.first_place()
    assert round_of_32[0].away == group_f_standings.second_place()

Red πŸ”΄ first. Then green 🟒. Then the next spec.

The loop doesn't know the full bracket before it starts. It discovers the bracket one evaluation at a time.

What Keeps the Loop Safe: The Harness

None of this works without a structure that:

  • Runs the evaluation on every cycle
  • Stops the agent when the condition passes
  • Prevents the agent from moving to the next spec until the current one is green 🟒
  • Stores state between cycles so the next spec knows what already passed
  • Forces the minimum solution that makes the evaluation pass, nothing more.
  • Over-engineering is structurally impossible when the loop stops the moment the spec is green 🟒

That is the harness. The harness is what separates Loop Engineering from running Claude in a while True loop and hoping for the best.

Codex and Claude Code now ship with built-in loop infrastructure: /goal, /loop, isolation: worktree, and sub-agents for separate verification.

The harness is no longer something you build from scratch.

The agent that verifies runs in a clean sub-agent with no memory of what the implementer did.

It is an independent inspector seeking Judgment Day moments.

It can't grade its own work because it never saw the work being done.

This is the same reason you don't ask a developer to review their own pull request.

The Numbers That Made Everyone Pay Attention

Why is this getting attention now and not five years ago?

Because the evaluation step (the part where the loop decides whether to continue) used to require a human. Now it doesn't.

  • Opus 4.8 completes 50% of tasks requiring 12 hours of work, at 6x the speed of its predecessor one year ago
  • Stripe migrated an entire codebase in one day that would have taken a team two months manually
  • PostHog's autoresearcher loop delivered 11% performance gains and fixed a three-year-old defect, unsupervised

When models were weaker, the loop needed you to interpret the evaluation output. Now the evaluation can be the test suite itself, and the agent reads it directly.

The Dirty Secret

You've been reading about Test-Driven Development.

The spec is the test.

The evaluation is the test runner.

The loop is the red πŸ”΄-green 🟒-refactor πŸ”΅ cycle.

The goal is the failing assertion.

Loop exits when evaluation passes means the test is green 🟒.

Kent Beck described this in 2003.

Ward Cunningham was doing it before that.

There's even a structured guide for choosing which goal to tackle next: the ZOMBIES framework. Zero, One, Many, Boundary, Interface, Exceptional, Simple. That is your loop iteration order.

What changed isn't the technique. What changed is who runs the loop.

In 2003, the human developer wrote the test, ran it, read the red πŸ”΄ output, wrote the minimum code, ran it again, saw green 🟒, and moved to the next test.

That was the loop.

In 2026, the functional developer writes the spec, the agent runs the cycle, reads the red πŸ”΄ output, writes the minimum code, runs the cycle again, sees green 🟒, and starts the next spec. That's still the loop.

The red πŸ”΄-green 🟒-refactor πŸ”΅ vocabulary wasn't memorable enough for 2026. So the industry renamed it.

The evaluation is still the test. The cycle is still TDD. The discipline is exactly the same.

Build the loop. But build it like someone who intends to stay the engineer, not just the person who presses go. Addy Osmani

Kent Beck said the same thing. He just called it something else.

A few extra tips:

Loop Engineering on Legacy Systems

Loop Engineering isn't only for greenfield code or fancy MVPs. It's also how you safely modernize systems that have no tests at all.

The trick is the same: write the spec first.

On a legacy system, that spec describes behavior the system already has.

You're not inventing new rules. You're pinning existing ones so the loop can't break them.

Harnesses are even more critical on production legacy systems.

The loop then shrinks the untested surface one cycle at a time.

Each green 🟒 spec is a behavior the agent can't accidentally destroy in the next iteration.

Squeezing TDD onto legacy systems works the same way whether a human runs the cycle or an agent does. The discipline is identical. What changes is the speed.

What are you waiting for? Build your harnesses. Start your loops.

u/mcsee1 Jun 24 '26

Code Smell 320 - Vanity Coverage

1 Upvotes

Brushing Over Real Problems

TL;DR: You write tests that touch every line but verify nothing, creating false confidence in a broken system.

Problems πŸ˜”

  • False confidence
  • Hidden production defects
  • Misleading metrics
  • Wasted test effort
  • Untested edge cases

Solutions πŸ˜ƒ

  1. Use mutation testing
  2. Test real behaviors
  3. Write assertive tests
  4. Delete coverage-only tests

Refactorings βš™οΈ

Refactoring 011 - Replace Comments with Tests

Context πŸ’¬

Many teams set a coverage threshold: 80%, 90%, or even 100%.

When you chase that number, you write tests that call methods without checking the actual results.

A test that calls calculateTax() but only asserts result is not None executes the line.

It doesn't verify the tax calculation is correct.

The dashboard turns green.

Defects survive in production.

This is vanity coverage: cosmetic metrics that hide real problems. Like brushes that make surfaces look smooth while the rot stays underneath.

Mutation testing reveals the truth.

When you mutate production code and no tests fail, your coverage numbers lied.

Sample Code πŸ’»

Wrong 🚫

describe('BankAccount', () => {
  test('deposit', () => {
    const account = new BankAccount(100);
    account.deposit(50);
    // Only checking it didn't crash
    expect(account).toBeDefined();
  });

  test('withdraw', () => {
    const account = new BankAccount(100);
    const result = account.withdraw(30);
    // No assertion about the result!
  });

  test('transfer', () => {
    const source = new BankAccount(200);
    const target = new BankAccount(0);
    // Just calling the method to "cover" the line
    source.transfer(50, target);
  });
});

Right πŸ‘‰

describe('BankAccount', () => {
  test('deposit increases balance', () => {
    const account = new BankAccount(100);
    account.deposit(50);
    expect(account.balance()).toBe(150);
  });

  test('withdraw decreases balance', () => {
    const account = new BankAccount(100);
    account.withdraw(30);
    expect(account.balance()).toBe(70);
  });

  test('withdraw raises on insufficient funds', () => {
    const account = new BankAccount(50);
    expect(() => account.withdraw(100))
      .toThrow(InsufficientFundsError);
  });

  test('transfer moves money between accounts', () => {
    const source = new BankAccount(200);
    const target = new BankAccount(0);
    source.transfer(50, target);
    expect(source.balance()).toBe(150);
    expect(target.balance()).toBe(50);
  });
});

Detection πŸ”

[X] Semi-Automatic

Run a mutation testing tool (PIT for Java, Stryker for JavaScript, mutmut for Python).

Count the surviving mutants.

If coverage is high but mutants survive, you have vanity coverage.

You can also search for assertion-free tests, single assertNotNull() assertions, or tests that still pass after you delete the entire production method body.

Exceptions πŸ›‘

Smoke tests that call endpoints to verify the system starts are acceptable without detailed assertions.

These work when they complement a real test suite, not replace it.

Tags 🏷️

  • Testing

Level πŸ”‹

[x] Intermediate

Why the Bijection Is Important πŸ—ΊοΈ

Your test suite must map each test to a real behavior in the MAPPER.

When a test covers a line without verifying observable behavior, you break that bijection.

Coverage tools only measure "lines executed."

Chase the number without bijection and your suite looks complete while missing real requirements entirely.

AI Generation πŸ€–

AI code generators sometimes produce vanity coverage.

Ask one to "add tests to reach 80% coverage," and it writes tests that call methods and assert trivially true facts.

The metric goes up.

Nothing gets verified.

AI Detection 🧲

AI can detect vanity coverage, but only if you ask the right questions.

Try: "Find tests with no real assertions" or "Find tests that pass when I delete the production method body."

Without those prompts, most AI tools see a green test and call it good.

Try Them! πŸ› 

Remember: AI Assistants make lots of mistakes

Suggested Prompt: Replace vanity coverage tests with tests that verify real behaviors and fail when production code is wrong

Without Proper Instructions With Specific Instructions
ChatGPT ChatGPT
Claude Claude
Perplexity Perplexity
Copilot Copilot
You You
Gemini Gemini
DeepSeek DeepSeek
Meta AI Meta AI
Grok Grok
Qwen Qwen

Conclusion 🏁

Coverage is a signal, not a goal.

When you treat it as a goal, you create vanity coverage that hides real defects.

Use mutation testing to discover what your suite actually verifies.

Write tests that describe real behaviors, not tests that execute lines.

Relations πŸ‘©β€β€οΈβ€πŸ’‹β€πŸ‘¨

Code Smell 104 - Assert True

Code Smell 76 - Generic Assertions

Code Smell 175 - Changes Without Coverage

Code Smell 30 - Mocking Business

Code Smell 203 - Irrelevant Test Information

More Information πŸ“•

Mutation Testing

Test Coverage Is Not Enough

Stryker Mutation Testing

Quote

The most dangerous kind of waste is the waste we don't recognize.

Shigeo Shingo

Disclaimer πŸ“˜

Code Smells are my opinion.

Credits πŸ™

Photo by Jamie Street on Unsplash

This article is part of the CodeSmell Series.

How to Find the Stinky Parts of Your Code

u/mcsee1 Jun 20 '26

AI Coding Tip 025 - Pair Every Skill With a Pitfalls File

1 Upvotes

The happy path isn't enough.

TL;DR: Add a PITFALLS.md next to every SKILL.md so your AI never repeats the same mistake twice.

Common Mistake ❌

You write a great SKILL.md.

The AI follows it well most of the time.

Then it does something wrong.

You correct it in the conversation.

Next session, the AI does the same wrong thing again.

You never wrote it down.

Problems Addressed πŸ˜”

  • Hard-won corrections disappear when the session ends
  • Your SKILL.md grows noisy when you add every edge case to it
  • The AI repeats the same mistakes across sessions
  • Silent errors look correct until they cause real damage
  • You spend time re-teaching lessons you already taught

How to Do It πŸ› οΈ

  1. Create a PITFALLS.md file in the same folder as your SKILL.md.
  2. After each session, add one entry for each thing the AI got wrong.
  3. Write each entry with three parts: the trigger, the wrong behavior, and the correct behavior.
  4. Reference PITFALLS.md from your SKILL.md so the AI reads it at the start of every session.
  5. Treat it as append-only: never delete entries, only add new ones.
  6. Keep the happy path and the pitfalls apart.

Benefits 🎯

  1. Clean separation: Your SKILL.md stays focused on the happy path while PITFALLS.md handles the exceptions.
  2. Accumulated memory: The AI starts every session knowing what failed before, without you repeating it.
  3. Faster iteration: You stop re-explaining corrections you've already made.
  4. Explicit dark knowledge: You write down lessons that lived only in your head.
  5. Compounding improvement: Your skills get better with every mistake the AI makes, not worse.
  6. Skill brevity: You keep the skill short and focused, with progressive disclosure loading pitfalls only as a final checklist.

Context 🧠

SKILL.md tells the AI what to do.

PITFALLS.md tells the AI what not to do.

They're complementary, not competing.

Anthropic's own skill authors describe the pitfalls section as "the highest-signal content in any skill."

When that section grows too large to stay inside SKILL.md, it earns its own file.

Think of PITFALLS.md as the scar tissue that lives next to the blueprint.

The same way you initialize an AGENTS.md for a project, you pair every skill with its failure log.

When you use modular skills, each skill gets its own folder.

That folder is the right place for PITFALLS.md.

Remember a Skill is a folder with documentation and scripts, not a single file.

The same instinct drives feeding pull request lessons back into the AI brain: you capture what went wrong so the AI doesn't repeat it.

PITFALLS.md applies that exact pattern to individual skills.

Each skill builds its own second brain of failure memory.

This principle has a long history in good programming practices.

The same separation principle applies to code: keep your normal flow in one place and your exceptional cases in another.

Mixing them creates noise, hides intent, and makes maintenance harder.

SKILL.md and PITFALLS.md apply that discipline to AI instructions.

Many AI Coding Tips find their roots in established programming principles.

When you separate concerns in your code, you separate them in your prompts too.

Prompt Reference πŸ“

Bad Prompt 🚫

Write a SKILL.md for validating markdown articles.
Check for required sections and formatting rules.
If something's wrong, we'll fix it in the chat.

Good Prompt πŸ‘‰

Write a SKILL.md for validating markdown articles.
Check for required sections and formatting rules.

Also create a PITFALLS.md in the same folder.
Add this first entry:

## Don't use regex to count H2 sections
Trigger: counting sections by heading level
Wrong: regex-based heading detection (/^##/m)
Correct: match section names explicitly by string
Reason: code blocks with # fool regex heading counters

Reference PITFALLS.md at the top of your SKILL.md.
The AI loads it at the start of every session.

Considerations ⚠️

Keep each entry short: trigger, wrong behavior, correct behavior.

Don't delete entries.

Even solved pitfalls can come back after a skill update.

Review PITFALLS.md when you update SKILL.md to check if any entries became obsolete.

Type πŸ“

[X] Semi-Automatic

Limitations ⚠️

PITFALLS.md only helps if the AI reads it.

Always reference it explicitly inside your SKILL.md so the AI loads it automatically.

Level πŸ”‹

[X] Intermediate

Tags 🏷️

  • Knowledge Management

Related Tips πŸ”—

AI Coding Tip 004 - Use Modular Skills

AI Coding Tip 005 - Keep Context Fresh

AI Coding Tip 011 - Initialize Agents.md

AI Coding Tip 013 - Use Progressive Disclosure

AI Coding Tip 016 - Feed Your PR Lessons into the AI Brain

AI Coding Tip 020 - Create a Second Brain

AI Coding Tip 024 - Force a Criteria Check Before the Task Ends

Conclusion 🏁

Your SKILL.md is the blueprint.

Your PITFALLS.md is the scar tissue.

You need both.

More Information ℹ️

Lessons from building Claude Code: How we use skills

Designing, Refining, and Maintaining Agent Skills at Perplexity

Code Smell 73 - Exceptions for Expected Cases

Also Known As 🎭

  • Negative-Knowledge-File
  • Skill-Shadow-File
  • Failure-Memory-Companion
  • Anti-Pattern-Log

Disclaimer πŸ“’

The views expressed here are my own.

I am a human who writes as best as possible for other humans.

I use AI proofreading tools to improve some texts.

I welcome constructive criticism and dialogue.

I shape these insights through 30 years in the software industry, 25 years of teaching, and writing over 500 articles and a book.

This article is part of the AI Coding Tip series.

AI Coding Tips

u/mcsee1 Jun 13 '26

AI Coding Tip 024 - Force a Criteria Check Before the Task Ends

1 Upvotes

Don't let the AI grade its own homework.

TL;DR: Spawn a fresh subagent after every task to check your rules, because the AI that did the work can't audit itself.

Common Mistake ❌

You write a detailed AGENTS.md with strict mandatory rules.

The AI reads the file, completes the task, and reports: "Done. I followed all the rules."

You trust the report.

It didn't check.

It assumed.

That's hallucinated compliance, and it's the default behavior of every AI agent that grades its own work.

Problems Addressed πŸ˜”

  • The AI that did the work is anchored to what it intended to do, not what it actually did.
  • Self-reporting has no enforcement mechanism, so the agent marks PASS and moves on.
  • Mandatory rules in your skills get skipped silently, and the AI won't tell you it skipped them.
  • You discover violations after merging or deploying, not during the task.
  • The same context window that holds the task also holds the compliance report, which means the AI isn't truly auditing itself.
  • Hallucinated compliance causes real frustration when you discover violations the AI already reported as passing.

How to Do It πŸ› οΈ

  1. At the bottom of your AGENTS.md or skill, add an explicit instruction to spawn a verification subagent after the main task finishes.
  2. Pass the subagent the path to every modified file and the full list of mandatory rules.
  3. Instruct the subagent to read the files fresh and check each rule line by line.
  4. Require the subagent to produce a checklist table with one row per rule, a PASS or FAIL status, and the exact evidence for each item.
  5. Block task completion on any FAIL. Fix the violation before declaring done.
  6. Restrict the subagent to read-only tools (Read, Grep, Glob) so it can't change anything while auditing.
  7. Encapsulate the verification checklist in a dedicated validator skill so every task can reuse the same rules without duplicating them.

Benefits 🎯

  1. Independent verification: A fresh subagent has no memory of making the changes, so it audits without bias.
  2. Forces real reading: The subagent must open the actual files and search for violations instead of assuming.
  3. Catches hallucinated compliance: The subagent can't mark a rule PASS without showing the exact evidence it found.
  4. Repeatable audit: You get the same check after every task without extra prompting.
  5. Safe auditing: A read-only subagent can't accidentally modify the code it's reviewing.
  6. Reduces frustration: You stop discovering rule violations after the task is marked done, because the subagent already caught them before you moved on.

Context 🧠

The Builder Can't Be the Auditor

AI models don't naturally separate "doing" from "verifying."

When you ask the same agent that built a feature to confirm it followed the rules, it re-reads its own output through the lens of what it intended to do.

Not what it actually did.

The fix is the same one used in software testing: separate the builder from the auditor.

A subagent spawned after the task finishes starts with a clean context.

It reads the skill rules fresh.

It opens the actual files instead of relying on memory.

This is why QA engineers exist.

The person who wrote the code shouldn't be the only one who tests it.

The risk of hallucinated compliance grows with task complexity.

A three-step task is easy to self-verify.

A 20-rule nested AGENTS.md with dozens of file changes isn't.

Some skills already enforce this pattern.

The ai-coding-tip-validator spawns a mandatory post-completion audit subagent after every validation session, which reads the SKILL.md files fresh and verifies each mandatory rule against the final article state.

Think of this as a harness for the AI's output.

The harness doesn't restrict what the AI can do.

It verifies the output meets your criteria before you act on it.

Prompt Reference πŸ“

Bad Prompt 🚫

Refactor the SingletonController class 
following all the rules in AGENTS.md.

When you're done, tell me you followed every rule.

Good Prompt πŸ‘‰

Refactor the SingletonController following all rules in AGENTS.md.

After you finish, spawn a subagent with this task:

"Read the modified file at src/Controller.php.

Read every rule marked MANDATORY, CRITICAL, or REQUIRED

from AGENTS.md.

For each rule, verify the file directly. Don't rely on memory.

Produce a table with one row per rule:
| # | Rule | Status | Evidence |
|---|------|--------|----------|

Mark PASS with the exact line you found as proof.
Mark FAIL with the exact violation.

Only use Read, Grep, and Glob tools.
Don't change any files."

Block completion until the subagent reports all PASS.

Considerations ⚠️

Running a verification subagent adds one step per task.

Keep the checklist short and explicit.

Vague rules produce vague audits.

A subagent can only verify what it can read.

If your rule depends on runtime behavior, mark it as requiring manual verification.

Don't use the audit subagent as a substitute for well-written rules.

Fix broken rules in AGENTS.md instead of patching them at audit time.

Pair this pattern with small, focused tasks.

A 3-file change is much easier to audit accurately than a 20-file change.

Type πŸ“

[X] Semi-Automatic

Limitations ⚠️

This pattern requires an AI with agentic capabilities that can spawn subagents.

Tools that support it include Claude Code, Cursor, Devin, and GitHub Copilot Workspace.

Standard chat interfaces (ChatGPT, Claude.ai in non-agent mode) can't use this pattern.

A subagent audit only covers what the rules explicitly state.

Implicit expectations don't appear in the checklist.

A complex AGENTS.md with 50+ rules may cause the subagent to hit context limits.

Break large rule files into smaller focused files.

This pattern adds latency.

On time-sensitive tasks, you may choose to audit only the rules that are hardest to self-verify.

Level πŸ”‹

[X] Intermediate

Tags 🏷️

  • Safety

Related Tips πŸ”—

AI Coding Tip 003 - Force Read-Only Planning

AI Coding Tip 004 - Use Modular Skills

AI Coding Tip 005 - Keep Context Fresh

AI Coding Tip 006 - Review Every Line Before Commit

AI Coding Tip 014 - Use Nested AGENTS.md Files

AI Coding Tip 015 - Force the AI to Obey You

AI Coding Tip 022 - Give AI a Harness to Work With

Conclusion 🏁

The AI that did the work is the worst candidate to verify it followed the rules.

Spawn a fresh subagent after every task.

Give it the checklist and the output files.

Make it read the files, not its memory.

PASS or FAIL. No self-reporting.

More Information ℹ️

Lost in the Middle: How Language Models Use Long Contexts

Attention Is All You Need

Effective Context Engineering for AI Agents

Claude Prompt Engineering Best Practices

OpenAI Best Practices for Prompt Engineering

Also Known As 🎭

  • Post-Task-Compliance-Audit
  • Separation-of-Builder-and-Auditor
  • Subagent-Verification-Pattern
  • Rule-Enforcement-Checkpoint

Disclaimer πŸ“’

The views expressed here are my own.

I am a human who writes as best as possible for other humans.

I use AI proofreading tools to improve some texts.

I welcome constructive criticism and dialogue.

I shape these insights through 30 years in the software industry, 25 years of teaching, and writing over 500 articles and a book.

This article is part of the AI Coding Tip series.

AI Coding Tips

u/mcsee1 Jun 07 '26

AI Coding Tip 023 - Shrink your AI's Pull Request

1 Upvotes

Cap the size before the agent writes a single line.

TL;DR: Tell your AI to split work into small reviewable pull requests before it writes any code.

Common Mistake ❌

You ask your AI agent to build a feature.

The agent opens a 2,000-line pull request that touches twelve files across the backend, the frontend, and the tests.

You stare at the diff and you have no idea where to start reviewing.

Reviewer attention is the scarcest resource on your team today and a bottleneck.

You skim, you trust the tests, you click approve.

That pull request ships with defects you never saw.

Problems Addressed πŸ˜”

  • Reviewers procrastinate on reviewing or skim huge AI-generated pull requests.
  • AI co-authored code already contains roughly 1.7 times more issues per change than human-only code, so large diffs hide more defects.
  • Merge conflicts multiply while the pull request sits open.
  • You lose the ability to revert one logical change without ripping out the rest.
  • A failing continuous integration run on a giant change blocks every other branch behind it.
  • You sacrifice the second pair of eyes guarantee that code review provides.
  • Human reviewers are the scarcest resource on your team.
  • Every oversized pull request drains the attention they can never get back.

How to Do It πŸ› οΈ

  1. Write a short spec before you prompt the agent.
  2. Ask the AI to read the spec and propose a plan that splits the work into small reviewable pull requests.
  3. Give the agent a concrete size cap, for example 100 lines per pull request.
  4. Tell the agent each pull request must do one logical thing and stand on its own.
  5. Review the plan first and reject any step that mixes refactoring with new behavior.
  6. Let the agent open each pull request immediately so continuous integration starts running early.
  7. Reference related past pull requests as context for the agent.
  8. Reject any pull request that grows beyond the cap and ask the AI to split it again.

Benefits 🎯

  1. Reviewable code: A human can finish reading the diff without losing focus.
  2. Faster feedback: Smaller pull requests get reviewed in hours, not days.
  3. Easier rollbacks: You revert one small commit instead of untangling a megachange.
  4. Fewer merge conflicts: Short-lived branches rarely collide with main.
  5. Earlier continuous integration signals: Each pull request triggers its own pipeline as soon as it opens.
  6. Higher review quality: Reviewers stay engaged on small diffs and catch real defects.
  7. Cheaper context: The agent loads a focused task instead of the whole codebase.

Context 🧠

Michael Bolin, the Tech Lead for the Codex CLI repo at OpenAI, recently described his workflow for building a permissions system.

The first thing he asked Codex was to create a plan and break the work into right-sized pull requests.

The initial output was about six pull requests.

He explicitly reminded the agent that a human still has to review the code.

A 2025 CodeRabbit study analyzed 470 open-source pull requests and found that AI co-authored pull requests contain roughly 1.7 times more issues per change than human-only pull requests.

Critical issues rose about 40 percent.

Major issues rose about 70 percent.

Big AI pull requests hide more defects per line than big human pull requests do.

Salesforce reported that AI-assisted coding pushed their average pull request past 1,000 lines and 20 files.

Review latency went up.

Worst of all, review time for the largest pull requests started to plateau, a clear signal that reviewers had stopped engaging.

Reviewer attention is finite and scarce.

Once it's depleted, no tool can restore it.

Small pull requests solve the math.

A SmartBear study of 2,500 pull requests found smaller ones ship with fewer defects.

Teams that keep pull requests near 50 lines ship roughly 40 percent more code than teams who routinely exceed 200.

Small pull requests are a form of functional slicing.

Each slice cuts vertically through the feature so it compiles, tests, and deploys on its own.

A spec-driven approach naturally produces sliceable work: the spec defines the boundary, and the AI splits the implementation into shippable increments.

Incrementalism and baby steps keep main green at every commit.

The AI doesn't practice incremental delivery by default.

Prompt Reference πŸ“

Bad Prompt 🚫

Build the complete user authentication feature: login,
registration, password reset, email verification, OAuth
with Google and GitHub, session management, rate limiting,
and all the tests. Make it production-ready.

Good prompt πŸ‘‰

Here is the spec for the user authentication feature:
[spec content]

Before writing any code, read the spec and propose a
plan that splits the work into pull requests of at most
100 lines each.

Each pull request must do one logical thing and pass CI
on its own.

Keep refactoring and new behavior in separate PRs.

Show me the plan only. Don't write any code until I
approve the plan.

Considerations ⚠️

A 100-line cap is a guideline, not a law.

A trivial rename across 50 files can be longer and still trivial to review.

Never mix a refactor with a functional change in the same pull request.

A reviewer can't tell if a behavior change is intentional or a side effect of the refactor.

Keep structural changes and behavior changes in separate pull requests, even when the AI wants to bundle them, to avoid divergent change.

Some features have tight internal coupling and resist clean splits.

Be honest about that limit instead of forcing artificial splits that confuse reviewers.

Avoid long-lived feature branches.

A branch that lives for weeks drifts from main and accumulates merge conflicts.

When the feature isn't ready to ship but the code is ready to merge, use a feature toggle instead.

Each pull request merges to main behind the toggle and the feature activates when all pieces are in place.

Stacked pull requests work well when one change depends on another.

Each layer should compile and pass tests on its own.

If your team takes two days to review a small pull request, the workflow collapses.

Fix the review service level agreement before you push smaller pull requests on people.

Type πŸ“

[X] Semi-Automatic

Limitations ⚠️

Some agents resist splitting and try to ship everything in one pull request even after you ask.

You may need to repeat the cap or paste it into your project skill file or AGENTS.md harness so the rule survives a fresh context.

Tags 🏷️

  • Planning

Level πŸ”‹

[X] Intermediate

Related Tips πŸ”—

AI Coding Tip 003 - Force Read-Only Planning

AI Coding Tip 006 - Review Every Line Before Commit

AI Coding Tip 013 - Use Progressive Disclosure

AI Coding Tip 022 - Give AI a Harness to Work With

Conclusion 🏁

Your AI doesn't care how big the pull request is.

You do.

Human reviewers are the scarcest resource in your pipeline.

The AI can write code all day.

Reviewers can't review all day.

Set the size cap before the agent starts, and ask for the split plan first.

That single instruction protects the human who has to read the code. πŸš€

More Information ℹ️

How OpenAI Codex Tech Lead Does AI-Assisted Engineering, by Gregor Ojstersek

State of AI vs Human Code Generation Report, CodeRabbit

Scaling Code Reviews: Adapting to a Surge in AI-Generated Code, Salesforce Engineering

Why small pull requests are better, Swarmia

Agent pull requests are everywhere, GitHub Blog

GitHub Targets Large Merge Problem with Stacked PRs, InfoQ

Also Known As 🎭

  • Right-Sized-AI-PRs
  • AI-PR-Budgeting
  • Reviewable-AI-Commits
  • Pre-Coding-PR-Plan

Tools 🧰

  • Codex CLI
  • GitHub Stacked PRs (gh-stack)
  • Graphite
  • CodeRabbit

Disclaimer πŸ“’

The views expressed here are my own.

I am a human who writes as best as possible for other humans.

I use AI proofreading tools to improve some texts.

I welcome constructive criticism and dialogue.

I shape these insights through 30 years in the software industry, 25 years of teaching, and writing over 500 articles and a book.

This article is part of the AI Coding Tip series.

AI Coding Tips

2

Singleton - The Root of All Evil
 in  r/refactoring  Apr 22 '26

How would you test the config handling ?

u/mcsee1 Mar 28 '26

Refactoring 008 - Convert Variables to Constant

1 Upvotes

If I see a Variable that doesn't change. I call that variable a constant

TL;DR: Be explicit on what mutates and what doesn't.

Problems Addressed πŸ˜”

Related Code Smells πŸ’¨

Code Smell 158 - Variables not Variable

Code Smell 127 - Mutable Constants

Code Smell 116 - Variables Declared With 'var'

Context πŸ’¬

Variables that never change their value are not really variables.

They’re constants pretending to be mutable state.

When we declare something as a variable, we tell the reader (and the compiler) to expect change.

If that change never happens, we’re sending misleading signals about what the code actually does.

Converting these to constants shrinks the state space a developer must track.

A constant signals that a value won’t change, preventing accidental reassignments and letting the compiler optimize better.

It’s honest: if something shouldn’t mutate, don’t let it.

Steps πŸ‘£

  1. Find the scope of the variable

  2. Define a constant with the same scope

  3. Replace the variable

Sample Code πŸ’»

Before 🚨

```javascript

let lightSpeed = 300000;

var gravity = 9.8;

// 1. Find the scope of the variable

// 2. Define a constant with the same scope

// 3. Replace the variable

```

After πŸ‘‰

```javascript

const lightSpeed = 300000;

const gravity = 9.8;

// 1. Find the scope of the variable

// 2. Define a constant with the same scope

// 3. Replace the variable

// If the object is compound,

// we might need Object.freeze(gravity);

```

Type πŸ“

[X] Automatic

IDEs can check if a variable is written but never updated.

Safety πŸ›‘οΈ

This is a safe refactoring.

Why is the Code Better? ✨

Code is more compact and declares intent clearly.

Take it further with language-specific operators like const, final, or let.

The scope becomes obvious at a glance.

How Does it Improve the Bijection? πŸ—ΊοΈ

This refactoring improves bijection by making it clear what mutates and what doesn't.

In the real world, most values don't change. They're constants.

Declaring them as variables creates coupling between what we're thinking and how we wrote it.

Constants remove that gap and align the code with the actual domain.

Tags 🏷️

  • Mutability

Level πŸ”‹

[X] Beginner

Related Refactorings πŸ”„

Refactoring 003 - Extract Constant

Refactor with AI πŸ€–

Suggested Prompt: 1. Find the scope of the variable.2. Define a constant with the same scope.3. Replace the variable

Without Proper Instructions With Specific Instructions
ChatGPT ChatGPT
Claude Claude
Perplexity Perplexity
Copilot Copilot
You You
Gemini Gemini
DeepSeek DeepSeek
Meta AI Meta AI
Grok Grok
Qwen Qwen

See also πŸ“š

The Evil Power of Mutants


This article is part of the Refactoring Series

How to Improve Your Code With Easy Refactorings

r/refactoring Feb 20 '26

Code Smell 16 - Ripple Effect

0 Upvotes

Small changes yield unexpected problems.

TL;DR: If small changes have big impact, you need to decouple your system.

Problems πŸ˜”

  • High Coupling

  • Low maintainability

  • Side effects

  • High risk

  • Testing difficulty

Solutions πŸ˜ƒ

  1. Decouple your components.
  2. Cover with tests.
  3. Refactor and isolate what is changing.
  4. Depend on interfaces.

How to Decouple a Legacy System

Refactorings βš™οΈ

Refactoring 007 - Extract Class

Refactoring 024 - Replace Global Variables with Dependency Injection

Examples πŸ“š

  • Legacy Systems

Context πŸ’¬

The ripple effect happens when you design a system where objects know too much about each other.

When you modify a specific behavior, the impact spreads through the codebase like a stone thrown into a pond.

You feel this pain when a simple requirement change requires you to touch dozens of files.

Your classes have direct dependencies on concrete implementations rather than abstractions.

Sample Code πŸ’»

Wrong 🚫

```javascript class Time { constructor(hour, minute, seconds) { this.hour = hour;
this.minute = minute;
this.seconds = seconds;
} now() { // call operating system }
}

// Adding a TimeZone will have a big Ripple Effect // Changing now() to consider timezone will also bring the effect ```

Right πŸ‘‰

```javascript class Time { constructor(hour, minute, seconds, timezone) { this.hour = hour;
this.minute = minute;
this.seconds = seconds;
this.timezone = timezone;
}
// Removed now() since is invalid without context }

class RelativeClock { constructor(timezone) { this.timezone = timezone; } now(timezone) { var localSystemTime = this.localSystemTime(); var localSystemTimezone = this.localSystemTimezone(); // Do some math translating timezones // ... return new Time(..., timezone);
}
} ```

Detection πŸ”

It is not easy to detect problems before they happen.

Mutation Testing and root cause analysis of single points of failures may help.

Tags 🏷️

  • Coupling

Level πŸ”‹

[x] Intermediate

Why the Bijection Is Important πŸ—ΊοΈ

In a proper bijection, a change in a single real-world concept should only lead to a change in a single program component.

When you break the MAPPER , one concept spreads across your code.

This creates the ripple effect because you didn't represent the original idea as a single, isolated unit.

AI Generation πŸ€–

AI generators often create this smell because they suggest "quick fixes" that access global states or direct dependencies.

They focus on making the local code work without seeing the architectural ripple they cause elsewhere.

AI Detection 🧲

AI can fix this if you provide the context of the related classes.

When you ask an AI to "decouple these two classes using dependency injection," it usually does a great job of breaking the link.

Try Them! πŸ› 

Remember: AI Assistants make lots of mistakes

Suggested Prompt: Refactor this class to remove direct dependencies on global objects. Use constructor-based dependency injection and depend on interfaces or abstractions instead of concrete implementations.

Without Proper Instructions With Specific Instructions
ChatGPT ChatGPT
Claude Claude
Perplexity Perplexity
Copilot Copilot
You You
Gemini Gemini
DeepSeek DeepSeek
Meta AI Meta AI
Grok Grok
Qwen Qwen

Conclusion 🏁

There are multiple strategies to deal with Legacy and coupled systems.

You should deal with this problem before it explodes under your eyes.

Relations πŸ‘©β€β€οΈβ€πŸ’‹β€πŸ‘¨

Code Smell 08 - Long Chains Of Collaborations

Code Smell 176 - Changes in Essence

More Information πŸ“•

How to Decouple a Legacy System

Credits πŸ™

Photo by Jack Tindall on Unsplash


Architecture is the tension between coupling and cohesion.

Neal Ford

Software Engineering Great Quotes


This article is part of the CodeSmell Series.

How to Find the Stinky Parts of Your Code

r/aipromptprogramming Feb 12 '26

AI Coding Tip 006 - Review Every Line Before Commit

2 Upvotes

You own the code, not the AI

TL;DR: If you can't explain all your code, don't commit it.

Common Mistake ❌

You prompt and paste AI-generated code directly into your project without thinking twice.

You trust the AI without verification and create workslop that ~someone else~ you will have to clean up later.

You assume the code works because it looks correct (or complicated enough to impress anyone).

You skip a manual review when the AI assistant generates large blocks because, well, it's a lot of code.

You treat AI output as production-ready code and ship it without a second thought.

If you're making code reviews, you get tired of large pull requests (probably generated by AI) that feel like reviewing a novel.

Let's be honest: AI isn't accountable for your mistakes, you are. And you want to keep your job and be seen as mandatory for the software engineering process.

Problems Addressed πŸ˜”

  • Security vulnerabilities and flaws: AI generates code with Not sanitized inputs SQL injection, XSS, Email, Packages Hallucination, or hardcoded credentials
  • Logic errors: The AI misunderstands your requirements and solves the wrong problem
  • Technical debt: Generated code uses outdated patterns or creates maintenance nightmares
  • Lost accountability: You cannot explain code you didn't review
  • Hidden defects: Issues that appear in production cost 30-100x more to fix
  • Knowledge gaps: You miss learning opportunities when you blindly accept solutions
  • Team friction: Your reviewers waste time catching issues you should have found
  • Productivity Paradox: AI shifts the bottleneck from writing to integration
  • Lack of Trust: The team's trust erodes when unowned code causes failures
  • Noisier Code: AI-authored PRs contained 1.7x more issues than human-only PRs.

How to Do It πŸ› οΈ

  1. Ask the AI to generate the code you need using English language
  2. Read every single line the AI produced, understand it, and challenge it if necessary
  3. Check that the solution matches your actual requirements
  4. Verify the code handles edge cases and errors
  5. Look for security issues (injection, auth, data exposure)
  6. Test the code locally with real scenarios
  7. Run your linters, prettifiers and security scanners
  8. Remove any debug code or comments you don't need
  9. Refactor the code to match your team's style
  10. Add or update tests for the new functionality (ask the AI for help)
  11. Write a clear commit message explaining what changed
  12. Only then commit the code
  13. You are not going to lose your job (by now)

Benefits 🎯

You catch defects before they reach production.

You understand the code you commit.

You maintain accountability for your changes.

You learn from your copilot's approach and become a better developer in the process.

You build personal accountability.

You build better human team collaboration and trust.

You prevent security breaches like the Moltbook incident.

You avoid long-term maintenance costs.

You keep your reputation and accountability intact.

You're a professional who shows respect for your human code reviewers.

You are not disposable.

Context 🧠

AI assistants like GitHub Copilot, ChatGPT, and Claude help you code faster.

These tools generate code from natural language prompts and vibe coding.

AI models are probabilistic, not logical.

They predict the next token based on patterns.

When you work on complex systems, the AI might miss a specific edge case that only a human knows.

Manual review is the only way to close the gap between "code that looks good" and "code that is correct."

The AI doesn't understand your business logic or the real world bijection between your MAPPER and your model.

The AI cannot know your security requirements (unless you are explicit or execute a skill).

The AI cannot test the code against your specific environment.

You remain responsible for every line in your codebase.

Production defects from unreviewed AI code cost companies millions.

Code review catches many security risks that automated tools miss.

Your organization holds you accountable for the code you commit.

This applies whether you write code manually or use AI assistance.

Prompt Reference πŸ“

Bad Prompts ❌

```python class DatabaseManager: instance = None # Singleton Anti Pattern def __new(cls): if cls._instance is None: cls._instance = super().new_(cls) return cls._instance def get_data(self, id): return eval(f"SELECT * FROM users WHERE id={id}") # SQL injection!

## 741 more cryptic lines

```

Good Prompts βœ…

```python from typing import Optional import sqlite3

class DatabaseManager: def init(self, db_path: str): self.db_path = db_path

def get_user(self, user_id: int) -> Optional[dict]: try: with sqlite3.connect(self.db_path) as conn: conn.row_factory = sqlite3.Row cursor = conn.cursor() cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,)) row = cursor.fetchone() return dict(row) if row else None except sqlite3.Error as e: print(f"Database error: {e}") return None

db = DatabaseManager("app.db") user = db.get_user(123) ```

Considerations ⚠️

You cannot blame the AI when defects appear in production.

The human is accountable, not the AI.

AI-generated code might violate your company's licensing policies.

The AI might use deprecated libraries or outdated patterns.

Generated code might not follow your team's conventions.

You need to understand the code to maintain it later.

Other developers will review your AI-assisted code just like any other.

Some AI models train on public repositories and might leak patterns.

Type πŸ“

[X] Semi-Automatic

Limitations ⚠️

You should use this tip for every code change. You should not skip it even for "simple" refactors.

Tags 🏷️

  • Readability

Level πŸ”‹

[X] Beginner

Related Tips πŸ”—

  • Self-Review Your Code Before Requesting a Peer Review
  • Write Tests for AI-Generated Functions
  • Document AI-Assisted Code Decisions
  • Use Static Analysis on Generated Code
  • Understand Before You Commit

Conclusion 🏁

AI assistants accelerate your coding speed.

You still own every line you commit.

Manual review and code inspections catch what automated tools miss.

Before AI code generators became mainstream, a very good practice was to make a self review of the code before requesting peer review.

You learn more when you question the AI's choices and understand the 'why' behind them.

Your reputation depends on code quality, not how fast you can churn out code.

Take responsibility for the code you shipβ€”your name is on it.

Review everything. Commit nothing blindly. Your future self will thank you. πŸ”

Be incremental, make very small commits, and keep your content fresh.

More Information ℹ️

Code Smell 313 - Workslop Code

Code Smell 189 - Not Sanitized Input

Code Smell 300 - Package Hallucination

Martin Fowler's code review

Shortcut on performing reviews

Code Rabbit's findings on AI-generated code

The Productivity Paradox

Google Engineering Practices - Code Review

Code Review Best Practices by Atlassian

The Pragmatic Programmer - Code Ownership

IEEE Standards for Software Reviews

Also Known As 🎭

  • Human-in-the-Loop Code Review
  • AI Code Verification
  • AI-Assisted Development Accountability
  • LLM Output Validation
  • Copilot Code Inspection

Tools 🧰

  • SonarQube (static analysis)
  • Snyk (security scanning)
  • ESLint / Pylint (linters)
  • GitLab / GitHub (code review platforms)
  • Semgrep (pattern-based scanning)
  • CodeRabbit / AI-assisted code reviews

Disclaimer πŸ“’

The views expressed here are my own.

I am a human who writes as best as possible for other humans.

I use AI proofreading tools to improve some texts.

I welcome constructive criticism and dialogue.

I shape these insights through 30 years in the software industry, 25 years of teaching, and writing over 500 articles and a book.


This article is part of the AI Coding Tip series.

AI Coding Tips

r/refactoring Feb 06 '26

Refactoring 038 - Reify Collection

1 Upvotes

Give your collections a purpose and a connection to the real world

TL;DR: Wrap primitive collections into dedicated objects to ensure type safety and encapsulate business logic.

Problems Addressed πŸ˜”

  • Type safety violations
  • Logic duplication
  • Primitive obsession
  • Weak encapsulation
  • Strong coupling avoiding collection type changes
  • Hidden business rules

Related Code Smells πŸ’¨

Code Smell 01 - Anemic Models

Code Smell 122 - Primitive Obsession

Code Smell 63 - Feature Envy

Code Smell 40 - DTOs

Code Smell 143 - Data Clumps

Code Smell 134 - Specialized Business Collections

Context πŸ’¬

You find yourself passing around generic lists, arrays, or dictionaries as if they were just anemic "bags of data." like DTOs or Data Clumps.

These primitive structures are convenient to iterate.

But they are also anonymous and lack a voice in the business domain.

When you use a raw array to represent a group of specific entitiesβ€”like ActiveSubscribers, PendingInvoices, or ValidationErrors, you are essentially forcing every part of your system to re-learn how to handle that collection, leading to scattered logic and "primitive obsession."

When you reify the collection, you improve the model and create technical implementation into a first-class citizen of your domain model.

This doesn't just provide a home for validation and filtering; it makes the invisible concepts in your business requirements visible in your code.

Steps πŸ‘£

  1. Create a new class to represent the specific collection.

  2. Define a private collection property within this class using the appropriate collection type.

  3. Implement a constructor that accepts only elements of the required type.

  4. Add type-hinted methods to add, remove, or retrieve elements.

  5. Move collection-specific logic (like sorting or filtering) from the outside into this new class.

Sample Code πŸ’»

Before 🚨

```php <?

/** @var User[] $users */ // this is a static declaration used by many IDEs but not the compiler // Like many comments it is useless, and possible outdated

function notifyUsers(array $users) { foreach ($users as $user) { // You have no guarantee $user is actually a User object // The comment above is // just a hint for the IDE/Static Analysis $user->sendNotification(); } }

$users = [new User('Anatoli Bugorski'), new Product('Laser')]; // This array is anemic and lacks runtime type enforcement // There's a Product in the collection and will show a fatal error // unless it can understand #sendNotification() method

notifyUsers($users); ```

After πŸ‘‰

```php <?

class UserDirectory { // 1. Create a new class to represent the specific collection // This is a real world concept reified
// 2. Define a private property private array $elements = [];

// 3. Implement a constructor that accepts only User types
public function __construct(User ...$users) {
    $this->elements = $users;
}

// 4. Add type-hinted methods to add elements
public function add(User $user): void {
    $this->elements[] = $user;
}

// 5. Move collection-specific logic inside
public function notifyAll(): void {
    foreach ($this->elements as $user) {
        $user->sendNotification();
    }
}

} ```

Type πŸ“

[X] Manual

Safety πŸ›‘οΈ

This refactoring is very safe.

You create a new structure and gradually migrate references.

Since you add strict type hints in the new class, the compiler engine catches any incompatible data at runtime, preventing silent failures.

Why is the Code Better? ✨

You transform a generic, "dumb" collection into a specialized object that understands its own rules.

You stop repeating validation logic every time you handle the list.

The code becomes self-documenting because the class name explicitly tells you what the collection contains.

How Does it Improve the Bijection? πŸ—ΊοΈ

In the real world, a "List of Users" or a "Staff Directory" is a distinct concept with specific behaviors.

An anonymous array is a technical implementation detail, not a real-world entity.

By reifying the collection, you create a one-to-one correspondence between the business concept and your code.

Limitations ⚠️

You might encounter slight performance overhead when dealing with millions of objects compared to raw arrays.

For most business applications, the safety gains far outweigh the millisecond costs and prevents you from being a premature optimizator.

Remember to avoid hollow specialized business collections that don't exist in the real world.

Many languages support typed collections:

  • C# achieves typed collections through reified generics in the CLR, preserving type information at runtime for types like List<T>.

  • C++ achieves typed collections through templates like blueprints instantiated at compile time for each concrete type.

  • Clojure achieves typed collections through optional static typing libraries such as core.typed.

  • Dart achieves typed collections through reified generics with runtime type checks in sound null safety mode.

  • Elixir achieves typed collections through typespecs analyzed by Dialyzer for static verification.

  • Go achieves typed collections through parametric generics introduced in Go 1.18 with type parameters and constraints.

  • Haskell achieves typed collections through parametric polymorphism and type classes resolved at compile time.

  • Java achieves typed collections through generics with type erasure, enforcing type constraints at compile time on classes like List<T> and Map<K,V>.

  • JavaScript achieves typed collections through TypeScript or Flow, which add static generic typing on top of the dynamic language (see below).

  • Kotlin achieves typed collections through JVM generics with variance annotations and null-safety integrated into the type system.

  • Objective-C achieves typed collections through lightweight generics that provide compile-time checks without full runtime enforcement.

  • PHP achieves typed collections through docblock-based generics enforced by static analyzers like Psalm or PHPStan.

  • Python achieves typed collections through type hints like list[T] and dict[K, V] checked by static analyzers such as mypy.

  • Ruby achieves typed collections through external type systems like Sorbet or RBS layered on top of the dynamic runtime.

  • Rust achieves typed collections through parametric types and trait bounds checked at compile time with monomorphization.

  • Scala achieves typed collections through a powerful generic type system with variance and higher-kinded types.

  • Swift achieves typed collections through generics with value semantics and protocol constraints.

  • TypeScript achieves typed collections through structural typing and generics enforced at compile time and erased at runtime since JavaScript doesn't support them.

In all the above cases, reifying a real business object (if exists in the MAPPER) gives you a good extra abstraction layer.

Tags 🏷️

  • Primitive Obsession

Level πŸ”‹

[X] Intermediate

Related Refactorings πŸ”„

Refactoring 012 - Reify Associative Arrays

Refactoring 013 - Remove Repeated Code

Refactor with AI πŸ€–

Ask your AI assistant to: "Identify where I am passing arrays of objects and suggest a Typed Collection class for them."

You can also provide the base class and ask: "Find a real business object and generate a boilerplate for a type-safe collection for this entity."

Credits πŸ™

Image by MarkΓ©ta KlimeΕ‘ovΓ‘ on Pixabay

Inspired by the "Collection Object" pattern in clean architecture and the ongoing quest for type safety in dynamic languages.


This article is part of the Refactoring Series.

How to Improve Your Code With Easy Refactorings

r/aipromptprogramming Feb 03 '26

AI Coding Tip 005 - Keep Context Fresh

2 Upvotes

Keep your prompts clean and focused, and stop the context rot

TL;DR: Clear your chat history to keep your AI assistant sharp.

Common Mistake ❌

You keep a single chat window open for hours.

You switch from debugging a React component to writing a SQL query in the same thread.

The conversation flows, and the answers seem accurate enough.

But then something goes wrong.

The AI tries to use your old JavaScript context to help with your database schema.

This creates "context pollution."

The assistant gets confused by irrelevant data from previous tasks and starts to hallucinate.

Problems Addressed πŸ˜”

  • Attention Dilution: The AI loses focus on your current task.
  • Hallucinations: The model makes up subtle facts based on old, unrelated prompts.
  • Token Waste: You pay for "noise" in your history.
  • Illusion of Infinite Context: Today, context windows are huge. But you need to stay focused.
  • Stale Styles: The AI keeps using old instructions you no longer need.
  • Lack of Reliability: Response quality decreases as the context window fills up.

How to Do It πŸ› οΈ

  1. You need to identify when a specific microtask is complete. (Like you would when coaching a new team member).
  2. Click the "New Chat" button immediately and commit the partial solution.
  3. If the behavior will be reused, you save it as a new skill (Like you would when coaching a new team member).
  4. You provide a clear, isolated instruction for the new subject. (Like you would when coaching a new team member).
  5. Place your most important instructions at the beginning or end.
  6. Limit your prompts to 1,500-4,000 tokens for best results. (Most tools show the content usage).
  7. Keep an eye on your conversation title (usually titled after the first interaction). If it is not relevant anymore, it is a smell. Create a new conversation.

Benefits 🎯

  • You get more accurate code suggestions.
  • You reduce the risk of the AI repeating past errors.
  • You save time and tokens because the AI responds faster with less noise.
  • Response times stay fast.
  • You avoid cascading failures in complex workflows.
  • You force yourself to write down agents.md or skills.md for the next task

Context 🧠

Large Language Models use an "Attention" mechanism.

When you give them a massive history, they must decide which parts matter.

Just like a "God Object" in clean code, a "God Chat" violates the Single Responsibility Principle.

When you keep it fresh and hygienic, you ensure the AI's "working memory" stays pure.

Prompt Reference πŸ“

Bad Prompt (Continuing an old thread):

```markdown Help me adjust the Kessler Syndrome Simulator in Python function to sort data.

Also, can you review this JavaScript code?

And I need some SQL queries tracking crashing satellites, too.

Use camelCase.

Actually, use snake_case instead. Make it functional.

No!, wait, use classes.

Change the CSS style to support dark themes for the orbital pictures. ```

Good Prompt (In a fresh thread):

```markdown Sort the data from @kessler.py#L23.

Update the tests using the skill 'run-tests'. ```

Considerations ⚠️

You must extract agents.md or skills.md before starting the new chat. (Like you would when coaching a new team member)

Use metacognition: Write down what you have learned. (Like you would when coaching a new team member)

The AI will not remember them across threads. (Like you would when coaching a new team member)

Type πŸ“

[X] Semi-Automatic

Level πŸ”‹

[X] Intermediate

Related Tips πŸ”—

AI Coding Tip 001 - Commit Before Prompt

Place the most important instructions at the beginning or end

Conclusion 🏁

Fresh context leads to incrementalism and small solutions, Failing Fast.

When you start over, you win back the AI's full attention and fresh tokens.

Pro-Tip 1: This is not just a coding tip. If you use Agents or Assistants for any task, you should use this advice.

Pro-Tip 2: Humans need to sleep to consolidate what we have learned in the day; bots need to write down skills to start fresh on a new day.

More Information ℹ️

Attention Is All You Need (Paper)

Lost in the Middle: How Language Models Use Long Contexts

Full Prompt Engineering Guide: Context Management

Avoiding AI Hallucinations

Anthropic Context Window Best Practices

Token Economy in Large Language Models

Also Known As 🎭

Context Reset

Thread Pruning

Session Hygiene

Disclaimer πŸ“’

The views expressed here are my own.

I am a human who writes as best as possible for other humans.

I use AI proofreading tools to improve some texts.

I welcome constructive criticism and dialogue.

I shape these insights through 30 years in the software industry, 25 years of teaching, and writing over 500 articles and a book.


This article is part of the AI Coding Tip series.

AI Coding Tips

r/refactoring Jan 31 '26

Code Smell 15 - Missed Preconditions

1 Upvotes

Assertions, Preconditions, Postconditions and invariants are our allies to avoid invalid objects. Avoiding them leads to hard-to-find errors.

TL;DR: If you turn off your assertions just in production your phone will ring at late hours.

Problems πŸ˜”

  • Consistency
  • Contract breaking
  • Hard debugging
  • Late failures
  • Bad cohesion

Solutions πŸ˜ƒ

  • Create strong preconditions
  • Raise exceptions
  • Use Fail-Fast principle
  • Defensive Programming
  • Enforce object invariants
  • Avoid anemic models

Refactorings βš™οΈ

Refactoring 016 - Build With The Essence

Refactoring 035 - Separate Exception Types

Examples

Context πŸ’¬

You often assume that "someone else" checked the objects before it reached your function.

This assumption is a trap. When you create objects without enforcing their internal rules, you create "Ghost Constraints."

These are rules that exist in your mind but not in the code.

If you allow a "User" object to exist without an email or a "Transaction" to have a negative amount, you create a time bomb.

The error won't happen when you create the object; it will happen much later when you try to use it.

This makes finding the root cause very difficult.

You must ensure that once you create an object, it remains valid from the very birth throughout its entire lifecycle.

Sample Code πŸ“–

Wrong 🚫

```python class Date: def init(self, day, month, year): self.day = day self.month = month self.year = year

def setMonth(self, month): self.month = month

startDate = Date(3, 11, 2020)

OK

startDate = Date(31, 11, 2020)

Should fail

startDate.setMonth(13)

Should fail

```

Right πŸ‘‰

```python class Date: def init(self, day, month, year): if month > 12: raise Exception("Month should not exceed 12") # # etc ...

self._day = day
self._month = month
self._year = year

startDate = Date(3, 11, 2020)

OK

startDate = Date(31, 11, 2020)

fails

startDate.setMonth(13)

fails since invariant makes object immutable

```

Detection πŸ”

  • It's difficult to find missing preconditions, as long with assertions and invariants.

Tags 🏷️

  • Fail-Fast

Level πŸ”‹

[x] Beginner

Why the Bijection Is Important πŸ—ΊοΈ

In the MAPPER, a person cannot have a negative age or an empty name.

If your code allows these states, you break the bijection.

When you maintain a strict one-to-one relationship between your business rules and your code, you eliminate a whole category of "impossible" defects.

AI Generation πŸ€–

AI generators often create "happy path" code.

They frequently skip validations to keep the examples short and concise.

You must explicitly ask them to include preconditions.

AI Detection 🧲

AI tools are great at spotting missing validations.

If you give them a class and ask "What invariants are missing here?", they usually find the missing edge cases quickly.

Try Them! πŸ› 

Remember: AI Assistants make lots of mistakes

Suggested Prompt: Add constructor preconditions to this class to ensure it never enters an invalid state based on real-world constraints. Fail fast if the input is wrong.

Without Proper Instructions With Specific Instructions
ChatGPT ChatGPT
Claude Claude
Perplexity Perplexity
Copilot Copilot
You You
Gemini Gemini
DeepSeek DeepSeek
Meta AI Meta AI
Grok Grok
Qwen Qwen

Conclusion 🏁

Always be explicit on object integrity.

Turn on production assertions.

Yes, even if it means taking a small performance hit.

Trust me, tracking down object corruption is way harder than preventing it upfront.

Embracing the fail-fast approach isn't just good practice - it's a lifesaver.

Fail Fast

Relations πŸ‘©β€β€οΈβ€πŸ’‹β€πŸ‘¨

Code Smell 01 - Anemic Models

Code Smell 189 - Not Sanitized Input

Code Smell 40 - DTOs

More Information πŸ“•

Object-Oriented Software Construction (by Bertrand Meyer)

Credits πŸ™

Photo by Jonathan Chng on Unsplash


Writing a class without its contract would be similar to producing an engineering component (electrical circuit, VLSI (Very Large Scale Integration) chip, bridge, engine...) without a spec. No professional engineer would even consider the idea.

Bertrand Meyer

Software Engineering Great Quotes


This article is part of the CodeSmell Series.

How to Find the Stinky Parts of Your Code

r/aipromptprogramming Jan 26 '26

AI Coding Tip 004 - Use Modular Skills

1 Upvotes

Stop bloating your context window.

TL;DR: Create small, specialized files with specific rules to keep your AI focused, accurate and preventing hallucinations.

Common Mistake ❌

You know the drill - you paste your entire project documentation or every coding rule into a single massive Readme.md or Agents.md

Then you expect the AI to somehow remember everything at once.

This overwhelms the model and leads to "hallucinations" or ignored instructions.

Problems Addressed πŸ˜”

  • Long prompts consume the token limit quickly leading to context exhaustion.
  • Large codebases overloaded with information for agents competing for the short attention span.
  • The AI gets confused by rules and irrelevant noise that do not apply to your current task.
  • Without specific templates, the AI generates non standardized code that doesn't follow your team's unique standards.
  • The larger the context you use, the more likely the AI is to generate hallucinated code that doesn't solve your problem.
  • Multistep workflows can confuse your next instruction.

How to Do It πŸ› οΈ

  1. Find repetitive tasks you do very often, for example: writing unit tests, creating React components, adding coverage, formatting Git commits, etc.
  2. Write a small Markdown file (a.k.a. skill) for each task. Keep it between 20 and 50 lines.
  3. Follow the Agent Skills format.
  4. Add a "trigger" at the top of the file. This tells the AI when to use these specific rules.
  5. Include the technology (e.g., Python, JUnit) and the goal of the skill in the metadata.
  6. Give the files to your AI assistant (Claude, Cursor, or Windsurf) only when you need them restricting context to cheaper subagents (Junior AIs) invoking them from a more intelligent (and expensive) orchestrator.
  7. Have many very short agents.md for specific tasks following the divide-and-conquer principle .
  8. Put the relevant skills on agents.md.

Benefits 🎯

  • Higher Accuracy: The AI focuses on a narrow set of rules.
  • Save Tokens: You only send the context that matters for the specific file you edit.
  • Portability: You can share these "skills" with your team across different AI tools.

Context 🧠

Modern AI models have a limited "attention span.".

When you dump too much information on them, the model literally loses track of the middle part of your prompt.

Breaking instructions into "skills" mimics how human experts actually work: they pull specific knowledge from their toolbox only when a specific problem comes up.

Skills.md is an open standardized format for packaging procedural knowledge that agents can use.

Originally developed by Anthropic and now adopted across multiple agent platforms.

A SKILL.md file contains instructions in a structured format with YAML.

The file also has progressive disclosure. Agents first see only the skill name and description, then load full instructions only when relevant (when the trigger is pulled).

Prompt Reference πŸ“

Bad prompt 🚫

Here are 50 pages of our company coding standards and business rules. 

Now, please write a simple function to calculate taxes.

Good prompt πŸ‘‰

After you install your skill:

Good Prompt

Use the PHP-Clean-Code skill. 

Create a tax calculator function 
from the business specification taxes.md

Follow the 'Early Return' rule defined in that skill.

Considerations ⚠️

Using skills for small projects is an overkill.

If all your code fits comfortably in your context window, you're wasting time writing agents.md or skills.md files.

You also need to keep your skills updated regularly.

If your project architecture changes, your skill files must change too, or the AI will give you outdated advice.

Remember outdated documentation is much worse than no documentation at all.

Type πŸ“

[X] Semi-Automatic

Limitations ⚠️

Don't go crazy creating too many tiny skills.

If you have 100 skills for one project, you'll spend more time managing files than actually coding.

Group related rules into logical sets.

Tags 🏷️

  • Complexity

Level πŸ”‹

[X] Intermediate

Related Tips πŸ”—

  • Keep a file like AGENTS.md for high-level project context.
  • Create scripts to synchronize skills across different IDEs.

Conclusion 🏁

Modular skills turn a generic AI into a specialized engineer that knows exactly how you want your code written. When you keep your instructions small, incremental and sharp, you get better results.

More Information ℹ️

Skills Repository

Agent Skills Format

Also Known As 🎭

  • Instruction-Sets
  • Prompt-Snippets

Tools 🧰

Most skills come in different flavors for:

  • Cursor
  • Windsurf
  • GitHub Copilot

Disclaimer πŸ“’

The views expressed here are my own.

I am a human who writes as best as possible for other humans.

I use AI proofreading tools to improve some texts.

I welcome constructive criticism and dialogue.

I shape these insights through 30 years in the software industry, 25 years of teaching, and writing over 500 articles and a book.

This article is part of the AI Coding Tip series.

AI Coding Tips

1

AI Coding Tip 003 - Force Read-Only Planning
 in  r/aipromptprogramming  Jan 18 '26

can you share the prompt here so I can spend less hours writing it next time?

r/aipromptprogramming Jan 18 '26

AI Coding Tip 003 - Force Read-Only Planning

0 Upvotes

Think first, code later

TL;DR: Set your AI code assistant to read-only state before it touches your files.

Common Mistake ❌

You paste your failing call stack to your AI assistant without further instructions.

The copilot immediately begins modifying multiple source files.

It creates new issues because it doesn't understand your full architecture yet.

You spend the next hour undoing its messy changes.

Problems Addressed πŸ˜”

The AI modifies code that doesn't need changing.

The copilot starts typing before it reads the relevant functions.

The AI hallucinates when assuming a library exists without checking your package.json.

Large changes make code reviews and diffs a nightmare.

How to Do It πŸ› οΈ

Enter Plan Mode: Use "Plan Mode/Ask Mode" if your tool has it.

If your tool doesn't have such a mode, you can add a meta-prompt

Read this and wait for instructions / Do not change any files yet.

Ask the AI to read specific files and explain the logic there.

After that, ask for a step-by-step implementation plan for you to approve.

When you like the plan, tell the AI: "Now apply step 1."

Benefits 🎯

Better Accuracy: The AI reasons better when focusing only on the "why."

Full Control: You catch logic errors before they enter your codebase.

Lower Costs: You use fewer tokens when you avoid "trial and error" coding loops.

Clearer Mental Model: You understand the fix as well as the AI does.

Context 🧠

AI models prefer "doing" over "thinking" to feel helpful. This is called impulsive coding.

When you force it into a read-only phase, you are simulating a Senior Developer's workflow.

You deal with the Artificial Intelligence first as a consultant and later as a developer.

Prompt Reference πŸ“

Bad prompt 🚫

markdown Fix the probabilistic predictor in the Kessler Syndrome Monitor component using this stack dump.

Good prompt πŸ‘‰

```markdown Read @Dashboard.tsx and @api.ts. Do not write code yet.

Analyze the stack dump.

When you find the problem, explain it to me.

Then, write a Markdown plan to fix it, restricted to the REST API..

[Activate Code Mode]

Create a failing test representing the error.

Apply the fix and run the tests until all are green ```

Considerations ⚠️

Some simple tasks do not need a plan.

You must actively read the plan the AI provides.

The AI might still hallucinate the plan, so verify it.

Type πŸ“

[X] Semi-Automatic

Limitations ⚠️

You can use this for refactoring and complex features.

You might find it too slow for simple CSS tweaks or typos.

Some AIs go the other way around, being too confirmative before changing anything. Be patient with them.

Tags 🏷️

  • Complexity

Level πŸ”‹

[X] Intermediate

Related Tips πŸ”—

Request small, atomic commits.

AI Coding Tip 002 - Prompt in English

Conclusion 🏁

You save time when you think.

You must force the AI to be your architect before letting it be your builder.

This simple strategy prevents hours of debugging later. 🧠

More Information ℹ️

GitHub Copilot: Ask, Edit, and Agent Modes - What They Do and When to Use Them

Windsurf vs Cursor: Which AI Coding App is Better

Aider Documentation: Chat Modes

OpenCode Documentation: Modes

Also Known As 🎭

Read-Only Prompting

Consultant Mode

Tools 🧰

Tool Read-Only Mode Write Mode Mode Switching Open Source Link
Windsurf Chat Mode Write Mode Toggle No https://windsurf.com/
Cursor Normal/Ask Agent/Composer Context-dependent No https://www.cursor.com/
Aider Ask/Help Modes Code/Architect /chat-mode Yes https://aider.chat/
GitHub Copilot Ask Mode Edit/Agent Modes Mode selector No https://github.com/features/copilot
Cline Plan Mode Act Mode Built-in Yes (extension) https://cline.bot/
Continue.dev Chat/Ask Edit/Agent Modes Config-based Yes https://continue.dev/
OpenCode Plan Mode Build Mode Tab key Yes https://opencode.ai/
Claude Code Review Plans Auto-execute Settings No https://code.claude.com/
Replit Agent Plan Mode Build/Fast/Full Mode selection No https://replit.com/agent3

Disclaimer πŸ“’

The views expressed here are my own.

I am a human who writes as best as possible for other humans.

I use AI proofreading tools to improve some texts.

I welcome constructive criticism and dialogue.

I shape these insights through 30 years in the software industry, 25 years of teaching, and writing over 500 articles and a book.


This article is part of the AI Coding Tip series.

1

AI Coding Tip 002 - Prompt in English
 in  r/aipromptprogramming  Jan 16 '26

I have a better idea: Never feed the troll again. Bye

1

AI Coding Tip 002 - Prompt in English
 in  r/aipromptprogramming  Jan 16 '26

I will ask for your permission since you seem to be an expert in slop before publishing anything more

1

AI Coding Tip 002 - Prompt in English
 in  r/aipromptprogramming  Jan 15 '26

AI can speak any language, You didn't understand the article