# Clean Code

> Clean code lifecycle for Go/Rust/TypeScript/Bun/Python — writing, reviewing, refactoring. Naming, functions, DRY, code smells, duplication (literal/logical/structural), dead-code removal with false-positive guardrails, safe refactoring. Triggers: 'clean code', 'código limpo', 'refactor', 'refatorar', 'remove duplication', 'remover duplicação', 'dead code', 'código morto', 'code smells', '/clean-code'.

- Skill: `bruno-cunha-souza/clean-code` (Agent Skill, multi-file: 10 files)
- Install (CLI): `npx skillmds@latest add bruno-cunha-souza/clean-code`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bruno-cunha-souza/clean-code/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Bruno-Cunha-Souza (https://skillmd.com/u/bruno-cunha-souza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/bruno-cunha-souza/clean-code

---


# Clean Code Lifecycle

> "Code is clean if it can be read, and enhanced by a developer other than its original author." — Grady Booch

## When to Use

- **Writing new code**: To ensure high quality from the start.
- **Reviewing Pull Requests**: To provide constructive, principle-based feedback.
- **Refactoring legacy code**: To identify and remove code smells.
- **Improving team standards**: To align on industry-standard best practices.

## Prerequisites

| Tool | Purpose | Install |
|------|---------|---------|
| `jscpd` | Multi-language clone detection | `npm install -g jscpd` |
| `pmd` | Java/multi-language CPD | [pmd.github.io](https://pmd.github.io/) |
| `fd` | Fast file finder | `brew install fd` / `apt install fd-find` |
| `rg` | Fast content search | `brew install ripgrep` / `apt install ripgrep` |
| `golangci-lint` | Go meta-linter (50+ linters) | `go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest` |
| `clippy` | Rust idiomatic linter (500+ lints) | `rustup component add clippy` |
| `biome` | Fast TS/JS linter + formatter | `bun install -D @biomejs/biome` / `npm install -D @biomejs/biome` |
| `knip` | Find unused TS exports/deps/files | `bunx knip` / `npx knip` |
| `ruff` | Python linter — unused imports/vars, dedup hints | `pipx install ruff` |
| `vulture` | Python dead-code detection with confidence tiers | `pipx install vulture` |
| Project linter | Language-specific checks | Check project config (`.eslintrc`, `.golangci.yml`, `biome.json`, `pyproject.toml`) |

## Phase 0 — Project Context Discovery

Before applying any clean code principle, **understand the project you're working in.** Refactoring or deduplicating without context leads to wrong abstractions, broken conventions, and wasted effort.

### Discovery Commands

```bash
# 1. Find project documentation — README, ADRs, contributing guides
fd -t f -i '(README|CONTRIBUTING|ADR|ARCHITECTURE|CONVENTIONS|STYLE_GUIDE)' .

# 2. Find configuration files that reveal conventions and tooling
fd -t f '(\.eslintrc|\.prettierrc|\.editorconfig|\.golangci|pyproject\.toml|biome\.json)' .

# 3. Check for a CLAUDE.md or similar AI-agent instructions
fd -t f 'CLAUDE.md' .

# 4. Read the project's commit style to match refactoring commits
git log --oneline -20

# 5. Check for existing shared utilities — avoid creating duplicates
fd -t f -i '(utils|helpers|shared|common|lib)' src/
```

### Key Questions

| Question | Why it matters | Where to find it |
|----------|---------------|------------------|
| Does the project have a style guide or coding conventions? | Your refactoring must follow existing patterns, not introduce new ones | `CONTRIBUTING.md`, linter configs, ADRs |
| Are there existing shared utility modules? | Before extracting a helper, check if one already exists | `utils/`, `shared/`, `lib/`, `common/` dirs |
| What's the test strategy (unit, integration, e2e)? | Determines how you verify refactoring safety | `README.md`, CI config, test directory structure |
| Are there architectural boundaries (modules, packages, bounded contexts)? | Deduplicating across boundaries may violate the architecture intentionally | `ARCHITECTURE.md`, ADRs, module/package structure |
| Is there a dependency injection or service pattern in use? | Extracting code the wrong way can break DI wiring | Entry points, main files, DI containers |

### Decision Rules

- **If a style guide exists** → follow it, even if it contradicts Clean Code principles. Project consistency wins over theoretical purity.
- **If shared utils already exist** → add to them instead of creating parallel helpers.
- **If ADRs document a decision to keep duplication** → respect it. Not all duplication is accidental.
- **If no tests exist** → write characterization tests before any refactoring (see Phase 3).
- **If no documentation exists** → read code structure, git history, and CI config to infer conventions.

> **Rule: context before cleanup.** A "clean" refactoring that ignores project conventions creates more mess than the duplication it removed.

## Phase 1 — Code Quality Audit

Scan the codebase for code smells. Each smell includes a description and detection method.

| # | Smell | Description | Detection |
|---|-------|-------------|-----------|
| 1 | **Rigidity** | One change forces a cascade of dependent changes | Count how many files a single-line change touches |
| 2 | **Fragility** | Breaks in many places when you make a change | Look for high coupling with no clear interface boundary |
| 3 | **Immobility** | Useful parts are entangled with unneeded details | Functions that import half the project to do a simple task |
| 4 | **Viscosity** | Easier to hack than to follow the design | Devs keep bypassing an abstraction — it's too cumbersome |
| 5 | **Needless Complexity** | Premature abstraction or speculative generality | Unused interfaces, empty abstract methods, config nobody changes |
| 6 | **Needless Repetition** | Same logic in multiple places | `npx jscpd ./src` or review similar function bodies |
| 7 | **Feature Envy** | A method accesses another object's data more than its own | Chains: `order.getCustomer().getAddress().getCity()` |
| 8 | **Shotgun Surgery** | A single change requires edits across many files | `git log --name-only` — same files always change together |
| 9 | **Divergent Change** | One class changed for many different reasons | File with commits from unrelated features |

### Principle Checks

For each file under review, verify against the core principles. See [references/PRINCIPLES.md](references/PRINCIPLES.md) for full details.

- **Names**: Intention-revealing, searchable, pronounceable?
- **Functions**: Small (<30 lines), do one thing, ≤2 arguments?
- **Comments**: Can any comment be eliminated by making the code clearer?
- **Formatting**: Newspaper metaphor — high-level at top, details at bottom?
- **Objects**: Law of Demeter respected? No `a.getB().getC().doSomething()`?
- **Error Handling**: Exceptions over return codes? No null returns/passes?
- **Tests**: F.I.R.S.T. principles followed?
- **Classes**: Single Responsibility Principle?

### Language-Specific Checks

For language-specific smells, idioms, and detection commands:

- **Go**: See [references/GOLANG.md](references/GOLANG.md) — stuttering names, empty interface abuse, `init()` side effects, naked returns, oversized interfaces, functional options
- **Rust**: See [references/RUST.md](references/RUST.md) — `unwrap()` abuse, unnecessary `clone()`, stringly typed APIs, `Arc<Mutex<>>` overuse, monolithic error enums, boolean parameters
- **TypeScript**: See [references/TYPESCRIPT.md](references/TYPESCRIPT.md) — `any` abuse, excessive type assertions, enum vs union, barrel file bloat, god interfaces, class overuse
- **Bun**: See [references/BUN.md](references/BUN.md) — Node.js APIs vs Bun natives, unnecessary polyfills, `dotenv`/`jest`/`express` replacements, `Bun.file`/`Bun.serve`/`Bun.password`
- **Python**: See [references/PYTHON.md](references/PYTHON.md) — dict-as-object, `if/elif` dispatch chains, `**kwargs` soup, boolean flag params, stateless classes, import-time side effects, sync/async twins

## Phase 2 — Duplication & Dead Code Detection

Duplication is the same knowledge, logic, or intent expressed in more than one place. Dead code is a symbol nothing reaches. Both inflate the change surface — find them before Phase 3 touches anything.

### 2.1 Duplication

| Class | Signature | Cost of leaving it |
|-------|-----------|--------------------|
| **Literal** | Identical blocks copied verbatim | Copies drift; the next fix lands in one of them, not all |
| **Logical** | Same outcome, different names or control flow | Invisible to clone tools — only reading similar signatures finds it |
| **Structural** | Repeated `if/else` or `switch` chains spelling out the same decision | Adding one case means editing N sites; one gets forgotten |
| **Data** | Constants, URLs, configs, error envelopes repeated across files | Values drift silently; the copies disagree |

Detection commands per class, how to read the Structural signal from commit history, and the **when NOT to deduplicate** rules: [references/DUPLICATION.md](references/DUPLICATION.md).

> **Rule of Three.** Tolerate two copies. Extract on the third. A premature abstraction — the shared function that needs 4 parameters and 2 boolean flags to serve every caller — is worse than the duplication it removed.

### 2.2 Dead Code

| Type | Example |
|------|---------|
| **Unreferenced function / type** | No call site in source, tests, templates, config, or CI |
| **Unused import** | Flagged by the linter, with no side-effect or re-export role |
| **Assigned-never-read variable / unused parameter** | Flagged by the linter, not fixed by an interface signature |
| **Unreachable branch** | Dead feature flag, code after `return`, provably constant guard |
| **Unused dependency / orphan file** | Manifest or module graph shows zero importers |

Command matrix per language, plus the twelve false-positive guardrails that must clear before any deletion: [references/DEAD_CODE.md](references/DEAD_CODE.md).

> **Rule: two independent signals before deleting.** A tool finding plus a manual sweep of non-source assets. Reflection, DI registries, framework decorators, serialization, and string-based routing are invisible to every dead-code tool — deleting a live symbol passes the test suite and breaks production.

## Phase 3 — Safe Refactoring

Apply refactoring patterns to resolve the issues found in Phases 1 and 2. For concrete before/after diffs, see [references/PATTERNS.md](references/PATTERNS.md).

### Available Patterns

| Pattern | Use When | Result |
|---------|----------|--------|
| **Extract Function** | Identical blocks across multiple call sites | Auth check in every handler → middleware |
| **Extract Constant/Config** | Magic values repeated across files | `30 * time.Second` in 3 files → `config.DefaultTimeout` |
| **Generic/Parameterized Function** | Near-identical functions differing by one call | `GetUser`, `GetOrder` → `getByID[T]` |
| **Template Method / Strategy** | Similar flows with one varying step | PDF/CSV generators → `GenerateReport(data, renderer)` |
| **Substitute Algorithm** | Two functions reach the same result by different means | Two CSV parsers → keep the streaming one, delete the other |
| **Replace Conditional with Polymorphism** | The same `switch`/`if-elif` chain repeated across files | Channel switch in 3 handlers → dispatch table, then interface |

### Step 1 — Secure the starting point

```bash
# Ensure all tests pass BEFORE you start
go test ./...          # Go
cargo test             # Rust
bun test               # Bun
npm test               # Node/TS
pytest                 # Python

# Ensure a clean git state
git status             # should be clean, or stash first
git stash              # if needed

# Create a dedicated branch
git checkout -b refactor/describe-the-change
```

**Rule: never refactor on a dirty working tree.** Mixing feature changes with refactoring makes rollback impossible.

### Step 2 — One transformation at a time

Each refactoring step must be **atomic** — a single, small, independently verifiable change.

| Step | Action | Verify |
|------|--------|--------|
| 1 | Extract function / constant / type | Run tests |
| 2 | Replace first call site with the new abstraction | Run tests |
| 3 | Replace next call site | Run tests |
| 4 | Remove old dead code — follow the [DEAD_CODE.md](references/DEAD_CODE.md) protocol: two signals, guardrails cleared, own commit | Run tests |
| 5 | Commit | `git commit -m "refactor: extract getByID generic handler"` |

**Never batch multiple extractions into a single step.**

```bash
# After EACH small change:
go test ./...  # or your project's test command
git add -p     # stage only the relevant change
git commit -m "refactor: step N — description"
```

### Step 3 — Verify behavior preservation

```bash
# Go: check exported symbols haven't changed
go doc ./pkg/handlers
go vet ./...
golangci-lint run ./...

# Rust: clippy + format + test
cargo clippy -- -W clippy::pedantic
cargo fmt -- --check
cargo test

# TypeScript / Bun: type check + lint
bunx tsc --noEmit          # or: npx tsc --noEmit
bunx biome check .         # or: npx eslint .
bun test                   # or: npx vitest run

# Python: type check + lint + test
ruff check .
ruff format --check .
mypy --strict .
pytest -q

# Find unused exports and dependencies (TypeScript / Bun)
bunx knip

# Run integration/e2e tests if available
npm run test:e2e

# Check for unused imports/variables introduced by refactoring
go vet ./...                                            # Go
cargo machete                                           # Rust
bunx knip                                               # TypeScript / Bun
npx eslint --rule '{"no-unused-vars": "error"}' src/    # TypeScript (eslint)
ruff check --select=F401,F841,ARG .                     # Python — unused imports/vars/params
vulture src/ tests/ --min-confidence 90                 # Python — dead symbols
```

### Step 4 — Rollback strategy

```bash
# Undo current uncommitted change (keep committed steps)
git checkout -- .

# Revert just one committed step
git revert <commit-hash>

# Abandon the entire refactoring branch
git checkout main
git branch -D refactor/describe-the-change
```

The branch-per-refactoring approach means you never risk `main`.

### Common Pitfalls

- **Changing behavior during refactoring**: Resist the urge to "fix that bug while I'm here." Refactoring and behavior changes are separate commits — always.
- **Refactoring without tests**: If the code has no tests, **write characterization tests first** — tests that capture current behavior, even if that behavior has bugs.
- **Big-bang refactoring**: Rewriting an entire module at once. Prefer the Strangler Fig pattern — replace piece by piece.
- **Skipping the test run**: "It's just a rename." Type aliases, reflection, serialization, string-based routing — all break on renames.

### Plan-only mode

Composing `/clean-code /only-plan` suppresses every write in Phase 3 — no branch, no commits, no edits. Phases 0–2 run normally, and each planned transformation becomes a numbered step in the single `IMPLEMENTATION_PLAN.md` at the project root, following `@only-plan`'s section contract. This skill writes no file of its own in either mode.

## Related Skills

- `@only-plan` — compose as `/clean-code /only-plan` for a refactor plan at the project root instead of applied edits.
- `@ponytail-review` — the complementary lens: what to **delete** (speculative abstractions, dependencies the stdlib replaces, config nobody sets) rather than what to restructure.
- `@code-optimization` — when duplication or a smell has a measurable performance cost; it grades Impact and writes `OPTIMIZATION_REPORT.md`.
- `@code-review` — broad severity-ranked review; its Rule-of-Three findings hand off here for the refactor mechanics.
- `@code-debugger` — a smell that is actually a live defect belongs there first. Refactor after the fix is green.

## References

- [PRINCIPLES](references/PRINCIPLES.md) — the eight Clean Code principle families
- [PATTERNS](references/PATTERNS.md) — six refactoring patterns with before/after diffs
- [DUPLICATION](references/DUPLICATION.md) — taxonomy, detection probes, when NOT to deduplicate
- [DEAD_CODE](references/DEAD_CODE.md) — per-language detection, false-positive guardrails, removal protocol
- [GOLANG](references/GOLANG.md) · [RUST](references/RUST.md) · [TYPESCRIPT](references/TYPESCRIPT.md) · [BUN](references/BUN.md) · [PYTHON](references/PYTHON.md) — language-specific smells and idioms

## Implementation Checklist

- [ ] Is this function smaller than 30 lines?
- [ ] Does this function do exactly one thing?
- [ ] Are all names searchable and intention-revealing?
- [ ] Have I avoided comments by making the code clearer?
- [ ] Am I passing too many arguments?
- [ ] Is there a failing test for this change?
- [ ] Is there duplicated logic that could be extracted into a shared function?
- [ ] Are magic strings/numbers extracted into named constants?
- [ ] Did I check for existing utilities before writing a new helper?
- [ ] If I extracted a shared abstraction, is it used in 3+ places (Rule of Three)?
- [ ] Did two independent signals confirm each dead symbol before deleting it (tool finding + non-source sweep)?
- [ ] Did I clear the false-positive guardrails (reflection, DI, framework decorators, serialization, entry points, published API)?
- [ ] Is each deletion in its own commit, separate from any refactor?
- [ ] Did I run all tests before AND after refactoring?
- [ ] Is each refactoring step in its own commit (one transformation per commit)?
- [ ] Did I avoid mixing behavior changes with structural refactoring?
- [ ] If the code had no tests, did I write characterization tests before refactoring?

