Chuyển đến nội dung chính

Lesson 19: Technical Debt & Maintainability

Manage technical debt when using Vibe Coding. GitClear data about code churn. Refactoring strategies. Dead code detection. Documentation generation. Architectural decisions. Long-term maintainability.

💻 Programming — Lesson 19 Lesson 19: Technical Debt & Maintainability

Vibe Coding with GitHub Copilot: From Basics to Advanced

Part 6: Professional Vibe Coding — Quality, Security & Production

xdev.asia

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.