1 คะแนน โดย GN⁺ 2 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • 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 โดยตรงจาก background tea.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 สร้างไว้เป็นครั้งแรก และพบว่า struct Model ตัวเดียวถือทั้ง 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 View trait/interface, ห้าม view เข้าถึง state ของ view อื่น, ข้อมูล async ต้องเข้ามาในรูปแบบ AppMsg variants เท่านั้น และ App struct ต้องรับผิดชอบแค่ 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
  • หลักการ 5: AI ไม่ได้เป็นเจ้าของ state transition

    • แกนสำคัญของโครงสร้าง Bubble Tea คือ state ต้องเปลี่ยนภายใน Update() ที่ขับเคลื่อนด้วย message เท่านั้น แต่ k10s ฝ่าฝืนหลักนี้
    • handler ของ updateTableMsg คืนค่า tea.Cmd closure และภายใน 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

กฎป้องกันที่ต้องใส่ใน CLAUDE.md และ agents.md

  • เงื่อนไขคงที่ของสถาปัตยกรรม

    • แต่ละ view ต้อง implement View trait/interface และต้องไม่เข้าถึง state ของ view อื่น
    • ข้อมูล async ทั้งหมดต้องเข้ามาเป็น AppMsg variants และ background task ห้าม mutate field โดยตรง
    • การเพิ่ม view ใหม่ต้องไม่บังคับให้แก้ view เดิม
    • App struct ต้องเป็นเพียง thin router ที่ดูแล navigation และ message dispatch
  • กฎเรื่อง ownership ของ state

    • ห้ามเพิ่มฟิลด์ state เฉพาะ view ลงใน App/Model struct
    • แต่ละ view ต้องมีอยู่เป็น struct แยก และประกาศ key binding ของตัวเอง
    • app ต้อง dispatch key ไปยัง active view และ keybinding ใหม่ต้องเพิ่มใน keymap ของ view นั้น ไม่ใช่ใน global handler
    • ถ้าการเพิ่ม view บังคับให้ต้องแก้ 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() เท่านั้น
  • กฎเรื่อง 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 ต้องนิยาม AppMsg variant ใหม่

วิธีการสร้างใหม่

  • 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 ความคิดเห็น

 
GN⁺ 2 시간 전
ความคิดเห็นจาก Hacker News
  • คนที่บอกว่าโค้ดที่ถูกสร้างมานั้นใช้ได้ ส่วนใหญ่คือ คนที่ไม่ได้อ่านโค้ดนั้น
    มาตรการบรรเทาที่บทความเสนอมาก็อยู่ได้ไม่นาน เวลาที่ออกแบบระบบหรือคอมโพเนนต์ มักจะมี เงื่อนไขคงตัว เกิดขึ้น เช่น “วิวหนึ่งจะไม่เข้าถึงสถานะของอีกวิว” แล้วสักวันก็ต้องเพิ่มฟีเจอร์ที่ชนกับเงื่อนไขนั้น
    ตอนนั้นก็มักต้องเลือกว่าจะยอมทิ้งฟีเจอร์ไป, ฝืนแปะมันลงบนเงื่อนไขคงตัวแบบขัด ๆ และไม่มีประสิทธิภาพ, หรือไม่ก็เปลี่ยนเงื่อนไขคงตัวนั้นเอง การเลือกนี้ไม่ใช่แค่ปัญหาเรื่องบริบท แต่เป็นเรื่องของวิจารณญาณ และโมเดลปัจจุบันพลาดเรื่องนี้บ่อยเกินไป
    ถ้าระบุข้อจำกัดทางสถาปัตยกรรมไว้ เอเจนต์ก็มักจะบิดตัวให้เข้ากับข้อจำกัดนั้นแม้ในเวลาที่ควรต้องเปลี่ยนมัน แล้วสร้างโค้ดซับซ้อนที่บำรุงรักษาไม่ได้ ถ้าไม่อ่านละเอียดกว่าโค้ดที่คนเขียน สุดท้ายก็จะได้ “โค้ดที่กินตัวเอง” และรู้ตัวตอนสายเกินไป

    • ถ้าคุณเขียนโค้ดดีเป็น ก็มีหลายเทคนิคที่ทำให้ AI เขียนโค้ดดีได้ และมันทำได้จริง
      แก่นคือการหาว่าจุดไหนที่ 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 ของฟังก์ชันที่ต้องการ เป็นต้น
    • ในภาษาปัจจุบัน codebase มีความซับซ้อนระดับ global และเงื่อนไขคงตัวที่ต้องการก็ไม่แสดงออกชัด จึงขยายต่อได้ไม่ดี
      ต่อให้บอกหลายครั้งว่า “ห้ามมี 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 ที่ตัวเองเข้าใจได้อย่างสบายใจ

    • มันเป็นกฎที่สมเหตุสมผลถ้าจะรักษา mental model ที่แข็งแรงเกี่ยวกับ sanity ของโค้ดและการเติบโตของ codebase แต่ที่ทำงานซึ่งความคาดหวังเรื่อง ความเร็วในการส่งมอบ เปลี่ยนไปมากหลังยุค AI กลับทำตามได้ยาก
      ต้องหาจุดสมดุลที่ยังเชื่อมต่อกับ codebase อยู่ โดยไม่กลายเป็นคอขวดของทีม
    • ผมเคยพยายามทำตามกฎคล้ายกัน แต่เจอโจทย์คณิตศาสตร์ยากมาก
      Claude เป็นนักคณิตศาสตร์ระดับปริญญาเอก ส่วนผมไม่ใช่ แต่ผมรู้ชัดเจนว่าคำตอบที่ต้องการควรมีคุณสมบัติอะไร และจะทดสอบความถูกต้องอย่างไร ดังนั้นผมจึงเก็บคำตอบของ Claude ไว้แทนคำตอบแบบง่ายและไร้เดียงสาของตัวเอง พร้อมเขียนไว้ใน pull request ว่าเป็นแบบนั้น และทุกคนก็มองว่านี่เป็นการตัดสินใจที่ถูกต้อง
      เลยสงสัยว่าควรมีข้อยกเว้นสำหรับกรณีแบบนี้ไหม ถ้า AI เก่งกว่าผมมากทั้งในคณิตศาสตร์ขั้นสูงและการเขียนโค้ด คำถามที่น่าสนใจกว่าคือผมจะเลิกเขียนโค้ดเองทั้งหมดหรือไม่ แม้จะสูญเสียความสามารถในการตัดสินโค้ดโดยตรงไป แต่ยังตัดสินเทสต์ได้
    • ผมชอบคำว่า หนี้ความเข้าใจ มากกว่า “หนี้ทางการรับรู้”
      เพราะหนี้ที่สะสมอยู่นั้นก็คือการขาดความเข้าใจในโค้ดโดยตรง จึงแม่นกว่า
    • สำหรับโปรเจกต์ส่วนตัว วิธีที่สนุกกว่าสำคัญกว่าอยู่แล้วจึงไม่เป็นไร แต่ในงานจริง เราก็ไม่ได้ทำงานโดยต้องเข้าใจทุกชั้นทั้งหมด ตั้งแต่ dependency, งานของเพื่อนร่วมทีม, บริการภายนอก ไปจนถึงซิลิคอนข้างล่าง
      ไม่เข้าใจว่าทำไมอยู่ ๆ AI ถึงต้องถูกปฏิบัติไม่เหมือนอย่างอื่น
      สุดท้ายก็ต้องตัดสินจากความเสี่ยงและผลตอบแทน ต้องชั่งดูว่าถ้าผิดจะเสียหายอะไร มีโอกาสแค่ไหนที่จะตรวจเจอจากเทสต์และรีวิว และถ้าถูกจะได้ประโยชน์อะไร ไลบรารีกับบริการภายนอกก็เหมือนกัน
      กฎการเงินที่ซับซ้อนในสัญญาคริปโตที่แก้ไขไม่ได้และไม่มีเทสต์ กับ viewer สำหรับแสดงผลข้อมูล log ภายใน เป็นความเสี่ยงคนละระดับกันโดยสิ้นเชิง
    • ผมเคยใช้แนวทางคล้ายกัน แต่สุดท้ายคิดว่าการทำตามกฎข้อสองให้พอจริง ๆ นั้นยากเกินจริง
      ในทางทฤษฎีมันดูดี แต่ในความเป็นจริงคุณจะเลือกทางลัดทางความคิดโดยไม่รู้ตัวอยู่เสมอ
      ถ้าเทียบตอนแก้ปัญหาใน codebase ที่ไม่คุ้นเคยด้วยตัวเอง กับตอนที่คิดว่าตัวเอง “เข้าใจอย่างสมบูรณ์” สิ่งที่เอเจนต์ทำไปแล้ว ปริมาณที่ยังอยู่ในหัวหลังผ่านไปหนึ่งสัปดาห์นั้นต่างกันมาก ถ้าทำเอง มันจะสะสมเป็นความรู้ทั่วไปและส่วนสำคัญมักจะยังอยู่ แต่ถ้าพยายามครอบครองสิ่งที่เอเจนต์ทำให้เหมือนเป็นของตัวเอง แม้ตอนนั้นจะเหมือนเข้าใจ แต่จะลืมเร็วมาก
      เพราะงั้นผมจึงสรุปว่าในกรณีแบบนี้ความช่วยเหลือจาก LLM ส่วนใหญ่เป็นโทษต่อเป้าหมายของผม แม้ยังไม่ต้องนับเรื่องเวลาและแรงกดดันทางธุรกิจอื่น ๆ ก็ตาม
  • ผมก็เจอแบบเดียวกัน
    มันหลอกกันแบบนี้ AI ทำฟีเจอร์ได้เยอะใน codebase ที่ดี และมันยังดูเร็ว ปลอดภัย และแม่นยำกว่า โดยเฉพาะในโดเมนที่เราไม่ค่อยรู้จัก
    เวลาผ่านไป codebase โตขึ้น ใช้เวลาสำรวจมากขึ้น และอัตราความล้มเหลวสูงขึ้น คุณไม่อยากยอมรับเลยยิ่งเร่งมันหนักขึ้น จนสุดท้ายต้องหยุดเมื่อการเปลี่ยนแปลงแทบทำไม่ได้แล้ว
    พอกลับไปดูโค้ด คำว่า spaghetti ยังเบาไป มันเหมือน กำแพงเมืองจีน
    สุดท้ายผมลบออกไป 75,000 บรรทัดจากทั้งหมด 140,000 บรรทัด และรู้สึกว่า 3 เดือนที่หมกมุ่นกับ agent coding อย่างหนักนั้นสูญเปล่า สร้างฟีเจอร์ที่ไร้ประโยชน์ เพิ่มบั๊ก สูญเสีย mental model ของโค้ด พลาดการตัดสินใจยาก ๆ ที่จะมองเห็นได้ก็ตอนอยู่ในโค้ดจริง ๆ และสุดท้ายก็ทำให้ผู้ใช้ผิดหวัง

    • สิ่งที่น่าสนใจคือผลลัพธ์แบบนี้ยังทำให้คนแปลกใจ
      ไม่ได้จะประชดนะ แต่อยากรู้จริง ๆ ว่าความคาดหวังตอนแรกคืออะไร และมันมาจากไหน
      ดูเหมือน LLM จะถูกคาดหวังต่างออกไป ถ้าคุณเอาคำอธิบายฟีเจอร์แบบย่อส่งให้ “นักพัฒนา” สุ่มคนหนึ่งที่รู้จักกันแค่ออนไลน์ แล้วได้กอง implementation ที่พังครึ่ง ๆ กลับมา คงไม่มีใครแปลกใจ
      แต่บางครั้งคนกลับคาดหวังปาฏิหาริย์จากเครื่องจักรที่เพ้อเจ้อยืดยาว ทั้งที่ไม่คาดหวังแบบนั้นจากมนุษย์ด้วยซ้ำ อยากรู้ว่าความเชื่อมั่นนั้นมาจากไหน
    • ผมคิดว่า codebase ใหญ่ควรเป็นการรวมกันของ codebase เล็ก ๆ
      เหมือนเมืองใหญ่ที่เป็นการรวมกันของเมืองเล็ก ๆ คุณมีแผนที่ แล้วก็ซูมเข้าไปทำงานในพื้นที่เฉพาะนั้น ไม่จำเป็นต้องรู้รายละเอียดทุกอย่างของนิวยอร์กเพื่อไปดื่มกาแฟหนึ่งแก้ว
      การสร้าง สถาปัตยกรรมที่ดีและดูแลได้ เป็นความรับผิดชอบของคนใช้เครื่องมือ AI ไม่ได้ขัดขวางสิ่งนั้น และถ้าถือเครื่องมือให้ถูก มันอาจช่วยด้วยซ้ำ
    • น่าจะมีทางแก้เชิง workflow นอกจากการเลิกใช้ AI
      เช่น ถือว่าโค้ด AI ที่สร้างมานั้นเป็น legacy code ทันที แล้ววางขอบเขตการ encapsulate ที่แข็งแรงกับอินเทอร์เฟซที่นิยามชัด ก่อนค่อยรวมเข้ากับกระบวนการที่ manual มากขึ้น
      มันมีสเปกตรัมตั้งแต่ prompt ครั้งเดียวไปจนถึงการสร้างโค้ด inline และวิธีที่เหมาะก็ขึ้นกับปัญหาและตำแหน่งของมันใน codebase
      การสร้างแบบครั้งเดียวเหมาะกับช่วง prototype ที่ต้องทำสเปกเดิมซ้ำบ่อย พอ prototype เริ่มนิ่งก็ลดระดับลงมาเป็นการสร้างระดับโมดูลหรือไฟล์ให้เป็นระบบมากขึ้น และระหว่างนั้นก็ต้องรักษา mental model ที่ดีไว้ในชั้นนั้นต่อไป
    • สงสัยว่าคุณอ่านโค้ดที่สร้างมาหรือเปล่า หรือ auto-commit ทั้งหมดไปเลย
      ถ้าอ่านแต่ไม่เข้าใจ ก็ให้มันใส่คอมเมนต์ละเอียดให้แต่ละส่วนก็ได้ แต่ถ้ารู้อยู่แล้วว่าโมเดลจะลำบากขึ้นเมื่อ codebase ใหญ่ขึ้น งั้นยิ่งซับซ้อนขึ้นก็ยิ่งต้องตรวจผลลัพธ์เข้มงวดขึ้น
    • ผมยังไม่เคยจัดการ codebase ใหญ่ ๆ แต่สงสัยว่าคงใช้ workflow แบบ Working Effectively with Legacy Code ได้
      คือสร้างเกาะของโค้ดคุณภาพสูงขึ้นมา ให้ AI ช่วยรื้อฟื้นเจตนาของนักพัฒนาและกฎธุรกิจ และสร้าง seam กับ unit test ในโมดูลเป้าหมาย
      AI ไม่จำเป็นต้องมีไว้เพื่อเพิ่ม throughput อย่างเดียว มันอาจเป็นเครื่องมือสำรวจและ refactor ที่ยืดหยุ่น เพื่อช่วยการเขียนด้วยมือหรือการลงมือผ่านเอเจนต์ในภายหลังก็ได้
  • ทุกครั้งที่เห็นบทความแบบนี้ ผมอดเปรียบเทียบความเร็วที่คนบอกว่าได้จาก AI กับความเร็วที่ผมได้จากการเขียนโค้ดเองไม่ได้
    บังเอิญว่าผมทำโปรเจกต์ 3D MMO มา 7 เดือน ตอนนี้เล่นได้แล้ว คนก็สนุกด้วย กราฟิกก็ดี และยัดคนเข้าเซิร์ฟเวอร์ได้หลายร้อยคนสบาย ๆ สถาปัตยกรรมก็ค่อนข้างดี ทำให้เพิ่มฟีเจอร์ได้ง่าย และน่าจะออกได้หลังพัฒนารวมประมาณ 1 ปี
    แต่บทความต้นฉบับใช้เวลา 7 เดือนกับ vibe coding แล้วยังทำ TUI พื้นฐานไม่ได้เลย ความเร็วในการทำฟีเจอร์อาจรู้สึกว่าสูง แต่สำหรับ UI พื้นฐานแบบนี้มันช้าอย่างไม่น่าเชื่อ มีไลบรารี TUI ดี ๆ มากมาย และมันก็แค่ประเภทงานที่เอาข้อมูลที่ต้องใช้มาใส่ตาราง ซึ่งเขียนเองไม่กี่สัปดาห์ก็ได้
    เวลาใช้ AI มันให้ความรู้สึกเหมือนคืบหน้าเร็วมาก แต่ในความจริงดูเหมือนหลายครั้งจะช้ากว่าการเขียนเองมาก ข้อมูลด้านผลิตภาพก็ดูเหมือนสนับสนุนว่าผู้ใช้ AI รู้สึกว่าเร็วขึ้น แต่ผลผลิตจริงกลับน้อยลง

    • ตัวชี้วัดนี้ขึ้นอยู่มากว่า ใคร ใช้ AI กับ งานอะไร
      เวลาส่วนใหญ่ของงานพัฒนาซอฟต์แวร์หมดไปกับการประชุมเพื่อทำความคาดหวังของผู้มีส่วนได้ส่วนเสียกับแนวทางแก้ให้ตรงกัน ซึ่ง AI แทบไม่ช่วยเลยในมุมนั้น ถ้าจะเทียบจำนวนชั่วโมงคนตั้งแต่ข้อเสนอไปจนเข้า test loop ก็คงได้ผลลัพธ์ที่น่าผิดหวัง
      แต่ในงานแก้ปัญหา, แก้บั๊ก, และ implement วิธีแก้ที่ได้รับอนุมัติแล้ว ผมรู้สึกว่าดีขึ้นอย่างน้อย 10 เท่าเมื่อเทียบกับก่อนหน้า ไม่ใช่แค่เวลาอย่างเดียว ความสามารถในการตีความพฤติกรรมที่สังเกตได้และสืบสวนปัญหาก็ดีขึ้นด้วย
      อย่างไรก็ตาม ก็มีคนที่ใช้ AI แล้วสร้างผลลัพธ์ที่ถูกต้องและมีคุณค่าไม่ได้ ถ้าคุณรู้ชัดว่าต้องการอะไรและต้องการมันอย่างไร AI จะช่วยได้มาก มันทำสิ่งที่ผมต้องทำอยู่แล้วได้เร็วกว่า แต่ถ้าคุณยังไม่รู้แน่ชัดว่าต้องการอะไร AI จะเป็นโทษต่อความคืบหน้า
    • ผมเองก็เพิ่งได้ข้อสรุปแบบเดียวกัน
      เวลาคนโชว์สิ่งที่ทำด้วย LLM มันไม่ค่อยน่าประทับใจ เพราะส่วนใหญ่เป็นของที่ถ้าทำเองด้วยมือก็ใช้เวลาไม่นานมากอยู่แล้ว
      ผมก็ไม่ได้สังเกตเห็นว่าซอฟต์แวร์ที่น่าประทับใจเพิ่มขึ้น ซึ่งดูสอดคล้องกับข้อเท็จจริงที่ว่า LLM ตอนนี้ถูกใช้แก้ปัญหาง่าย ๆ มากกว่าปัญหาสำคัญ
    • คนที่รู้สึกว่าได้ประโยชน์สูงสุดจาก LLM มีโอกาสสูงว่าจะเป็นคนที่แต่เดิมไม่รู้วิธีสร้างซอฟต์แวร์ดี ๆ หรือยังไม่มีความสามารถพอจะสร้างมัน
    • ผมก็รู้สึกว่า 7 เดือนนี่แปลก ถึงเขียนด้วยภาษาใหม่ก็คงไม่นานขนาดนั้น
      อีกเรื่องที่พูดถึงกันน้อยคือ คุณภาพโค้ด
      codebase ที่ทำแบบ vibe coding เป็นตัวอย่างยอดเยี่ยมว่า LLM ไม่ได้เก่งการเขียนโค้ดขนาดนั้น มันแก้ความผิดพลาดของตัวเองแล้วก็สร้างซ้ำอีกทันที และการใช้แพตเทิร์นก็ไม่สม่ำเสมอ
      ช่วงหลัง ๆ Claude ยังเลือกสไตล์โค้ด “น่าสนใจ” ที่ไม่เข้ากับสไตล์ของ codebase ปัจจุบันอีกด้วย
    • ตระกูล GPT ถูกออกแบบมาเพื่อสร้างข้อความ หรือก็คือภาษาและโค้ด ซึ่งเป็นทั้งเป้าหมายและชีวิตของมัน ทำให้ดูเหมือนภายในระบบจะเอนเอียงไปทาง สร้างทุกอย่างเองทั้งหมด
      ต้องใช้ภาษาสไตล์ “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 สมัยใหม่ทั้งหมด เราอาจค่อย ๆ ปรับตัวด้วยการสร้างเครื่องมือและเฟรมเวิร์กใหม่
    • ดูเหมือนหลายคนจะพลาดจุดที่ว่าเราต้องตรวจทุกอย่างที่ AI ทำ
      จากมุมของคนที่ยังมีประสบการณ์ด้านโค้ดไม่มาก ผมกำลังเรียนรู้มากกว่าที่เคยจากการตรวจผลลัพธ์และดูว่าอะไรถูกอะไรผิด
      เพราะงั้นผมก็ไม่คิดว่ามันจะดีขึ้นแบบก้าวกระโดดเร็ว ๆ นี้ เวลามีคนถามว่า “ทำไมผลลัพธ์จาก Claude ของคุณถึงดูดีจัง” คำตอบของผมคือ “ผมดูอย่างระมัดระวัง หาเจอว่าตรงไหนมีปัญหา แล้วบอก Claude ให้แก้” แค่นั้นจริง ๆ แต่พอพูดแบบนี้ แววตาคนฟังก็เริ่มลอยแล้ว
      มันเหมือน Google ทำให้ค้นหาข้อมูลง่ายขึ้น แต่ไม่ได้ลบปัจจัยมนุษย์ในการแยกแยะข้อมูลดีและข้อมูลแย่ออกไป
    • ถ้าจะใช้เอเจนต์แล้วไม่เกลียดมันหมดใจหรือไม่ล้มเหลว ก็มีแต่วิธีนี้
      คิดปัญหาก่อน ออกแบบโครงสร้างกับ API ก่อน แล้วค่อยให้ AI ลงมือ implement
  • ชื่อเรื่องบอกว่า “กลับมาเขียนโค้ดด้วยมือ” แต่สิ่งที่ทำจริงคือ “ทำ งานออกแบบ ด้วยมือก่อนที่โค้ดจะถูกเขียน”
    จากนั้นก็ดูเหมือนยังปล่อยให้ Claude เป็นคนสร้างโค้ดอยู่ดี
    ที่หนักกว่านั้นคือยากจะเข้าใจว่า 7 เดือนที่ผ่านมาเขาคิดว่าโปรเจกต์ vibe coding ทำงานได้ดี ทั้งที่ไม่ได้ดู source code ที่ถูกสร้างขึ้นมาเลย และยังซื้อโดเมนไปแล้วด้วย

    • พูดสั้น ๆ คือเป็นชื่อแบบ clickbait และจุดประสงค์ของบทความก็ดูเหมือนจะเรียกความสนใจให้โปรเจกต์ของตัวเอง
    • ผมก็เคยซื้อโดเมนของโปรเจกต์ภายในไม่กี่นาทีหลังคิดไอเดียได้
      ถ้าเป็น side project และค่อย ๆ ตรวจตาม diff ไปทีละนิด การไม่ได้ดูโค้ดลึกมากก็ไม่ถึงกับแปลกสุดขั้ว แน่นอนว่าเป็นวิธีทำงานอีกแบบ แต่ก็ไม่ถึงขั้นบ้า
  • มันให้ความรู้สึกเหมือนกำลังดูนักพัฒนา สปีดรัน บทเรียนของการจัดการโปรเจกต์และการจัดการผลิตภัณฑ์
    ตอนนี้พวกเขาเริ่มเห็นแล้วว่าสเปกมีประโยชน์ และการเขียนโค้ดผิดจำนวนมากไม่ได้ทำให้โปรเจกต์เร็วขึ้น นักพัฒนามักรำคาญว่าการประชุมและการถกเถียงขัดขวางการเขียนโค้ด แต่กระบวนการเหล่านั้นมักมีไว้เพื่อกันไม่ให้ทุกคนเขียนสิ่งที่ผิดมากขึ้น
    พวกเขายังได้เรียนรู้ว่าการจัดการงานมีประโยชน์ และตอนนี้เมื่อมีการพูดกันมากขึ้นว่าต้องออกแบบทุกอย่างล่วงหน้า ก็เหมือนกำลังมุ่งไปสู่ waterfall development
    ต่อไปก็คงจะตั้งชื่อให้การทำ prototype, พูดถึงฟีเจอร์แบบค่อยเป็นค่อยไปที่ต้องดูแลทั้ง requirement เก่าและใหม่พร้อมกัน และสุดท้ายก็คงจะบอกว่าลูกค้าต้องมีส่วนร่วมมากขึ้น
    ควรไปดูว่าผู้จัดการโครงการกับผู้จัดการผลิตภัณฑ์ทำอะไรกันจริง ๆ พวกเขาเป็นคนนำผลิตภัณฑ์ที่ชื่อว่าโค้ด แต่ไม่ได้ถูกคาดหวังให้อ่านโค้ด และต้องทำสิ่งนั้นให้สำเร็จด้วยภาษาธรรมชาติล้วน ๆ

    • จริงเลย คนพวกนี้ดูเหมือนไม่เคยเป็นผู้จัดการมาก่อน
      คิดว่าคนไม่เคยพังหรือไง คิดว่าทีมไม่เคยหลงทางแล้วเผาเวลาไปเป็นสัปดาห์หรือเป็นเดือนหรือไง ตอนนี้ด้วย vibe coding คุณได้สัมผัสทุกอย่างนั้นภายใน 30 นาที จากมุมของอดีต technical product manager มันให้ความรู้สึก เหมือนกันเป๊ะ
  • จริง ๆ แล้วดูเหมือนไม่ได้กลับไปเขียนโค้ดด้วยมือ จึงงงกับช่องว่างระหว่างชื่อเรื่องกับข้อสรุป

    • ผมคิดว่าตั้งชื่อยั่วเพื่อให้ HN งับแล้วดันขึ้นหน้าแรก
    • ตัวบทความเองก็ดูเหมือนไม่ได้เขียนด้วยมือเหมือนกัน สิ่งที่ขึ้นไปอยู่บนสุดของ HN คงไม่ใช่ตัวบทความ แต่เป็นชื่อเรื่อง