15.11 Guardrails
บทนี้รวบรวม "กฎ" ที่จะป้องกันไม่ให้ debt ใหม่เกิดขึ้นอีกในระบบ แต่ละข้อ map กลับไปยัง debt ในบทก่อนหน้า
หลักการ
ทุก debt ใน Volume นี้สะท้อนการตัดสินใจหรือการละเว้นการตัดสินใจบางอย่าง Guardrails คือกฎที่บังคับให้การตัดสินใจครั้งต่อไปเป็นไปในทิศทางที่ดีกว่า
กฎทั้งหมดนี้ต้องสามารถตรวจสอบได้อัตโนมัติ (CI/CD) ไม่ใช่แค่เอกสารปฏิบัติ
15 Guardrail
G1 · ไม่มี console.log ใน production code
ป้องกัน: LEG-24
ใช้ structured logger (slog, zerolog, pino, winston) เท่านั้น
การตรวจ: ESLint rule no-console (ยกเว้นใน dev-only file), golangci-lint forbidigo
G2 · ไม่มี execSync / shell-out ใน request handler
ป้องกัน: SEC-1, PERF-1
ถ้าจำเป็นต้องเรียก binary ใช้ arg-array form (execFile, os/exec ที่ไม่ผ่าน shell)
การตรวจ: ESLint no-restricted-syntax สำหรับ execSync, code review
G3 · ไม่มี secret ใน source
ป้อลง: SEC-3, SEC-4, SEC-11
ใช้ secret manager หรือ environment variable เท่านั้น
การตรวจ: pre-commit hook ที่ scan pattern ของ secret (เช่น trufflehog, gitleaks)
G4 · engines.node / go.mod version บังคับ
ป้องกัน: LEG-18
CI ตรวจ engines field และ version ที่ใช้จริง
การตรวจ: CI step ที่ run node --version และ compare กับ engines
G5 · Lockfile commit + CI ตรวจ drift
ป้องกัน: LEG-15, LEG-16
CI รัน yarn install --frozen-lockfile (หรือ npm ci) ถ้า lockfile drift ให้ fail
การตรวจ: CI โดยตรง
G6 · Strict types
ป้องกัน: LEG-17
Go strict-by-default; TypeScript strict: true สำหรับ TS ที่เหลือ
การตรวจ: tsc --noEmit + golangci-lint
G7 · Migration forward-only
ป้องกัน: CORR-6
ไม่มี ORM auto-sync ใน prod migration ใช้ golang-migrate, goose, atlas
การตรวจ: CI step ที่รัน migration บน test DB
G8 · design system / icon / DnD / date library ตัวเดียวต่อแอป
ป้องกัน: LEG-26
policy document + dependency audit ใน CI
การตรวจ: lint rule + manual review
G9 · ไม่มี _bk / _copy files
ป้องกัน: LEG-14
.gitignore + pre-commit hook ที่ปฏิเสธไฟล์ที่ตรง pattern
การตรวจ: pre-commit + CI
G10 · PII ไม่อยู่ใน URL
ป้องกัน: SEC-8
ตัวระบุ sensitive อยู่ใน POST body หรือ signed token เท่านั้น
การตรวจ: code review + log scanner ที่หา pattern ของ identityCard, empCode ใน URL
G11 · CORS allow-list explicit
ป้องกัน: SEC-5
ไม่มี origin: '*' ใน production
การตรวจ: lint + integration test
G12 · Rate limit ทุก public endpoint
ป้องกัน: SEC-10
middleware ที่ apply rate limit โดย default route ที่ต้องการ exempt ต้องระบุชัดเจน
การตรวจ: code review + integration test
G13 · Casbin policy test สำหรับทุก (role × menu × action)
ป้องกัน: CORR-4
test matrix ที่ cover ทุกกรณี ส่งผลใน CI
การตรวจ: test suite
G14 · Bounded context < 400 LOC ต่อไฟล์
ป้องกัน: PERF-6, LEG-21, LEG-23
god file fail CI
การตรวจ: lint rule max-lines
G15 · i18n ที่ presentation layer
ป้องกัน: LEG-30
error carry code ไม่ carry message ในภาษาใดภาษาหนึ่ง
การตรวจ: code review + lint
ตารางสรุป Guardrail → Debt
| Guardrail | Debt ที่ป้องกัน | การตรวจ | Effort เริ่มต้น |
|---|---|---|---|
| G1 | LEG-24 | ESLint, golangci-lint | 1 วัน |
| G2 | SEC-1, PERF-1 | ESLint, review | 1 วัน |
| G3 | SEC-3, SEC-4, SEC-11 | gitleaks, pre-commit | 1 วัน |
| G4 | LEG-18 | CI | 0.5 วัน |
| G5 | LEG-15, LEG-16 | CI | 0.5 วัน |
| G6 | LEG-17 | tsc, golangci-lint | 0.5 วัน |
| G7 | CORR-6 | CI | 1 วัน |
| G8 | LEG-26 | lint, review | ongoing |
| G9 | LEG-14 | pre-commit, CI | 0.5 วัน |
| G10 | SEC-8 | review, log scan | ongoing |
| G11 | SEC-5 | lint, test | 0.5 วัน |
| G12 | SEC-10 | test | 1 วัน |
| G13 | CORR-4 | test | 2-3 วัน |
| G14 | PERF-6, LEG-21, LEG-23 | lint | 0.5 วัน |
| G15 | LEG-30 | review, lint | ongoing |
Effort รวมเริ่มต้น: ~2 สัปดาห์ เพื่อตั้ง guardrail ทั้งหมด
กลยุทธ์การนำไปใช้
ลำดับความสำคัญ
- เริ่มจาก G3, G5, G7 — ป้องกันความเสี่ยงที่ร้ายแรงที่สุด (secret, lockfile, migration)
- ตามด้วย G1, G2, G4, G6 — พื้นฐานของ code quality
- ตามด้วย G9, G11, G14 — lint ที่ลด noise
- สุดท้าย G8, G10, G12, G13, G15 — ที่ต้องการ culture change
อย่าเข้มงวดเกินไปตอนเริ่ม
ถ้านำ guardrail ทั้ง 15 มาใช้พร้อมกัน CI จะ fail ทันทีเพราะ debt เดิม — แนะนำ:
- เปิด guardrail เป็น warning ก่อน (ไม่ block)
- แก้ debt ที่ flag ทีละรายการ
- ค่อยเปลี่ยนเป็น error เมื่อ debt ลดลง
กรรมวิธี "grandfathering"
สำหรับ debt ที่มีอยู่แล้วและยังไม่ได้แก้ ใช้ inline comment ที่ mark ว่ายกเว้น:
// eslint-disable-next-line no-console (LEGACY: LEG-24, แก้ใน Wave 1)
console.log('debug info');แต่ละ exception ต้องมี:
- debt ID ที่เชื่อมโยง
- Wave ที่จะแก้
- วันที่จะ review อีกครั้ง
Guardrail เป็นพื้นฐานของ engineering culture
Guardrails ไม่ใช่แค่ lint rule — เป็นวิธีที่ทีมตกลงกันว่าจะทำงานอย่างไร เมื่อ guardrail ทำงาน CI จะบังคับ และนักพัฒนาใหม่จะเรียนรู้ได้จาก error message
ถ้าไม่มี guardrails, debt จะกลับมาสะสมอีก แม้หลัง Wave 0/1/2/3 เสร็จ
บทถัดไปจะสรุป scorecard ที่เปรียบเทียบสถานะปัจจุบัน vs หลัง Wave 0-1 vs หลัง Wave 2-3