📕 Phần 4 · Nâng Cao · Chương 9

Best Practices & Testing

Nâng tầm code từ "chạy được" lên "production-ready" — với code quality tools, unit testing, documentation đúng chuẩn và Git workflow chuyên nghiệp.

4 giờ học
📝 4 bài học
Test coverage thực tế
📊 Mức độ: Nâng cao

🎯 Mục tiêu học tập

  • Setup ESLint + Prettier để code nhất quán và không lỗi
  • Viết unit tests với Jest (JS) và pytest (Python)
  • Viết README chuyên nghiệp và JSDoc documentation
  • Áp dụng Git workflow với Conventional Commits và branching strategy
1

Bài 9.1 — Code Quality: ESLint + Prettier

ESLint
Static analysis tool bắt lỗi và anti-patterns trong code JavaScript/TypeScript không cần chạy. Đặc biệt quan trọng với AI-generated code.
Prettier
Opinionated code formatter — thiết lập style (indent, quotes, dongs...) một lần, áp dụng toàn team tự động. Không còn tranh cãi code style.
Pre-commit Hook
Script chạy tự động trước mỗi git commit. Dug Husky để chạy ESLint+Prettier. Nếu fail, commit bị từ chối — bảo vệ có hiệu quả.
Test Coverage
Phần trăm các dòng code được chạy bởi tests. Coverage 80%+ là mục tiêu thực tế cho business logic. Không cần 100%.
TDD (Test-Driven Development)
Viết test trước, code sau: Red (test fail) → Green (code pass) → Refactor. Tests là specification của behavior, không chỉ là safety net.
Mocking
Thay thế dependency thật bằng version giả trong tests. Mock database, API calls, file system — giúp tests chạy nhanh và deterministic.

Tại sao cần Linter và Formatter?

ESLint — Phát hiện lỗi

Tìm bugs tiềm năng, unused variables, anti-patterns trước khi runtime. Đặc biệt quan trọng với AI-generated code.

Prettier — Format code

Tự động format code: indent, quotes, trailing commas... Không cần tranh luận style, mọi người code như nhau.

Pre-commit Hooks

Tự động chạy ESLint + Prettier trước mỗi commit với Husky. Code xấu sẽ không được commit.

Setup ESLint + Prettier cho dự án Node.js/React

bash
npm install -D eslint prettier eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks

# Init ESLint config
npx eslint --init
javascript — .eslintrc.cjs
module.exports = {
  env: {
    browser: true,
    es2022:  true,
    node:    true,
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'prettier', // Tắt các rules conflict với Prettier — phải để cuối
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType:  'module',
    ecmaFeatures: { jsx: true },
  },
  rules: {
    'no-unused-vars':    ['warn', { argsIgnorePattern: '^_' }],
    'no-console':        ['warn', { allow: ['warn', 'error'] }],
    'react/prop-types':  'warn',
    'react/react-in-jsx-scope': 'off', // React 17+ không cần import React
  },
  settings: {
    react: { version: 'detect' },
  },
};
javascript — .prettierrc
{
  "semi":          true,
  "singleQuote":   true,
  "tabWidth":      2,
  "trailingComma": "es5",
  "printWidth":    100,
  "arrowParens":   "avoid"
}

Setup Husky — Pre-commit Hooks

bash
npm install -D husky lint-staged
npx husky install

# Add pre-commit hook
npx husky add .husky/pre-commit "npx lint-staged"
javascript — package.json (thêm phần lint-staged)
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md,css}": [
      "prettier --write"
    ]
  }
}
💡
AI + ESLint = Code review tự động

Khi AI generate code và ESLint báo lỗi, paste lỗi vào Copilot Chat: "ESLint báo lỗi này: [error message]. Fix cho tôi và giải thích tại sao?" — Cách học tốt nhất là sửa lỗi thực tế.


2

Bài 9.2 — Unit Testing

Quy Trình TDD — Red, Green, Refactor

🔴
RED — Viết test thất bại trước

Viết test mô tả behavior mong muốn TRƯỚC KHI có code. Test phải fail vì chưa implement. Đây là lúc define "done" rõ ràng nhất.

🟢
GREEN — Viết code tối thiểu để pass

Viết CODE ÍT NHẤT để test pass. Không cần đẹp, không cần tối ưu. Mục tiêu duy nhất: test xanh.

🔵
REFACTOR — Cải thiện code

Refactor code sạch hơn, loại bỏ duplication, tối ưu. Tests vẫn phải pass sau khi refactor. Đây là "safety net".

Jest / Vitest Matchers — Tham Khảo Nhanh

MatcherDùng choVí dụ
toBe(val)So sánh bằng (===)expect(2+2).toBe(4)
toEqual(obj)So sánh deep equality (object/array)expect(user).toEqual({id: 1, name: 'An'})
toBeNull()Kiểm tra nullexpect(result).toBeNull()
toBeTruthy() / toBeFalsy()Truthy / Falsy checkexpect(token).toBeTruthy()
toContain(item)Array/string chứa itemexpect(list).toContain('react')
toThrow(msg?)Function ném lỗiexpect(() => fn()).toThrow('Invalid')
toHaveBeenCalled()Mock function đã được gọiexpect(mockFn).toHaveBeenCalledTimes(1)
toMatchObject(obj)Object chứa subsetexpect(response).toMatchObject({status: 200})
resolves / rejectsPromise resolved/rejectedawait expect(fn()).resolves.toBe('ok')

JavaScript: Testing với Jest + Vitest

bash
# Cho Vite/React project — dùng Vitest (nhanh hơn Jest, tích hợp Vite)
npm install -D vitest @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom

# Cho Node.js — dùng Jest
npm install -D jest @jest/globals supertest
javascript — utils/formatPrice.test.js
import { describe, it, expect } from 'vitest';
import { formatPrice, formatDate } from './formatters';

describe('formatPrice', () => {
  it('formats number to VND currency', () => {
    expect(formatPrice(100000)).toBe('100.000 ₫');
    expect(formatPrice(1500000)).toBe('1.500.000 ₫');
  });

  it('returns "0 ₫" for 0', () => {
    expect(formatPrice(0)).toBe('0 ₫');
  });

  it('handles negative numbers', () => {
    expect(formatPrice(-5000)).toBe('-5.000 ₫');
  });
});

describe('formatDate', () => {
  it('formats ISO date to dd/MM/yyyy', () => {
    expect(formatDate('2024-01-15')).toBe('15/01/2024');
  });
});
javascript — API Integration Test với Supertest
const request = require('supertest');
const app = require('../src/app');
const { PrismaClient } = require('@prisma/client');

const prisma = new PrismaClient();
let authToken;

beforeAll(async () => {
  // Tạo user test và lấy token
  const res = await request(app)
    .post('/api/auth/register')
    .send({ email: 'test@test.com', username: 'testuser', password: 'Password123' });
  
  authToken = res.body.token;
});

afterAll(async () => {
  // Dọn sạch test data
  await prisma.user.deleteMany({ where: { email: 'test@test.com' } });
  await prisma.$disconnect();
});

describe('POST /api/todos', () => {
  it('creates a todo when authenticated', async () => {
    const res = await request(app)
      .post('/api/todos')
      .set('Authorization', `Bearer ${authToken}`)
      .send({ title: 'Test todo', priority: 'high' });

    expect(res.status).toBe(201);
    expect(res.body).toMatchObject({
      title:     'Test todo',
      priority:  'high',
      completed: false,
    });
  });

  it('returns 401 without auth token', async () => {
    const res = await request(app).post('/api/todos').send({ title: 'Test' });
    expect(res.status).toBe(401);
  });

  it('returns 400 for empty title', async () => {
    const res = await request(app)
      .post('/api/todos')
      .set('Authorization', `Bearer ${authToken}`)
      .send({ title: '' });

    expect(res.status).toBe(400);
  });
});

Python: Testing với pytest

python — tests/test_organizer.py
import pytest
from pathlib import Path
from organizer import categorize_file, organize_directory

# Dùng tmp_path fixture của pytest (tự tạo và xóa folder tạm)
def test_categorize_image(tmp_path):
    file = tmp_path / "photo.jpg"
    assert categorize_file(file) == "Images"

def test_categorize_document(tmp_path):
    file = tmp_path / "report.pdf"
    assert categorize_file(file) == "Documents"

def test_categorize_unknown(tmp_path):
    file = tmp_path / "data.xyz"
    assert categorize_file(file) == "Others"

def test_organize_moves_files(tmp_path):
    # Tạo files test
    (tmp_path / "photo.jpg").touch()
    (tmp_path / "doc.pdf").touch()
    (tmp_path / "music.mp3").touch()
    
    organize_directory(tmp_path)
    
    # Kiểm tra files đã di chuyển đúng chỗ
    assert (tmp_path / "Images"    / "photo.jpg").exists()
    assert (tmp_path / "Documents" / "doc.pdf").exists()
    assert (tmp_path / "Audio"     / "music.mp3").exists()

def test_organize_handles_empty_dir(tmp_path):
    # Không nên raise exception với folder rỗng
    organize_directory(tmp_path)
    assert True  # Reached without exception

Prompt AI để generate tests

text — Prompt mẫu
Đây là function tôi cần test:
[paste function code]

Viết unit tests với Jest/Vitest cho function này:
- Happy path (input hợp lệ)
- Edge cases (empty, null, boundary values)
- Error cases (invalid input)
Đặt mỗi test case trong describe/it rõ ràng

3

Bài 9.3 — Documentation Chuyên Nghiệp

README chuẩn dự án

markdown — README.md template
# Tên Dự Án

> Một dòng mô tả ngắn gọn và hấp dẫn về dự án.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org)

## ✨ Tính năng

- ✅ Authentication (JWT)
- ✅ CRUD operations
- ✅ File upload
- 🚧 Email notifications (coming soon)

## 🚀 Bắt đầu nhanh

### Yêu cầu hệ thống
- Node.js 18+
- npm 9+

### Cài đặt

```bash
git clone https://github.com/your-username/project-name.git
cd project-name
npm install
cp .env.example .env   # Điền thông tin cấu hình
npm run dev
```

Truy cập: http://localhost:3000

## ⚙️ Cấu hình (.env)

| Biến                | Mô tả                     | Mặc định   |
|---------------------|---------------------------|------------|
| `PORT`              | Port server                | `3000`     |
| `DATABASE_URL`      | Chuỗi kết nối database     | Bắt buộc   |
| `JWT_SECRET`        | Secret key cho JWT         | Bắt buộc   |
| `ALLOWED_ORIGIN`    | CORS allowed origin        | `http://localhost:5173` |

## 📡 API Endpoints

| Method | Endpoint           | Mô tả              | Auth |
|--------|--------------------|--------------------|------|
| POST   | `/api/auth/register` | Đăng ký tài khoản | ❌   |
| POST   | `/api/auth/login`    | Đăng nhập          | ❌   |
| GET    | `/api/todos`         | Lấy danh sách todo | ✅   |
| POST   | `/api/todos`         | Tạo todo mới       | ✅   |

## 🧪 Chạy Tests

```bash
npm test              # Chạy tất cả tests
npm run test:watch    # Watch mode
npm run test:coverage # Xem test coverage
```

## 🤝 Đóng góp

Pull requests được chào đón. Xem [CONTRIBUTING.md](CONTRIBUTING.md) để biết thêm.

## 📝 License

MIT © [Tên của bạn]

4

Bài 9.4 — Git Workflow Chuyên Nghiệp

Conventional Commits

Conventional Commits là quy ước đặt tên commit message theo format chuẩn. AI rất giỏi generate commit messages chuẩn.

text — Format
type(scope): description

[optional body]

[optional footer]

------- CÁC TYPES -------
feat:     Thêm tính năng mới
fix:      Sửa bug
docs:     Thay đổi documentation
style:    Format code (không đổi logic)
refactor: Refactor code (không feat, không fix)
test:     Thêm hoặc sửa tests
chore:    Cập nhật build scripts, dependencies

------- VÍ DỤ -------
feat(auth): add JWT refresh token endpoint
fix(api): return 404 when post not found
docs(readme): add API endpoints table
test(todos): add edge cases for empty title
chore(deps): upgrade prisma to v5.8

Branching Strategy (GitHub Flow)

bash
# Main branch = production-ready, luôn deployable
# Mỗi feature/fix = 1 branch riêng

# 1. Tạo feature branch từ main
git checkout main
git pull origin main
git checkout -b feat/add-search-filter

# 2. Code, commit thường xuyên
git add -p                         # Chọn từng hunk để add
git commit -m "feat(posts): add search by title"

# 3. Push và tạo Pull Request
git push -u origin feat/add-search-filter
# → Tạo PR trên GitHub → Code review → Merge

# 4. Sau khi merge, xóa branch
git checkout main
git pull origin main
git branch -d feat/add-search-filter

Dùng AI để viết commit messages

bash
# Xem diff trước khi commit
git diff --staged

# Nhờ Copilot CLI generate commit message
# (Copilot sẽ đọc diff và suggest message chuẩn Conventional Commits)
git commit   # Copilot sẽ suggest message trong terminal
💡
GitHub Copilot CLI

Cài gh copilot extension: gh extension install github/gh-copilot. Sau đó dùng gh copilot suggest để nhờ AI suggest git commands, hoặc gh copilot explain để AI giải thích một lệnh phức tạp.


5

Bài 9.5 — Setup Testing Hoàn Chỉnh: Unit Test + Integration Test + CI/CD

Chúng ta sẽ setup hệ thống testing đầy đủ cho cả backend (Vitest + Supertest) và frontend (React Testing Library), sau đó tích hợp vào CI/CD pipeline với GitHub Actions.

Bước 1 — Setup Testing Cho Backend (Express API)

bash — Cài testing dependencies cho backend
cd backend

# Cài Vitest (nhanh hơn Jest, tương thích Jest API)
npm install --save-dev vitest supertest @vitest/coverage-v8

# Thêm script vào package.json
# "test": "vitest run"
# "test:watch": "vitest"
# "test:coverage": "vitest run --coverage"

# Tạo thư mục test
mkdir tests tests/unit tests/integration
touch tests/unit/utils.test.js tests/integration/auth.test.js tests/integration/notes.test.js
javascript — tests/unit/utils.test.js
// tests/unit/utils.test.js — Unit test cho helper functions
// Prompt Copilot: "Viết unit tests cho các functions trong utils.js"
import { describe, it, expect } from 'vitest';

// Ví dụ: test helper function validateEmail
function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function validatePassword(password) {
  return password.length >= 8;
}

describe('validateEmail', () => {
  it('trả về true với email hợp lệ', () => {
    expect(validateEmail('user@example.com')).toBe(true);
    expect(validateEmail('user+tag@domain.co')).toBe(true);
  });

  it('trả về false với email không hợp lệ', () => {
    expect(validateEmail('notanemail')).toBe(false);
    expect(validateEmail('@domain.com')).toBe(false);
    expect(validateEmail('')).toBe(false);
  });
});

describe('validatePassword', () => {
  it('chấp nhận password đủ 8 ký tự', () => {
    expect(validatePassword('12345678')).toBe(true);
    expect(validatePassword('LongPassword123')).toBe(true);
  });

  it('từ chối password ngắn hơn 8 ký tự', () => {
    expect(validatePassword('short')).toBe(false);
    expect(validatePassword('')).toBe(false);
  });
});
javascript — tests/integration/auth.test.js
// tests/integration/auth.test.js — Integration test cho auth endpoints
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import app from '../../src/app.js'; // Export app riêng (không gọi app.listen)
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

// Cleanup database trước và sau test suite
beforeAll(async () => {
  await prisma.user.deleteMany({ where: { email: { contains: '@test.com' } } });
});

afterAll(async () => {
  await prisma.user.deleteMany({ where: { email: { contains: '@test.com' } } });
  await prisma.$disconnect();
});

describe('POST /api/auth/register', () => {
  it('đăng ký thành công với thông tin hợp lệ', async () => {
    const res = await request(app)
      .post('/api/auth/register')
      .send({ email: 'newuser@test.com', password: 'Password123', name: 'Test User' });

    expect(res.status).toBe(201);
    expect(res.body).toHaveProperty('token');
    expect(res.body.user.email).toBe('newuser@test.com');
    expect(res.body.user).not.toHaveProperty('password'); // Không trả về password
  });

  it('trả về 409 khi email đã tồn tại', async () => {
    // Đăng ký lần 2 với cùng email
    const res = await request(app)
      .post('/api/auth/register')
      .send({ email: 'newuser@test.com', password: 'Password123' });

    expect(res.status).toBe(409);
    expect(res.body.error).toMatch(/đã được đăng ký/);
  });
});

describe('POST /api/auth/login', () => {
  it('đăng nhập thành công với thông tin đúng', async () => {
    const res = await request(app)
      .post('/api/auth/login')
      .send({ email: 'newuser@test.com', password: 'Password123' });

    expect(res.status).toBe(200);
    expect(res.body).toHaveProperty('token');
  });

  it('trả về 401 với mật khẩu sai', async () => {
    const res = await request(app)
      .post('/api/auth/login')
      .send({ email: 'newuser@test.com', password: 'WrongPassword' });

    expect(res.status).toBe(401);
  });
});
bash — Chạy tests
# Chạy tất cả tests một lần
npm test

# Chạy tests ở chế độ watch (tự reload khi save file)
npm run test:watch

# Chạy tests với coverage report
npm run test:coverage

# Chạy chỉ một file test cụ thể
npx vitest run tests/integration/auth.test.js

# Chạy tests match pattern tên
npx vitest run --reporter=verbose

Bước 2 — Setup ESLint + Prettier

bash — Cài và cấu hình ESLint
# Cài ESLint (chạy trong thư mục gốc project)
npm install --save-dev eslint @eslint/js

# Khởi tạo config ESLint
npx eslint --init
# Trả lời các câu hỏi:
# - To check syntax and find problems
# - CommonJS (hoặc ESM tùy project)
# - None of these (nếu không dùng framework)
# - No (TypeScript)
# - Node.js
# - JSON format

# Cài Prettier
npm install --save-dev prettier eslint-config-prettier

# Tạo .prettierrc
echo '{"semi":true,"singleQuote":true,"tabWidth":2,"printWidth":100}' > .prettierrc

# Tạo .eslintignore
echo 'node_modules\ndist\ncoverage' > .eslintignore
bash — Cài Husky (pre-commit hooks)
# Cài Husky và lint-staged
npm install --save-dev husky lint-staged

# Khởi tạo Husky
npx husky init

# Tạo pre-commit hook
echo 'npx lint-staged' > .husky/pre-commit

# Thêm lint-staged config vào package.json:
# "lint-staged": {
#   "*.js": ["eslint --fix", "prettier --write"],
#   "*.{json,md}": ["prettier --write"]
# }

# Test pre-commit hook
git add .
git commit -m "test: add testing setup"
# Hook sẽ tự động chạy ESLint + Prettier trước khi commit

Bước 3 — Setup GitHub Actions CI/CD

bash — Tạo file CI workflow
mkdir -p .github/workflows
touch .github/workflows/ci.yml
yaml — .github/workflows/ci.yml
name: CI — Lint, Test, Build

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test-backend:
    name: Backend Tests
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: backend/package-lock.json

      - name: Install backend dependencies
        working-directory: ./backend
        run: npm ci

      - name: Run ESLint
        working-directory: ./backend
        run: npm run lint

      - name: Run tests
        working-directory: ./backend
        env:
          DATABASE_URL: "file:./test.db"
          JWT_SECRET: "ci-test-secret-key-32-characters-long"
          NODE_ENV: "test"
        run: npm test

  test-frontend:
    name: Frontend Build Check
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: frontend/package-lock.json

      - name: Install frontend dependencies
        working-directory: ./frontend
        run: npm ci

      - name: Build frontend
        working-directory: ./frontend
        run: npm run build
CI/CD Pipeline Hoạt Động!

Mỗi khi bạn push code lên GitHub, workflow sẽ tự động chạy: lint → test → build. Pull Request sẽ bị block nếu có test fail. Đây là quy trình chuyên nghiệp mà mọi team làm việc.

💡 Mẹo từ ThanhDoIT
  • Test names nên đọc như câu chuyện: "given X, when Y, then Z". Ví dụ: "given valid email, when user registers, then returns 201 with token".
  • Đừng mock quá nhiều trong tests — mock chỉ external services (APIs, email). Test với real database (SQLite in-memory) cho integration tests.
  • Commit message quan trọng hơn bạn nghĩ. Sau 6 tháng, git log là cách duy nhất để hiểu tại sao code được viết như vậy.
  • Husky + lint-staged = code review tự động. Mỗi commit đều pass linter — team không cần nói "nhớ chạy eslint" nữa.

6

Bài 9.6 — AI-Assisted Code Review & Refactoring

Code review là kỹ năng quan trọng nhất để trở thành developer giỏi. AI có thể review code của bạn như một senior developer — bắt bugs, suggest improvements, và giải thích tại sao. Đây là bài học về cách dùng AI để nâng cao chất lượng code liên tục.

🔍
Code Review Dimensions
  • Correctness: logic có đúng không?
  • Security: có vulnerabilities không?
  • Performance: bottlenecks, N+1 queries
  • Maintainability: dễ đọc, dễ thay đổi
♻️
Refactoring Patterns
  • Extract function / Extract class
  • Replace magic numbers with constants
  • Eliminate duplication (DRY)
  • Simplify conditionals

Prompt Template: Code Review Toàn Diện

Hãy review code này theo vai trò Senior Developer với 10 năm kinh nghiệm.
Phân tích theo các dimension sau:

**1. CORRECTNESS**
- Logic có đúng không? Có edge cases nào bị bỏ qua?
- Error handling có đầy đủ không?
- Race conditions hay async issues?

**2. SECURITY** (OWASP Top 10)
- Input validation đủ chưa?
- Data exposure risks?
- Authentication/Authorization checks?

**3. PERFORMANCE**
- Có N+1 query problem không? (database)
- Unnecessary re-renders? (React)
- Memory leaks tiềm ẩn?
- Có thể cache gì?

**4. MAINTAINABILITY**
- Function/class có làm quá nhiều việc? (Single Responsibility)
- Có magic numbers/strings cần extract thành constants?
- Naming: variable names có rõ ràng không?
- Duplication: có code nào cần refactor DRY?

**5. TESTS**
- Cần viết thêm tests cho trường hợp nào?
- Test coverage hiện tại có đủ không?

Code cần review:
[paste code vào đây]

Format output: từng issue với severity (critical/major/minor) + code fix cụ thể.
Framework review 5 chiều này giúp Copilot cung cấp feedback có cấu trúc thay vì góp ý lan man.

Refactoring Thực Tế: Before & After

typescript — BEFORE: Code xấu cần refactor
// ❌ BEFORE: Nhiều vấn đề
async function processOrder(o: any) {
  if (o.status == 'pending') {
    const db = await getDb();
    const u = await db.query(`SELECT * FROM users WHERE id = ${o.user_id}`); // SQL injection!
    if (u.rows.length > 0) {
      if (u.rows[0].balance >= o.amount) {
        await db.query(`UPDATE users SET balance = balance - ${o.amount} WHERE id = ${o.user_id}`);
        await db.query(`UPDATE orders SET status = 'completed' WHERE id = ${o.id}`);
        // Gửi email nhưng không handle error
        sendEmail(u.rows[0].email, 'Order completed');
        return true;
      } else {
        return false;
      }
    }
  }
  return false;
}
typescript — AFTER: Refactored với AI
// ✅ AFTER: Clean, safe, maintainable
interface Order { id: number; userId: number; amount: number; status: OrderStatus; }
type OrderStatus = 'pending' | 'completed' | 'failed' | 'cancelled';

class InsufficientBalanceError extends Error {
  constructor() { super('Insufficient balance'); this.name = 'InsufficientBalanceError'; }
}

async function processOrder(order: Order): Promise<void> {
  if (order.status !== 'pending') {
    throw new Error(`Cannot process order with status: ${order.status}`);
  }

  const user = await prisma.user.findUniqueOrThrow({ where: { id: order.userId } });

  if (user.balance < order.amount) {
    throw new InsufficientBalanceError();
  }

  // Transaction: cả hai update xảy ra cùng lúc hoặc không cái nào
  await prisma.$transaction([
    prisma.user.update({
      where: { id: order.userId },
      data: { balance: { decrement: order.amount } }
    }),
    prisma.order.update({
      where: { id: order.id },
      data: { status: 'completed' }
    })
  ]);

  // Không block order process nếu email fail
  sendOrderConfirmationEmail(user.email, order).catch(err => {
    console.error('Failed to send confirmation email:', err);
    // Log to monitoring service but don't throw
  });
}
Refactor đoạn code này cho tôi theo các nguyên tắc:
- Single Responsibility: mỗi function chỉ làm 1 việc
- DRY: loại bỏ code trùng lặp
- Early return: reduce nesting levels
- TypeScript: thêm types đầy đủ, không dùng any
- Error handling: sử dụng custom errors thay vì return boolean
- Security: fix SQL injection nếu có
- Database: wrap multiple writes trong transaction

[paste code vào đây]

Sau khi refactor, giải thích từng thay đổi và tại sao nó tốt hơn.
Liệt kê cụ thể các nguyên tắc refactoring → Copilot apply đúng theo checklist, không bỏ sót.

Code Metrics — Đo Lường Chất Lượng Code

MetricTargetTool
Test Coverage> 70% cho utils, > 50% tổng thểJest --coverage
Cyclomatic Complexity< 10 per functionESLint complexity rule
Function Length< 30 lines (guideline)ESLint max-lines-per-function
Bundle Size (Frontend)< 200KB initial JSvite-bundle-analyzer
Lighthouse Score> 90 PerformanceChrome DevTools
TypeScript Strict0 type errorstsc --noEmit
Dependency AgeNo critical CVEsnpm audit
💡 Workflow Code Review Hàng Ngày
  • Trước commit: Highlight code mới viết → Copilot /review → fix issues
  • Khi viết PR: Paste diff vào Copilot Chat → "Review PR này, tìm bugs và suggest improvements"
  • Học từ AI: Sau khi nhận feedback, hỏi thêm "Tại sao approach này tốt hơn? Nguyên tắc design pattern nào?"
  • Weekly: Chạy npm audit + update dependencies — hỏi Copilot về breaking changes
🎯 Thực Hành: Self Code Review Session
  1. Lấy file code phức tạp nhất trong project của bạn (>50 lines)
  2. Mở Copilot Chat, paste code + prompt review 5 chiều ở trên
  3. Với từng issue Copilot tìm thấy: hiểu tại sao, rồi fix
  4. Refactor 1 function dùng prompt template refactoring
  5. Chạy npm test -- --coverage → xem coverage report, viết thêm tests cho functions coverage thấp
  6. So sánh code trước và sau refactoring — cảm nhận sự khác biệt
🎯 Bài Tập Tổng Kết Chương 9 — Quality Audit
  1. Setup ESLint + Prettier + Husky cho project của bạn — từ giờ mỗi commit đều qua linter
  2. Viết unit tests cho 5 functions quan trọng nhất — target 70% coverage
  3. Dùng prompt review 5 chiều để review lại toàn bộ auth module
  4. Refactor 2 functions phức tạp nhất theo nguyên tắc Single Responsibility
  5. Viết README hoàn chỉnh với badges (CI status, coverage, license)
  6. Thử thách: Setup SonarCloud (free cho open source) — scan toàn bộ codebase và fix tất cả issues Critical/Major
⚠️ 5 Cạm Bẫy Phổ Biến Trong Testing & Code Quality
  • Test chỉ happy path: 90% bugs xảy ra ở edge cases — empty input, null, concurrent requests. Copilot prompt: “Generate edge case tests cho function này”
  • Mock quá nhiều: Unit test mock mọi thứ → test pass nhưng production fail. Kết hợp integration tests với real dependencies để đảm bảo end-to-end.
  • Test coverage ≠ code quality: 100% coverage với tests trivial vẫn là code xấu. Focus vào test meaningful behaviors, không chase số %.
  • Bỏ qua ESLint warnings: Warnings hôm nay = bugs ngày mai. Treat warnings as errors trong CI: thêm "error": true trong ESLint config.
  • Refactor không có tests trước: Không bao giờ refactor code quan trọng mà chưa có test bao phủ. Viết tests trước → refactor → tests vẫn xanh = safe.
✅ Dấu Hiệu Code Đạt Chất Lượng Production
  • npm run lint → 0 errors, 0 warnings (hoặc chỉ intentional suppressions)
  • npm test -- --coverage → ≥70% statement coverage cho utils, ≥50% tổng thể
  • Mọi function public có JSDoc/docstring, mọi API endpoint có mô tả rõ ràng
  • Git log có commit messages theo Conventional Commits (feat:, fix:, docs:...)
  • Code review 5 chiều với Copilot: 0 issues Critical, ≤3 issues Major
  • README đầy đủ — người lạ có thể clone repo và chạy app trong 5 phút

🗒 Tóm Tắt Chương 9

  • ESLint + Prettier + Husky = "safety net" — setup 1 lần, chạy mãi mãi
  • Unit test: test 1 function riêng lẻ, fast, no side effects; Integration: nhiều layers, real DB
  • AI review code theo framework 5 chiều: Correctness, Security, Performance, Maintainability, Tests
  • Refactoring không phải viết lại — là làm code sạch hơn mà không thay đổi behavior
  • Code metrics: coverage >70%, complexity <10, bundle <200KB — đo và cải thiện liên tục
  • Conventional Commits + GitHub Flow: workflow chuyên nghiệp cho solo hoặc team
Zalo: 0898 619 966 Z Gọi: 0898 619 966