The Art of Code Review
Lessons from reviewing production code in fintech, Web3, and enterprise systems—where code quality directly impacts user trust and business outcomes.
The Art of Code Review
Working across fintech, Web3, and enterprise systems has taught me that code reviews are not just gatekeeping—they’re your last line of defense before code reaches production. In payment systems and blockchain applications, bugs don’t just frustrate users; they can cost real money and erode trust permanently.
Why Code Reviews Are Non-Negotiable
In high-stakes environments, code reviews serve as:
- Risk Mitigation: Catch security vulnerabilities, logic errors, and edge cases before they reach production
- Knowledge Distribution: Prevent single points of failure by ensuring multiple people understand critical systems
- Standards Enforcement: Maintain consistency in architecture, patterns, and quality across a growing codebase
- Continuous Learning: Both reviewer and author learn from the exchange
What to Review (Priority Order)
Critical: Security and Financial Logic
In Payment Systems:
- Validate all monetary calculations (rounding, currency conversion, fee calculations)
- Check for race conditions in transaction processing
- Verify authentication/authorization on sensitive endpoints
- Ensure PCI-DSS compliance for card data handling
In Web3 Applications:
- Validate wallet connection security
- Check for reentrancy vulnerabilities in contract interactions
- Verify transaction signing flows
- Ensure proper error handling for blockchain failures
Real Example from Production:
// ❌ Dangerous: Floating point arithmetic with money
const total = amount * exchangeRate + fee;
// ✅ Safe: Use integer arithmetic (smallest currency unit)
const amountInPence = Math.round(amount * 100);
const feeInPence = Math.round(fee * 100);
const totalInPence = Math.round(amountInPence * exchangeRate) + feeInPence;
Important: Architecture and Performance
State Management:
- Are API calls properly cached to avoid unnecessary network requests?
- Is loading/error state handled consistently?
- Are optimistic updates used where appropriate?
Component Structure:
- Is the component too large? Should it be split?
- Are concerns properly separated (presentation vs logic)?
- Is the component reusable or overly coupled?
Real Example from MonieWorld:
// ❌ Poor: Fetching on every render
function RecipientList() {
const [recipients, setRecipients] = useState([]);
useEffect(() => {
fetch('/api/recipients')
.then((r) => r.json())
.then(setRecipients);
}, []); // Still refetches on remount
}
// ✅ Better: Use React Query for caching
function RecipientList() {
const { data: recipients, isLoading } = useQuery({
queryKey: ['recipients'],
queryFn: fetchRecipients,
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
});
}
Nice-to-Have: Style and Conventions
Let automation handle these:
- Code formatting (Prettier)
- Linting rules (ESLint)
- Type coverage (TypeScript)
The Review Process That Works
1. Automated Checks Run First
Never review code manually until CI passes:
- Unit tests (Jest, React Testing Library)
- E2E tests (Playwright)
- Type checking (TypeScript)
- Linting (ESLint)
- Coverage thresholds
From MonieWorld CI Pipeline:
- 85%+ test coverage required
- Zero TypeScript errors
- All Playwright E2E tests passing
- SonarQube quality gate passed
2. Author Self-Reviews
Before requesting review, I always:
- Read my own diff top to bottom
- Remove console.logs and debugging code
- Verify all test cases pass locally
- Add screenshots for UI changes
- Write meaningful PR description
PR Template That Works:
## Problem
Users couldn't see their card details without multiple authentication prompts
## Solution
Implemented 2FA with auto-purging after 60 seconds
## Testing
- Unit tests: Card component, security logic
- E2E tests: Full card viewing flow with 2FA
- Manual testing: Tested on 3 card types
## Screenshots
[Before] [After]
3. Reviewer Checklist
When reviewing, I systematically check:
Correctness:
- Does the code do what the PR says it does?
- Are edge cases handled? (null, undefined, empty arrays, errors)
- Are loading and error states managed?
Security:
- Is user input validated and sanitized?
- Are authentication checks in place?
- Is sensitive data (passwords, tokens, card numbers) properly handled?
Testing:
- Do tests actually test the right things?
- Are happy paths and error cases both covered?
- Are tests readable and maintainable?
Maintainability:
- Will this be understandable in 6 months?
- Are variable/function names descriptive?
- Is there unnecessary complexity?
4. Giving Effective Feedback
Use Categories:
- 🔴 Blocking: Must be fixed (security, bugs, breaking changes)
- 🟡 Important: Should be addressed (performance, maintainability)
- 🟢 Suggestion: Nice to have (optimizations, preferences)
Be Specific:
❌ "This won't scale"
✅ "This fetches all recipients on every search. Consider implementing
pagination or debounced search to reduce API load."
❌ "Bad naming"
✅ "Consider renaming `handleClick` to `handlePaymentSubmit` for clarity
about what action is being performed."
Explain the Why:
// Not just what, but why
"Let's use React Query here instead of useState + useEffect because:
1. Automatic caching reduces API calls
2. Built-in loading/error states
3. Automatic refetching keeps data fresh
4. Better TypeScript support"
Common Pitfalls (And How to Avoid Them)
Perfectionism Paralysis
The Problem: Requesting changes to make code “perfect” when it’s already good enough.
The Solution: Ask yourself: “Does this change materially improve the code’s correctness, security, or maintainability?” If not, it’s probably a suggestion, not a blocker.
Rubber Stamping
The Problem: Approving without actually reading the code thoroughly.
The Solution: If you don’t have time for a proper review, say so. It’s better to delay a PR than to approve something you haven’t properly reviewed—especially in financial systems.
Scope Creep in Reviews
The Problem: “While you’re here, can you also refactor this unrelated thing?”
The Solution: File a separate ticket. Keep PRs focused on their stated purpose.
Tools That Actually Help
From production experience:
- SonarQube: Catches code smells, security vulnerabilities, and technical debt
- GitHub Actions / Jenkins: Automated CI/CD prevents broken code from merging
- Playwright: E2E tests catch integration issues that unit tests miss
- React Testing Library: Ensures components work from user perspective
- TypeScript: Catches type errors before runtime
Real-World Impact
At MonieWorld, implementing rigorous code review practices contributed to:
- 99.6% transaction success rate in production
- 90% reduction in production bugs compared to pre-review baseline
- Zero financial discrepancies since launch
- Zero security vulnerabilities in card management systems
In Web3 at Unlockd, code reviews caught:
- Smart contract interaction bugs that would have lost user funds
- Wallet connection race conditions causing failed transactions
- Gas estimation errors leading to stuck transactions
Conclusion
Code reviews are not bureaucracy—they’re essential quality control, especially when handling money, sensitive data, or blockchain transactions. The best code review is one that:
- Catches real issues before production
- Teaches both reviewer and author something new
- Maintains quality without blocking progress
- Creates a culture where excellence is the norm
Remember: every PR merged is code you’re putting your name on. Review accordingly.