AI Blindspots – จุดบอดของ LLM ที่ค้นพบระหว่างการเขียนโค้ดด้วย AI
(ezyang.github.io)จุดบอดของ LLM ที่ค้นพบระหว่างการเขียนโค้ดด้วย AI โดยอิงจาก Claude Sonnet
- Stop Digging → เปลี่ยนทิศทางได้ยากเมื่อเกิดปัญหา
- Use Static Types → จำเป็นต้องตั้งค่า static type
- Black Box Testing → พึ่งพารายละเอียดการติดตั้งใช้งานมากเกินไป
- Use MCP Servers → ปัญหาเรื่องการตั้งค่าและความปลอดภัยของเซิร์ฟเวอร์ MCP
- Preparatory Refactoring → อาจทำ refactoring ที่ไม่จำเป็น
- Mise en Place → หากตั้งค่าสภาพแวดล้อมล้มเหลวจะเกิดปัญหา
- Stateless Tools → เกิดปัญหากับเครื่องมือที่พึ่งพาสถานะ
- Respect the Spec → มีโอกาสละเมิดสเปกสูง
- Bulldozer Method → ทำงานซ้ำมากเกินไป
- Memento → เกิดปัญหาจากการเข้าใจบริบทไม่เพียงพอ
- Requirements, not Solutions → ต้องทำให้ข้อกำหนดชัดเจน
- Scientific Debugging → เกิดปัญหาเมื่อแก้แบบเดาสุ่ม
- Use Automatic Code Formatting → เกิดความไม่สอดคล้องของสไตล์โค้ด
- The Tail Wagging the Dog → หมกมุ่นกับปัญหาเล็กน้อยมากกว่างานสำคัญ
- Keep Files Small → เกิดปัญหาเมื่อแก้ไขไฟล์ขนาดใหญ่
- Know Your Limits → โมเดลขาดการรับรู้ขีดจำกัดของตัวเอง
- Read the Docs → เกิดข้อผิดพลาดกับข้อมูลที่อยู่นอกเหนือจากความรู้ที่เรียนมา
- Culture Eats Strategy → ขาดความสม่ำเสมอของสไตล์โค้ด
- Walking Skeleton → ควรให้ระบบขั้นต่ำสุดทำงานได้ก่อน
- Rule of Three → เมื่อโค้ดซ้ำควรทำ refactoring
ไม่ขุดลึกต่อเมื่อเจอปัญหา (Stop Digging)
- ปัจจุบัน LLM ยังขาดความสามารถในการหยุดงานที่กำลังทำอยู่และเปลี่ยนทิศทางเองเมื่อเกิดปัญหา
- ตัวอย่าง: ระหว่างทำฟีเจอร์ X หากพบว่าต้องทำฟีเจอร์ Y ก่อน LLM ก็ยังพยายามทำงานเดิม (X) ให้เสร็จ
- แม้จะเป็นข้อดีในแง่ที่ LLM ทำตามคำสั่งอย่างเคร่งครัด แต่ก็ยากที่จะตระหนักถึงปัญหาและเปลี่ยนทิศทาง
- กลยุทธ์เพื่อหลีกเลี่ยงปัญหา
- ในขั้นวางแผน ควรใช้ reasoning model เพื่อจัดลำดับความสำคัญของงานและกำหนดงานที่ต้องทำก่อน
- agentic LLM อย่าง Sonnet จะอ่านไฟล์และวางแผนงานได้ → สามารถระบุงานที่จำเป็นได้แม้ผู้ใช้จะไม่ได้สั่งอย่างชัดเจน
- ตามอุดมคติแล้ว LLM ควรรับรู้ปัญหาและขอให้ผู้ใช้ยืนยันได้
- แต่สิ่งนี้ใช้ context มาก จึงอาจดีกว่าหากให้ LLM สำหรับการเฝ้าระวังต่างหากจัดการเรื่องนี้
-
Example
- หลังแก้วิธีสุ่มตัวอย่างเลขสุ่มของ Monte Carlo simulation ได้ขอให้ Claude Code แก้เทสต์
- implementation ใหม่ไม่เป็น deterministic ทำให้ผลการทดสอบผ่าน/ไม่ผ่านแบบสุ่ม
- Claude Code ไม่รับรู้จุดนี้และพยายามแก้ปัญหาด้วยการผ่อนเงื่อนไขของเทสต์
- ทั้งที่ควรเสนอให้ทำ refactoring เพื่อให้ simulation กลับมาเป็น deterministic
- หลังแก้วิธีสุ่มตัวอย่างเลขสุ่มของ Monte Carlo simulation ได้ขอให้ Claude Code แก้เทสต์
ใช้ static types (Use Static Types)
- ข้อถกเถียงเรื่องระบบ dynamic type vs static type เป็นเรื่องของสมดุลระหว่างความง่ายในการทำ prototype กับการบำรุงรักษาระยะยาว
- LLM สามารถจัดการ boilerplate code และ refactoring ได้ จึงลดภาระในการเลือกภาษาที่เหมาะกับการทำ prototype
- ดังนั้นจึงสามารถเลือกภาษาที่เหมาะกับการดูแลรักษาระยะยาวได้มากกว่า
- กลยุทธ์ในการแก้ type error
- ตั้งค่าในเอเจนต์ให้ LLM รับรู้ type error ที่เกิดขึ้นหลังการแก้ไข
- วิธีนี้ช่วยให้ระบุไฟล์อื่นที่ต้องแก้ตามได้ง่าย
- ข้อควรระวัง
- ในกรณีของ Python และ JavaScript ใช้ระบบ gradual typing → จำเป็นต้องตั้งค่า type checker ให้เข้มงวด
- โดยหลักการแล้ว Rust เหมาะกับ LLM แต่ปัจจุบันยังสร้างโค้ดได้ไม่ดีเท่า Python/JavaScript
การทดสอบแบบกล่องดำ (Black Box Testing)
- การทดสอบแบบกล่องดำคือการทดสอบการทำงานของคอมโพเนนต์โดยไม่รู้โครงสร้างภายใน
- LLM ทำตามหลักการของ black box testing ได้ยาก เพราะไฟล์ implementation ถูกใส่อยู่ใน context
- ในกรณีของ Sonnet 3.7 (ใช้ Cursor) มีแนวโน้มจะพยายามรักษาความสอดคล้องของโค้ด → พยายามลดความซ้ำซ้อนในไฟล์เทสต์
- แต่สำหรับ black box testing การคงความซ้ำซ้อนไว้กลับช่วยตรวจจับบั๊กได้ดีกว่า
- แนวทางแก้ที่เหมาะสม
- LLM ควรสามารถ mask หรือสรุปรายละเอียด implementation จากไฟล์ที่โหลดมาได้
- สถาปนิกควรกำหนดขอบเขตของการซ่อนข้อมูลให้ชัดเจน
-
Example
- ตอนที่ Sonnet 3.7 แก้เทสต์ที่ล้มเหลว มันได้เปลี่ยนค่าคงที่ที่ hardcode ไว้ให้คำนวณจากอัลกอริทึมต้นฉบับ
- ทั้งที่จริงควรคงค่าคงที่เดิมเอาไว้
- ตอนที่ Sonnet 3.7 แก้เทสต์ที่ล้มเหลว มันได้เปลี่ยนค่าคงที่ที่ hardcode ไว้ให้คำนวณจากอัลกอริทึมต้นฉบับ
ใช้เซิร์ฟเวอร์ MCP (Use MCP Servers)
- เซิร์ฟเวอร์ Model Context Protocol(MCP) มอบอินเทอร์เฟซมาตรฐานให้ LLM โต้ตอบกับสภาพแวดล้อมได้
- มีการใช้เซิร์ฟเวอร์ MCP อย่างกว้างขวางในโหมด agent ของ Cursor และใน Claude Code
- LLM สามารถค้นหาและแก้ไขไฟล์ที่ต้องการผ่านการเรียก MCP ได้โดยไม่ต้องมีระบบ RAG แยกต่างหาก
- โมเดลสามารถรันทดสอบหรือ build แล้วแก้ปัญหาต่อได้ทันที
- ข้อพิจารณาเมื่อต้องเขียนเซิร์ฟเวอร์ MCP แบบกำหนดเอง
- ใน Cursor สามารถเปิดใช้งาน YOLO mode แล้วเพิ่มคำสั่งเชลล์ลงในกฎของ Cursor ได้
- อันตราย → คำสั่งเชลล์ตามอำเภอใจอาจทำให้สภาพแวดล้อมเสียหายได้
- ทางเลือก: เขียนเซิร์ฟเวอร์ MCP แบบกำหนดเองที่เปิดเผยเฉพาะคำสั่งที่กำหนด → เพิ่มความปลอดภัย
- อย่างไรก็ตาม ณ เดือนมีนาคม 2025 Cursor ยังรองรับการตั้งค่าเซิร์ฟเวอร์ MCP รายโปรเจ็กต์ได้ไม่ดีนัก
- ใน Cursor สามารถเปิดใช้งาน YOLO mode แล้วเพิ่มคำสั่งเชลล์ลงในกฎของ Cursor ได้
-
Example
- Sonnet 3.7 ใช้ MCP ระหว่างทำ type check และแก้ข้อผิดพลาดในโปรเจ็กต์ TypeScript
- จัดการได้อัตโนมัติโดยไม่ต้องคัดลอกและวางเอาต์พุตจากเทอร์มินัลด้วยตนเอง
- แต่ก็อาจมีกรณีที่อนุมานคำสั่งผิด เช่น
npm run typecheck
- Sonnet 3.7 ใช้ MCP ระหว่างทำ type check และแก้ข้อผิดพลาดในโปรเจ็กต์ TypeScript
การทำ refactoring เตรียมงาน (Preparatory Refactoring)
- Preparatory refactoring คือกลยุทธ์ที่ทำ refactoring ก่อนเริ่มงานเปลี่ยนแปลง เพื่อให้งานง่ายขึ้น
- เนื่องจาก refactoring เป็นงานที่คงความหมายเดิมไว้ จึงประเมินได้ง่ายกว่าการเปลี่ยนแปลงจริง
- ทำ refactoring ก่อนแล้วค่อยทำงานเปลี่ยนแปลง → ช่วยให้ตรวจทานและแก้ข้อผิดพลาดได้ง่าย
- ปัญหาของ LLM ในปัจจุบัน
- มีแนวโน้มจะพยายามทำทุกอย่างในครั้งเดียวโดยไม่ทำ preparatory refactoring ก่อน
- อาจทำแม้แต่งานจัดระเบียบที่ไม่จำเป็น → มีโอกาสเกิดการ refactoring มากเกินไป
- Cursor Sonnet 3.7 มีความแม่นยำในการทำตามคำสั่งต่ำ → อาจเกิด refactoring ที่ไม่เกี่ยวข้อง
- แนวทางปรับปรุง
- จำเป็นต้องสั่งอย่างชัดเจนว่าในขั้น refactoring ก่อนแก้ไขนั้น ให้ LLM แก้เฉพาะโค้ดในส่วนนั้น
- กำหนดขอบเขตของโค้ดที่ LLM จะแก้ให้ชัดเจน → ป้องกันการแก้ไขที่ไม่จำเป็น
-
Example
- สั่งให้ LLM แก้ import error → หลังแก้แล้วกลับเพิ่ม type annotation ให้กับ lambda function
- บาง annotation ถูกเพิ่มผิด ทำให้เกิด agent loop
- สั่งให้ LLM แก้ import error → หลังแก้แล้วกลับเพิ่ม type annotation ให้กับ lambda function
Mise en Place
- ในการทำอาหาร mise en place คือการจัดเตรียมวัตถุดิบและเครื่องมือทั้งหมดไว้ให้พร้อมก่อนเริ่มงาน
- สำหรับ LLM mise en place คือการตั้งค่ากฎ MCP และสภาพแวดล้อมการพัฒนาให้ครบถ้วนก่อนเริ่มงาน
- Sonnet 3.7 อ่อนแอต่อการซ่อมแซมสภาพแวดล้อมที่พัง
- มักพยายามแก้ปัญหาด้วยการคัดลอกคำสั่งจาก StackOverflow มาวาง → เสี่ยงทำให้สภาพแวดล้อมเสียหาย
- จึงต้องตั้งค่าสภาพแวดล้อมให้ถูกต้องตั้งแต่ก่อนเริ่มงาน เพื่อไม่ให้ Sonnet ติดอยู่ใน debugging loop
-
Example
- เนื่องจากปัญหา
npm linkทำให้ VSCode ไม่รู้จัก import จากโปรเจ็กต์โลคัลอื่น- Cursor ระหว่างแก้ lint และเทสต์กลับหมกมุ่นกับการแก้ปัญหานี้ แต่ไม่รับรู้ว่าจำเป็นต้องรัน
npm unlink
- Cursor ระหว่างแก้ lint และเทสต์กลับหมกมุ่นกับการแก้ปัญหานี้ แต่ไม่รับรู้ว่าจำเป็นต้องรัน
- เนื่องจากปัญหา
การใช้เครื่องมือแบบไร้สถานะ (Stateless Tools)
- เครื่องมือควรทำงานแบบแยกอิสระในแต่ละครั้งโดยไม่เก็บสถานะ
- เชลล์ขึ้นอยู่กับสถานะของไดเรกทอรีทำงานปัจจุบัน → อาจทำให้เกิดความสับสนจากการเก็บสถานะ
- Sonnet 3.7 ติดตามสถานะของไดเรกทอรีทำงานปัจจุบันได้ไม่แม่นยำ
- จำเป็นต้องตั้งค่าให้ทุกคำสั่งสามารถรันได้จากไดเรกทอรีรากของโปรเจกต์
- แนวทางปรับปรุง
- ลดการใช้คำสั่งเครื่องมือที่ต้องเปลี่ยนสถานะให้เหลือน้อยที่สุด
- หากจำเป็นต้องมีสถานะจริง ๆ ให้ส่งสถานะปัจจุบันให้โมเดลอย่างต่อเนื่องเพื่อรักษาความสอดคล้อง
-
Example
- หากโปรเจกต์ TypeScript ประกอบด้วย 3 โมดูลคือ common, backend และ frontend
- เมื่อ Cursor รันจากราก จำเป็นต้อง
cdไปยังไดเรกทอรีที่เหมาะสม → ทำให้เกิดความสับสนเรื่องไดเรกทอรี - เมื่อเปิดแต่ละโมดูลเป็น workspace แยกกันแล้วทำงาน ปัญหาก็ได้รับการแก้ไข
- เมื่อ Cursor รันจากราก จำเป็นต้อง
- หากโปรเจกต์ TypeScript ประกอบด้วย 3 โมดูลคือ common, backend และ frontend
การเคารพสเปก (Respect the Spec)
- เมื่อต้องเปลี่ยนแปลงระบบ ต้องแยกให้ชัดเจนว่าส่วนใดแก้ไขได้และส่วนใดแก้ไขไม่ได้
- เมื่อต้องแก้ไข public API จำเป็นต้องป้องกันไม่ให้ compatibility แบบย้อนหลังพัง
- เมื่อต้องรวมเข้ากับระบบภายนอก ต้องอิงตาม API ที่มีอยู่จริง → แก้ไขตามใจต้องการไม่ได้
- หากเทสต์ล้มเหลว ห้ามลบเทสต์ → ต้องหาสาเหตุและแก้ไข
- ปัญหาของ LLM
- มีโอกาสสูงที่จะละเมิดข้อกำหนด → ลบเทสต์ เปลี่ยน API ฯลฯ ได้อย่างอิสระ
- การทำตามสเปกเป็นเรื่องสามัญสำนึก แต่ก็อาจต้องระบุไว้ในพรอมป์ต์อย่างชัดเจน
- ขอบเขตบางอย่างอาจพบได้ผ่าน code review เท่านั้น
-
Example
- Sonnet แก้ไขเทสต์ไม่สำเร็จแล้วแทนที่เนื้อหาเทสต์ด้วย
assert True - ฟังก์ชัน public คืนค่า dict ที่มีคีย์
pass→ Sonnet พยายามเปลี่ยนเป็นpass_(ปัญหาคำสงวน)
- Sonnet แก้ไขเทสต์ไม่สำเร็จแล้วแทนที่เนื้อหาเทสต์ด้วย
วิธีแบบรถไถดันดิน (Bulldozer Method)
- วิธีแบบรถไถดันดินคือกลยุทธ์ที่แก้ปัญหาด้วยงานซ้ำ ๆ ที่เรียบง่าย และเร่งความเร็วจากผลของการเรียนรู้
- การเขียนโค้ดด้วย AI โดยธรรมชาติแล้วเก่งกับงานที่ทำซ้ำ → หากใช้โทเค็นมากพอก็ทำ refactoring ขนาดใหญ่ได้
- ปัญหาที่มนุษย์อาจยอมแพ้เพราะคิดว่า “ปริมาณงานมากเกินไป” ก็อาจให้ LLM แก้ได้
- อย่างไรก็ตาม LLM อาจทำงานเดิมซ้ำไปซ้ำมา จึงจำเป็นต้องตรวจสอบว่าจริง ๆ แล้วมันกำลังทำอะไรอยู่
-
Example
- เมื่อแก้ฟังก์ชันหลักใน Haskell หรือ Rust อาจต้องมีการ refactoring เป็นวงกว้าง
- LLM สามารถทำกระบวนการ อ่านข้อผิดพลาดจากคอมไพเลอร์ → แก้ไข → คอมไพล์ใหม่ ให้เป็นอัตโนมัติได้
- เมื่อต้องแก้ค่าทดสอบที่ hardcode ไว้ LLM สามารถรันเทสต์ซ้ำแล้วแก้ให้อัตโนมัติได้
- เมื่อแก้ฟังก์ชันหลักใน Haskell หรือ Rust อาจต้องมีการ refactoring เป็นวงกว้าง
เมเมนโต (Memento)
- LLM จำสถานะไม่ได้ → ต้องกลับมาทำความเข้าใจ codebase ใหม่ตั้งแต่ต้นในทุกงาน
- ทำงานได้โดยอาศัยเพียงพรอมป์ต์, บริบทแบบ explicit/implicit และไฟล์ที่โมเดลโหลดมาในโหมดเอเจนต์
- เพราะต้องทำความเข้าใจ codebase ใหม่ทุกครั้ง หากการตั้งค่าเริ่มต้นล้มเหลว โอกาสทำงานผิดพลาดก็สูง
- กลยุทธ์ป้องกันปัญหา
- จัดเตรียมเอกสารที่ LLM สามารถอ้างอิงได้อย่างชัดเจน
- ตั้งค่าให้โมเดลค้นหาข้อมูลที่ต้องการได้ง่าย
- ให้บริบทของทั้งโปรเจกต์ก่อน แล้วค่อยขอการเปลี่ยนแปลงสำคัญ
-
Example
- ขอให้ Sonnet 3.7 วางแผนการทดสอบ end-to-end สำหรับโปรเจกต์เดิม
- มันเข้าใจผิดว่าวัตถุประสงค์ทั้งหมดของโปรเจกต์คือการทดสอบ → จึงแก้ไข README ให้เน้นเรื่องการทดสอบ
- ขอให้ Sonnet 3.7 วางแผนการทดสอบ end-to-end สำหรับโปรเจกต์เดิม
การทำให้ข้อกำหนดชัดเจน (Requirements, not Solutions)
- ความผิดพลาดที่พบบ่อยในวิศวกรรมซอฟต์แวร์คือเสนอวิธีแก้ทันทีโดยยังไม่ได้กำหนดข้อกำหนดให้ชัดเจน
- หากจำกัดพื้นที่ของปัญหาได้มากพอ เพียงกำหนดข้อกำหนดให้ชัด วิธีแก้ก็จะถูกตัดสินไปโดยอัตโนมัติ
- หากข้อกำหนดไม่ชัด อาจเกิดการถกเถียงที่ไม่จำเป็นเกี่ยวกับวิธีแก้
- ปัญหาของ LLM
- LLM ไม่รู้ข้อกำหนด → จะสร้างคำตอบที่มีความน่าจะเป็นสูงสุดจากแพตเทิร์นที่ฝึกมา
- หากสั่งงานโดยไม่มีข้อกำหนดที่ชัดเจน อาจได้ผลลัพธ์ที่หลงประเด็น
- สามารถแก้การตีความผิดได้ด้วยการปรับพรอมป์ต์ → แต่หากการตีความผิดยังค้างอยู่ในบริบท ก็จะแก้ได้ยาก
- แนวทางปรับปรุง
- หากต้องการวิธีแก้ในรูปแบบเฉพาะ ควรสั่งอย่างชัดเจน
- LLM ทำตามคำสั่งได้ตรงมาก ดังนั้นหากสั่งผิดวิธี ก็อาจได้ผลลัพธ์ที่ไม่แม่นยำ
-
Example
- เมื่อขอให้ Sonnet สร้าง visualization มันจะสร้าง SVG เป็นค่าเริ่มต้น
- หากระบุว่า “โต้ตอบได้” มันจะสร้างแอปพลิเคชันที่อิง React → คีย์เวิร์ดเพียงคำเดียวทำให้ผลลัพธ์ต่างกันมาก
- เมื่อขอให้ Sonnet สร้าง visualization มันจะสร้าง SVG เป็นค่าเริ่มต้น
การดีบักเชิงวิทยาศาสตร์ (Scientific Debugging)
- วิธีแก้บั๊กแบ่งได้เป็นสองแบบ
- ลองแก้แบบสุ่มแล้วปล่อยให้เป็นเรื่องดวง
- วิเคราะห์เชิงตรรกะว่าระบบทำงานอย่างไร แล้วหาสาเหตุของความไม่ตรงกันระหว่างสถานะจริงกับสถานะที่คาดไว้
- การดีบักเชิงวิทยาศาสตร์ (การวิเคราะห์เชิงตรรกะ) เป็นแนวทางที่ดีกว่าในระยะยาว
- ปัญหาของ LLM
- LLM มีความสามารถในการให้เหตุผลจำกัด จึงเข้าถึงแบบวิทยาศาสตร์ได้ยาก
- “เดาคำตอบที่ถูก” แล้วรีบลองแก้ทันที → ถ้าล้มเหลวก็วนไปแก้แบบสุ่มซ้ำ ๆ (agent loop)
- งานดีบักเหมาะกับโมเดลที่เน้นการให้เหตุผลอย่าง Grok 3 และ DeepSeek-R1 มากกว่า
- แนวทางปรับปรุง
- สั่งให้โมเดลวิเคราะห์สาเหตุ หรือให้ผู้ใช้ระบุสาเหตุ → เพิ่มอัตราความสำเร็จในการแก้ไข
- หากบอกสาเหตุของปัญหาได้อย่างแม่นยำ โมเดลก็จะเสนอวิธีแก้ที่ดีกว่าได้
-
Example
- Sonnet 3.7 เจอข้อผิดพลาดในการติดตั้งแพ็กเกจในสภาพแวดล้อม uv พื้นฐานที่ไม่มี pip
- หลังหาสาเหตุไม่เจอ มันจึงลองแบบสุ่มซ้ำไปมา → สิ้นเปลืองโทเค็นและดีบักไม่สำเร็จ
- Sonnet 3.7 เจอข้อผิดพลาดในการติดตั้งแพ็กเกจในสภาพแวดล้อม uv พื้นฐานที่ไม่มี pip
การใช้การจัดรูปแบบโค้ดอัตโนมัติ (Use Automatic Code Formatting)
- เครื่องมือจัดรูปแบบโค้ดอัตโนมัติ เช่น gofmt, rustfmt, black เป็นต้น มีประโยชน์ต่อการรักษาสไตล์โค้ดให้สม่ำเสมอ
- LLM ทำตามกฎเชิงกลไกได้ไม่ดีนัก (เช่น ห้ามมีช่องว่างในบรรทัดว่าง, จำกัดความยาวบรรทัด 78 ตัวอักษร ฯลฯ)
- ควรปล่อยให้เครื่องมือจัดการเรื่อง formatting และให้ LLM ไปโฟกัสกับงานที่ซับซ้อนกว่า
- หลักการเดียวกันนี้ใช้กับการแก้ lint ได้เช่นกัน
- แนะนำให้ใช้ lint ที่แก้ไขอัตโนมัติได้
- ควรให้ทรัพยากรของ LLM ไปโฟกัสกับการแก้ปัญหาที่ซับซ้อน
หางส่ายหมา (The Tail Wagging the Dog)
- หมายถึงสถานการณ์ที่ปัญหาเล็กน้อยกลับมีอิทธิพลเหนือปัญหาที่สำคัญกว่า
- อาจเกิดกรณีที่หมกมุ่นกับการแก้ปัญหาระดับล่างจนลืมเป้าหมายหลักของการเขียนโค้ดทั้งหมด
- LLM จะใส่ข้อมูลทุกอย่างลงในบริบทของเซสชันแชต → ทำให้ตัดสินความสำคัญได้ยาก
- แนวทางปรับปรุง
- ให้พรอมป์ต์ที่ชัดเจนตั้งแต่ต้น → ช่วยชี้นำให้ LLM โฟกัสกับงานสำคัญ
- Claude Code ใช้ sub-agent เพื่อทำงานเฉพาะด้าน จึงช่วยป้องกันไม่ให้บริบทส่วนกลางปนเปื้อน
-
Example
- หากขอให้ LLM คิดวิธีทำงานบางอย่าง มันอาจพยายามลงมือทำงานจริงแทนการคิด
รักษาไฟล์ให้มีขนาดเล็ก (Keep Files Small)
- การถกเถียงเรื่องขนาดไฟล์โค้ดมีมาอย่างยาวนาน
- ใช้หลัก single responsibility (หนึ่งคลาสต่อหนึ่งไฟล์) เทียบกับการยอมรับไฟล์ขนาดใหญ่ตามความเหมาะสม
- หากไฟล์ใหญ่เกินไป อาจเกิดปัญหาเมื่อระบบ RAG โหลดบริบทเป็นรายไฟล์
- ใน IDE อย่าง Cursor อาจเกิดความล้มเหลวในการใช้แพตช์ → ต่อให้สำเร็จก็ใช้เวลานาน
- ตัวอย่าง: ใน Cursor 0.45.17 การใช้การแก้ไข 55 จุดกับไฟล์ขนาด 64KB ใช้เวลาค่อนข้างนาน
- Sonnet 3.7 แก้ไขไฟล์ที่ใหญ่กว่า 128KB ได้ยาก (จำกัดด้วย context window 200K โทเค็น)
- แนวทางปรับปรุง
- รักษาไฟล์ให้เล็ก → LLM จะจัดการสิ่งอย่าง import ให้โดยอัตโนมัติได้
-
Example
- Sonnet 3.7 พยายามย้ายคลาสทดสอบเล็ก ๆ ออกจากไฟล์ Python ขนาด 471KB
- แม้การแก้ไขจะเล็ก แต่ตัว patcher ของ Cursor กลับใช้การแก้ไขไม่สำเร็จ
- Sonnet 3.7 พยายามย้ายคลาสทดสอบเล็ก ๆ ออกจากไฟล์ Python ขนาด 471KB
รู้ขีดจำกัดของตัวเอง (Know Your Limits)
- เมื่ออยู่ในสถานการณ์ที่ขาดเครื่องมือหรือมีข้อจำกัดด้านความสามารถ ต้องตระหนักถึงปัญหาและขอความช่วยเหลือ
- Sonnet 3.7 ยังไม่เก่งในการรับรู้ข้อจำกัดของตัวเอง
- หากให้พรอมป์ต์ที่ชัดเจน ก็สามารถรับรู้ข้อจำกัดได้ → จำเป็นต้องตั้งคำเตือนเรื่องการเกิดภาพหลอนในหัวข้อเฉพาะ
- ปัญหา
- Sonnet 3.7 เข้าใจผิดว่าตัวเองสามารถรันคำสั่งเชลล์ได้
- หากไม่มีคำสั่งเชลล์ อาจพยายามสร้างเชลล์สคริปต์แบบสุ่ม → เสี่ยงทำให้สภาพแวดล้อมเสียหาย
- อาจพูดว่า "จะรัน X" แล้วกลับสร้างการเรียกใช้งานของ Y ที่ต่างออกไปโดยสิ้นเชิง
- Sonnet 3.7 เข้าใจผิดว่าตัวเองสามารถรันคำสั่งเชลล์ได้
- แนวทางปรับปรุง
- ปรับพรอมป์ต์ หรือจัดเตรียมเครื่องมือเฉพาะทางที่ทำเฉพาะงานที่ต้องการ
- หากมีเครื่องมือเฉพาะ ก็สามารถป้องกันการเรียกเชลล์มั่ว ๆ ได้
- ปรับพรอมป์ต์ หรือจัดเตรียมเครื่องมือเฉพาะทางที่ทำเฉพาะงานที่ต้องการ
-
Example
- Sonnet 3.7 พยายามสร้างเชลล์สคริปต์ผิด ๆ ตอนให้สิทธิ์รันไฟล์
- หลังเกิดข้อผิดพลาดของคำสั่ง ก็พยายามแก้แบบผิด ๆ ซ้ำไปซ้ำมา
- Sonnet 3.7 พยายามสร้างเชลล์สคริปต์ผิด ๆ ตอนให้สิทธิ์รันไฟล์
อ่านเอกสาร (Read the Docs)
- เมื่อต้องเรียนรู้เฟรมเวิร์กหรือไลบรารีใหม่ สามารถทำงานง่าย ๆ ได้ด้วยการแก้โค้ดจากทิวทอเรียล
- แต่ท้ายที่สุดจำเป็นต้องอ่านเอกสารตั้งแต่ต้นจนจบเพื่อเข้าใจวิธีการทำงานโดยรวม
- ข้อดีของ LLM
- เฟรมเวิร์กยอดนิยมมักถูกนำไปพรีเทรนไว้แล้ว จึงจำวิธีใช้งานส่วนใหญ่ได้
- แต่เครื่องมือเฉพาะทางหรือเครื่องมือที่ออกมาหลัง knowledge cutoff อาจทำให้เกิดภาพหลอนได้
- Sonnet ไม่รองรับการค้นหาเว็บ → ต้องป้อนเอกสารให้เองแบบแมนนวล
- ใน Cursor หากใส่ URL ก็สามารถรวมเข้าเป็นคอนเท็กซ์ได้อัตโนมัติ
-
Example
- เมื่อขอให้ LLM เขียน YAML สำหรับ Python function calling กลับสร้างการตั้งค่าที่ผิด
- หลังให้เอกสารแล้วก็แก้ได้สำเร็จ และปรับปรุงรูปแบบผลลัพธ์ได้ดีขึ้น
- เมื่อขอให้ LLM เขียน YAML สำหรับ Python function calling กลับสร้างการตั้งค่าที่ผิด
วัฒนธรรมชนะกลยุทธ์ (Culture Eats Strategy)
- วัฒนธรรมของทีมส่งผลชี้ขาดต่อความสามารถในการนำกลยุทธ์ไปปฏิบัติ
- LLM สร้างโค้ดตามสไตล์ที่เรียนรู้ไว้ล่วงหน้าและตาม context window
- มักเอนเอียงไปทางไลบรารีหรือสไตล์ที่ปรากฏบ่อยในคอนเท็กซ์
- หากไม่ระบุชัดเจน ก็จะใช้สไตล์เริ่มต้น
- กลยุทธ์ในการปรับสไตล์ของ LLM
- แก้กฎของ Cursor (ปรับพรอมป์ต์)
- รีแฟกเตอร์สไตล์ของโค้ดเดิมให้เป็นรูปแบบที่ต้องการ → ส่งผลต่อการทำนายโทเค็นถัดไป
- ขนาดของโค้ดเบสมีอิทธิพลมากกว่าพรอมป์ต์ → การแก้โค้ดเบสคือทางแก้ระดับรากฐาน
-
Example
- Sonnet 3.7 ชอบโค้ดแบบ synchronous ใน Python
- เมื่อต้องการให้สร้างโค้ดแบบ asynchronous จึงย้ายโค้ดเดิมส่วนใหญ่ไปเป็น async แล้วสำเร็จ
- Sonnet 3.7 ชอบโค้ดแบบ synchronous ใน Python
วอร์กกิงสเกเลตัน (Walking Skeleton)
- วอร์กกิงสเกเลตันคือกลยุทธ์การสร้างระบบ end-to-end ขั้นต่ำ
- แม้ยังไม่สมบูรณ์ ก็ทำให้ทั้งระบบทำงานได้ก่อน แล้วค่อยปรับรายละเอียดภายหลัง
- ในยุคของการเขียนโค้ดด้วย LLM การสร้างทั้งระบบอย่างรวดเร็วทำได้ง่ายขึ้น
- เมื่อระบบทำงานได้ ขั้นตอนถัดไปก็จะชัดเจนขึ้น → การไปให้ถึงสถานะที่ใช้งานได้อย่างรวดเร็วเป็นเรื่องสำคัญ
- เนื่องจาก LLM ไม่สามารถนำโค้ดที่มันเขียนไปใช้ได้โดยตรง การทำให้ระบบอยู่ในสถานะทำงานได้จึงสำคัญ
กฎข้อที่สาม (Rule of Three)
- อนุญาตให้คัดลอกโค้ดเดิมได้ถึงสองครั้ง และเมื่อคัดลอกเป็นครั้งที่สามต้องรีแฟกเตอร์
- เป็นเวอร์ชันที่พัฒนาต่อจากหลักการ DRY (Don't Repeat Yourself)
- ทำให้จังหวะในการกำจัดโค้ดซ้ำชัดเจนขึ้น → รีแฟกเตอร์เมื่อเกิดการคัดลอกครั้งที่สาม
- ปัญหาของ LLM
- LLM มีแนวโน้มสร้างโค้ดซ้ำ
- หากขอให้แก้โดยไม่มีพรอมป์ต์กำกับ มักจะคัดลอกโค้ดทั้งก้อนขึ้นมาใหม่แล้วแก้
- การกำจัดโค้ดซ้ำจะเกิดขึ้นก็ต่อเมื่อโมเดลตัดสินใจเองว่าจะทำ → จึงต้องมีคำสั่งที่ชัดเจน
- แนวทางปรับปรุง
- ต้องสั่งให้กำจัดโค้ดซ้ำอย่างชัดเจน
- หากในโค้ดเดิมมีความซ้ำมาก โมเดลก็อาจสร้างความซ้ำต่อไปเรื่อย ๆ ได้
-
Example
- เมื่อขอให้ LLM เขียนโค้ดทดสอบ พบว่าตรรกะเดียวกันถูกทำซ้ำในหลายเทสต์
- หลังสั่งอย่างชัดเจนให้สร้างเมธอดช่วย ก็แก้ปัญหาได้
- โหมดเอเจนต์
- เมื่อขอให้ LLM เขียนโค้ดทดสอบ พบว่าตรรกะเดียวกันถูกทำซ้ำในหลายเทสต์
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
LLM ทำผิดพลาดในแบบที่ต่างจากมนุษย์ และจับข้อผิดพลาดเหล่านั้นได้ยาก
เมื่อ LLM ไม่รู้ข้อกำหนด มันจะเติมคำตอบที่มีแนวโน้มเป็นไปได้มากที่สุดจากข้อมูลฝึก
การทำให้ข้อกำหนดชัดเจนเป็นสิ่งสำคัญในวิศวกรรมซอฟต์แวร์
LLM มีความสามารถด้านการเขียนโค้ดในระดับ "โปรแกรมเมอร์รุ่นจูเนียร์ที่ฉลาดมาก"
LLM พยายามตอบมากเกินไป
เมื่อโพสต์ในบล็อกมีจำนวนมากขึ้น ก็จำเป็นต้องจัดระเบียบ
คำแนะนำที่มีประโยชน์เมื่อเขียนโค้ดร่วมกับ LLM
LLM อ่อนเรื่องการคำนวณและเลขคณิต
ประเด็นที่ควรพิจารณาเมื่อทำงานร่วมกับนักพัฒนาโค้ดที่เป็นมนุษย์
กรณีที่ LLM สามตัวพบ "บั๊ก" ที่ไม่มีอยู่จริง