จากโพสต์ที่แล้ว เราเรียน GitHub Actions ที่ช่วย automate CI/CD — แต่ pipeline ดีแค่ไหน ถ้า code ข้างในห่วยก็ไม่ช่วย!
วันนี้มาเรียนรู้ Code Quality — ทำยังไงให้ code อ่านง่าย, maintain ได้, bug น้อย และทีมทำงานร่วมกันได้ดี 🔍🐕
🤔 ทำไม Code Quality สำคัญ?
ลองนึกภาพว่าคุณต้องกลับมาแก้ code ที่เขียนเมื่อ 6 เดือนก่อน — ถ้า code อ่านไม่รู้เรื่อง ไม่มี test ไม่มี documentation คุณจะเสียเวลาทำความเข้าใจมากกว่าเวลาแก้จริง! Code Quality ไม่ใช่ "เรื่องสวยงาม" แต่คือ ต้นทุนจริง — code คุณภาพต่ำ = bug เยอะ + แก้ช้า + deploy กลัว + developer ใหม่สับสน
| Code ดี 😊 | Code แย่ 😰 |
|---|---|
| อ่านแล้วเข้าใจใน 5 นาที | อ่าน 1 ชั่วโมงยังงง |
| แก้ bug ง่าย → แก้จุดเดียว | แก้ 1 จุด พัง 10 จุด |
| คนใหม่ onboard ได้เร็ว | คนเดิมลาออก = ตาย |
| Deploy มั่นใจ | Deploy แล้วสวดมนต์ |
| Technical debt ต่ำ | Technical debt สะสมจนล้มละลาย |
💡 "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler
🧹 Linting & Formatting — บังคับให้เขียนสวย
Linting คือการใช้เครื่องมือตรวจสอบ code อัตโนมัติเพื่อหา bugs, bad practices, และ style issues ส่วน Formatting คือการจัด code ให้สวยเหมือนกันทั้งทีม (indentation, quotes, semicolons) — ไม่ต้องเถียงกันอีกว่า tab หรือ space! เมื่อ setup แล้ว ทุกคนในทีมจะเขียน code style เดียวกันอัตโนมัติ
ESLint (JavaScript/TypeScript)
ESLint เป็น linter ยอดนิยมที่สุดสำหรับ JavaScript/TypeScript — สามารถ customize rules ได้ตามความต้องการของทีม ตั้งแต่ห้ามใช้ any type ไปจนถึงบังคับ naming conventions ตัวอย่างด้านล่างแสดง config ที่แนะนำ:
# ติดตั้ง
npm install --save-dev eslint @eslint/js typescript-eslint
# eslint.config.js
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
'no-console': 'warn', // console.log = warning
'no-unused-vars': 'error', // ตัวแปรไม่ใช้ = error
'no-var': 'error', // ห้ามใช้ var
'prefer-const': 'error', // ใช้ const ถ้าไม่ reassign
'eqeqeq': 'error', // ห้าม == ต้อง ===
'@typescript-eslint/no-explicit-any': 'warn',
}
}
];
# รัน
npx eslint . # ตรวจ
npx eslint . --fix # ตรวจ + แก้อัตโนมัติ
Prettier (Auto-format)
Prettier ทำงานร่วมกับ ESLint — ESLint ดูแล "ถูก-ผิด" ส่วน Prettier ดูแล "สวย-ไม่สวย" แค่ตั้งค่าครั้งเดียว Prettier จะ format code ให้สวยทุกครั้งที่ save — ไม่ต้องเสียเวลาจัด whitespace เอง:
# ติดตั้ง
npm install --save-dev prettier
# .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80
}
# รัน
npx prettier --write . # format ทุกไฟล์
npx prettier --check . # ตรวจ (ใน CI)
Python — Ruff (เร็วที่สุด!)
Ruff เป็น Python linter + formatter ตัวใหม่ที่เขียนด้วย Rust — เร็วกว่า flake8 + isort + black รวมกัน 10-100 เท่า! แทนที่จะต้องตั้งค่า 3 tools แยกกัน Ruff ทำได้ทุกอย่างในตัวเดียว:
# ติดตั้ง
pip install ruff
# pyproject.toml
[tool.ruff]
line-length = 88
select = ["E", "F", "W", "I", "N", "UP"]
ignore = ["E501"]
[tool.ruff.format]
quote-style = "double"
# รัน
ruff check . # lint
ruff check . --fix # lint + fix
ruff format . # format (แทน black)
Pre-commit Hooks — บังคับก่อน commit
ปัญหาของ linting/formatting คือ developer อาจลืมรัน! Pre-commit hooks แก้ปัญหานี้โดยบังคับให้ lint + format อัตโนมัติก่อนทุก commit — ถ้า code ไม่ผ่าน commit จะ fail ไม่ต้องพึ่ง "วินัย" อีกต่อไป เพราะระบบบังคับให้:
# ติดตั้ง
npm install --save-dev husky lint-staged
npx husky init
# .husky/pre-commit
npx lint-staged
# package.json
{
"lint-staged": {
"*.{js,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml}": [
"prettier --write"
]
}
}
# ตอนนี้ทุกครั้งที่ git commit → auto lint + format!
# ❌ code ไม่ผ่าน lint = commit ไม่ได้
🔍 Code Review — ศิลปะการ review code
เครื่องมืออัตโนมัติจับ bugs ได้ แต่ไม่สามารถบอกได้ว่า "approach นี้ถูกต้องไหม?" หรือ "มี design ที่ดีกว่านี้ไหม?" นั่นคือหน้าที่ของ Code Review — กระบวนการที่ developer คนอื่นอ่านและให้ feedback กับ code ของเรา ไม่ใช่แค่หา bugs แต่เป็นการแชร์ความรู้ สร้างมาตรฐาน และป้องกันปัญหาที่เครื่องมือมองไม่เห็น
Code Review Flow
กระบวนการ Code Review ที่ดีควรมี flow ที่ชัดเจน — ตั้งแต่ developer เปิด PR ไปจนถึงการ merge เข้า main branch สิ่งสำคัญคือต้อง review ภายใน 24 ชั่วโมง เพื่อไม่ให้ block workflow ของทีม:
Reviewer — ดูอะไรบ้าง?
Reviewer ที่ดีไม่ใช่แค่ดู syntax — แต่ต้องดูทั้ง correctness, design, readability, performance, security และ test coverage หลักการสำคัญคือ "Could a new team member understand this code?" ถ้าตอบว่าไม่ แสดงว่า code ต้องปรับปรุง:
| หมวด | ดูอะไร | ตัวอย่าง |
|---|---|---|
| 🐛 Correctness | ทำงานถูกมั้ย | Edge cases, off-by-one, null checks |
| 📖 Readability | อ่านรู้เรื่องมั้ย | ชื่อตัวแปร, comments, structure |
| 🏗️ Design | ออกแบบดีมั้ย | SOLID, DRY, separation of concerns |
| ⚡ Performance | เร็วพอมั้ย | N+1 queries, unnecessary loops |
| 🔒 Security | ปลอดภัยมั้ย | SQL injection, XSS, hardcoded secrets |
| 🧪 Tests | มี test ครอบคลุมมั้ย | Happy path + edge cases + error cases |
ตัวอย่าง Review Comment ที่ดี
Review comment ที่ดีต้อง อธิบาย "ทำไม" ไม่ใช่แค่บอกว่า "แก้ตรงนี้" — เพื่อให้ developer เรียนรู้และไม่ทำผิดซ้ำ ควรแนะนำวิธีแก้ พร้อม code ตัวอย่าง และใช้ tone ที่ constructive ไม่ใช่ criticize:
// ✅ Review comment ที่ดี: // "This could throw if `user` is null. // Consider adding a null check: // `if (!user) return res.status(404).json(...)`"
// ✅ อีกตัวอย่าง: // "Nit: prefer `const` here since `items` // is never reassigned."
🐕 Review Culture: Review code ไม่ใช่ review คน — "code นี้น่าจะ..." ไม่ใช่ "คุณเขียนแย่..." | ใช้ "Nit:" สำหรับ nitpick เล็กๆ | ชมเมื่อเห็น code ดี 👍
🧠 SOLID Principles — หลักการ code ที่ดี
SOLID คือ 5 หลักการออกแบบ software ที่ช่วยให้ code flexible, maintainable, และ extensible คิดค้นโดย Robert C. Martin (Uncle Bob) แม้ชื่อจะดูน่ากลัว แต่แก่นแท้คือ — แยกส่วนให้ชัดเจน, แต่ละส่วนทำหน้าที่เดียว, เปลี่ยนแปลงได้ง่าย มาดูแต่ละตัว:
ตัวอย่าง: Single Responsibility
Single Responsibility Principle (SRP) บอกว่า "แต่ละ class/function ควรมีเหตุผลในการเปลี่ยนแปลงแค่อันเดียว" ถ้า function ทำทั้ง validate, save, send email → แก้ email logic อาจพัง save logic! ตัวอย่างด้านล่างเปรียบเทียบ code ที่ละเมิด SRP กับ code ที่ทำตาม:
// ✅ ดี: แยก responsibility ชัดเจน + class OrderService { + constructor(validator, payment, notifier, inventory) { + this.validator = validator; + this.payment = payment; + this.notifier = notifier; + this.inventory = inventory; + } + async process(order) { + this.validator.validate(order); + await this.payment.charge(order); + await this.inventory.update(order); + await this.notifier.send(order); + } + }
🚨 Code Smells — กลิ่นของ code ที่ต้องแก้
Code Smells คือ "สัญญาณ" ว่า code อาจมีปัญหา — ไม่ใช่ bugs โดยตรง แต่เป็น patterns ที่บ่งบอกว่า design ไม่ดี ถ้าปล่อยไว้จะกลายเป็น technical debt ที่แก้ยากขึ้นเรื่อยๆ เหมือน "กลิ่น" — ไม่ได้ทำร้ายทันที แต่บอกว่ามีอะไรเน่าอยู่!
| Code Smell | ปัญหา | วิธีแก้ |
|---|---|---|
| 🔢 Magic Numbers | if (status === 3) | ใช้ constant: ORDER_STATUS.COMPLETED |
| 📏 Long Function | Function 200+ บรรทัด | แยกเป็น functions เล็กๆ |
| 📦 God Class | Class ทำทุกอย่าง | แยกตาม responsibility (SRP) |
| 🔄 Duplicated Code | Copy-paste เหมือนกัน | Extract เป็น shared function |
| 🏗️ Deep Nesting | if ซ้อน 5+ ชั้น | Early return, guard clauses |
| 📝 Dead Code | Code ที่ไม่ถูกเรียกใช้ | ลบทิ้ง (Git เก็บ history ให้) |
| 🔗 Feature Envy | ใช้ data ของ class อื่นเยอะ | ย้าย method ไป class นั้น |
| 💬 Bad Naming | let x, tmp, data2 | ชื่อบอกความหมาย |
ตัวอย่าง: Deep Nesting → Early Return
หนึ่งใน code smells ที่พบบ่อยที่สุดคือ Deep Nesting — if ซ้อน if ซ้อน if จนอ่านไม่รู้เรื่อง วิธีแก้คือ Early Return Pattern — เช็ค error cases ก่อนแล้ว return ออกเลย ทำให้ "happy path" อยู่ด้านนอกสุดและอ่านง่าย:
// ✅ ดี: Early return (flat) + function getDiscount(user) { + if (!user) return 0; + if (!user.isPremium) return 0; + if (user.orders > 10) return 0.2; + return 0.1; + }
📊 Static Analysis — ให้เครื่องตรวจ
นอกจาก linter แล้ว ยังมี Static Analysis tools ที่วิเคราะห์ code เชิงลึกกว่า — หา code smells อัตโนมัติ, วัด complexity, ตรวจจับ security vulnerabilities, และ track technical debt ตลอดเวลา เครื่องมือเหล่านี้เป็นเหมือน "ตาที่สาม" ที่ช่วย reviewer
SonarQube / SonarCloud
SonarQube (self-hosted) หรือ SonarCloud (managed) เป็น platform วิเคราะห์ code ที่ครบเครื่องที่สุด — ใช้กับ GitHub Actions ได้ สแกนทุก PR และแสดงผลลัพธ์เป็น dashboard พร้อม metrics ต่างๆ:
# GitHub Actions + SonarCloud
name: Quality Gate
on: [push, pull_request]
jobs:
sonarcloud:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for blame
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.projectKey=myorg_myapp
-Dsonar.organization=myorg
-Dsonar.sources=src
-Dsonar.tests=tests
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
SonarQube วัดอะไรบ้าง?
SonarQube วัด code quality ใน 5 มิติหลัก — Bugs, Vulnerabilities, Code Smells, Coverage, และ Duplications แต่ละมิติมีเกรด (A-E) ที่ช่วยให้เห็นภาพรวมได้เร็ว:
| Metric | วัดอะไร | เป้าหมาย |
|---|---|---|
| 🐛 Bugs | Code ที่น่าจะผิด | 0 (A rating) |
| 🔓 Vulnerabilities | Security issues | 0 (A rating) |
| 💩 Code Smells | Maintainability issues | น้อยที่สุด |
| 📋 Coverage | Test coverage % | > 80% |
| 🔄 Duplications | Duplicated code % | < 3% |
| 🏗️ Complexity | Cyclomatic complexity | < 15 per function |
| 📏 Debt | Technical debt เวลาแก้ | Trend ลดลง |
Quality Gate — ผ่านหรือไม่ผ่าน
Quality Gate คือ "ด่าน" ที่กำหนดว่า code ต้องผ่านเกณฑ์ขั้นต่ำก่อน merge ได้ — เช่น coverage ต้อง > 80%, ห้ามมี Critical bugs, duplications < 3% ถ้าไม่ผ่าน PR จะ fail อัตโนมัติ:
# Quality Gate Conditions (SonarQube default)
# ──────────────────────────────────────────
# ✅ PASS ถ้า:
# - New code coverage > 80%
# - New code duplications < 3%
# - New bugs = 0 (A rating)
# - New vulnerabilities = 0 (A rating)
# - New code smells ≤ A rating
# - New security hotspots reviewed 100%
#
# ❌ FAIL → PR ไม่สามารถ merge ได้!
# → บังคับคุณภาพอัตโนมัติ ไม่ต้องพึ่ง reviewer
🛠️ Tools รวม — ครบชุด
สรุปเครื่องมือทั้งหมดที่ใช้ในการรักษา Code Quality — แบ่งตาม category เพื่อให้เลือกใช้ได้ง่าย ไม่จำเป็นต้องใช้ทุกตัว เริ่มจาก linter + formatter ก่อน แล้วค่อยเพิ่มตามความพร้อม:
| หมวด | JavaScript/TS | Python | General |
|---|---|---|---|
| Linter | ESLint, Biome | Ruff, Pylint, Flake8 | — |
| Formatter | Prettier, Biome | Ruff format, Black | — |
| Type Check | TypeScript | mypy, Pyright | — |
| Static Analysis | SonarCloud | SonarCloud, Bandit | SonarQube |
| Complexity | ESLint rules | radon, xenon | CodeClimate |
| Dead Code | ts-prune, knip | vulture | — |
| Pre-commit | Husky + lint-staged | pre-commit | — |
| AI Review | GitHub Copilot, CodeRabbit, Sourcery | ||
📋 CI/CD Quality Pipeline
ถึงเวลารวมทุกเครื่องมือเข้าด้วยกันใน GitHub Actions! Pipeline ด้านล่างแสดง quality checks ที่ควรรันทุก PR เรียงจากเร็วไปช้า — format check (วินาที) → lint (วินาที) → type check (นาที) → tests + coverage (นาที) → SonarCloud (นาที):
# .github/workflows/quality.yml
name: Code Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
# Step 1: Format check
- name: Check formatting
run: npx prettier --check .
# Step 2: Lint
- name: Lint
run: npx eslint .
# Step 3: Type check
- name: Type check
run: npx tsc --noEmit
# Step 4: Tests + coverage
- name: Tests
run: npm test -- --coverage
# Step 5: Coverage threshold
- name: Check coverage threshold
run: |
COVERAGE=$(jq '.total.lines.pct' coverage/coverage-summary.json)
echo "Coverage: $COVERAGE%"
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "❌ Coverage below 80%!"
exit 1
fi
# Step 6: SonarCloud
- uses: SonarSource/sonarcloud-github-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Pipeline ข้างต้นจะแสดงการเรียงลำดับขั้นตอนการตรวจสอบจากเร็วไปช้า และจากพื้นฐานไปซับซ้อน การเริ่มด้วย formatting check จะให้ feedback เร็วที่สุดหาก code ไม่เป็นไปตามมาตรฐาน ตามด้วย linting ที่จะตรวจสอบ syntax และ basic rules
Type checking จะช่วยจับข้อผิดพลาดเรื่อง type safety ซึ่งสำคัญมากใน TypeScript การรัน tests พร้อมกับ coverage จะยืนยันว่า functionality ยังทำงานถูกต้อง และมี test coverage ที่เพียงพอ ขั้นตอนสุดท้ายคือ SonarCloud ที่จะทำการวิเคราะห์เชิงลึกและรวบรวม metrics ทั้งหมด
📝 Clean Code Checklist
หลังจากที่เราได้เรียนรู้เครื่องมือและกระบวนการต่างๆ มาแล้ว สิ่งสำคัญคือการนำหลักการและแนวปฏิบัติเหล่านี้มาใช้ในการเขียน code ในชีวิตประจำวัน Clean Code Checklist ต่อไปนี้จะเป็นแนวทางที่นักพัฒนาสามารถใช้เป็น guideline ในการเขียนและ review code
Checklist นี้เป็นการรวบรวมหลักการสำคัญที่ทำให้ code มีคุณภาพ โดยแต่ละข้อจะเน้นในด้านต่างๆ ตั้งแต่การตั้งชื่อ, การออกแบบ function, ไปจนถึงกระบวนการ development ที่ดี การปฏิบัติตาม checklist นี้จะช่วยยกระดับคุณภาพ code อย่างมีระบบ:
- Naming: ชื่อตัวแปร/function บอกความหมาย —
getUserByIdไม่ใช่getData - Functions: สั้น ทำอย่างเดียว ไม่เกิน 20 บรรทัด
- DRY: Don't Repeat Yourself — extract shared logic
- KISS: Keep It Simple, Stupid — อย่า over-engineer
- Comments: อธิบาย "ทำไม" ไม่ใช่ "อะไร" — code บอก what, comment บอก why
- Error Handling: handle errors explicitly ไม่ใช่ catch แล้วเงียบ
- No Magic: ใช้ constants แทน magic numbers/strings
- Consistent: ทั้ง codebase ใช้ style เดียวกัน (enforce ด้วย linter)
- Tests: ทุก function สำคัญต้องมี test
- Small PRs: PR เล็กๆ review ง่าย — ไม่เกิน 400 lines changed
Clean Code Checklist ข้างต้นครอบคลุมหลักการสำคัญทุกด้านของการเขียน code ที่มีคุณภาพ ตั้งแต่ technical aspects อย่าง naming conventions และ error handling ไปจนถึง process aspects อย่าง code review และการจัดการ PR ขนาดเล็ก การปฏิบัติตามทุกข้ออาจดูเยอะ แต่เมื่อกลายเป็นนิสัยแล้วจะทำได้โดยอัตโนมัติ
💡 กฎ Boy Scout: "Leave the code better than you found it" — เจอ code smell ก็แก้ทีละนิด ไม่ต้องรอ refactor ใหญ่
กฎ Boy Scout เป็นหลักการง่ายๆ แต่มีพลังมาก การแก้ไข code smell เล็กๆ น้อยๆ ทีละนิดจะช่วยป้องกันไม่ให้ technical debt สะสมจนกลายเป็นปัญหาใหญ่ แนวทางนี้ยั่งยืนกว่าการรอให้มีปัญหาใหญ่แล้วค่อย refactor ทั้งหมด และยังช่วยให้ทีมมีความรู้สึก ownership กับ codebase มากขึ้น
สรุป
Code Quality เป็นเรื่องที่ต้องทำอย่างต่อเนื่องและเป็นระบบ ไม่ใช่เรื่องที่ทำครั้งเดียวแล้วเสร็จ การใช้เครื่องมือต่างๆ ที่กล่าวมาในบทความนี้จะช่วยให้การสร้างและรักษาคุณภาพ code เป็นไปอย่างอัตโนมัติและมีประสิทธิภาพมากขึ้น
สิ่งสำคัญที่สุดคือการสร้าง culture ที่ให้ความสำคัญกับคุณภาพ code ในทีม เครื่องมือต่างๆ เป็นเพียงตัวช่วย แต่ใจสำคัญอยู่ที่ mindset และวัฒนธรรมของทีมที่ต้องการเขียน code ที่ดี ช่วยเหลือกัน และเรียนรู้จากกัน นี่คือจุดเริ่มต้นของ sustainable software development:
- Linting + Formatting = ESLint + Prettier (JS) / Ruff (Python) บังคับ style ด้วยเครื่อง
- Pre-commit hooks = Husky + lint-staged → commit ไม่ได้ถ้า code ไม่ผ่าน
- Code Review = ดู correctness, readability, design, security, tests
- SOLID Principles = 5 หลักการออกแบบ OOP ที่ทำให้ code maintain ง่าย
- Code Smells = magic numbers, deep nesting, god class → fix ด้วย early return, extract function
- Static Analysis = SonarQube/SonarCloud วัด bugs, vulnerabilities, coverage, debt
- Quality Gates = PR merge ไม่ได้ถ้าไม่ผ่าน quality threshold
- Clean Code = good naming, small functions, DRY, KISS, Boy Scout Rule 🔍🐕
การนำแนวปฏิบัติเหล่านี้มาใช้ไม่จำเป็นต้องทำทุกอย่างพร้อมกัน สามารถเริ่มจากเครื่องมือพื้นฐานอย่าง linter และ formatter ก่อน แล้วค่อยๆ เพิ่ม static analysis, code review process และ quality gates ตามความพร้อมของทีม สิ่งสำคัญคือความสม่ำเสมอและการปรับปรุงอย่างต่อเนื่อง เพราะ code quality ไม่ใช่จุดหมาย แต่เป็นการเดินทางที่ไม่มีวันสิ้นสุด 🔍🐕