Code Quality Code Review DevOps

Code Quality & Review — เขียน Code ดีๆ ที่ทีมอยากอ่าน 🔍Code Quality & Review — Writing Code Your Team Wants to Read 🔍

By Anirach Mingkhwan DevOps & Vibe Coding 2026
Code Quality — เขียน Code ดีๆ ที่ทีมอยากอ่าน
ในบทความนี้
  1. 🤔 ทำไม Code Quality สำคัญ?
  2. 🧹 Linting & Formatting — บังคับให้เขียนสวย
  3. 🔍 Code Review — ศิลปะการ review code
  4. 🧠 SOLID Principles — หลักการ code ที่ดี
  5. 🚨 Code Smells — กลิ่นของ code ที่ต้องแก้
  6. 📊 Static Analysis — ให้เครื่องตรวจ
  7. 🛠️ Tools รวม — ครบชุด
  8. 📋 CI/CD Quality Pipeline
  9. 📝 Clean Code Checklist
  10. สรุป
In this post
  1. 🤔 Why Does Code Quality Matter?
  2. 🧹 Linting & Formatting — Making Tidy Code Mandatory
  3. 🔍 Code Review — The Art of Reviewing Code
  4. 🧠 SOLID Principles — The Rules of Good Code
  5. 🚨 Code Smells — When Code Starts to Stink
  6. 📊 Static Analysis — Let the Machine Inspect It
  7. 🛠️ The Full Toolkit
  8. 📋 CI/CD Quality Pipeline
  9. 📝 Clean Code Checklist
  10. Summary

จากโพสต์ที่แล้ว เราเรียน 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 ของทีม:

📝 PR สร้าง
Dev เปิด Pull Request
→
🤖 CI ตรวจ
Lint, Test, Build
→
👀 Human Review
Logic, Design, Style
→
✅ Approve
Merge to main

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 is wrong" → ไม่บอกว่าผิดยังไง // "Fix this" → ไม่บอกว่าแก้ยังไง
// ✅ 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) แม้ชื่อจะดูน่ากลัว แต่แก่นแท้คือ — แยกส่วนให้ชัดเจน, แต่ละส่วนทำหน้าที่เดียว, เปลี่ยนแปลงได้ง่าย มาดูแต่ละตัว:

S
Single Responsibility
Class/function ทำแค่อย่างเดียว
O
Open/Closed
เปิดให้ extend ปิดไม่ให้แก้
L
Liskov Substitution
Subclass แทน parent ได้
I
Interface Segregation
แยก interface เล็กๆ ดีกว่า 1 อันใหญ่
D
Dependency Inversion
พึ่ง abstraction ไม่พึ่ง implementation

ตัวอย่าง: Single Responsibility

Single Responsibility Principle (SRP) บอกว่า "แต่ละ class/function ควรมีเหตุผลในการเปลี่ยนแปลงแค่อันเดียว" ถ้า function ทำทั้ง validate, save, send email → แก้ email logic อาจพัง save logic! ตัวอย่างด้านล่างเปรียบเทียบ code ที่ละเมิด SRP กับ code ที่ทำตาม:

// ❌ แย่: function ทำหลายอย่าง - function handleOrder(order) { - validateOrder(order); // validate - calculateTotal(order); // calculate - chargePayment(order); // payment - sendEmail(order); // notification - updateInventory(order); // inventory - logAnalytics(order); // analytics - }
// ✅ ดี: แยก 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 Numbersif (status === 3)ใช้ constant: ORDER_STATUS.COMPLETED
📏 Long FunctionFunction 200+ บรรทัดแยกเป็น functions เล็กๆ
📦 God ClassClass ทำทุกอย่างแยกตาม responsibility (SRP)
🔄 Duplicated CodeCopy-paste เหมือนกันExtract เป็น shared function
🏗️ Deep Nestingif ซ้อน 5+ ชั้นEarly return, guard clauses
📝 Dead CodeCode ที่ไม่ถูกเรียกใช้ลบทิ้ง (Git เก็บ history ให้)
🔗 Feature Envyใช้ data ของ class อื่นเยอะย้าย method ไป class นั้น
💬 Bad Naminglet x, tmp, data2ชื่อบอกความหมาย

ตัวอย่าง: Deep Nesting → Early Return

หนึ่งใน code smells ที่พบบ่อยที่สุดคือ Deep Nesting — if ซ้อน if ซ้อน if จนอ่านไม่รู้เรื่อง วิธีแก้คือ Early Return Pattern — เช็ค error cases ก่อนแล้ว return ออกเลย ทำให้ "happy path" อยู่ด้านนอกสุดและอ่านง่าย:

// ❌ แย่: Deep nesting - function getDiscount(user) { - if (user) { - if (user.isPremium) { - if (user.orders > 10) { - return 0.2; - } else { - return 0.1; - } - } else { - return 0; - } - } else { - return 0; - } - }
// ✅ ดี: 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วัดอะไรเป้าหมาย
🐛 BugsCode ที่น่าจะผิด0 (A rating)
🔓 VulnerabilitiesSecurity issues0 (A rating)
💩 Code SmellsMaintainability issuesน้อยที่สุด
📋 CoverageTest coverage %> 80%
🔄 DuplicationsDuplicated code %< 3%
🏗️ ComplexityCyclomatic complexity< 15 per function
📏 DebtTechnical 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/TSPythonGeneral
LinterESLint, BiomeRuff, Pylint, Flake8—
FormatterPrettier, BiomeRuff format, Black—
Type CheckTypeScriptmypy, Pyright—
Static AnalysisSonarCloudSonarCloud, BanditSonarQube
ComplexityESLint rulesradon, xenonCodeClimate
Dead Codets-prune, knipvulture—
Pre-commitHusky + lint-stagedpre-commit—
AI ReviewGitHub 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 อย่างมีระบบ:

  1. Naming: ชื่อตัวแปร/function บอกความหมาย — getUserById ไม่ใช่ getData
  2. Functions: สั้น ทำอย่างเดียว ไม่เกิน 20 บรรทัด
  3. DRY: Don't Repeat Yourself — extract shared logic
  4. KISS: Keep It Simple, Stupid — อย่า over-engineer
  5. Comments: อธิบาย "ทำไม" ไม่ใช่ "อะไร" — code บอก what, comment บอก why
  6. Error Handling: handle errors explicitly ไม่ใช่ catch แล้วเงียบ
  7. No Magic: ใช้ constants แทน magic numbers/strings
  8. Consistent: ทั้ง codebase ใช้ style เดียวกัน (enforce ด้วย linter)
  9. Tests: ทุก function สำคัญต้องมี test
  10. 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 ไม่ใช่จุดหมาย แต่เป็นการเดินทางที่ไม่มีวันสิ้นสุด 🔍🐕

In the previous post we looked at GitHub Actions and how it automates CI/CD — but however good the pipeline is, it cannot save bad code inside it!

Today we look at Code Quality — how to make code readable, maintainable, low on bugs, and pleasant for a team to work on together 🔍🐕


🤔 Why Does Code Quality Matter?

Picture yourself coming back to fix code you wrote six months ago — if it is unreadable, has no tests and no documentation, you will spend more time understanding it than actually fixing it! Code quality is not a matter of "prettiness"; it is a real cost — low-quality code means more bugs, slower fixes, deploys you dread, and new developers left confused.

Good code 😊Bad code 😰
Understood in five minutesAn hour of reading and still lost
Easy bug fixes → one place to changeFix one thing, break ten others
New people onboard quicklyThe one who knows it leaves = disaster
Deploy with confidenceDeploy and pray
Low technical debtTechnical debt piles up until you are bankrupt
💡 "Any fool can write code that a computer can understand. Good programmers write code that humans can understand." — Martin Fowler

🧹 Linting & Formatting — Making Tidy Code Mandatory

Linting means using tools that inspect code automatically to find bugs, bad practices and style issues, while Formatting means laying code out identically across the whole team (indentation, quotes, semicolons) — no more arguing about tabs versus spaces! Once it is set up, everyone on the team writes in the same style automatically.

ESLint (JavaScript/TypeScript)

ESLint is the most popular linter for JavaScript and TypeScript — its rules can be customized to whatever the team needs, from banning the any type to enforcing naming conventions. The example below shows a config worth starting from:

# install
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',         // unused variable = error
      'no-var': 'error',                 // never use var
      'prefer-const': 'error',           // use const when never reassigned
      'eqeqeq': 'error',               // no ==, always ===
      '@typescript-eslint/no-explicit-any': 'warn',
    }
  }
];

# run
npx eslint .                            # check
npx eslint . --fix                      # check + fix automatically

Prettier (Auto-format)

Prettier works alongside ESLint — ESLint looks after "right or wrong", Prettier looks after "tidy or untidy". Configure it once and Prettier formats your code on every save — no more hand-arranging whitespace:

# install
npm install --save-dev prettier

# .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 80
}

# run
npx prettier --write .                  # format every file
npx prettier --check .                  # check (in CI)

Python — Ruff (The Fastest!)

Ruff is a new Python linter and formatter written in Rust — 10 to 100 times faster than flake8 + isort + black combined! Instead of configuring three separate tools, Ruff does all of it in one:

# install
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"

# run
ruff check .                            # lint
ruff check . --fix                      # lint + fix
ruff format .                           # format (replaces black)

Pre-commit Hooks — Enforced Before the Commit

The trouble with linting and formatting is that a developer can forget to run them! Pre-commit hooks solve that by running lint and format automatically before every commit — if the code does not pass, the commit fails. No more relying on "discipline", because the system enforces it:

# install
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"
    ]
  }
}

# now every git commit → auto lint + format!
# ❌ code that fails lint = no commit

🔍 Code Review — The Art of Reviewing Code

Automated tools catch bugs, but they cannot tell you "is this approach correct?" or "is there a better design here?". That is the job of Code Review — the process where another developer reads your code and gives feedback. It is not only about finding bugs; it spreads knowledge, sets standards, and catches the problems no tool can see.

Code Review Flow

A good code review process needs a clear flow — from the developer opening a PR all the way to the merge into the main branch. The important part is reviewing within 24 hours, so the team's workflow is never blocked:

📝 PR opened
Dev opens a Pull Request
→
🤖 CI checks
Lint, Test, Build
→
👀 Human Review
Logic, Design, Style
→
✅ Approve
Merge to main

What Should a Reviewer Look At?

A good reviewer does not just read syntax — they look at correctness, design, readability, performance, security and test coverage. The guiding question is "Could a new team member understand this code?" If the answer is no, the code needs work:

CategoryWhat to look forExamples
🐛 CorrectnessDoes it actually work?Edge cases, off-by-one, null checks
📖 ReadabilityCan it be understood?Variable names, comments, structure
🏗️ DesignIs it well designed?SOLID, DRY, separation of concerns
⚡ PerformanceIs it fast enough?N+1 queries, unnecessary loops
🔒 SecurityIs it safe?SQL injection, XSS, hardcoded secrets
🧪 TestsIs it covered by tests?Happy path + edge cases + error cases

What a Good Review Comment Looks Like

A good review comment explains "why" instead of simply saying "change this" — so the developer learns and does not repeat the mistake. Suggest the fix, show sample code, and keep the tone constructive rather than critical:

// ❌ A poor review comment: // "This is wrong" → does not say how it is wrong // "Fix this" → does not say how to fix it
// ✅ A good review comment: // "This could throw if `user` is null. // Consider adding a null check: // `if (!user) return res.status(404).json(...)`"
// ✅ Another example: // "Nit: prefer `const` here since `items` // is never reassigned."
🐕 Review Culture: Review the code, not the person — "this code could…" rather than "you wrote this badly…" | Use "Nit:" for small nitpicks | Praise good code when you see it 👍

🧠 SOLID Principles — The Rules of Good Code

SOLID is a set of five software design principles that keep code flexible, maintainable and extensible, formulated by Robert C. Martin (Uncle Bob). The names sound intimidating, but the core is simple — separate concerns clearly, give each part one job, and make change easy. Here they are one by one:

S
Single Responsibility
A class or function does one thing only
O
Open/Closed
Open for extension, closed for modification
L
Liskov Substitution
A subclass can stand in for its parent
I
Interface Segregation
Several small interfaces beat one big one
D
Dependency Inversion
Depend on abstractions, not implementations

Example: Single Responsibility

The Single Responsibility Principle (SRP) says that each class or function should have exactly one reason to change. If a function validates, saves and sends email all at once, touching the email logic can break the save logic! The example below compares code that violates SRP with code that follows it:

// ❌ Bad: one function doing many things - function handleOrder(order) { - validateOrder(order); // validate - calculateTotal(order); // calculate - chargePayment(order); // payment - sendEmail(order); // notification - updateInventory(order); // inventory - logAnalytics(order); // analytics - }
// ✅ Good: responsibilities cleanly separated + 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 — When Code Starts to Stink

Code smells are "signals" that code may be in trouble — not bugs in themselves, but patterns that point to a poor design. Left alone they turn into technical debt that gets harder to pay off. They are like a smell: nothing hurts you immediately, but something in there is rotting!

Code SmellProblemFix
🔢 Magic Numbersif (status === 3)Use a constant: ORDER_STATUS.COMPLETED
📏 Long FunctionA function of 200+ linesSplit it into small functions
📦 God ClassA class that does everythingSplit it by responsibility (SRP)
🔄 Duplicated CodeIdentical copy-pasteExtract a shared function
🏗️ Deep Nestingif nested 5+ levels deepEarly return, guard clauses
📝 Dead CodeCode that is never calledDelete it (Git keeps the history)
🔗 Feature EnvyHeavy use of another class's dataMove the method into that class
💬 Bad Naminglet x, tmp, data2Names that carry meaning

Example: Deep Nesting → Early Return

One of the most common code smells is deep nesting — if inside if inside if until nobody can read it. The fix is the early return pattern: check the error cases first and return immediately, which leaves the "happy path" at the outermost level and easy to read:

// ❌ Bad: deep nesting - function getDiscount(user) { - if (user) { - if (user.isPremium) { - if (user.orders > 10) { - return 0.2; - } else { - return 0.1; - } - } else { - return 0; - } - } else { - return 0; - } - }
// ✅ Good: 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 — Let the Machine Inspect It

Beyond linters there are static analysis tools that examine code far more deeply — finding code smells automatically, measuring complexity, detecting security vulnerabilities, and tracking technical debt over time. Think of them as a "third eye" helping the reviewer.

SonarQube / SonarCloud

SonarQube (self-hosted) or SonarCloud (managed) is the most complete code-analysis platform there is — it plugs into GitHub Actions, scans every PR, and reports the results on a dashboard full of 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

What Does SonarQube Measure?

SonarQube measures code quality across five main dimensions — bugs, vulnerabilities, code smells, coverage and duplications. Each carries a grade from A to E, which makes the overall picture quick to read:

MetricWhat it measuresTarget
🐛 BugsCode that is probably wrong0 (A rating)
🔓 VulnerabilitiesSecurity issues0 (A rating)
💩 Code SmellsMaintainability issuesAs few as possible
📋 CoverageTest coverage %> 80%
🔄 DuplicationsDuplicated code %< 3%
🏗️ ComplexityCyclomatic complexity< 15 per function
📏 DebtTechnical debt, as time to fixA falling trend

Quality Gate — Pass or Fail

A quality gate is the checkpoint that defines the minimum a change must meet before it can be merged — coverage above 80%, no critical bugs, duplications under 3%. Fail it and the PR fails automatically:

# Quality Gate Conditions (SonarQube default)
# ──────────────────────────────────────────
# ✅ PASS if:
#   - 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 → the PR cannot be merged!
# → quality enforced automatically, no need to rely on the reviewer

🛠️ The Full Toolkit

Here is every tool used to keep code quality up, grouped by category so it is easy to choose from. You do not need all of them — start with a linter and a formatter, then add more as the team is ready:

CategoryJavaScript/TSPythonGeneral
LinterESLint, BiomeRuff, Pylint, Flake8—
FormatterPrettier, BiomeRuff format, Black—
Type CheckTypeScriptmypy, Pyright—
Static AnalysisSonarCloudSonarCloud, BanditSonarQube
ComplexityESLint rulesradon, xenonCodeClimate
Dead Codets-prune, knipvulture—
Pre-commitHusky + lint-stagedpre-commit—
AI ReviewGitHub Copilot, CodeRabbit, Sourcery

📋 CI/CD Quality Pipeline

Time to bring every tool together in GitHub Actions! The pipeline below shows the quality checks worth running on every PR, ordered fastest to slowest — format check (seconds) → lint (seconds) → type check (a minute) → tests + coverage (minutes) → SonarCloud (minutes):

# .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 }}

The pipeline above orders its checks from fast to slow, and from basic to sophisticated. Starting with the formatting check gives the fastest possible feedback when code does not meet the standard, followed by linting, which catches syntax and the basic rules.

Type checking then catches type-safety mistakes, which matters enormously in TypeScript. Running the tests together with coverage confirms that the functionality still works and that test coverage is adequate. The final step is SonarCloud, which performs the deep analysis and gathers all of the metrics together.


📝 Clean Code Checklist

Having gone through the tools and the processes, the thing that matters is putting these principles and practices to work in the code you write every day. The Clean Code Checklist below is a guideline developers can use both when writing and when reviewing code.

The checklist gathers the principles that make code good, each one covering a different angle — from naming, to function design, to sound development process. Following it raises code quality in a systematic way:

  1. Naming: variable and function names carry meaning — getUserById, not getData
  2. Functions: short, doing one thing, no more than 20 lines
  3. DRY: Don't Repeat Yourself — extract shared logic
  4. KISS: Keep It Simple, Stupid — do not over-engineer
  5. Comments: explain "why", not "what" — the code says what, the comment says why
  6. Error Handling: handle errors explicitly, never catch and swallow silently
  7. No Magic: use constants instead of magic numbers and strings
  8. Consistent: one style across the whole codebase (enforced by a linter)
  9. Tests: every important function has a test
  10. Small PRs: small PRs are easy to review — no more than 400 lines changed

The checklist covers every important aspect of quality code, from technical matters such as naming conventions and error handling through to process matters such as code review and keeping PRs small. Following all of it may look like a lot, but once it becomes habit it happens automatically.

💡 The Boy Scout Rule: "Leave the code better than you found it" — when you meet a code smell, fix it a little at a time; there is no need to wait for a big refactor.

The Boy Scout Rule is simple but powerful. Fixing small code smells a little at a time stops technical debt from accumulating into a serious problem. It is a more sustainable approach than waiting for the problem to grow and then refactoring everything at once, and it gives the team a stronger sense of ownership over the codebase.


Summary

Code quality is something you work at continuously and systematically, not something you do once and finish. The tools covered in this post make building and preserving that quality automatic, and far more effective.

The most important thing is building a team culture that values code quality. Tools are only aids; the heart of it is a mindset and a team culture that wants to write good code, help one another, and learn from one another. That is where sustainable software development begins:

  • Linting + Formatting = ESLint + Prettier (JS) / Ruff (Python), style enforced by machine
  • Pre-commit hooks = Husky + lint-staged → no commit if the code does not pass
  • Code Review = look at correctness, readability, design, security, tests
  • SOLID Principles = five OOP design principles that keep code easy to maintain
  • Code Smells = magic numbers, deep nesting, god class → fix with early return, extract function
  • Static Analysis = SonarQube/SonarCloud measuring bugs, vulnerabilities, coverage, debt
  • Quality Gates = a PR cannot merge unless it passes the quality threshold
  • Clean Code = good naming, small functions, DRY, KISS, the Boy Scout Rule 🔍🐕

None of this has to arrive all at once. Start with the basic tools — a linter and a formatter — then add static analysis, a code review process and quality gates as the team is ready. What matters is consistency and continuous improvement, because code quality is not a destination but a journey with no end 🔍🐕

บทความจากซีรีส์ DevOps & Vibe Coding 2026From the DevOps & Vibe Coding 2026 series
← Previous
GitHub Actions — Automate ทุกอย่างใน CI/CD Pipeline
Next →
Automated Testing with GitHub — ทดสอบอัตโนมัติทุก Push