Tutorials / Tips & tricks / Simplify changed code
📝 Written ● Intermediate Updated 2026-05-19

Simplify changed code with /simplify

A diff that compiles, passes tests, and matches the requirement can still be twice as long as it should be — missed reuse opportunities, over-abstraction, defensive code for impossible cases. /simplify is a focused review pass that names those specific smells and rewrites them. Run it before code review, not after.

"Done" and "minimal" are different bars

0

When an agent finishes a feature, the natural stopping condition is "tests pass and it does the thing." That's the functional bar. The quality bar — minimal, idiomatic, no dead weight — usually isn't checked.

The result, repeatable across enough projects to be a pattern:

  • A new helper that duplicates an existing utility three folders over.
  • A four-layer abstraction for a problem with one caller.
  • Guard clauses for inputs that the caller's type system already excludes.
  • Configuration knobs nobody asked for.
  • Comments narrating what the next line does — in English, slower than the code.

/simplify is a separate review pass with a checklist for these specific failure modes. It reads the diff, names what's wrong by category, and either rewrites it or proposes the rewrite for your approval. Treat it as a step in the workflow, not an optional polish.

When to run it

1

The right moment is after "tests pass," before "open a PR." Earlier and you're simplifying code that's still in flux. Later and the smells are already in review and your reviewer is the one finding them.

  • After LingCode finishes a feature — run it on the working tree before committing.
  • After a hand-written change — your own diff is fair game. The skill doesn't care who wrote the code.
  • Before requesting code review — preempt the "this is too long" comments by removing the cause.
Don't run /simplify on code you don't own. Simplifying someone else's PR in their branch is a great way to invent a 200-line merge conflict. Run it on your own changes only.

What it actually looks at

2

The five categories /simplify reviews against, in priority order:

  • Reuse. Did this diff add a function that already exists elsewhere in the codebase? LingCode greps for similar shapes before accepting new helpers.
  • Over-abstraction. Is there a protocol/interface with one conformer, a factory with one product, a generic with one concrete type? Inline it.
  • Defensive code for impossible inputs. Null checks on a non-nullable type. if (count < 0) on an unsigned int. Catch-and-rethrow that adds nothing. Delete.
  • Dead branches and unused params. The "I might need this later" parameter that no caller passes. The error case that's never reachable.
  • Comments that narrate code. // increment counter above counter += 1 is noise; // rate-limited to 5/s by upstream is signal. Keep the second kind, drop the first.

Running it

3

In the LingCode chat, type:

/simplify

With no arguments, it scopes to your current uncommitted changes (git diff HEAD). LingCode reads the diff, runs the checklist, and produces a structured report.

To scope explicitly:

# Just one file.
/simplify Sources/Auth/LoginViewModel.swift

# A range of commits — useful before a force-push.
/simplify since main

# A specific PR-shaped scope.
/simplify HEAD~3..HEAD

The report comes back as a list of findings grouped by category, each with a file/line reference and a suggested rewrite. Findings without a confident rewrite are flagged as "consider" rather than "fix."

What a typical report looks like

4

Edited for shape, with realistic content:

### Reuse (1)
- LoginViewModel.swift:42 — added formatPhoneNumber(). The same
  function exists at Utilities/PhoneFormatter.swift:18 and is
  already imported in this file. Replace the local helper with a
  call to PhoneFormatter.format(_:).

### Over-abstraction (2)
- AuthService.swift:8 — protocol AuthProviding has one conformer
  (RealAuthService) and is only injected in one place. Inline:
  delete the protocol, depend on RealAuthService directly. Re-add
  the protocol if and when a second conformer arrives.
- LoginCoordinator.swift:30 — generic NavigationDestination<T>
  is instantiated only as NavigationDestination<LoginRoute>.
  Specialize to NavigationDestinationLogin.

### Defensive code (1)
- LoginViewModel.swift:67 — guard let email = email else { ... }
  where `email` is typed `String` (non-optional). The branch is
  unreachable. Delete.

### Consider (1)
- AuthService.swift:55 — three-argument `login(email:password:
  remember:)` defaults `remember = false` and no caller passes
  `true`. Consider dropping the parameter, re-adding when needed.

Apply fixes? [y/N]

Categories separate the "obviously delete this" findings from the "judgment call" findings. The "Consider" section is where you push back; the rest is usually safe to apply.

Applying the fixes

5

Answer y and LingCode rewrites the files. The standard verify-before-claiming-done loop kicks in: it re-runs the test suite after the changes, and reports test status as part of the result.

If a fix breaks a test, LingCode reverts that specific fix and tells you which finding was unsafe. The other fixes stay applied. You end up with the diff that simplified cleanly.

Review the rewrite before committing. /simplify is mechanical pattern-matching on top of judgment — usually right, sometimes overzealous. Skim the resulting diff; you don't have to accept every rewrite.

What it doesn't do

6

Boundaries worth knowing:

  • It doesn't change behavior. Findings are about how the code is shaped, not whether it does the right thing. Use code review for that.
  • It doesn't optimize for performance. "Efficiency" in the skill description means "no obvious wasted work" — duplicate calls, dead branches — not micro-optimization. Hot paths still need profiling.
  • It doesn't rewrite working code that's just verbose by your taste. If a 30-line function reads cleanly and has no reuse/abstraction/defensiveness issues, /simplify leaves it alone. Style preferences are not smells.
  • It doesn't touch tests. Test code follows different rules — duplication is often desirable, defensive setup is often necessary. Different review pass.

Pair with verification

7

The natural workflow:

  1. LingCode implements the feature.
  2. Tests pass.
  3. /simplify — strip the over-build.
  4. Tests still pass (verify, don't assume).
  5. Commit and request review.

Step 4 is where a lot of "simplification" workflows leak — fixes get applied, no one reruns the suite, regressions ship. /simplify runs the suite itself; if you skip it via manual edits, run the suite by hand. See verify before claiming done for the discipline.

Use this in LingCode

8

Package the workflow as a skill so LingCode applies the right checklist every time you ask it to simplify:

---
name: simplify
description: Review changed code for reuse, quality, and efficiency, then fix any issues found. Triggers: 'simplify this', 'clean up the code', 'reduce', 'too verbose', 'review my diff', 'is there a simpler way', 'shorter version', 'refactor for clarity'. Actions: scan diff for five categories — missed reuse opportunities, over-abstraction, defensive code for impossible cases, dead branches, narrating comments. Output: structured report + edits. Explicitly NOT: behavior changes, performance optimization, taste rewrites, test changes.
---

Review the current diff (default: git diff HEAD; or the scope the
user names) against five categories, in priority order:

1. Reuse — does this diff add code that already exists elsewhere
   in the codebase? Grep for similar shapes before accepting new
   helpers. Prefer call-site updates over duplicate utilities.

2. Over-abstraction — protocols with one conformer, factories
   with one product, generics with one concrete type. Inline.
   Re-add the abstraction when a second use case appears.

3. Defensive code for impossible inputs — guard clauses on
   non-nullable types, range checks the type system already
   enforces, catch-and-rethrow that adds nothing. Delete.

4. Dead branches and unused parameters — error cases that can't
   reach, params no caller passes, "for future use" knobs.
   Delete.

5. Comments that narrate code — "// increment counter" above
   "counter += 1" is noise. Keep comments that explain *why* (a
   constraint, a non-obvious invariant), drop comments that
   describe *what*.

Group findings by category. For each, give file:line, the smell,
and a concrete rewrite. Use a "Consider" bucket for judgment
calls.

After applying fixes, re-run the test suite. Revert any fix that
breaks a test; keep the rest.

Do not change behavior. Do not optimize for performance. Do not
rewrite working code that's just verbose by taste. Do not touch
test files.

Save as ~/.lingcode/skills/simplify/SKILL.md — see Install a skill for the exact location and how skills get discovered.

Get LingCode →

What's next