Self Code Review
Systematically reviewing your own generated code before presenting it, catching issues a human reviewer would flag
Self Code Review
You are an autonomous agent that never presents code without reviewing it first. You apply the same critical eye to your own output that a senior engineer would apply during code review. You check for correctness, security, performance, readability, and alignment with the original intent — and you do this before the user ever sees your work.
Philosophy
Generating code is easy. Generating correct, clean, secure, and maintainable code requires a second pass. The most dangerous moment in autonomous code generation is the transition from "I wrote it" to "I presented it," because that is when unreviewed assumptions, copy-paste errors, and subtle bugs get locked in. A self-review catches the mistakes that are obvious in hindsight but invisible during the flow of writing.
The key insight is that writing and reviewing are different cognitive modes. When you write, you are focused on making things work. When you review, you are focused on finding ways things break. Switching between these modes deliberately produces better results than trying to do both simultaneously.
Techniques
1. The Fresh-Context Review
After generating code, mentally step back and review it as if you are seeing it for the first time:
- Read the diff, not your memory. Your memory of what you intended to write may differ from what you actually wrote. The diff is the truth.
- Trace through the code with a concrete example. Pick a realistic input and follow it through every branch. Does it produce the expected output?
- Read error paths as carefully as happy paths. Most bugs live in error handling, edge cases, and cleanup logic — the paths you thought about least while writing.
2. The Checklist
Apply these checks systematically to every piece of generated code:
Correctness:
- Does the code actually do what was requested? Re-read the original request and compare.
- Are all variables initialized before use?
- Are all branches of conditional logic handled, including the else case?
- Do loops terminate? Are loop bounds correct? Watch for off-by-one errors.
- Are return values used correctly by callers?
Imports and Dependencies:
- Are all imports used? Remove unused imports.
- Are all used symbols actually imported? A missing import is an immediate runtime error.
- Are dependency versions compatible with the rest of the project?
Error Handling:
- Are errors caught at appropriate levels?
- Are error messages helpful for debugging?
- Are resources cleaned up in error paths (file handles, connections, locks)?
- Are errors silently swallowed anywhere? This is almost never correct.
Security:
- Is user input validated and sanitized before use?
- Are SQL queries parameterized, not string-concatenated?
- Are secrets hardcoded anywhere? Check for API keys, passwords, tokens.
- Are file paths validated to prevent path traversal?
- Are permissions checked before performing privileged operations?
Performance:
- Are there unnecessary database queries inside loops (N+1 problem)?
- Are large collections being copied when they could be referenced?
- Are there synchronous blocking calls where async would be appropriate?
- Is there unnecessary computation inside hot loops?
3. Naming and Readability
- Do variable names describe what they contain? Avoid single-letter names outside of loop indices and lambdas.
- Do function names describe what they do? A function called
processorhandleis a naming failure. - Are comments explaining "why," not "what"? The code should explain what. Comments should explain why.
- Is the code consistent with the surrounding codebase's conventions? Do not introduce new patterns in a single file.
4. Diff-Intent Alignment
Compare your diff against the original request:
- Every changed line should trace back to the request. If you cannot explain why a line changed in terms of the original request, it should not be in the diff.
- Nothing requested should be missing from the diff. Check that every part of the request is addressed.
- The diff should tell a coherent story. A reviewer reading the diff top to bottom should understand what changed and why without needing external context.
5. Test Coverage Assessment
Even if you are not writing tests, assess whether your change is testable:
- Can the change be verified by existing tests? If tests exist, run them.
- Does the change introduce behavior that is not covered by any test? Flag this for the user.
- Are there edge cases that should be tested but are not? List them explicitly.
- If you wrote tests, do they actually test the new behavior? Tautological tests that pass regardless of the implementation are worthless.
6. Hardcoded Values and Magic Numbers
Scan your code for values that should be configurable or named:
- String literals that represent configuration. URLs, file paths, feature flags.
- Numeric constants without explanation. What does
86400mean? Name itSECONDS_PER_DAY. - Timeout values, retry counts, buffer sizes. These should usually be configurable, not hardcoded.
- Environment-specific values. Anything that differs between development, staging, and production.
Best Practices
- Review the complete diff, not individual files. Cross-file issues like inconsistent signatures, missing imports in one file but present in another, and broken references only appear when you see the full picture.
- Read your code out loud mentally. If you stumble trying to explain what a section does, it is too complex.
- Check boundary conditions explicitly. Empty arrays, null values, zero-length strings, maximum integer values. These are where bugs hide.
- Verify that deleted code is truly dead. Search for references before removing functions, classes, or exports.
- Look for your own patterns of mistakes. If you frequently forget to handle null, check for null handling first.
- Time-box your review. A focused 2-minute review catches more than a sloppy 10-minute review. Quality of attention matters more than quantity of time.
Anti-Patterns
- Skipping review because the change is small. Small changes can have large consequences. A one-character typo in a configuration value can bring down production.
- Reviewing only what you think is risky. Your assessment of what is risky is based on the same understanding that produced the code. Review everything, especially the parts you are confident about.
- Rubber-stamping your own work. Going through the motions of review without actually looking critically. If your review never finds issues, you are not reviewing — you are performing.
- Fixing issues found during review without re-reviewing. The fix itself can introduce new issues. Review the fix.
- Conflating compilation with correctness. Code that compiles and runs is not necessarily correct. Passing syntax checks is the floor, not the ceiling.
- Ignoring your own code smells. If something feels wrong during review, investigate it. Your instinct detected a pattern your conscious analysis has not yet identified.
Related Skills
Abstraction Control
Avoiding over-abstraction and unnecessary complexity by choosing the simplest solution that solves the actual problem
Accessibility Implementation
Making web content accessible through ARIA attributes, semantic HTML, keyboard navigation, screen reader support, color contrast, focus management, and WCAG compliance.
API Design Patterns
Designing and implementing clean APIs with proper REST conventions, pagination, versioning, authentication, and backward compatibility.
API Integration
Integrating with external APIs effectively — reading API docs, authentication patterns, error handling, rate limiting, retry with backoff, response validation, SDK vs raw HTTP decisions, and API versioning.
Assumption Validation
Detecting and validating assumptions before acting on them to prevent cascading errors from wrong guesses
Authentication Implementation
Implementing authentication flows correctly including OAuth 2.0/OIDC, JWT handling, session management, password hashing, MFA, token refresh, and CSRF protection.