Code quality metrics help measure and improve software maintainability. This comprehensive guide covers essential metrics and tools.
Why Code Quality Matters
flowchart TD
A[High Quality Code] --> B[Easy to maintain]
A --> C[Fewer bugs]
A --> D[Faster development]
A --> E[Better team collaboration]
F[Low Quality Code] --> G[Technical debt]
F --> H[Frequent bugs]
F --> I[Slow development]
F --> J[Frustrated team]
Complexity Metrics
Cyclomatic Complexity
// Simple function - complexity 1
function isActive(user) {
return user.status === 'active';
}
// More complex - complexity 4
function canEdit(user, resource) {
if (user.isAdmin) { // 1
return true;
} else if (user.isOwner(resource)) { // 2
return true;
} else if (user.hasPermission('edit')) { // 3
return resource.status === 'published';
}
return false;
}
// Even worse - consider refactoring
function processOrder(order) {
if (order.status === 'pending') { // 1
if (order.payment) { // 2
if (order.payment.status === 'paid') { // 3
return processPayment(order);
} else if (order.payment.status === 'failed') { // 4
return notifyFailure(order);
}
} else if (order.type === 'subscription') { // 5
return processSubscription(order);
}
} else if (order.status === 'completed') { // 6
return ship(order);
}
return null;
}
Cognitive Complexity
// Low cognitive complexity
function getStatus(user) {
return user.isActive ? 'Active' : 'Inactive';
}
// High cognitive complexity - hard to understand
function calculateTotal(order) {
let total = 0;
if (order.items) { // Nesting
for (const item of order.items) { // Loop + nesting
if (item.price) { // Nesting
if (item.quantity > 10) { // Deep nesting
total += item.price * item.quantity * 0.9;
} else {
total += item.price * item.quantity;
}
}
}
}
if (order.discount) { // More nesting
total -= total * order.discount;
}
return total; // Hard to trace
}
Lines of Code
// Bad: Long function
function processUserData(user) { // 50 lines
// validation
// transformation
// storage
// notification
// logging
}
// Good: Short, focused functions
function processUserData(user) {
validateUser(user);
transformUser(user);
saveUser(user);
notifyUser(user);
logUserAction(user);
}
Maintainability Index
SonarQube Metrics
# sonarqube.yml analysis
sonar.projectKey=my-project
sonar.sources=src
sonar.tests=test
sonar.coverage.jacoco.xmlReportPath=target/jacoco.xml
sonar.issuesReport.html.enable=true
Quality Gates
# Quality gate thresholds
quality_gate:
bugs:
- critical: 0
- major: 0
security:
- vulnerabilities: 0
maintainability:
- rating: A
- technical_debt: < 5%
coverage:
- line_coverage: > 80%
Linting
ESLint Configuration
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: { jsx: true },
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['react', '@typescript-eslint'],
rules: {
'no-console': 'warn',
'no-unused-vars': 'error',
'prefer-const': 'error',
'max-len': ['error', { code: 100 }],
'complexity': ['error', 10]
}
};
Pre-commit Hooks
# .husky/pre-commit
npm run lint
npm run test
npm run typecheck
# .husky/pre-commit
- name: Lint
command: npm run lint
- name: Test
command: npm test
- name: Check types
command: npm run typecheck
Static Analysis
TypeScript
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true
}
}
Snyk (Security)
# .snyk
version: 1
exclude:
- '*.spec.js'
- 'test/**'
policy:
critical:
- '*'
# Run snyk
snyk test
snyk monitor
Code Coverage
Jest Coverage
// jest.config.js
module.exports = {
collectCoverage: true,
coverageThreshold: {
'global': {
branches: 70,
functions: 70,
lines: 70,
statements: 70
},
'./src/utils/': {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},
coverageReporters: ['text', 'lcov', 'html'],
coverageDirectory: 'coverage'
};
Coverage Reports
# Generate coverage
npm test -- --coverage
# View in CI
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
flags: unittests
Code Review Metrics
What to Look For
## Code Review Checklist
### Readability
- [ ] Variable names are descriptive
- [ ] Functions are small and focused
- [ ] No magic numbers
- [ ] Complex logic is documented
### Maintainability
- [ ] DRY - no duplication
- [ ] Functions follow single responsibility
- [ ] Dependencies are clear
- [ ] Error handling is consistent
### Performance
- [ ] No unnecessary loops
- [ ] Proper data structures used
- [ ] Database queries are optimized
### Security
- [ ] Input validation
- [ ] No sensitive data exposed
- [ ] Proper authentication
- [ ] SQL injection prevention
Review Metrics
# Track review metrics
review_metrics:
avg_review_time: 2.5 hours
review_comments_per_pr: 8
approval_time_p80: 4 hours
re_revision_rate: 15%
Technical Debt
Tracking Debt
// TODO comments with context
// TODO(chore): Refactor to use Result pattern - 2024-01
// TODO(performance): Add caching layer - 2 days
// TODO(security): Implement rate limiting - 4 hours
Debt Ratio
# SonarQube
technical_debt:
ratio: 5%
effort_to_remediation: 2 days
# Acceptable thresholds
- Maintainability rating: A
- Technical debt ratio: < 5%
- Debt remediation effort: < 1 sprint
Automated Quality Gates
GitHub Actions Gate
name: Quality Gate
on:
pull_request:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint
run: npm run lint
- name: Type Check
run: npm run typecheck
- name: Test with Coverage
run: npm test -- --coverage
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Check Coverage
run: |
COVERAGE=$(npm run test:coverage -- --json | jq '.total.lines.pct')
if [ "$COVERAGE" -lt 80 ]; then
echo "Coverage $COVERAGE% is below 80%"
exit 1
fi
Best Practices
Do
// DO: Keep functions small
function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// DO: Use clear naming
function calculateOrderTotalWithDiscount(orderItems, discountCode) { }
// DO: Write tests
describe('calculateTotal', () => {
it('should sum all item prices', () => {
expect(calculateTotal([{price: 10}, {price: 20}])).toBe(30);
});
});
Don’t
// DON'T: Use magic numbers
if (status === 2) { } // What is 2?
// DON'T: Create God objects
class UserManagerOrderManagerPaymentManager { }
// DON'T: Ignore warnings
// linter: disable-next-line - Why disabled?
Tools Comparison
| Tool | Purpose | Type |
|---|---|---|
| ESLint | JavaScript linting | Static |
| Prettier | Code formatting | Static |
| TypeScript | Type checking | Static |
| SonarQube | Quality analysis | Static + Dynamic |
| Snyk | Security scanning | Static |
| Coveralls | Coverage tracking | Dynamic |
External Resources
Conclusion
Code quality metrics help teams:
- Identify problems early
- Maintain consistent standards
- Reduce technical debt
- Improve maintainability
Key metrics to track:
- Complexity (cyclomatic, cognitive)
- Code coverage
- Security vulnerabilities
- Maintainability index
Automate quality gates to catch issues before they reach production.
Comments