บทที่ 8 — 3 บั๊กจริงที่ CI จับได้ (และวิธีคิดตอนดีบัก)
ทั้งสามเคสนี้เกิดขึ้นจริงในโปรเจกต์นี้ ทุกเคส ผ่านฉลุยบนเครื่องคนเขียน แต่ CI จับได้ — เปิด PR เก่าดูประกอบได้เลย
เคสที่ 1: npm ci ล้ม เพราะ lock file ไม่ตรง
อาการ — PR #4 ทั้งสอง job แดงภายใน 20 วินาที:
npm error `npm ci` can only install packages when your package.json and
package-lock.json are in sync.
npm error Missing: @emnapi/[email protected] from lock fileสาเหตุ — ตอนติดตั้ง lucide-react (ชุดไอคอน) บน macOS ตัว npm บันทึกเฉพาะ dependency ย่อยที่เครื่อง Mac ต้องใช้ลง package-lock.json แต่พอ CI รันบน Linux มันต้องการแพ็กเกจย่อยอีกชุดที่ไม่มีในไฟล์ → npm ci ที่เข้มงวดเลยปฏิเสธทันที
สิ่งที่ลองแล้วไม่หาย — สั่ง npm install --package-lock-only เพื่ออัปเดต lock file แล้ว push ใหม่ → ยังแดงเหมือนเดิม เพราะมันอัปเดตแบบเพิ่มจากของเดิมซึ่งยังขาดข้อมูลข้ามแพลตฟอร์มอยู่
วิธีแก้ที่ได้ผล — สร้าง lock file ใหม่ทั้งไฟล์:
rm -rf node_modules package-lock.json
npm installบทเรียน — package-lock.json ต้อง commit ขึ้น repo เสมอ และเวลามันพังแบบ "ขาดของ" ให้สร้างใหม่ทั้งไฟล์ ดีกว่าไล่แก้ทีละบรรทัด
เกร็ด: บั๊กนี้เกิดซ้ำอีกครั้งตอนสร้างเว็บคู่มือที่คุณกำลังอ่านอยู่นี่แหละ — ติดตั้ง library ตัวเดียว (
marked) บน Mac แล้ว CI แดงด้วย error บรรทัดเดียวกันเป๊ะ นี่คือเหตุผลที่เราจดเคสพวกนี้ไว้ พอเจอซ้ำก็แก้จบใน 2 นาทีแทนที่จะงงอีกครึ่งชั่วโมง
เคสที่ 2: typecheck ล้มบนเครื่องสะอาด
อาการ — CI แดงที่ด่าน typecheck ทั้งที่ในเครื่องผ่าน:
src/app/layout.tsx(20,50): error TS2304: Cannot find name 'LayoutProps'.สาเหตุ — Next.js 16 สร้าง type บางตัว (เช่น LayoutProps, PageProps) ให้อัตโนมัติตอนรัน dev หรือ build โดยเก็บไว้ในโฟลเดอร์ .next/types ซึ่งไม่ได้ commit ขึ้น repo (มันเป็นไฟล์ที่สร้างใหม่ได้)
เครื่องเราเคยรัน npm run dev มาก่อน ไฟล์เลยมีอยู่แล้ว → ผ่าน
CI เป็นเครื่องใหม่ที่ไม่เคยรันอะไร → ไม่มีไฟล์ → พัง
วิธีพิสูจน์ว่าคิดถูก (ขั้นตอนสำคัญที่สุด — อย่าเดาแล้วแก้เลย):
rm -rf .next # ทำให้เครื่องตัวเองสะอาดเหมือน CI
npx tsc --noEmit # พังเหมือนกันจริง ✓ เท่ากับเข้าใจสาเหตุถูกแล้ววิธีแก้ — เพิ่มขั้นตอนสร้าง type ก่อน typecheck ใน ci.yml:
- name: Generate route types
run: npx next typegen
- name: Typecheck
run: npx tsc --noEmitบทเรียน — "ในเครื่องผมผ่านนะ" เป็นประโยคอันตราย ต่างกันตรง สภาพแวดล้อม ไม่ใช่ตรงโค้ด เวลาเจอแบบนี้ให้ทำเครื่องตัวเองให้เหมือน CI แล้วไล่ดู
เคสที่ 3: กฎ lint ของ React จับปัญหาที่กำลังจะเกิด
อาการ — PR #7 (ระบบตะกร้า) ด่าน lint แดง:
error Calling setState synchronously within an effect can trigger cascading rendersเบื้องหลัง — ก่อนหน้านั้นเราเจอบั๊กจริงในเบราว์เซอร์อยู่แล้ว: ใส่ของลงตะกร้า → เข้าหน้าตะกร้า → ของหาย ตะกร้าว่างเปล่า
โค้ดตอนนั้นเขียนด้วย effect สองตัว: ตัวหนึ่งอ่านตะกร้าจาก localStorage ตอนเปิดหน้า อีกตัวเขียนตะกร้าลง localStorage ทุกครั้งที่ข้อมูลเปลี่ยน ผลคือรอบแรกสุด ตัว "เขียน" ทำงานก่อนที่ตัว "อ่าน" จะเสร็จ → เขียนค่าว่างทับของจริงที่เก็บไว้
วิธีแก้ — เลิกใช้ effect แล้วเปลี่ยนไปใช้ useSyncExternalStore ซึ่งเป็นเครื่องมือที่ React ออกแบบมาสำหรับ "ข้อมูลที่อยู่นอก React" (เช่น localStorage) โดยเฉพาะ:
const items = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);ผลพลอยได้คือได้แก้ปัญหาอื่นไปด้วย — ตัวเลขบน badge ตะกร้าทุกจุดในหน้าอัปเดตพร้อมกัน และไม่มีอาการหน้ากระพริบตอนโหลด
บทเรียน — กฎ lint ไม่ใช่เรื่องความสวยงามของโค้ด หลายข้อมาจากบั๊กที่คนทั้งโลกเคยเจอมาแล้ว เจอ lint เตือนให้อ่านว่ามันเตือนเรื่องอะไร อย่าเพิ่งรีบปิดกฎ
วิธีคิดเวลาดีบัก (ใช้ได้ทุกเคส)
4 ขั้นที่โปรเจกต์นี้ใช้ทุกครั้งที่เจอบั๊ก
1. ทำให้พังซ้ำให้ได้
ถ้ายังทำให้เกิดซ้ำไม่ได้ แปลว่ายังไม่รู้ว่าปัญหาคืออะไร — การแก้ตอนนี้คือการเดา ตัวอย่างเคส 2: ลบโฟลเดอร์ .next แล้วพังซ้ำได้ = ผ่านขั้นนี้
rm -rf .next && npx tsc --noEmit
2. ไล่เส้นทางที่พัง
อ่าน error/log ของจริงทีละบรรทัด หาว่าพังที่ขั้นตอนไหน ไฟล์ไหน บรรทัดไหน อย่าเดาจากความรู้สึก
3. พยายามล้มสมมติฐานตัวเอง
ถ้าคิดว่า 'น่าจะเพราะ X' ให้หาการทดสอบที่จะพิสูจน์ว่า X ไม่ใช่สาเหตุ ถ้าล้มไม่ได้ แปลว่าน่าจะใช่จริง — ขั้นนี้กันการแก้ผิดจุดแล้วคิดว่าหายแล้ว
4. แก้แล้วพิสูจน์ว่าหาย
รันการทดสอบเดิมที่เคยทำให้พัง ต้องผ่าน ไม่ใช่แค่ 'ไม่เห็น error แล้ว'
สั่ง AI ด้วยกระบวนการนี้ได้เลย เช่น 'ทำให้บั๊กเกิดซ้ำให้ดูก่อน แล้วค่อยเสนอวิธีแก้'
สิ่งที่ห้ามทำ: แก้แบบเดาสุ่มไปเรื่อย ๆ จนบังเอิญเขียว เพราะคุณจะไม่รู้ว่ามันหายจริงหรือแค่ซ่อนอยู่
ลองทำเอง: เปิด https://github.com/Natt-Woramet/apexhuas/pull/4 แล้วไล่ดูตามลำดับเวลา — จะเห็นว่า CI แดงก่อน มี commit แก้ตามมา แล้วค่อยเขียว ลองอ่าน commit message ของ commit ที่แก้ดูว่าเขาอธิบายสาเหตุไว้ยังไง
สรุปครึ่งแรก
ถึงตรงนี้คุณควรทำได้แล้ว: สั่งงาน AI ให้ได้ผลที่ใช้ได้, clone repo, แตก branch, commit, เปิด PR, อ่านผล CI และแก้เมื่อมันแดง
ต่อจากนี้คือครึ่งหลัง — เรื่องที่ทำให้โค้ดที่ merge แล้วกลายเป็นเว็บจริงบนอินเทอร์เน็ต: Docker, server, โดเมน, HTTPS และท่อ deploy อัตโนมัติ