แก้ไปแก้มาทั้งอาทิตย์ ยังตอบไม่ได้ว่าอันไหนทำพัง
ต้นทุนจริงคือระยะห่างระหว่างทำพังกับรู้ว่าพัง ย่นมันด้วย atomic commit ปักหมุดด้วย tag มี smoke test บน flow ที่แพงที่สุด และเก็บความจริงไว้ที่เดียว
แก้มาสามวันแล้ว
ตอบได้มั้ยว่าการแก้ครั้งไหนที่ทำมันพัง
ตอบไม่ได้
แล้ววันนี้กำลังจะทำอะไรต่อ ก๊อป error ไปให้ AI แก้อีกรอบ แล้วก็ลุ้นว่ารอบนี้จะไม่ไปพังที่อื่น
ลองไล่ดูว่าเกิดอะไรขึ้นจริงๆ
สั่ง AI แก้อะไรสักอย่าง มันแก้ให้ 6 ไฟล์ กด accept แอปยังเปิดได้ หน้าที่เปิดดูอยู่ก็ยังปกติ ก็ไปทำเรื่องอื่นต่อ
สามวันผ่านไป มีคนทักมาว่าหน้า checkout กดไม่ได้
ระหว่างสามวันนั้นแก้ไปอีกยี่สิบรอบ มี commit ก้อนเดียวใหญ่ๆ เขียนว่า "fix stuff"
จะย้อนกลับไปจุดที่ยังดีก็ย้อนไม่ได้ เพราะจุดที่ยังดีไม่เคยถูกทำเครื่องหมายไว้
เราไม่ได้เจอ bug ตอนที่มันเกิด เราเจอตอนที่บังเอิญมีคนไปกดโดน
ทุกครั้งที่แก้ เราไม่ได้แก้ เรากำลังเดา แล้วรอให้คนอื่นมาบอกว่าเดาผิด
อาการที่แก้ของใหม่แล้วของเก่าพัง มันมีชื่อของมัน เรียกว่า regression
หลักการที่ทำให้เรื่องนี้จบ มีข้อเดียว
ระยะห่างระหว่าง "ทำพัง" กับ "รู้ว่าพัง" คือต้นทุนจริงของงานเขียนโปรแกรม
ไม่ใช่จำนวนบั๊ก ไม่ใช่ความเก่ง ระยะห่างนี้ต่างหาก
พังแล้วรู้ใน 3 วินาที คือแก้ไฟล์เดียว เรื่องยังอยู่ในหัว จบใน 2 นาที
พังแล้วรู้อีก 3 วัน คือของทับกันไปยี่สิบชั้น ต้องรื้อ ต้องเดา ต้องแก้ไปแก้มา แล้วบางทีแก้แล้วพังใหม่
ปัญหาเดียวกันเป๊ะ แต่ราคาต่างกันเป็นสิบเท่า เพราะเวลาที่ปล่อยให้ผ่านไป
วงการเรียกมันว่า feedback loop ยิ่งวงสั้น ยิ่งถูก
ทุกอย่างที่จะพูดต่อจากนี้ คือวิธีทำให้วงมันสั้นลง ไม่มีอะไรมากกว่านั้น
แก้ทีละเรื่อง commit ทีละเรื่อง (atomic commit)
เวลาพังจะได้ตอบได้ว่าอันไหนทำ ปล่อยให้ AI แก้รวดเดียวสิบไฟล์แล้ว commit ทีเดียว คำถามว่าอันไหนทำพังจะไม่มีคำตอบตลอดไป
แล้วพอปล่อยของขึ้นไปแต่ละรอบ ปักหมุดไว้ด้วย
git tag v0.1.4
แค่นี้ ไม่ต้องคิดเยอะเรื่องเลข มันคือจุดเซฟในเกม ตอนนั้นของยังดีอยู่ เราบันทึกไว้
วันที่พังแล้วหาไม่เจอจริงๆ เราไม่ต้องนั่งไล่ทีละบรรทัด เรากลับไปยืนที่จุดเซฟล่าสุดที่ยังดี แล้วดูว่าตั้งแต่จุดนั้นถึงตอนนี้มีอะไรเปลี่ยนไปบ้าง
ขอบเขตการค้นหาจากทั้งโปรเจกต์ เหลือแค่ช่วงสั้นๆ ระหว่างสองจุด
แต่ถ้ามี commit ก้อนเดียวใหญ่ๆ เขียนว่า "fix stuff" แล้วไม่เคยปักหมุดอะไรไว้เลย ก็ไม่มีที่ให้กลับไป
มีเทสต์ไว้ อย่างน้อยหนึ่งตัวบนเส้นทางที่สำคัญที่สุด อันนี้เรียกว่า smoke test
เทสต์คือคนที่นั่งกดแอปแทนเรา กดทุกหน้า ทุกครั้ง ภายในไม่กี่วินาที เราไม่มีทางกดครบเองทุกรอบ แต่มันทำได้
ไม่ต้องเขียนเองก็ได้ บอก AI ให้เขียนให้ แล้วสิ่งที่ได้กลับมาหน้าตาประมาณนี้
test('สมัคร ล็อกอิน จ่ายเงิน', async ({ page }) => {
await page.goto('/signup')
await page.fill('#email', '[email protected]')
await page.click('text=สมัคร')
await page.click('text=จ่ายเงิน')
await expect(page.getByText('สำเร็จ')).toBeVisible()
})
อ่านออกใช่มั้ย มันคือลำดับการกดที่เราทำเองทุกวันนั่นแหละ แค่ย้ายไปให้เครื่องกดแทน หน้าที่เราคือเปิดอ่านว่ามันกดถูกขั้นตอนจริง
เก็บความจริงไว้ที่เดียว (single source of truth)
ที่แก้ไปแก้มาบ่อยสุดคือเรื่องเดียวกันที่ถูกเขียนไว้สามที่ แล้วแก้ไปแค่ที่เดียว ราคาสินค้าอยู่ในหน้า cart อยู่ใน backend อยู่ในอีเมลยืนยัน แก้หน้าเดียวจบตรงนั้น อีกสองที่รอวันระเบิด
แบ่งของเป็นส่วนๆ ให้ชัด
ถ้าโค้ดหน้า login ไปแตะตะกร้าสินค้าได้ แปลว่าการแก้ทุกครั้งมีรัศมีทำลายล้าง (blast radius) เท่ากับทั้งแอป การที่ของสองส่วนพันกันจนแยกไม่ออกแบบนี้ เรียกว่า coupling
สี่ข้อนี้ไม่ได้ทำให้ทำพังน้อยลงเลยสักข้อ มันแค่ทำให้รู้เร็วขึ้น
และไม่มีใครเคยบอกเรื่องพวกนี้กับคนที่เริ่มจาก vibe coding เพราะมันไม่เคยถูกสอน มันเป็นของที่ค่อยๆ เจ็บเอาเองจนจำ
AI ทำให้ระยะนี้กว้างขึ้นอีก
เมื่อก่อนคนเขียนเองวันละไม่กี่สิบบรรทัด รัศมีของความพังเลยเล็กตามไปด้วย
ตอนนี้สั่งทีเดียวได้ 6 ไฟล์ ในสามสิบวินาที ของที่เปลี่ยนต่อวันเยอะขึ้นหลายเท่า
แต่สัญญาณเตือนเท่าเดิม คือศูนย์
พลังในการเปลี่ยนโตขึ้น ความสามารถในการรู้ว่าเปลี่ยนแล้วพังไม่โตตาม ช่องว่างนั้นแหละที่จ่ายอยู่ทุกวันในรูปของการแก้วนไปวนมา
เริ่มวันนี้ เอาอันเดียวพอ
เลือก flow ที่พังไม่ได้เด็ดขาดมาหนึ่งอัน อันที่ถ้าพังแล้วเสียเงินหรือเสียลูกค้า ส่วนใหญ่คือ สมัคร เข้าสู่ระบบ จ่ายเงิน
แล้วบอก AI แบบนี้
เขียนเทสต์ให้ flow นี้หน่อย: [สมัคร -> ล็อกอิน -> จ่ายเงิน]
- เทสต์เดียว จบทั้ง flow
- ห้าม mock ส่วนที่เป็นหัวใจของ flow
- รันด้วยคำสั่งเดียว
- เขียนคำสั่งที่ใช้รันไว้ใน README ด้วย
จากนั้นเปิด CLAUDE.md ในโปรเจกต์ แล้วแปะสามบรรทัดนี้ลงไป
## ก่อนบอกว่าเสร็จ
- ก่อนจะบอกว่าทำเสร็จ ให้รันเทสต์ แล้วแปะผลลัพธ์ที่รันได้จริงมาให้ดู
- เทสต์ไม่ผ่าน ห้ามบอกว่าเสร็จ
- แก้ทีละเรื่อง commit ทีละเรื่อง อย่ารวบ
ยังไม่ต้องมีเทสต์ครบทุกหน้า ยังไม่ต้องรู้จักคำว่า coverage
ขั้นต่อไปเมื่อพร้อมคือ CI ซึ่งแปลว่าให้เครื่องรันเทสต์ให้อัตโนมัติทุกครั้งที่ push ขึ้น GitHub เราจะรู้ว่าพังตั้งแต่ก่อนของขึ้นเว็บจริง แต่ยังไม่ต้องรีบ เอาเทสต์ตัวแรกให้รอดก่อน
แค่ย่นสามวัน ให้เหลือสามสิบวินาที ใน flow ที่แพงที่สุดที่มี
ครั้งหน้าที่มีคนทักมาว่าพัง อย่างน้อยจะตอบได้ว่าอันไหน
ศัพท์พวกนี้เอาไปค้นต่อได้เลย ทุกคำมีคนเขียนอธิบายไว้เยอะมาก
feedback loop คือระยะห่างที่พูดถึงทั้งโพสต์
regression คือแก้ของใหม่แล้วของเก่าพัง
atomic commit คือ commit ละเรื่อง ไม่รวบ
single source of truth คือความจริงเก็บที่เดียว
blast radius คือรัศมีของความพังเวลาแก้หนึ่งจุด
git tag คือการปักหมุดจุดที่ของยังดี
git bisect คือคำสั่งไล่หา commit ที่ทำพัง ไว้ใช้ตอนที่หมุดไม่พอ
รู้ชื่อมันแล้วชีวิตง่ายขึ้นเยอะ เพราะสั่ง AI ได้ตรงขึ้น และอ่านคำตอบที่มันตอบกลับมารู้เรื่องขึ้น
ถ้าอยากอ่านต่อจริงจัง สองเล่มนี้
The Pragmatic Programmer ของ Hunt กับ Thomas เล่มนี้พูดเรื่อง DRY กับการไม่เก็บความจริงซ้ำที่ ตรงกับหัวข้อข้างบนเป๊ะ อ่านทีละบทได้ ไม่ต้องเรียงหน้า
Accelerate ของ Nicole Forsgren เล่มนี้เอาข้อมูลจริงจากหลายพันทีมมายืนยันว่าทีมที่ปล่อยของถี่และรู้ตัวเร็ว ทำงานได้ดีกว่าทีมที่ระวังตัวแล้วปล่อยนานๆ ที เป็นเวอร์ชันมีตัวเลขของเรื่องที่เพิ่งอ่านจบไป