1. Vibe Coding and Technical Debt
Vibe Coding speeds up writing code, but if not careful will create technical debt faster than ever.
Data from GitClear (2025):
| Metric | Before AI (2022) | Next AI (2025) | Change |
|---|---|---|---|
| Code churn | 3.3% | 7.1% | +115% |
| Lines added/dev/month | 1,100 | 1,800 | +64% |
| Lines deleted/dev/month | 200 | 550 | +175% |
| Moved/copied code | 5% | 11% | +120% |
Code churn = write down the code and then edit or delete it within 2 weeks. The doubling figure shows AI created incorrect code from the beginning.
2. Cause of Technical Debt from Vibe Coding
2.1. Duplication — Duplicate code
AI generates new code instead of reusing:
// AI tạo helper function mới function formatDate(date: Date): string { return date.toLocaleDateString('vi-VN'); }
// Mặc dù project đã có: // src/utils/date.ts → formatDate()
2.2. Inconsistency — Inconsistency
// File A: AI dùng async/await const data = await fetchUsers();// File B: AI dùng .then() fetchUsers().then(data => { ... });
// File C: AI dùng callback fetchUsers((err, data) => { ... });
2.3. Over-abstraction — Complicating
// AI hay tạo Factory + Strategy + Builder cho task đơn giản: class TaskBuilderFactory { createBuilder(type: string): TaskBuilder { // 50 lines of over-engineering } }
// Thực tế chỉ cần: function createTask(data: CreateTaskInput): Task { return prisma.task.create({ data }); }
3. Detect Technical Debt
3.1. Dead code detection
// Dùng knip để tìm dead code: npx knip
// Output: Unused files: src/utils/old-helper.ts Unused exports: TaskBuilderFactory (src/builders/task.ts) Unused dependencies: moment, lodash
3.2. Duplication detection
// Dùng jscpd: npx jscpd src/
// Output: Found 12 clones across 8 files Total duplicated lines: 156 (8.3% of total)
3.3. Complexity analysis
// Dùng Copilot để phân tích:
@workspace Analyze cyclomatic complexity of all functions.
List functions with complexity > 10 and suggest refactoring.
4. Refactoring with AI
4.1. Extract duplicates
// Prompt:
I have similar date formatting logic in 5 files.
Extract into a shared utility module.
Show me which files to update and what to extract.
4.2. Simplify complex functions
// Prompt:
This function has 15 if-else branches.
Refactor using:
- Strategy pattern for different task types
- Early returns to reduce nesting
- Extract validation into separate function
Keep the same behavior, add tests to verify.
4.3. Standardize patterns
// Prompt: Our codebase has inconsistent error handling: - Some files use try/catch - Some use .catch() - Some don't handle errors at all
Standardize all API calls to use async/await with a shared error handler middleware. Show me the changes needed file by file.
5. Documentation Generation with AI
// Prompt cho API documentation:
Generate OpenAPI 3.0 spec for all endpoints in src/routes/.
Include request/response schemas, auth requirements,
and example values.
// Prompt cho code documentation:
Add JSDoc comments to all exported functions in src/services/.
Include parameter descriptions, return types, and usage examples.
Explain the business logic, not just the code.
Architecture Decision Records (ADR)
// Prompt:
Create an ADR for our decision to use Prisma ORM with PostgreSQL.
Include:
- Context: why we needed an ORM
- Options considered: Prisma, TypeORM, Knex, Drizzle
- Decision and rationale
- Consequences and trade-offs
Follow the format in docs/adr/template.md
6. Prevent Technical Debt
6.1. Custom instructions for consistency
## Code Consistency Rules
- Use async/await for all asynchronous operations
- Import from @/utils for shared utilities
- Follow barrel export pattern (index.ts)
- Error handling: use AppError class with status codes
- Date formatting: use dayjs, never native Date methods
- API responses: use { data, error, meta } format
Check existing utilities before creating new ones
6.2. Architecture guardrails
// eslint-plugin-boundaries — enforce architecture:
{
"rules": {
"boundaries/element-types": [2, {
"default": "disallow",
"rules": [
// Controllers can import services, not other controllers
{ "from": "controllers", "allow": ["services", "types"] },
// Services can import repositories, not controllers
{ "from": "services", "allow": ["repositories", "types"] },
]
}]
}
}
6.3. Regular debt sprints
// Dùng AI để plan refactoring sprint:
@workspace Analyze the codebase and identify:
1. Top 5 files with highest complexity
2. Most duplicated code patterns
3. Unused dependencies and dead exports
4. Files that violate our architecture boundaries
Prioritize by impact and create a refactoring plan.
7. Metrics track Technical Debt
| Metric | Tools | Target |
|---|---|---|
| Code churn | GitClear, git log analysis | <5% in 2 weeks |
| Duplication | jscpd, SonarQube | <3% total codebase |
| Dead code | knip, ts-prune | 0 unused exports |
| Complexity | ESLint complexity rule | Max 15 per function |
| Dependency freshness | npm outdated, Renovate | No major behind |
| Test coverage | Jest coverage | >80% overall |
8. Summary
Golden rule: Vibe Coding is good when you can reading comprehension and maintenance every line of AI code generated. If you don't understand the code AI writes, that's technical debt.
| Strategy | Tools |
|---|---|
| Prevention | Custom instructions, architecture rules |
| Detection | knip, jscpd, SonarQube, AI review |
| Resolution | AI-assisted refactoring, debt sprints |
| Monitoring | Metrics dashboard, CI/CD gates |
Last post: Vibe Coding in Team & Production — enterprise adoption, CI/CD, collaboration, and the future of Vibe Coding.