ผมกำลังจะกลับไปเขียนโค้ดด้วยมือตัวเองอีกครั้ง
(blog.k10s.dev)- k10s เคยเป็น Kubernetes TUI ที่รองรับ GPU ซึ่งถูกสร้างขึ้นอย่างรวดเร็วด้วยการ vibe-coding กับ Claude แต่หลังจากเพิ่ม fleet view แล้ว state ของหลายหน้าจอก็พัง
model.goขยายใหญ่เป็นModelเดี่ยวขนาด 1690 บรรทัด และUpdate()ยาว 500 บรรทัด จนต้องแบกรับทั้ง UI, client, cache, navigation และ view state ไว้ทั้งหมด- AI เพิ่มฟีเจอร์ได้เร็ว แต่ก็ทำให้เกิด god object และ global key handler ที่ใหญ่ขึ้น และทุกครั้งที่เพิ่ม view ใหม่ก็ต้องเพิ่ม branch ใน handler เดิม
- ข้อมูลแบบอิงตำแหน่งใน
[]stringและการ mutate โดยตรงจาก backgroundtea.Cmdอาจทำให้เกิด column error และ data race อย่างชัดเจน - k10s เวอร์ชันใหม่จะเขียนใหม่ด้วย Rust และก่อน prompt แรกจะกำหนด interface, message type, ownership rule และ scope ไว้ใน CLAUDE.md ให้ตายตัว
เบื้องหลังที่ต้องเขียน k10s ใหม่
- k10s เริ่มต้นจากการเป็นแดชบอร์ด Kubernetes ที่รองรับ GPU โดยตั้งใจทำเป็นเครื่องมือ TUI ให้ผู้ดูแลคลัสเตอร์ NVIDIA เห็นข้อมูลอย่างการใช้งาน GPU, เมตริก DCGM, โหนดที่ว่าง และค่าใช้จ่าย
$32/hrได้ทันที - มันถูกเขียนด้วย Go และ Bubble Tea และสร้างขึ้นจากเซสชัน vibe-coding กับ Claude ตลอดประมาณ 7 เดือน, 234 commits, และราว 30 สุดสัปดาห์
- ในช่วงแรก ฟีเจอร์พื้นฐานแนวโคลน k9s อย่าง pods, nodes, deployments, services, command palette, live updates แบบ watch และ Vim keybindings ใช้งานได้ภายในเวลาเพียงประมาณ 3 สุดสัปดาห์
- ฟีเจอร์หลักอย่าง GPU fleet view เป็นหน้าจอที่แสดงการจัดสรร GPU ของแต่ละโหนด, การใช้งาน, เมตริกจาก DCGM, อุณหภูมิ, พลังงาน, หน่วยความจำ และสถานะด้วยสี โดย Claude สร้างทั้ง struct
FleetView, การกรองแท็บ GPU/CPU/All และการเรนเดอร์ allocation bars ให้ในครั้งเดียว - หลังจากเพิ่ม fleet view แล้ว เมื่อกลับไปที่ pods view ด้วย
:rs podsตารางกลับว่างเปล่า, live updates หยุดทำงาน, nodes view แสดง stale data จากตัวกรองของ fleet view และจำนวน fleet tab ก็ผิดด้วย - ระหว่างไล่หาปัญหา จึงได้อ่าน
model.goทั้งไฟล์ 1690 บรรทัด ที่ Claude สร้างไว้เป็นครั้งแรก และพบว่า structModelตัวเดียวถือทั้ง UI widgets, Kubernetes client, state ของ logs/describe/fleet, ประวัติการนำทาง, cache และการจัดการเมาส์ไว้ทั้งหมด - เมธอด
Update()มีขนาดประมาณ 500 บรรทัด และเป็นฟังก์ชัน dispatch แบบmsg.(type)ที่มี 110 switch/case branches - AI อาจสร้างฟีเจอร์ได้เร็ว แต่ถ้าปล่อยให้ทำต่อไปโดยไม่มีข้อจำกัด สถาปัตยกรรมจะพัง และความเร็วที่รู้สึกได้จะดูเหมือนความสำเร็จจนกว่าทั้งระบบจะพังพร้อมกัน
หลักการ 5 ข้อที่ได้จากซากปรักหักพัง
-
หลักการ 1: AI สร้างฟีเจอร์ได้ แต่ไม่ได้สร้างสถาปัตยกรรม
- Claude ทำฟีเจอร์เดี่ยว ๆ อย่าง fleet view, log streaming และ mouse support ได้ดี แต่แต่ละฟีเจอร์ถูกทำในบริบทของการ “ทำให้มันใช้งานได้ตอนนี้” โดยไม่คำนึงถึงความสัมพันธ์กับฟีเจอร์อื่นที่ใช้ state ร่วมกัน
- ใน handler ของ
resourcesLoadedMsgมีเงื่อนไขอย่างmsg.gvr.Resource == k8s.ResourceNodes && m.fleetView != nilทำให้ logic เฉพาะของ fleet view ไปปะปนอยู่ในเส้นทาง generic resource loading - ทุกครั้งที่ view ใหม่ต้องการ custom behavior ก็ต้องเพิ่ม branch ใน handler เดิม และต้องคอยล้างหลายฟิลด์ด้วยมือเพื่อไม่ให้ข้อมูลจาก view ก่อนหน้ารั่วเข้ามาใน view ใหม่
- ใน
model.goมีการ cleanup แบบกำหนดเองอย่างm.logLines = nil,m.allResources = nil,m.resources = nilกระจายอยู่ 9 จุด และถ้าพลาดไปแม้แต่จุดเดียว ก็จะมี ghost data จาก view ก่อนหน้าค้างอยู่ - ทางเลือกคือก่อนจะเริ่มเขียนโค้ด ต้องเขียน interface, message type และ ownership rule ที่ชัดเจนด้วยตัวเอง แล้วใส่ไว้ใน
CLAUDE.mdเป็น architecture invariant - ตัวอย่างกติกา เช่น ให้แต่ละ view implement
Viewtrait/interface, ห้าม view เข้าถึง state ของ view อื่น, ข้อมูล async ต้องเข้ามาในรูปแบบAppMsgvariants เท่านั้น และAppstruct ต้องรับผิดชอบแค่ navigation กับ message dispatch
-
หลักการ 2: god object คือผลลัพธ์เริ่มต้นที่ AI มักสร้าง
- AI มีแนวโน้มไปทางโครงสร้างที่ใช้ single struct ถือทุกอย่างไว้ เพื่อให้ตอบ prompt ตรงหน้าได้ด้วยพิธีรีตองน้อยที่สุด
- แม้แต่ key handling ก็ไม่ได้แยกตาม view โดยปุ่ม
sตัวเดียวทำงานเป็น autoscroll ใน logs view, เป็น shell ใน pods view และเป็น container shell ใน containers view - คำขออย่าง “เพิ่ม shell support ให้ pods” ถูกทำโดยวิธีแทรก branch เข้าไปใกล้ ๆ global key handler เดิม
- ปุ่ม
Enterก็แยกพฤติกรรมของ contexts view, namespaces view, logs view และ generic drill-down logic อยู่ใน flat dispatch เดียว โดยอาศัยการเทียบ string ของm.currentGVR.Resource - ภายในไฟล์
model.goเดียว มีการใช้m.currentGVR.Resource ==มากกว่า 20 ครั้ง ราวกับเป็น type discriminator และทุกครั้งที่เพิ่ม view ใหม่ ก็ต้องไปแก้ handler หลายตัว - ทางเลือกคือใส่กติกาไว้ใน
CLAUDE.mdว่าห้ามเพิ่มฟิลด์ state เฉพาะ view ลงในApp/Model, ให้แต่ละ view เป็น struct แยก และเก็บ key binding ไว้ใน keymap ของ view ที่ active - ต้องมี guardrail อย่าง “การเพิ่ม view ต้องเท่ากับการเพิ่มไฟล์ และถ้าจำเป็นต้องแก้ view เดิมให้หยุดแล้วถามก่อน” เพื่อไม่ให้ AI เลือกทางลัดที่สุดด้วยการเพิ่ม branch
-
หลักการ 3: ภาพลวงตาของความเร็วทำให้ scope บานปลาย
- เดิมที k10s เป็นเครื่องมือสำหรับกลุ่มผู้ใช้แคบ ๆ ที่ดูแล GPU training cluster แต่ vibe-coding ทำให้ฟีเจอร์อย่าง pods, deployments, services, command palette, mouse support, contexts และ namespaces ดูเหมือนได้มาฟรี
- ผลลัพธ์คือมันขยายจากเครื่องมือที่โฟกัส GPU ไปเป็น TUI อเนกประสงค์สำหรับผู้ใช้ Kubernetes ทุกคน หรือแทบจะกลายเป็นการสร้าง k9s ใหม่อีกครั้ง
- ใน
keyMapแบบแบน มี binding เฉพาะหลาย view ปะปนกันอยู่ในโครงสร้างเดียว เช่นFullscreen,Autoscroll,ToggleTime,WrapText,CopyLogs,ToggleLineNums,Describe,YamlView,Edit,Shell,FilterLogs,FleetTabNext,FleetTabPrev - ทั้ง
AutoscrollและShellใช้ปุ่มsเหมือนกัน และแม้ dispatch จะ “ทำงาน” ได้เพราะตรวจ resource ปัจจุบัน แต่ก็ทำให้ไม่สามารถเข้าใจ keybinding ได้ในเชิงพื้นที่เฉพาะ - ความเร็วในการเขียนโค้ดดูเหมือน “shipping” แต่ทุกฟีเจอร์กลับสร้างต้นทุนเป็น branch ใหม่อีกหนึ่งจุดใน god object
- ทางเลือกคือระบุขอบเขตไว้ใน
CLAUDE.mdให้ชัดว่า k10s เป็นเครื่องมือสำหรับผู้ดูแล GPU cluster และรองรับเฉพาะ fleet, node-detail, gpu-detail และ workload เท่านั้น ไม่เพิ่ม generic resource views หรือฟีเจอร์ที่ซ้ำกับ k9s - AI อาจให้ line budget ได้ไม่จำกัด แต่ complexity budget ยังมีขีดจำกัด จึงต้องปฏิเสธ scope ที่เกินมาตั้งแต่ต้น
-
หลักการ 4: ข้อมูลแบบอิงตำแหน่งคือระเบิดเวลา
- k10s flatten resource ที่ได้จาก Kubernetes API ลงทันทีเป็น
type OrderedResourceFields []string - ฟังก์ชัน sort ของ fleet view มอง
ra[3]เป็น Alloc,ra[2]เป็น Compute และra[0]เป็น Name โดย identity ของคอลัมน์ขึ้นอยู่กับคอมเมนต์และลำดับคอลัมน์ในresource.views.jsonเท่านั้น - ถ้าเพิ่มคอลัมน์หนึ่งตัวระหว่าง Instance กับ Compute ใน
resource.views.jsonการ sort, conditional render และ drill target ที่อ้างra[2],ra[3]อาจเพี้ยนแบบเงียบ ๆ ได้ทันที - compiler ไม่รู้ความหมายของ
[]stringและ JSON config ก็แสดงพฤติกรรมอย่าง sort, conditional rendering หรือ custom drill target ไม่ได้ จึงทำให้โค้ด Go ต้อง hardcode positional assumption ไว้ - AI มักเลือก
[]stringหรือVec<String>เพราะส่งเข้าวิดเจ็ตตารางได้ง่ายกว่า ในขณะที่ typed struct ต้องมีพิธีรีตองล่วงหน้ามากกว่า จึงมักหลุดจากเส้นทางที่เร็วที่สุด - ทางเลือกคือเก็บข้อมูลแบบมีโครงสร้างเป็น typed struct อย่าง
FleetNode,PodInfoไปจนถึงก่อน render จริง และให้การ sort ทำงานบน named field แทน positional access อย่างrow[3] - ตัวอย่างโครงสร้างอย่าง
FleetNode { name, instance_type, compute_class, alloc }ช่วยให้ identity ของคอลัมน์ถูกแสดงด้วย type และทำให้สถานะที่เป็นไปไม่ได้อย่างการ sort คอลัมน์ผิดไม่สามารถเกิดขึ้นได้ - “Making impossible states impossible” เป็นสำนวนที่ใช้ในชุมชน Elm/Rust หมายถึงการออกแบบ type ให้ไม่สามารถประกอบ invalid state ได้ตั้งแต่แรก แทนที่จะค่อยไปตรวจ runtime
- k10s flatten resource ที่ได้จาก Kubernetes API ลงทันทีเป็น
-
หลักการ 5: AI ไม่ได้เป็นเจ้าของ state transition
- แกนสำคัญของโครงสร้าง Bubble Tea คือ state ต้องเปลี่ยนภายใน
Update()ที่ขับเคลื่อนด้วย message เท่านั้น แต่ k10s ฝ่าฝืนหลักนี้ - handler ของ
updateTableMsgคืนค่าtea.Cmdclosure และภายใน closure นั้นมีการเปลี่ยนฟิลด์ของModelโดยตรงผ่านคำเรียกอย่างm.updateColumns(m.viewWidth),m.updateTableData(),m.table.SetCursor(savedCursor) - Bubble Tea รัน
tea.Cmdใน goroutine แยก ดังนั้นขณะที่ closure อ่านและเขียนm.resources,m.table,m.viewWidth,View()บน main goroutine ก็อาจกำลังอ่านฟิลด์เดียวกันอยู่ - ไม่มี lock หรือ mutex และ
<-m.updateTableChanแค่รอสัญญาณอัปเดต แต่ไม่ได้ป้องกันไม่ให้View()อ่าน state ที่ถูกเขียนไปได้เพียงครึ่งเดียว - โครงสร้างนี้คือ data race อย่างชัดเจน และส่วนใหญ่จะดูเหมือนใช้งานได้ แต่บางครั้งก็แสดงผลพังเป็นระยะ
- ทางเลือกคือให้ background worker ไม่ mutate UI state โดยตรง แต่ส่ง typed message ผ่าน channel แล้วให้ main event loop รับ message มาปรับ state
- กติกาเรื่อง concurrency คือ background task ห้ามแก้ UI state โดยตรง ต้องส่งผลลัพธ์กลับมาเป็น typed message และ
render()/view()ต้องเป็น pure function ที่ไม่มี side effect, I/O หรือการทำงานกับ channel
- แกนสำคัญของโครงสร้าง Bubble Tea คือ state ต้องเปลี่ยนภายใน
กฎป้องกันที่ต้องใส่ใน CLAUDE.md และ agents.md
-
เงื่อนไขคงที่ของสถาปัตยกรรม
- แต่ละ view ต้อง implement
Viewtrait/interface และต้องไม่เข้าถึง state ของ view อื่น - ข้อมูล async ทั้งหมดต้องเข้ามาเป็น
AppMsgvariants และ background task ห้าม mutate field โดยตรง - การเพิ่ม view ใหม่ต้องไม่บังคับให้แก้ view เดิม
Appstruct ต้องเป็นเพียง thin router ที่ดูแล navigation และ message dispatch
- แต่ละ view ต้อง implement
-
กฎเรื่อง ownership ของ state
- ห้ามเพิ่มฟิลด์ state เฉพาะ view ลงใน
App/Modelstruct - แต่ละ view ต้องมีอยู่เป็น struct แยก และประกาศ key binding ของตัวเอง
- app ต้อง dispatch key ไปยัง active view และ keybinding ใหม่ต้องเพิ่มใน keymap ของ view นั้น ไม่ใช่ใน global handler
- ถ้าการเพิ่ม view บังคับให้ต้องแก้ view เดิม ต้องหยุดและขอการยืนยันก่อน
- ห้ามเพิ่มฟิลด์ state เฉพาะ view ลงใน
-
ขอบเขต
- k10s ต้องเป็นเครื่องมือสำหรับผู้ดูแล GPU cluster ไม่ใช่สำหรับผู้ใช้ Kubernetes ทุกคน
- view ที่รองรับต้องจำกัดอยู่ที่ fleet, node-detail, gpu-detail และ workload
- ห้ามเพิ่ม generic resource view อย่าง pods, deployments, services
- ห้ามเพิ่มฟีเจอร์ที่เป็นการทำซ้ำความสามารถของ k9s
- คำขอฟีเจอร์ที่ไม่ช่วยผู้ดูแลงาน GPU training jobs ควรถูกปฏิเสธ
-
การแทนข้อมูล
- ห้าม flatten structured data เป็น
[]string,Vec<String>หรือ positional array - ข้อมูลต้องไหลในรูป typed struct ไปจนถึงก่อน render call
- identity ของคอลัมน์ต้องมาจากชื่อ field ของ struct ไม่ใช่ index ของ array
- ฟังก์ชัน sort ต้องทำงานบน typed field ไม่ใช่ positional access อย่าง
row[3] - การสร้าง string สำหรับแสดงผลควรเกิดขึ้นภายในฟังก์ชัน
render()/view()เท่านั้น
- ห้าม flatten structured data เป็น
-
กฎเรื่อง concurrency
- background task อย่าง watcher, scraper, API call ห้าม mutate UI state โดยตรง
- background task ต้องส่งผลลัพธ์กลับมาเป็น typed message ผ่าน channel
- main event loop เท่านั้นที่ควรนำ received message ไปใช้ในการ mutate state
render()/view()ต้องเป็น pure function ที่ไม่มี side effect, I/O หรือการทำงานกับ channel- หากต้องเปลี่ยน state จากผลของ async work ต้องนิยาม
AppMsgvariant ใหม่
วิธีการสร้างใหม่
- k10s จะถูกเขียนใหม่ด้วย Rust โดยเหตุผลไม่ใช่เพราะ Rust ดีกว่า แต่เพราะเป็นภาษาที่รู้สึกว่าสามารถบังคับทิศทางได้ด้วยตัวเอง
- ในภาษาที่ใช้มามากพอ เราจะสัมผัสได้ว่ามีอะไรผิดปกติก่อนที่จะอธิบายเป็นคำพูดได้ และสัมผัสแบบนี้เป็นสิ่งที่ vibe-coding แทนไม่ได้
- เมื่อ AI สร้างโค้ดที่ดูน่าเชื่อถือขึ้นมา เราจำเป็นต้องมีความสามารถในการจับได้ว่ามันเป็นขยะหรือไม่
- ในเวอร์ชันใหม่ มนุษย์จะทำ design work อย่าง concrete interface, message type และ ownership rule ด้วยมือตัวเองก่อนเริ่มเขียนโค้ด
- วิธีคิดเปลี่ยนจากเดิมที่ปล่อยให้ AI ตัดสินใจเรื่องสถาปัตยกรรมผิดพลาด ไปเป็นการกำหนดเอกสารเหล่านั้นไว้ก่อน prompt แรก
- ลิงก์ไปยัง TUI เดิมและโปรเจกต์อยู่ที่ k10s Github และ K10S.DEV
เพิ่มเติม
- Bubble Tea คือ Go TUI framework ที่อิง The Elm Architecture และปัญหาด้าน architecture ของ k10s ไม่ได้มาจาก Bubble Tea แต่เกิดจากการ implement ฝั่ง k10s เอง
- “Making impossible states impossible” เป็นสำนวนจากชุมชน Elm/Rust ที่หมายถึงการออกแบบ type เพื่อไม่ให้ invalid state ถูกประกอบขึ้นได้ แทนที่จะตรวจจับใน runtime
- เหมือนกับที่ “em-dash” อาจเป็นกลิ่นของงานเขียนจาก AI ในงานเขียนโค้ด “god-object” ก็อาจเป็นกลิ่นหนึ่งเช่นกัน และ vibe-coding อาจทำให้ต้นทุนของการลงมือทำดูถูกเกินจริง จนนำไปสู่การเสียโฟกัสและบวมพองของระบบ
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
คนที่บอกว่าโค้ดที่ถูกสร้างมานั้นใช้ได้ ส่วนใหญ่คือ คนที่ไม่ได้อ่านโค้ดนั้น
มาตรการบรรเทาที่บทความเสนอมาก็อยู่ได้ไม่นาน เวลาที่ออกแบบระบบหรือคอมโพเนนต์ มักจะมี เงื่อนไขคงตัว เกิดขึ้น เช่น “วิวหนึ่งจะไม่เข้าถึงสถานะของอีกวิว” แล้วสักวันก็ต้องเพิ่มฟีเจอร์ที่ชนกับเงื่อนไขนั้น
ตอนนั้นก็มักต้องเลือกว่าจะยอมทิ้งฟีเจอร์ไป, ฝืนแปะมันลงบนเงื่อนไขคงตัวแบบขัด ๆ และไม่มีประสิทธิภาพ, หรือไม่ก็เปลี่ยนเงื่อนไขคงตัวนั้นเอง การเลือกนี้ไม่ใช่แค่ปัญหาเรื่องบริบท แต่เป็นเรื่องของวิจารณญาณ และโมเดลปัจจุบันพลาดเรื่องนี้บ่อยเกินไป
ถ้าระบุข้อจำกัดทางสถาปัตยกรรมไว้ เอเจนต์ก็มักจะบิดตัวให้เข้ากับข้อจำกัดนั้นแม้ในเวลาที่ควรต้องเปลี่ยนมัน แล้วสร้างโค้ดซับซ้อนที่บำรุงรักษาไม่ได้ ถ้าไม่อ่านละเอียดกว่าโค้ดที่คนเขียน สุดท้ายก็จะได้ “โค้ดที่กินตัวเอง” และรู้ตัวตอนสายเกินไป
แก่นคือการหาว่าจุดไหนที่ AI ลำบาก แล้วทำให้มันง่ายลง เช่น ต้องมี บริบทที่เล็กมาก, การแยกโมดูลที่มีขอบเขตชัดเจน, โมดูลบริสุทธิ์ที่แยกจาก I/O, การซ่อนไว้หลังอินเทอร์เฟซ, เทสต์ 100 ตัวที่รันจบภายใน 1 วินาที, benchmark เป็นต้น
AI ทำงานได้ดีเมื่อมีขอบเขตและบริบทเล็ก ๆ ถ้าไม่ให้สิ่งนั้น ประสิทธิภาพก็จะแย่ลง และคนที่ใช้เครื่องมือต้องรับผิดชอบ
ไม่มีสเปกไหนทนความเป็นจริงได้ตลอดไป ต่อให้ศึกษากับออกแบบมาดีแค่ไหน สุดท้ายเงื่อนไขคงตัวบางข้อในสเปกก็จะถูกพิสูจน์ว่าผิด
เวลาเจอสถานการณ์นี้ระหว่างพัฒนา คนสามารถถอยออกมาหนึ่งก้าวแล้วคิดใหม่ได้ว่าเงื่อนไขคงตัวนั้นผิดหรือไม่ และถ้าเปลี่ยนจะกระทบอะไรบ้าง ตรงกันข้าม AI มักจะพยายามยัดวิธีแก้แบบแฮ็ก ๆ ภายใต้สมมติฐานหรือการออกแบบที่ผิด และขาดความเข้าใจลึกพอที่จะประเมินภาพรวมใหม่
มันอาจดีขึ้นได้ด้วย workflow และการตรวจสอบที่ดี แต่ไม่ใช่สิ่งที่เครื่องมืออย่าง Claude Code ทำได้ดีโดยปริยาย และมันมีข้อจำกัด
ตอนแรกตั้งหลักการที่เข้มไว้ แล้วลองย้ายบางจุดด้วยมือเพื่อสร้างความมั่นใจ งานย้ายทั้งหมดใหญ่และแพงมากจนเลื่อนมาเกือบ 10 ปี เลยพยายามเร่งด้วย AI เพื่อลดต้นทุน
AI พอใช้ได้กับ 80% ที่เป็นกรณีเชิงกลและเรียบง่าย ส่วนอีก 20% ต้องเปลี่ยนเฟรมเวิร์กเอง ส่วนใหญ่เป็นการเปลี่ยนเล็ก ๆ อย่างเพิ่ม field ใน API แต่มีหนึ่งสองกรณีที่ต้อง ออกแบบแนวคิดใหม่
backend ของระบบหนึ่งสร้างข้อมูลบางอย่างได้ใน 99% ของกรณี แต่มีบางกรณีสำคัญที่ตามตรรกะแล้วสร้างไม่ได้และต้องรับรายงานจากภายนอก ทว่าการ optimize สำคัญตัวหนึ่งถูกสร้างอยู่บนสมมติฐานว่า “สิ่งนั้นเป็นไปไม่ได้”
เครื่องมือ AI ไม่ตรวจพบสถานการณ์นี้และเพิ่ม logic การย้ายเข้าไปเหมือนมันจะทำงานได้ถูกต้อง โชคดีที่วิธี deploy ทำให้ยังไม่กลายเป็นบั๊กใน production แต่ระหว่างที่ถามคำถามให้ถูกกับทีมพาร์ตเนอร์ ก็พบว่าความต้องการแบบเดียวกันมีอยู่ที่อื่นด้วย
สุดท้ายปัญหาไม่บานปลายเพราะมีคนคนหนึ่งลงลึกกับมันจริง ๆ เครื่องมือตรวจสอบและโมเดลที่ฉลาดกว่านี้อาจทำให้งานย้ายแบบนี้ง่ายขึ้นในอนาคต แต่ตอนนี้โค้ดที่สร้างขึ้นแม้จะดูสวยก็ยังเปราะ และต้องคอยเฝ้าใกล้ชิดตลอด
มีแพตเทิร์นสถาปัตยกรรมแปลก ๆ ที่ใช้มาราวสองเดือน ใช้แต่ละครั้งก็รู้สึกขัด ๆ เล็กน้อย จนเมื่อคืนถึงเพิ่งตระหนักว่ามันไม่ใช่ abstraction ที่ดี และมีวิธีแยกที่ดีกว่า
ถ้าปล่อยให้ LLM สร้างโค้ด ความรู้สึกขัดนั้นจะจางลงมาก ทำให้ใช้เวลานานกว่าจะสังเกตปัญหาและหาทางแก้ได้ ส่วนรอบนอกจะให้สร้างก็ได้ แต่ฟังก์ชันแกนหลักส่วนใหญ่ยังควรเขียนเอง
ต่อให้เขียนเป็นภาษารูปแบบที่แม่นยำ LLM ใต้เอเจนต์ก็ยังขาดความสามารถจะเข้าใจว่าทำไมเงื่อนไขคงตัวนั้นถึงจำเป็นและสำคัญ อาจมี LLM ที่เชื่อม token กับ formal spec และเขียนพิสูจน์ได้ออกมาในอนาคต แต่โค้ดประหลาดที่เกิดจากส่วนไม่เป็นทางการของ prompt ก็ยังจะมีต่อไป
แค่เติมข้อจำกัดและ prompt ลงในรายการเชิงเทคนิคหรือสเปกไม่ได้ช่วยหยุดมัน ต่อให้ทำกับดักดีขึ้น สิ่งมีชีวิตก็ยังหนีออกไปได้
ปัญหาคือ การพองตัวของโค้ด ที่เกิดจากการเอาโค้ดมาแปะเพิ่มเพื่อให้ตรงกับ prompt หรืองาน บ่อยครั้งโค้ดที่น้อยกว่ากลับดีกว่า และต้องมีคนที่คาดเดาได้ว่าคนอื่นต้องการและคาดหวังอะไร ตัว generator นั้นดี แต่ต้องใช้แบบยั้งมือกว่านี้ เหมือนสายดับเพลิง
ตอน Copilot ยังเติมอัตโนมัติแค่บรรทัดเดียว คนก็บอกว่า “ยังไงทั้งฟังก์ชันก็ต้องเขียนเอง” พอมันเติมทั้งฟังก์ชันได้ ก็กลายเป็น “logic รอบฟังก์ชันยังไงก็ต้องเขียนเอง” พอทำส่วนนั้นได้อีก ก็เป็น “ฟีเจอร์ยังไงก็ต้องเขียนเอง”
ตอนนี้มันทำฟีเจอร์ได้แล้วก็เลยบอกว่า “แต่ สถาปัตยกรรม ยังไงก็ต้องเขียนเอง” ไม่รู้ว่าโมเดลพวกนี้จะแก้เรื่องสถาปัตยกรรมได้ไหม แต่การที่ความคาดหวังขยับไปเรื่อย ๆ ก็น่าสนใจ
ต่อให้ AI เติมแค่บรรทัดเดียว เติมทั้งฟังก์ชัน หรือเติมทั้งฟีเจอร์กับ ticket คุณก็ยังต้อง อ่านและเข้าใจโค้ด อยู่ดี
ผมใช้ AI ตลอดและมันก็ดีขึ้นเรื่อย ๆ แต่ก็ยัง รีวิวทุกบรรทัด ระดับบรรทัดเดี่ยววันนี้ก็ยังไม่ถึงกับดีกว่า tab autocomplete ของปีก่อนเสมอไป บางทีก็ดีมาก บางทีก็แย่มากจริง ๆ
LLM ยอดเยี่ยมกับการพัฒนาซอฟต์แวร์ แต่จะยอดเยี่ยมเมื่อ ไม่ปล่อยให้มันเขียนสถาปัตยกรรม คุณควรสร้างโมดูล, struct, enum เอง และถ้าเป็นไปได้ก็ควรเพิ่ม field กับ variant เองด้วย
วิธีที่ดีคือใส่ doc comment ให้แต่ละ struct, enum, field, module แล้วให้ LLM อ้างอิงโมดูลและโครงสร้างข้อมูลเหล่านั้น จากนั้นค่อยให้มันเติม body ของฟังก์ชันที่ต้องการ เป็นต้น
ต่อให้บอกหลายครั้งว่า “ห้ามมี blocking บน critical path เด็ดขาด” LLM ก็ยังใส่ blocking ลงบน critical path และต่อให้บอกว่า “ถ้าทำ X ต้องมีเทสต์ประเภท Y” มันก็ทำแค่ X แล้วลืมเทสต์
คนเองก็ทำตามคำสั่งได้ไม่ 100% แต่ LLM สุ่มมากกว่า ความผิดพลาดของคนจะไม่ค่อยเป็นการทำสิ่งตรงข้ามกับที่ต้องการแบบเป๊ะ ๆ บ่อยเท่า
LLM มองเห็นเงื่อนไขคงตัวสำคัญในโค้ดแล้วก็ยังสร้างทางเลี่ยง เขียนเทสต์ที่ทำให้ความล้มเหลวดูเหมือนความสำเร็จ และบอกว่าทำตามที่ขอแล้วก่อนจะฝังมันไว้ใน commit 5,000 บรรทัด
ผมมั่นใจว่า LLM นั้นยอดเยี่ยมและคืออนาคต นั่นจึงเป็นเหตุผลที่กำลังสร้างภาษา https://GitHub.com/Cuzzo/clear สำหรับมัน เราต้องก้าวข้ามปัญหาของภาษาที่บังคับให้ต้องใช้บริบทระดับ global ในที่ที่ไม่ควรต้องใช้ จึงจะทำงานร่วมกับมันได้ง่ายขึ้น
มีความสำเร็จอยู่บ้าง แต่ก็น่าหงุดหงิดมากจนบางทีก็สงสัยว่าคุ้มกับสุขภาพจิตหรือไม่
ไม่ได้แปลว่าสถาปัตยกรรมไม่สำคัญ แต่หมายถึงสถาปัตยกรรมที่เข้ากับเมื่อวาน ไม่จำเป็นต้องยังเข้ากับวันนี้เสมอไป
เวลาใช้ coding agent ผมตั้งกฎไว้ไม่กี่ข้อ
ข้อแรก ถ้าจะใช้เอเจนต์สร้างโค้ด มันต้องเป็นสิ่งที่ถ้ามีเวลา ผมมั่นใจอย่างยิ่งว่าตัวเองเขียนให้ถูกเองได้
ข้อสอง ถ้าไม่ใช่แบบนั้น ก็จะไม่เดินต่อจนกว่าจะเข้าใจสิ่งที่ถูกสร้างขึ้นมาทั้งหมด และสามารถเขียนซ้ำเองได้
ข้อสาม ถ้าผิดกฎข้อสอง ก็อาจสร้าง หนี้ทางการรับรู้ ได้ แต่ต้องชำระคืนทั้งหมดก่อนจะประกาศว่าโปรเจกต์เสร็จ
ยิ่งหนี้สะสมมาก ก็ยิ่งมีโอกาสที่คุณภาพของโค้ดที่สร้างภายหลังจะตกลง และมันให้ความรู้สึกเหมือนดอกเบี้ยทบต้น สำหรับโปรเจกต์ส่วนตัว วิธีนี้ทั้งสนุก ได้เรียนรู้มาก และสุดท้ายก็เหลือ codebase ที่ตัวเองเข้าใจได้อย่างสบายใจ
ต้องหาจุดสมดุลที่ยังเชื่อมต่อกับ codebase อยู่ โดยไม่กลายเป็นคอขวดของทีม
Claude เป็นนักคณิตศาสตร์ระดับปริญญาเอก ส่วนผมไม่ใช่ แต่ผมรู้ชัดเจนว่าคำตอบที่ต้องการควรมีคุณสมบัติอะไร และจะทดสอบความถูกต้องอย่างไร ดังนั้นผมจึงเก็บคำตอบของ Claude ไว้แทนคำตอบแบบง่ายและไร้เดียงสาของตัวเอง พร้อมเขียนไว้ใน pull request ว่าเป็นแบบนั้น และทุกคนก็มองว่านี่เป็นการตัดสินใจที่ถูกต้อง
เลยสงสัยว่าควรมีข้อยกเว้นสำหรับกรณีแบบนี้ไหม ถ้า AI เก่งกว่าผมมากทั้งในคณิตศาสตร์ขั้นสูงและการเขียนโค้ด คำถามที่น่าสนใจกว่าคือผมจะเลิกเขียนโค้ดเองทั้งหมดหรือไม่ แม้จะสูญเสียความสามารถในการตัดสินโค้ดโดยตรงไป แต่ยังตัดสินเทสต์ได้
เพราะหนี้ที่สะสมอยู่นั้นก็คือการขาดความเข้าใจในโค้ดโดยตรง จึงแม่นกว่า
ไม่เข้าใจว่าทำไมอยู่ ๆ AI ถึงต้องถูกปฏิบัติไม่เหมือนอย่างอื่น
สุดท้ายก็ต้องตัดสินจากความเสี่ยงและผลตอบแทน ต้องชั่งดูว่าถ้าผิดจะเสียหายอะไร มีโอกาสแค่ไหนที่จะตรวจเจอจากเทสต์และรีวิว และถ้าถูกจะได้ประโยชน์อะไร ไลบรารีกับบริการภายนอกก็เหมือนกัน
กฎการเงินที่ซับซ้อนในสัญญาคริปโตที่แก้ไขไม่ได้และไม่มีเทสต์ กับ viewer สำหรับแสดงผลข้อมูล log ภายใน เป็นความเสี่ยงคนละระดับกันโดยสิ้นเชิง
ในทางทฤษฎีมันดูดี แต่ในความเป็นจริงคุณจะเลือกทางลัดทางความคิดโดยไม่รู้ตัวอยู่เสมอ
ถ้าเทียบตอนแก้ปัญหาใน codebase ที่ไม่คุ้นเคยด้วยตัวเอง กับตอนที่คิดว่าตัวเอง “เข้าใจอย่างสมบูรณ์” สิ่งที่เอเจนต์ทำไปแล้ว ปริมาณที่ยังอยู่ในหัวหลังผ่านไปหนึ่งสัปดาห์นั้นต่างกันมาก ถ้าทำเอง มันจะสะสมเป็นความรู้ทั่วไปและส่วนสำคัญมักจะยังอยู่ แต่ถ้าพยายามครอบครองสิ่งที่เอเจนต์ทำให้เหมือนเป็นของตัวเอง แม้ตอนนั้นจะเหมือนเข้าใจ แต่จะลืมเร็วมาก
เพราะงั้นผมจึงสรุปว่าในกรณีแบบนี้ความช่วยเหลือจาก LLM ส่วนใหญ่เป็นโทษต่อเป้าหมายของผม แม้ยังไม่ต้องนับเรื่องเวลาและแรงกดดันทางธุรกิจอื่น ๆ ก็ตาม
ผมก็เจอแบบเดียวกัน
มันหลอกกันแบบนี้ AI ทำฟีเจอร์ได้เยอะใน codebase ที่ดี และมันยังดูเร็ว ปลอดภัย และแม่นยำกว่า โดยเฉพาะในโดเมนที่เราไม่ค่อยรู้จัก
เวลาผ่านไป codebase โตขึ้น ใช้เวลาสำรวจมากขึ้น และอัตราความล้มเหลวสูงขึ้น คุณไม่อยากยอมรับเลยยิ่งเร่งมันหนักขึ้น จนสุดท้ายต้องหยุดเมื่อการเปลี่ยนแปลงแทบทำไม่ได้แล้ว
พอกลับไปดูโค้ด คำว่า spaghetti ยังเบาไป มันเหมือน กำแพงเมืองจีน
สุดท้ายผมลบออกไป 75,000 บรรทัดจากทั้งหมด 140,000 บรรทัด และรู้สึกว่า 3 เดือนที่หมกมุ่นกับ agent coding อย่างหนักนั้นสูญเปล่า สร้างฟีเจอร์ที่ไร้ประโยชน์ เพิ่มบั๊ก สูญเสีย mental model ของโค้ด พลาดการตัดสินใจยาก ๆ ที่จะมองเห็นได้ก็ตอนอยู่ในโค้ดจริง ๆ และสุดท้ายก็ทำให้ผู้ใช้ผิดหวัง
ไม่ได้จะประชดนะ แต่อยากรู้จริง ๆ ว่าความคาดหวังตอนแรกคืออะไร และมันมาจากไหน
ดูเหมือน LLM จะถูกคาดหวังต่างออกไป ถ้าคุณเอาคำอธิบายฟีเจอร์แบบย่อส่งให้ “นักพัฒนา” สุ่มคนหนึ่งที่รู้จักกันแค่ออนไลน์ แล้วได้กอง implementation ที่พังครึ่ง ๆ กลับมา คงไม่มีใครแปลกใจ
แต่บางครั้งคนกลับคาดหวังปาฏิหาริย์จากเครื่องจักรที่เพ้อเจ้อยืดยาว ทั้งที่ไม่คาดหวังแบบนั้นจากมนุษย์ด้วยซ้ำ อยากรู้ว่าความเชื่อมั่นนั้นมาจากไหน
เหมือนเมืองใหญ่ที่เป็นการรวมกันของเมืองเล็ก ๆ คุณมีแผนที่ แล้วก็ซูมเข้าไปทำงานในพื้นที่เฉพาะนั้น ไม่จำเป็นต้องรู้รายละเอียดทุกอย่างของนิวยอร์กเพื่อไปดื่มกาแฟหนึ่งแก้ว
การสร้าง สถาปัตยกรรมที่ดีและดูแลได้ เป็นความรับผิดชอบของคนใช้เครื่องมือ AI ไม่ได้ขัดขวางสิ่งนั้น และถ้าถือเครื่องมือให้ถูก มันอาจช่วยด้วยซ้ำ
เช่น ถือว่าโค้ด AI ที่สร้างมานั้นเป็น legacy code ทันที แล้ววางขอบเขตการ encapsulate ที่แข็งแรงกับอินเทอร์เฟซที่นิยามชัด ก่อนค่อยรวมเข้ากับกระบวนการที่ manual มากขึ้น
มันมีสเปกตรัมตั้งแต่ prompt ครั้งเดียวไปจนถึงการสร้างโค้ด inline และวิธีที่เหมาะก็ขึ้นกับปัญหาและตำแหน่งของมันใน codebase
การสร้างแบบครั้งเดียวเหมาะกับช่วง prototype ที่ต้องทำสเปกเดิมซ้ำบ่อย พอ prototype เริ่มนิ่งก็ลดระดับลงมาเป็นการสร้างระดับโมดูลหรือไฟล์ให้เป็นระบบมากขึ้น และระหว่างนั้นก็ต้องรักษา mental model ที่ดีไว้ในชั้นนั้นต่อไป
ถ้าอ่านแต่ไม่เข้าใจ ก็ให้มันใส่คอมเมนต์ละเอียดให้แต่ละส่วนก็ได้ แต่ถ้ารู้อยู่แล้วว่าโมเดลจะลำบากขึ้นเมื่อ codebase ใหญ่ขึ้น งั้นยิ่งซับซ้อนขึ้นก็ยิ่งต้องตรวจผลลัพธ์เข้มงวดขึ้น
คือสร้างเกาะของโค้ดคุณภาพสูงขึ้นมา ให้ AI ช่วยรื้อฟื้นเจตนาของนักพัฒนาและกฎธุรกิจ และสร้าง seam กับ unit test ในโมดูลเป้าหมาย
AI ไม่จำเป็นต้องมีไว้เพื่อเพิ่ม throughput อย่างเดียว มันอาจเป็นเครื่องมือสำรวจและ refactor ที่ยืดหยุ่น เพื่อช่วยการเขียนด้วยมือหรือการลงมือผ่านเอเจนต์ในภายหลังก็ได้
ทุกครั้งที่เห็นบทความแบบนี้ ผมอดเปรียบเทียบความเร็วที่คนบอกว่าได้จาก AI กับความเร็วที่ผมได้จากการเขียนโค้ดเองไม่ได้
บังเอิญว่าผมทำโปรเจกต์ 3D MMO มา 7 เดือน ตอนนี้เล่นได้แล้ว คนก็สนุกด้วย กราฟิกก็ดี และยัดคนเข้าเซิร์ฟเวอร์ได้หลายร้อยคนสบาย ๆ สถาปัตยกรรมก็ค่อนข้างดี ทำให้เพิ่มฟีเจอร์ได้ง่าย และน่าจะออกได้หลังพัฒนารวมประมาณ 1 ปี
แต่บทความต้นฉบับใช้เวลา 7 เดือนกับ vibe coding แล้วยังทำ TUI พื้นฐานไม่ได้เลย ความเร็วในการทำฟีเจอร์อาจรู้สึกว่าสูง แต่สำหรับ UI พื้นฐานแบบนี้มันช้าอย่างไม่น่าเชื่อ มีไลบรารี TUI ดี ๆ มากมาย และมันก็แค่ประเภทงานที่เอาข้อมูลที่ต้องใช้มาใส่ตาราง ซึ่งเขียนเองไม่กี่สัปดาห์ก็ได้
เวลาใช้ AI มันให้ความรู้สึกเหมือนคืบหน้าเร็วมาก แต่ในความจริงดูเหมือนหลายครั้งจะช้ากว่าการเขียนเองมาก ข้อมูลด้านผลิตภาพก็ดูเหมือนสนับสนุนว่าผู้ใช้ AI รู้สึกว่าเร็วขึ้น แต่ผลผลิตจริงกลับน้อยลง
เวลาส่วนใหญ่ของงานพัฒนาซอฟต์แวร์หมดไปกับการประชุมเพื่อทำความคาดหวังของผู้มีส่วนได้ส่วนเสียกับแนวทางแก้ให้ตรงกัน ซึ่ง AI แทบไม่ช่วยเลยในมุมนั้น ถ้าจะเทียบจำนวนชั่วโมงคนตั้งแต่ข้อเสนอไปจนเข้า test loop ก็คงได้ผลลัพธ์ที่น่าผิดหวัง
แต่ในงานแก้ปัญหา, แก้บั๊ก, และ implement วิธีแก้ที่ได้รับอนุมัติแล้ว ผมรู้สึกว่าดีขึ้นอย่างน้อย 10 เท่าเมื่อเทียบกับก่อนหน้า ไม่ใช่แค่เวลาอย่างเดียว ความสามารถในการตีความพฤติกรรมที่สังเกตได้และสืบสวนปัญหาก็ดีขึ้นด้วย
อย่างไรก็ตาม ก็มีคนที่ใช้ AI แล้วสร้างผลลัพธ์ที่ถูกต้องและมีคุณค่าไม่ได้ ถ้าคุณรู้ชัดว่าต้องการอะไรและต้องการมันอย่างไร AI จะช่วยได้มาก มันทำสิ่งที่ผมต้องทำอยู่แล้วได้เร็วกว่า แต่ถ้าคุณยังไม่รู้แน่ชัดว่าต้องการอะไร AI จะเป็นโทษต่อความคืบหน้า
เวลาคนโชว์สิ่งที่ทำด้วย LLM มันไม่ค่อยน่าประทับใจ เพราะส่วนใหญ่เป็นของที่ถ้าทำเองด้วยมือก็ใช้เวลาไม่นานมากอยู่แล้ว
ผมก็ไม่ได้สังเกตเห็นว่าซอฟต์แวร์ที่น่าประทับใจเพิ่มขึ้น ซึ่งดูสอดคล้องกับข้อเท็จจริงที่ว่า LLM ตอนนี้ถูกใช้แก้ปัญหาง่าย ๆ มากกว่าปัญหาสำคัญ
อีกเรื่องที่พูดถึงกันน้อยคือ คุณภาพโค้ด
codebase ที่ทำแบบ vibe coding เป็นตัวอย่างยอดเยี่ยมว่า LLM ไม่ได้เก่งการเขียนโค้ดขนาดนั้น มันแก้ความผิดพลาดของตัวเองแล้วก็สร้างซ้ำอีกทันที และการใช้แพตเทิร์นก็ไม่สม่ำเสมอ
ช่วงหลัง ๆ Claude ยังเลือกสไตล์โค้ด “น่าสนใจ” ที่ไม่เข้ากับสไตล์ของ codebase ปัจจุบันอีกด้วย
ต้องใช้ภาษาสไตล์ “senior developer” เพื่อคอยกันไม่ให้มันทำซ้ำแบบนั้น
ส่วนที่ว่า “ก่อนเขียนโค้ดจะออกแบบอินเทอร์เฟซ, ชนิดข้อความ, กฎ ownership ที่เป็นรูปธรรมด้วยตัวเอง” นั่นแหละคือส่วนยากของการเขียนโค้ด
ถ้ามีสถาปัตยกรรมแล้ว การเขียนโค้ดนั้นง่ายมาก ถ้าไม่ได้เขียนเอง ก็จะสังเกตได้ยากว่าคุณออกแบบ API ที่ยอมให้เป็น null ได้ แต่ฐานข้อมูลไม่ยอม หรือถึงจะยอมก็ยังมีปัญหาเล็กอื่นที่หลุดไป
ผมไม่เข้าใจว่าทำไมเขียนบทความนี้แล้วยังไม่ตระหนักว่าปัญหาคือ AI ไม่ใช่แค่เพราะปล่อยให้ AI ทำสถาปัตยกรรม แต่เพราะไม่ได้เฝ้าดูทุกอย่างที่ AI ทำอย่างใกล้ชิดด้วย
AI คือ ตัวสร้างโค้ด ที่ถูกแต่งภาพให้ดูสวย และทุกอย่างที่มันทำต้องถูกตรวจสอบ ส่วนที่ยากของวิศวกรรมซอฟต์แวร์ไม่ใช่การเขียนโค้ด แต่คือทุกอย่างนอกเหนือจากนั้น
นักพัฒนาที่มองว่าการเขียนโค้ดยากจะชอบ AI coding มาก เพราะสิ่งที่เคยยากนั้นง่ายขึ้นแล้ว
ส่วนคนที่มองว่าการเขียนโค้ดนั้นง่าย สำหรับเขาโค้ดคือเรื่องของ abstraction, การบำรุงรักษา, และการขยายต่อ สิ่งที่ยากคือการวางรากฐานที่สมเหตุสมผลให้ซอฟต์แวร์เติบโตได้ และเมื่อหา abstraction ที่ถูกต้องได้ ที่เหลือก็จะค่อนข้างง่าย
สำหรับคนกลุ่มนี้ AI coding เป็นเครื่องมือที่มีประโยชน์ แต่ไม่ใช่เครื่องมือวิเศษ ผู้เขียนบทความต้นฉบับสังเกตเห็นข้อจำกัดของ AI แล้ว จึงอยู่ในกลุ่มที่สอง และได้เห็นส่วนยากที่ AI ทำไม่ได้
ฝั่งหนึ่งคือคนที่ใช้ tab autocomplete ขั้นเทพหรือ chatbot ข้างหน้าต่าง แล้วก็ยังรีวิวทุกอย่างอย่างชัดเจน ส่วนอีกฝั่งคือการโปรโมต editor แบบใหม่ที่ประสานงานเอเจนต์เป็นสิบตัวเหมือน Steve Yegge และดูเหมือนจะไม่ได้อ่านโค้ดส่วนใหญ่เลย: https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16d...
กลุ่มแรกยังคิดลึกเรื่องการออกแบบ, อินเทอร์เฟซ, โครงสร้างข้อมูล และรีวิวอย่างเข้มข้น ส่วนกลุ่มที่สองไม่ทำแบบนั้น ซึ่งน่ากังวลกว่า
มันทำตามแนวทาง plan → red/green/refactor โดยตัวแผนนั้นเองดูน่าเชื่อถือและมีเหตุผลทีเดียว เพราะดูดข้อมูลจากเอกสารและการถกเถียงในฟอรัมมาหมด
ปัญหาคือพอเริ่มงาน ก็ต้องมีจุดที่เอกสารกับ implementation ไม่ตรงกันจริง ๆ เสมอ อาจเพราะไม่เคยมีใครใช้ชุดเครื่องมือแบบนั้นมาก่อน เอกสารล้าสมัย หรือแค่เป็นบั๊กธรรมดา
ถึงอย่างนั้น ถ้าเป้าหมายของโปรเจกต์หรือฟีเจอร์ชัดพอ และรันกับเทสต์ในเครื่องได้ เอเจนต์ก็สามารถวนอยู่ในทางตันเชิงสถาปัตยกรรมแล้วหาทางออกได้ มันยังไปดู dependency, โค้ดไลบรารี และเสนอแพตช์ upstream ได้ด้วย ซึ่งคล้ายกับสิ่งที่ผมทำเวลา debug เชิงลึก
เพราะงั้นผมค่อนข้างพอใจกับการสั่งและคุมแทนที่จะลงมือทำงานน่าเบื่อเอง แต่สมาชิกทีมหลายคนกลับไม่ขุดปัญหาสถาปัตยกรรมลึกขนาดนี้ และมักใช้วิธี “เอสคาเลตไปหาสถาปนิก” เป็นค่าเริ่มต้น ซึ่งระยะยาวคงไม่ดี
หน้าต่างเวลาที่เรายังรันและเข้าใจทุกอย่างได้ดูเหมือนกำลังปิดลงเร็วมาก ถึงอย่างนั้นก็อาจเหมือนกับที่เราใช้คอมไพเลอร์โดยไม่ต้องเข้าใจกระบวนการแปลงเป็น machine code หรือการทำนาย branch กับการ cache ของ CPU สมัยใหม่ทั้งหมด เราอาจค่อย ๆ ปรับตัวด้วยการสร้างเครื่องมือและเฟรมเวิร์กใหม่
จากมุมของคนที่ยังมีประสบการณ์ด้านโค้ดไม่มาก ผมกำลังเรียนรู้มากกว่าที่เคยจากการตรวจผลลัพธ์และดูว่าอะไรถูกอะไรผิด
เพราะงั้นผมก็ไม่คิดว่ามันจะดีขึ้นแบบก้าวกระโดดเร็ว ๆ นี้ เวลามีคนถามว่า “ทำไมผลลัพธ์จาก Claude ของคุณถึงดูดีจัง” คำตอบของผมคือ “ผมดูอย่างระมัดระวัง หาเจอว่าตรงไหนมีปัญหา แล้วบอก Claude ให้แก้” แค่นั้นจริง ๆ แต่พอพูดแบบนี้ แววตาคนฟังก็เริ่มลอยแล้ว
มันเหมือน Google ทำให้ค้นหาข้อมูลง่ายขึ้น แต่ไม่ได้ลบปัจจัยมนุษย์ในการแยกแยะข้อมูลดีและข้อมูลแย่ออกไป
คิดปัญหาก่อน ออกแบบโครงสร้างกับ API ก่อน แล้วค่อยให้ AI ลงมือ implement
ชื่อเรื่องบอกว่า “กลับมาเขียนโค้ดด้วยมือ” แต่สิ่งที่ทำจริงคือ “ทำ งานออกแบบ ด้วยมือก่อนที่โค้ดจะถูกเขียน”
จากนั้นก็ดูเหมือนยังปล่อยให้ Claude เป็นคนสร้างโค้ดอยู่ดี
ที่หนักกว่านั้นคือยากจะเข้าใจว่า 7 เดือนที่ผ่านมาเขาคิดว่าโปรเจกต์ vibe coding ทำงานได้ดี ทั้งที่ไม่ได้ดู source code ที่ถูกสร้างขึ้นมาเลย และยังซื้อโดเมนไปแล้วด้วย
ถ้าเป็น side project และค่อย ๆ ตรวจตาม diff ไปทีละนิด การไม่ได้ดูโค้ดลึกมากก็ไม่ถึงกับแปลกสุดขั้ว แน่นอนว่าเป็นวิธีทำงานอีกแบบ แต่ก็ไม่ถึงขั้นบ้า
มันให้ความรู้สึกเหมือนกำลังดูนักพัฒนา สปีดรัน บทเรียนของการจัดการโปรเจกต์และการจัดการผลิตภัณฑ์
ตอนนี้พวกเขาเริ่มเห็นแล้วว่าสเปกมีประโยชน์ และการเขียนโค้ดผิดจำนวนมากไม่ได้ทำให้โปรเจกต์เร็วขึ้น นักพัฒนามักรำคาญว่าการประชุมและการถกเถียงขัดขวางการเขียนโค้ด แต่กระบวนการเหล่านั้นมักมีไว้เพื่อกันไม่ให้ทุกคนเขียนสิ่งที่ผิดมากขึ้น
พวกเขายังได้เรียนรู้ว่าการจัดการงานมีประโยชน์ และตอนนี้เมื่อมีการพูดกันมากขึ้นว่าต้องออกแบบทุกอย่างล่วงหน้า ก็เหมือนกำลังมุ่งไปสู่ waterfall development
ต่อไปก็คงจะตั้งชื่อให้การทำ prototype, พูดถึงฟีเจอร์แบบค่อยเป็นค่อยไปที่ต้องดูแลทั้ง requirement เก่าและใหม่พร้อมกัน และสุดท้ายก็คงจะบอกว่าลูกค้าต้องมีส่วนร่วมมากขึ้น
ควรไปดูว่าผู้จัดการโครงการกับผู้จัดการผลิตภัณฑ์ทำอะไรกันจริง ๆ พวกเขาเป็นคนนำผลิตภัณฑ์ที่ชื่อว่าโค้ด แต่ไม่ได้ถูกคาดหวังให้อ่านโค้ด และต้องทำสิ่งนั้นให้สำเร็จด้วยภาษาธรรมชาติล้วน ๆ
คิดว่าคนไม่เคยพังหรือไง คิดว่าทีมไม่เคยหลงทางแล้วเผาเวลาไปเป็นสัปดาห์หรือเป็นเดือนหรือไง ตอนนี้ด้วย vibe coding คุณได้สัมผัสทุกอย่างนั้นภายใน 30 นาที จากมุมของอดีต technical product manager มันให้ความรู้สึก เหมือนกันเป๊ะ
จริง ๆ แล้วดูเหมือนไม่ได้กลับไปเขียนโค้ดด้วยมือ จึงงงกับช่องว่างระหว่างชื่อเรื่องกับข้อสรุป