Refactor ไม่มีอาการ — ไม่เห็นตอน Claude ผิด สามรั้ว: เทส, commit อะตอม, คลิกด้วยมือ
Refactor คืองานที่ Claude อันตรายที่สุด บั๊กมีอาการ — ปุ่มไม่ตอบสนอง ค่าเป็น undefined มี stack trace — คุณบอกได้ว่าแก้ถูกหรือไม่ Refactor ไม่มีอาการ "ยังรันได้" อาจหมายถึง "เทสยังผ่าน" ขณะที่พฤติกรรมเปลี่ยนไปเงียบ ๆ คุณไม่รู้ตัว แล้วอีกหนึ่งสัปดาห์ production ก็ระเบิด
เพิ่งทำ refactor ที่ค่อนข้างใหญ่กับ Claude บน how2claude: ย้ายการจ่าย crypto x402 จาก PaymentHandler + FacilitatorClient ที่เขียนเองล้วน ๆ (139 บรรทัด) ไปเป็น gem x402-rails พร้อมกันก็ดึงการ map field ของ Purchase.create! / Subscription.create! — ที่ซ้ำอยู่ใน controller สองตัว — ออกมาเป็น class method ของ model หนึ่ง commit 4 ไฟล์เปลี่ยน 2 ไฟล์ลบ 2 ไฟล์เพิ่ม
Prompt ของผมคำเดียว: "refactor"
สั้นขนาดนั้นได้เพราะมีรั้วกั้นล้อมรอบ
ตอนที่ branch มาถึงจุดนี้ มี 221 เทส ทุกเส้นทางสำคัญของ flow การจ่ายถูกคลุมหมด
การกระทำ default ของ Claude ก่อน refactor ไม่ใช่ "ดูเทสก่อน" ดังนั้นผมสั่งให้มันรัน bin/rails test ก่อน ยืนยันว่าเขียว แล้วค่อยแตะอะไร
หลัง refactor รันอีกครั้ง ยังเขียว นี่ไม่ได้หมายความว่าไม่มี regression — หมายความว่าพฤติกรรมที่รู้จักยังไม่พัง
ถ้า codepath ของคุณไม่มีการคลุมเทส ให้ Claude เขียนเทสขั้นต่ำที่ล็อกพฤติกรรมปัจจุบันไว้ก่อน รัน commit แล้วค่อย refactor ไม่อย่างนั้นสิ่งที่มันทำไม่ใช่ refactor แต่เป็น rewrite — และคุณไม่มีทางยืนยันความเท่ากัน
Refactor นี้จริง ๆ คือสองเรื่อง:
Purchase / Subscription: controller → class method ของ modelฝั่ง frontend มีเรื่องที่สาม: เขียน flow การเซ็นฝั่ง JS ใหม่ด้วย viem + x402-fetch
ผมให้ Claude ตัดตามขอบเขตธรรมชาติ: backend + การดึง model เป็น commit เดียว (9f3e239), frontend เป็น commit แยก (93746d8) แต่ละ commit มีคำอธิบายครบ รายการไฟล์ และเหตุผลของการเปลี่ยน
ประโยชน์:
- diff อ่านได้ หนึ่ง commit หนึ่งเรื่อง
- ความละเอียดของ rollback ควบคุมได้ ถ้า production เจอบั๊ก frontend git revert 93746d8 ย้อนเฉพาะ frontend backend ยังอยู่
- ความสนใจของ Claude เองก็โฟกัส หนึ่ง commit หนึ่งเรื่อง — ความสนใจมันก็ครอบแค่เรื่องเดียว
พอ refactor เสร็จ ผมให้ Claude หยุดและโชว์ git diff --staged อย่าเพิ่งรันเทส อย่าเพิ่งรันแอป อ่าน diff ก่อน
สัญญาณที่ผมสแกน:
app/services/x402/payment_handler.rb ลบทั้งไฟล์ — โอเค นั่นคือจุดประสงค์ของการย้ายไป gem แต่ถ้ามันลบสิ่งที่ผมไม่ได้สั่งแตะ ผมหยุดและถามทันทีPurchase.create!(wallet_address: verify_result["payer"], ...) → Purchase.record_x402!(payment:, settlement:) ตอนนี้อ่าน payment[:payer] แหล่งเปลี่ยน (request.env ของ gem vs ค่า return ของ client เก่า) แต่ field ต้อง map หนึ่งต่อหนึ่งกับดัก 1: Stimulus controllers ของ gem โหลดไม่ขึ้นแบบเงียบ ๆ
gem x402-rails มาพร้อม Stimulus controllers ของตัวเอง Claude เขียนโค้ด เทสเขียวทั้งหมด ผมคลิกปุ่มจ่ายด้วยมือ — ไม่มีอะไรเกิดขึ้น
เหตุผล: ใน config/importmap.rb pin สำหรับ @hotwired/stimulus ชี้ไปที่ไฟล์ vendor ที่ไม่มีอยู่ และ importmap ทิ้ง pin นั้นอย่างเงียบ ๆ controllers ของ gem ไม่เคยถูกโหลด เทสจับไม่ได้ เพราะ bin/rails test ไม่รัน JS
กับดัก 2: YAML parse 0x... เป็น integer
wallet_address: 0x833589... — ไม่มีเครื่องหมายคำพูด YAML เห็น prefix 0x อ่านเป็นจำนวนเต็มฐาน 16 Facilitator ได้ค่าที่ไม่ใช่ string และปฏิเสธ Claude ไม่ได้หยุดคิดถึงกฎการ parse YAML ตอนเขียน config
กับดักทั้งสองจับได้เพราะผมคลิกปุ่มจริง ๆ เทสผ่านไม่เท่ากับ feature ทำงาน การยืนยันด้วยมือหลัง refactor ข้ามไม่ได้
Refactor คือสถานการณ์ "ปล่อย Claude ขับ" ที่เสี่ยงที่สุด รั้วไม่ใช่สำหรับ Claude แต่สำหรับคุณ — เพื่อให้เมื่อ Claude ทำพลาด คุณจับได้ใน 5 นาที ไม่ใช่ตอน production ไฟไหม้