- เมื่อค่อย ๆ รีแฟกเตอร์ ชั้นการเข้าถึงข้อมูล Rust ขนาด 17,155 บรรทัด ที่เอเจนต์เขียนขึ้น จำนวนโทเค็นขาเข้าที่ต้องใช้สำหรับการเปลี่ยนฟังก์ชันแบบเดียวกันลดลงจาก 159,564 เหลือ 27,360 หรือ ลดลง 83%
- ปริมาณโค้ดทั้งหมดแทบไม่เปลี่ยน แต่เมื่อแยกโค้ดที่เกี่ยวข้องออกเป็นไฟล์ที่มีความเชื่อมโยงกันสูง เอเจนต์จึงอ่านได้เฉพาะ ชุดไฟล์ขั้นต่ำ ที่จำเป็นต่อการแก้ไข
- โทเค็นขาเข้าไม่ได้ลดลงมากนักจนกว่าไฟล์ที่ใหญ่ที่สุดจะเล็กลงพอ สุดท้ายชั้นข้อมูลถูกแยกเป็นไฟล์ Rust 19 ไฟล์ และลดขนาดไฟล์สูงสุดจาก 17,155 บรรทัดเหลือ 3,695 บรรทัด
- โทเค็นขาออกและปริมาณงานที่ใช้พัฒนาฟังก์ชันแทบไม่เปลี่ยน และ Claude ก็ไม่สามารถเลือกหรือทำรีแฟกเตอร์ที่เหมาะสมได้อย่างสม่ำเสมอด้วยตัวเอง จึงต้องอาศัย คำแนะนำเชิงรุกจากมนุษย์ ทั้งในการวางแผนและการลงมือทำ
- หากคิดตามราคาโทเค็นขาเข้าของ Sonnet 5 ที่ $3/MTok การประหยัดต่อการแก้ไข 1 ครั้งมีเพียงประมาณ $0.397 แต่ยืนยันได้ว่าต้นทุนมีแนวโน้มลดลงซ้ำ ๆ ทุกครั้งที่มีการแก้ไขชั้นการเข้าถึงข้อมูลในภายหลัง
ไฟล์ 17,155 บรรทัดที่เอเจนต์สร้างขึ้น
- แอปพลิเคชันสำหรับสนับสนุนงานมีทั้งเว็บ UI ที่อัปเดตและค้นข้อมูลแบบไดนามิกได้, โมดัลและการบันทึกอัตโนมัติ, การเชื่อมต่อกับระบบภายนอก, งานแมชชีนเลิร์นนิงและการวิเคราะห์ข้อความ, งานเบื้องหลัง, และสภาพแวดล้อมดีพลอยอัตโนมัติ
- จากโค้ดทั้งหมดราว 150,000 บรรทัด นั้น Rust มีประมาณ 120,000 บรรทัด ที่เหลือเป็น TypeScript และ Terraform โดยส่วนใหญ่เอเจนต์เป็นผู้เขียนผ่าน Claude Code และบางส่วนใช้ Cursor
- นักพัฒนาแทบไม่ได้อ่านหรือรีวิวโค้ดเลย นอกจากบางครั้งที่เปิดดูด้วยความสนใจ
- ชั้นการเข้าถึงข้อมูลเติบโตเกิน 6,000 บรรทัดจากการทำซ้ำการตั้งค่า HTTP request และการเข้ารหัส/ถอดรหัส JSON เดิม ๆ ในทุกคิวรีอ่าน/เขียน และสุดท้ายไฟล์ Rust เดียวก็มีขนาดถึง 17,155 บรรทัด
- โมดูลนี้ไม่มีทั้งการลดความซ้ำซ้อนและภาษาภายใน ฟังก์ชันถูกแยกออกได้จำกัด และแทบไม่มีการแยกคลาส แต่มีอินเทอร์เฟซที่ต้องรักษาไว้และมีขอบเขตชัดเจน จึงเหมาะกับการทดลองรีแฟกเตอร์
วิธีวัดโดยทำการเปลี่ยนแบบเดียวกันซ้ำ ๆ
- เป้าหมายคือการตรวจสอบว่าการลงทุนโทเค็นกับการรีแฟกเตอร์ในปัจจุบันจะช่วยลด การใช้โทเค็นของการเปลี่ยนฟังก์ชันในอนาคต ได้หรือไม่
- เนื่องจากเอเจนต์ไม่เรียนรู้จากงานก่อนหน้า ในแต่ละขั้นจึงสั่งงานซับเอเจนต์ตัวใหม่ด้วยคำขอเปลี่ยนแบบเดียวกันทุกประการ เพื่อไม่ให้ผลจากการเรียนรู้ปะปนเข้ามา
- การทดลองดำเนินตามลำดับดังนี้
- เขียนแผนทั้งหมดตามหลักการรีแฟกเตอร์ที่เข้มงวด
- นิยามการเปลี่ยนตัวแทนด้วยพรอมป์ต์เดียว
- ให้ซับเอเจนต์ทำการเปลี่ยนและรายงานการใช้โทเค็นเพื่อวัดค่าอ้างอิง
- ทิ้งผลลัพธ์ของการเปลี่ยน จากนั้นใช้การรีแฟกเตอร์ 1 ขั้น
- ทำการเปลี่ยนแบบเดิมอีกครั้งและทิ้งผลลัพธ์ แล้วทำซ้ำขั้นตอนนี้ต่อไป
- บันทึกต้นทุนโทเค็น เวลาในการรัน และจำนวนบรรทัดโค้ดในแต่ละขั้น
- เนื่องจาก Claude ไม่สามารถให้จำนวนโทเค็นแบบเรียลไทม์ได้อย่างน่าเชื่อถือ จึงให้รายงานจำนวนอักขระขาเข้า/ขาออก แล้วใช้ tiktoken ประมาณค่าโทเค็นด้วยการหารจำนวนอักขระด้วย 4
ผลการวัดในแต่ละขั้น
- ในสถานะตั้งต้น ชั้นการเข้าถึงข้อมูลและไฟล์ที่ใหญ่ที่สุดมีขนาด 17,155 บรรทัด เท่ากัน โค้ด Rust ทั้งหมดมี 50,359 บรรทัด และการเปลี่ยนตัวแทนต้องใช้โทเค็นขาเข้า 159,564 โทเค็น โทเค็นขาออก 1,705 โทเค็น และเวลา 342 วินาที
- หลังขั้นที่ 15 ชั้นการเข้าถึงข้อมูลมี 16,608 บรรทัด ไฟล์ที่ใหญ่ที่สุดมี 3,695 บรรทัด โค้ด Rust ทั้งหมดมี 49,812 บรรทัด โดยใช้โทเค็นขาเข้า 27,360 โทเค็น โทเค็นขาออก 2,113 โทเค็น และเวลา 454 วินาที
- ในขั้นกลาง ๆ เมื่อไฟล์ที่ใหญ่ที่สุดเล็กลง โทเค็นขาเข้าก็ลดลงตาม
- หลังขั้นที่ 7 แยก
queries.rsแล้ว ไฟล์ที่ใหญ่ที่สุดเหลือ 15,670 บรรทัด และโทเค็นขาเข้าลดเหลือ 151,850 - หลังขั้นที่ 8 แยก
traits.rsแล้ว เหลือ 13,845 บรรทัด และโทเค็นขาเข้าเป็น 132,558 - หลังขั้นที่ 12 แยก
store/แล้ว เหลือ 9,269 บรรทัด และโทเค็นขาเข้าลดลงถึง 104,080 - หลังแยก
store/ครั้งสุดท้าย ไฟล์ที่ใหญ่ที่สุดเหลือ 3,695 บรรทัด และโทเค็นขาเข้าร่วงลงเหลือ 27,360
- หลังขั้นที่ 7 แยก
- ชั้นการเข้าถึงข้อมูลสุดท้ายประกอบด้วย ไฟล์ Rust 19 ไฟล์ และไฟล์ที่ใหญ่ที่สุดกลายเป็นไลบรารีทดสอบ
- ในการรีแฟกเตอร์เพิ่มเติม วิธีเดียวกันนี้ยังสามารถนำไปใช้กับไฟล์ทดสอบนั้นได้
เหตุผลที่โทเค็นขาเข้าลดลง 83%
- โทเค็นขาเข้าของงานเดียวกันลดจาก 159,564 เหลือ 27,360 ประหยัดได้ 132,204 โทเค็น
- เนื่องจากปริมาณโค้ดรวมของชั้นการเข้าถึงข้อมูลแทบไม่เปลี่ยน นี่จึงไม่ใช่ผลจากการมีโค้ดให้อ่านน้อยลง
- เอเจนต์สามารถระบุชุดไฟล์ขั้นต่ำที่จำเป็นต่อการทำงานได้ และค่อย ๆ อ่านเฉพาะขอบเขตโค้ดที่เล็กลงเรื่อย ๆ ซึ่งยืนยันได้จากเอาต์พุตการคิดของ Claude Code และสรุปการอ่านไฟล์
- หากเพียงแค่แบ่งไฟล์ให้ย่อยลงแบบสุ่ม ก็ยังอาจต้องอ่านหลายไฟล์เพื่อหาโค้ดที่เกี่ยวข้อง จึงยากจะได้ผลลัพธ์แบบเดียวกัน
- การลดลงมากที่สุดเกิดขึ้นในช่วงแยกครั้งสุดท้าย แต่ทำได้ก็เพราะขั้นก่อนหน้าได้ดึงส่วนซ้ำออกมาและสร้างโครงสร้างแกนกลางที่ใช้ซ้ำ จนสามารถแยกไฟล์ออกได้
- ลำดับนี้ไม่ได้ถูกออกแบบไว้ล่วงหน้าเพื่อเป้าหมายลดต้นทุน แต่เกิดจากกระบวนการรีแฟกเตอร์ทั่วไปที่เริ่มจากกำจัดความซ้ำซ้อนเฉพาะจุด แล้วเมื่อแกนกลางร่วมชัดเจนขึ้นจึงค่อยแตกออกเป็นไฟล์เล็ก ๆ
โทเค็นขาออกและผลด้านการเงิน
- โทเค็นขาออกที่ถูกสร้างขึ้น ขณะเขียนการเปลี่ยนตัวแทนแทบไม่เปลี่ยน จึงแปลว่าการรีแฟกเตอร์ไม่ได้ลดขนาดของการเปลี่ยนจริง
- ราคาของโทเค็นขาออกสูงกว่าโทเค็นขาเข้า 5 เท่า แต่ปริมาณจริงน้อยกว่ามาก
- หากคำนวณตามราคาโทเค็นขาเข้าของ Sonnet 5 ที่ $3/MTok การประหยัดต่อการแก้ไข 1 ครั้งอยู่ที่ประมาณ 39.7 เซนต์
- ยังไม่ชัดเจนว่าการประหยัดนี้จะสะสมในงานดีบัก ฟังก์ชันที่ซับซ้อนกว่า หรือการรีแฟกเตอร์ทั้งโค้ดเบสหรือไม่ และยังไม่ทราบว่าต้นทุนของการรีแฟกเตอร์เองมีเท่าไร
- ก็ยังไม่ชัดเช่นกันว่าจะมีการรีแฟกเตอร์ที่ลดโทเค็นขาออกได้หรือไม่ เพราะในการเปลี่ยนตัวแทนที่เรียบง่าย สัญญาณรบกวนจากการสร้างโค้ดแบบไม่กำหนดแน่นอนกลบความต่างที่เกิดจากโครงสร้าง
การรีแฟกเตอร์ที่ทำร่วมกับ Claude
- Claude ไม่สามารถดูโค้ดแล้วเลือกการรีแฟกเตอร์ที่จะใช้ได้ด้วยตัวเอง และผลลัพธ์จริงก็สอดคล้องกับงานที่พรอมป์ต์สั่งโดยตรง
- แม้ใน development harness จะมีขั้นรีแฟกเตอร์ระบุไว้อย่างชัดเจน แต่ Claude ก็ไม่ได้ใช้มันเพื่อปรับปรุงไฟล์ 17,155 บรรทัดนี้
- ในขั้นวางแผน Claude Code พบการแยกฟังก์ชันเป็นขั้นแรก ขณะที่ Claude.ai ระบุได้ไกลถึงการแยกคลาสไคลเอนต์ทั้งหมด
- สำหรับการเปลี่ยนแปลงเชิงกล ได้ใช้สคริปต์ Python ร่วมกับ
grepและsedแต่สคริปต์มักสับสนเพราะเรื่องการเยื้องบรรทัด - การแยกไฟล์ store ซึ่งให้คุณค่ามากที่สุด ถูกพลาดไปในการลองครั้งแรกและต้องนำกลับมาใช้ในขั้นถัดไป ทำให้จำนวนขั้นในผลการวัดไม่ตรงกับขั้นในแผนภาคผนวก
- การทดลองทั้งหมดใช้เวลาประมาณ 8 ชั่วโมง และส่วนใหญ่ดำเนินไปแบบไม่มีคนเฝ้า
- หลังผ่านไป 6 ชั่วโมง 40 นาที จึงพบขั้นที่ตกหล่นและเข้าไปแทรกแซง 1 ครั้ง
- สาเหตุที่ทำให้การรันทดสอบช้ามาก ไม่ใช่ Wi-Fi โรงแรมที่ช้า แต่เป็น Cargo temporary build cache ที่ใหญ่เกินไป
พรอมป์ต์การเปลี่ยนตัวแทน
- ซับเอเจนต์แต่ละตัวได้รับเพียงโค้ดเบสและเอกสารสถาปัตยกรรม แล้วต้องทำ
ItemWatchStoreasynchronous public trait แบบเดียวกัน - trait นี้มี 3 เมธอดดังต่อไปนี้
watch_item(&self, item_id: &str, user_id: &str) -> Result<()>unwatch_item(&self, item_id: &str, user_id: &str) -> Result<()>watched_items_for_user(&self, user_id: &str) -> Result<Vec<String>>
- ข้อมูล watch ถูกเก็บในคอลเลกชัน
item_watchesของ Firestore โดยมีฟิลด์itemId,userId,createdAt - คืนค่า
Vec<String>ของ item ID โดยไม่สร้าง Rust record struct แยกต่างหาก - ใน
FakeStoreเพิ่มฟิลด์Vec<(String, String)>ในหน่วยความจำ และในFirestoreStoreให้ติดตั้งใช้งานโดยคงรูปแบบ HTTP เดิมไว้ - ในตอนท้ายของคำตอบ ให้พิมพ์ JSON ของไฟล์ที่อ่าน จำนวนอักขระ และจำนวนอักขระของคำตอบ พร้อมสั่งไม่ให้ commit โค้ด
แผนรีแฟกเตอร์ที่ใช้
-
ขั้นที่ 1 — แยกคลาส
FirestoreClient- แยกความรับผิดชอบระหว่างการประสานคิวรีโดเมนกับการส่ง Firestore HTTP
- ย้าย
reqwest::Client,project_id,MetadataAuth, การจัดการ URL และ auth header ไปยังโครงสร้างใหม่ - ตามแผนจะลดโค้ดใน implementation ของ
FirestoreStoreลงราว 1,200 บรรทัด และเพิ่มในไคลเอนต์ราว 120 บรรทัด
-
ขั้นที่ 2 — แยกฟังก์ชัน
extract_doc_id,new_link- ทำให้การดึง ID ของ document parser 20 ตัว และการสร้าง
Linkที่ซ้ำ 62 ครั้งกลายเป็นส่วนกลางร่วมกัน - คาดว่าจะลดได้ราว 500 บรรทัด
- ทำให้การดึง ID ของ document parser 20 ตัว และการสร้าง
-
ขั้นที่ 3 — แยกฟังก์ชัน pipeline ของ link query
- ทำให้รูปแบบการรวบรวมผลลัพธ์คิวรีราว 15 จุด และการค้นหา target ID เดี่ยวราว 8 จุด กลายเป็นส่วนกลางร่วมกัน
- คาดว่าจะลดได้ราว 200 บรรทัด
-
ขั้นที่ 4 — แยกฟังก์ชันเงื่อนไขลิงก์ของ
FakeStoreInner- แยกการแปลง
inner.links.iter()ที่ซ้ำอยู่ในราว 15 เมธอดออกเป็น 2 เมธอด - คาดว่าจะลดได้ราว 120 บรรทัด
- แยกการแปลง
-
ขั้นที่ 5 — เพิ่มฟังก์ชันสร้างค่า Firestore
- แทนที่นิพจน์
json!ของสตริง ไทม์สแตมป์ ฯลฯ ที่ซ้ำกว่า 128 ครั้ง ด้วยการเรียกฟังก์ชัน 4 ตัว - เปลี่ยน macro หลายบรรทัดให้เป็นการเรียกแบบบรรทัดเดียว คาดว่าจะลดได้ราว 80 บรรทัด
- แทนที่นิพจน์
-
ขั้นที่ 6 — แยก
FieldsBuilder- รวมรูปแบบการสร้าง field map ของ encoder ราว 20 ตัวไว้ใน builder
- ลด encoder จากราว 40 บรรทัดเหลือราว 12 บรรทัด คาดว่าจะลดรวมได้ 500~600 บรรทัด
-
ขั้นที่ 7 — แยก
queries.rs- ย้ายค่าคงที่
LinkQuery32 ตัวและ type ที่เกี่ยวข้องไปยังโมดูลแยก - โดยไม่เปลี่ยนจุดเรียกใช้งานเดิม จะลด
mod.rsได้ราว 800 บรรทัด
- ย้ายค่าคงที่
-
ขั้นที่ 8 — แยก
traits.rs- ย้าย public trait 17 ตัวและ type ของข้อผิดพลาดที่เกี่ยวข้อง แล้ว re-export กลับ
- ลด
mod.rsลงราว 1,900 บรรทัด แต่ไฟล์ใหม่ก็จะมีราว 1,900 บรรทัดเช่นกัน
-
ขั้นที่ 9 — แยก
traits/ตามโดเมน- แบ่ง trait ออกเป็น
planning.rs,content.rs,people.rs,system.rs - โดยไม่เปลี่ยน definition และ call site จะจำกัดขนาดไฟล์ไว้ราว 300~650 บรรทัดต่อไฟล์
- แบ่ง trait ออกเป็น
-
ขั้นที่ 10 — แยก
codec.rs- ย้าย document encoder/decoder, parser,
FieldsBuilder, และฟังก์ชันสร้างค่าออกไป - หลังขั้นที่ 6 จะกลายเป็นโมดูลขนาดราว 400~500 บรรทัด และลด
mod.rsลงราว 500 บรรทัด
- ย้าย document encoder/decoder, parser,
-
ขั้นที่ 11 — แยก
fake_store.rs- ย้าย
FakeStore,FakeStoreInner, และ implementation ของ 18 trait ออกไป - ลด
mod.rsลงราว 4,700 บรรทัด
- ย้าย
-
ขั้นที่ 12 — แยก implementation ของ
FirestoreStore- ใน
store/mod.rsคงไว้เฉพาะ struct, constructor,FirestoreClient,MetadataAuthแล้วแยก implementation ของ trait เป็นไฟล์ตามโดเมน - เปลี่ยนไฟล์ขนาดราว 10,000 บรรทัดให้เป็น 10 ไฟล์ที่มีขนาด 120~650 บรรทัดต่อไฟล์ และทำให้
mod.rsเป็นไฟล์ re-export ราว 100 บรรทัด
- ใน
-
ขั้นที่ 13 — วางชุดทดสอบไว้คู่กับโมดูลเป้าหมาย
- ไม่เปลี่ยนโค้ดทดสอบ แต่ย้ายไปไว้ใต้แต่ละไฟล์ implementation
- ลด
mod.rsลงราว 2,000 บรรทัด และเพิ่มชุดทดสอบที่เกี่ยวข้อง 200~700 บรรทัดในแต่ละไฟล์
ข้อจำกัดและการทดลองต่อไป
- ไม่ได้แยกนับโทเค็นที่ใช้ในการเขียนแผนและลงมือรีแฟกเตอร์ จึงคำนวณ ต้นทุนการลงทุนรีแฟกเตอร์ ได้ไม่แม่นยำ
- ค่าสูงสุดโดยอิงจากการใช้งานรวมในช่วงเวลานั้นคือ 5 ล้านโทเค็น แต่ตัวเลขนี้รวมทั้งการเขียนแผนสองรอบ งานทดลองและออกแบบการเปลี่ยนตัวแทน รวมถึงงานอื่น ๆ ทั้งหมด
- สิ่งที่ทดลองยังอยู่ในช่วงกรีนฟิลด์ และเป็นแอปขนาดใหญ่เพียงตัวเดียวที่นักพัฒนาคนเดียวสร้างและดูแล จึงไม่สามารถสรุปผลทั่วไปได้
- งานถัดไปควรวัดโทเค็นของการรีแฟกเตอร์ให้แม่นยำขึ้น ทดสอบการเปลี่ยนที่ซับซ้อนขึ้น รีแฟกเตอร์ในขอบเขตที่กว้างขึ้น รีแฟกเตอร์อย่างต่อเนื่อง และเปรียบเทียบคุณค่าเชิงสัมพัทธ์ของแต่ละแนวทาง
- การทดลองครั้งนี้เป็นจุดเริ่มต้นสำหรับการวัดทั้งคุณค่าด้านเวลา/การเงินจากการรีแฟกเตอร์ และต้นทุนของการรีแฟกเตอร์เองไปพร้อมกัน
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
น่าสนใจที่ แนวปฏิบัติที่ดีของนักพัฒนา ซึ่งบริษัท IT ส่วนใหญ่เคยมองข้าม กำลังถูกคิดค้นขึ้นใหม่ในฐานะแนวปฏิบัติที่ดีของ AI
เมื่อก่อนถ้าบอกให้วางเอกสารไว้ในโค้ด ให้บอกบริบททั้งหมดของโปรเจกต์แทนที่จะโยนแค่งานใน Jira และให้รีแฟกเตอร์เพื่อผลิตภาพระยะยาว ก็มักถูกมองว่าน่าเบื่อ
ตอนนี้พอบอกเรื่องเดียวกันว่าเอกสารของ AI ควรอยู่ในโค้ดและ CLAUDE.md อย่าควบคุมละเอียดผ่านพรอมป์ และมารีแฟกเตอร์เพื่อผลิตภาพของ AI กัน กลับถูกมองว่าน่าสนใจ
มนุษย์แม้จะรู้วิธีที่ถูกต้อง แต่ก็อาจยุ่งหรือสมาธิหลุด ส่วน agent ไม่เบื่องานซ้ำซาก ดังนั้นขั้นตอนที่พิสูจน์แล้วว่าได้ผลกับมนุษย์แต่ยากจะใช้ให้ต่อเนื่อง จึงเป็นไปได้จริงในทางปฏิบัติ
มีการให้ agent แยกกันเขียน implementation และ test จากสเปกร่วม แล้วให้ agent ตรวจสอบอีกตัวมาทวนสอบ เพื่อไม่ให้ผลลัพธ์ของกันและกันปนเปื้อนกัน นี่คือการนำ วิศวกรรมคลีนรูม ที่ IBM พัฒนาขึ้นสำหรับมนุษย์ในทศวรรษ 1980 มาใช้กับ AI อย่างกว้างขวางและสม่ำเสมอ
นี่ไม่ใช่การเอาแนวปฏิบัติเก่ามาห่อใหม่ให้ดูเหมือนของใหม่ตามกระแส AI แต่เป็นการแสดงพร้อมหลักฐานว่าแนวปฏิบัติที่ดีซึ่งมีอายุกว่า 20 ปียังคงใช้ได้อยู่
agent ต้องเรียนรู้บริบทใหม่ในแต่ละเซสชัน ทำให้คุณค่าของแนวปฏิบัติที่ดีสูงขึ้นมาก และเห็นผลทันที
ถึงอย่างนั้น การสั่งให้ CLI รัน 100 ครั้งในหนึ่งชั่วโมงเพื่อทดสอบ usability ของ flag ใหม่ก็เป็นเรื่องดี
ในทางกลับกัน ถ้า AI ไม่มีฐานแบบนี้ ก็จะทำงานได้แย่มากหรือไม่ทำงานเลย ดังนั้น แนวปฏิบัติทางวิศวกรรมที่ดีต่อสุขภาพ จึงไม่ใช่มาตรการปรับปรุงระยะยาว แต่เป็นเงื่อนไขตั้งต้นที่จำเป็น
แม้ในความเป็นจริงจะไม่มีผลสุทธิเลยก็ตาม การใส่ AI เข้าไปใน workflow ก็มีประโยชน์ตรงที่สร้างเหตุผลให้เริ่มนำแนวปฏิบัติการพัฒนาที่ถูกต้องมาใช้ได้
ชอบที่บทความนี้วิจารณ์อย่าง เป็นรูปธรรมและเชิงปริมาณ โดยอิงจากวิธีที่เครื่องมือ AI ถูกใช้จริง
บทความที่แสดงด้วยค่าที่วัดได้ว่า AI ทำอะไรไม่ได้ มีประโยชน์กว่าบทความที่ถกความเสี่ยงทางสังคมแบบลอย ๆ โดยไม่มีกรณีใช้งานจริงมาก
ด้วยเหตุผลเดียวกัน รายงาน ที่สัมภาษณ์สมาชิก Boko Haram เพื่อศึกษาว่า AI ถูกนำไปใช้กับการก่อการร้ายอย่างไร ก็ชวนประทับใจเช่นกัน
ชอบ การรีแฟกเตอร์ ด้วยตัวเองโดยไม่ใช้ AI มากจริง ๆ
แม้จะไม่มีการเปลี่ยนแปลงที่มองเห็นได้ แต่รู้สึกพอใจที่ทำให้เว็บไซต์ซึ่งตอนนี้ยังไม่เห็นผลงานชัดเจน กลายเป็นสิ่งที่จัดการได้ง่ายขึ้นมากในอนาคต
กระบวนการค้นหาว่าโค้ดอ้อมแปลก ๆ ในอดีตกำลังแก้ปัญหาที่มี pattern ที่เป็นที่ยอมรับแล้วอยู่ แล้วค่อยย้ายไปสู่แนวปฏิบัติที่ดีโดยไม่สร้างหนี้ทางเทคนิคใหม่ เป็นเหมือนการเล่น puzzle ที่สนุก
เคย implement ทุกอย่างเองอย่างลวก ๆ แม้กระทั่ง authentication และได้เรียนรู้กลไกภายในด้วยวิธีที่ยาก ผลก็คือมีเรื่องให้รีแฟกเตอร์เล่นไปอีก 10 ปีข้างหน้า
เข้าใจอย่างเป็นรูปธรรมว่าคำว่า codebase คือระบบหนึ่งระบบ หมายถึงอะไร และเริ่มมองโค้ดในระดับสูงเหมือนเป็นองค์กรหรือใยตาข่ายที่ต่อเนื่อง ซึ่งสามารถดึงและกดให้เปลี่ยนรูปได้
การที่ AI อาจตัดกระบวนการเรียนรู้นี้ออกไป ทำให้ปัญหาของ junior developer เด่นชัดขึ้น หากต้องการได้ intuition ก็ไม่มีทางอื่นนอกจากลงไปขุดลึกด้วยตัวเอง และแม้ Naur จะเตือนไว้เมื่อ 40 ปีก่อน บทเรียนนี้ก็ยังถูกลืมซ้ำแล้วซ้ำเล่า
เมื่อเอเจนต์ทำรีแฟกเตอริง มองว่า การมีมนุษย์เข้ามามีส่วนร่วม เป็นสิ่งจำเป็น
แม้โมเดลสำหรับตรวจทานอาจหาส่วนที่โมเดลสร้างเน้นงานช่วงแรกแล้วพลาดไปได้ แต่ก็ยังน่าสงสัยว่ามันจะเข้าใจเป้าหมายของโปรเจกต์ทั้งหมดและวิธีที่โค้ดเชื่อมโยงกันจริง ๆ จนแยกแยะความซ้ำซ้อนหรือโครงสร้างที่งดงามกว่าได้หรือไม่
การให้เอเจนต์เขียนโค้ดรับหน้าที่รีแฟกเตอริง คล้ายกับการขอให้ศัลยแพทย์อุบัติเหตุช่วยเพิ่มสมรรถภาพทางกีฬาให้ และถ้าจะทำให้ถูกต้องต้องมีมุมมองแบบองค์รวม
แค่แบ่งไฟล์ใหญ่เป็นหลายไฟล์ถือเป็นรีแฟกเตอริงแบบผิวเผินเท่านั้น หากไม่มีทฤษฎีว่าโค้ดใดควรอยู่ด้วยกัน และอะไรควรถูกดึงออกมาเป็นฟังก์ชันยูทิลิตี ก็ใกล้เคียงกับการเอาจำนวนใหญ่ไปหั่นเป็นจำนวนเล็ก ๆ แล้วบวกกลับเข้ามาใหม่ มากกว่าจะเป็นการแยกตัวประกอบ
เอเจนต์บางครั้งสร้างระบบที่เก็บและคำนวณค่าที่ดึงมาจาก API อยู่แล้วซ้ำอีก แต่มนุษย์สามารถมองทั้งโปรเจกต์แล้วระบุอย่างแม่นยำได้ว่ามีข้อมูลที่ต้องใช้ในคีย์บางตัวของ JSON อยู่แล้ว
functools.partialแทน data class เพื่ออิมพลีเมนต์ Command pattern ก็จัดการได้แนวคิดที่ว่าเส้นแบ่งไฟล์แสดงขอบเขตของซับซิสเต็มเชิงตรรกะเพื่อให้อนุมานง่ายขึ้น และเนื้อหาในไฟล์อื่นโดยพื้นฐานถือเป็นสิ่งทึบ ก็มีอยู่มากในข้อมูลฝึก
มองว่าประโยชน์ของโครงสร้างนี้ไม่ได้มีแค่คุณลักษณะบังเอิญของการรับรู้มนุษย์ แต่ยังมีด้านที่เป็นภววิสัยด้วย
ประสบการณ์รีแฟกเตอริง วิธีรับมือกับโค้ดเบสของคนอื่น และการที่ในฐานะผู้ออกแบบเดิมสามารถอธิบายเจตนาในอดีตและปัจจุบันได้ ล้วนช่วยมาก
เป็นภาษานอกกระแสที่มีความเปราะบางด้าน dependency ต่ำและ LLM จัดการง่าย หลังจากตัดอคติฝั่ง JavaScript และ Python ออกแล้ว ความเร็วงานก็เพิ่มขึ้นมาก
เป็นโปรเจกต์ JSR-223 ที่เขียนสคริปต์ด้วยภาษายอดนิยมหลายภาษา แต่ทั้งหมดรันบน JVM ตามข้อกำหนดของสภาพแวดล้อม: https://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform
ในตลาดจ้างงานนี้ ต้องใช้ เอเจนต์เขียนโค้ดที่อิงโมเดลแนวหน้ารุ่นล่าสุด และรู้ความสามารถกับข้อจำกัดของมันอย่างแม่นยำ หากให้การประเมินในทางตรงข้ามในการสัมภาษณ์ อาจเป็นเหตุให้ไม่ผ่านได้
บริบทที่กระชับ ไม่ได้แค่ลดการใช้โทเค็น แต่ยังปรับปรุงการให้เหตุผล และทำให้ใส่เลเยอร์ได้มากขึ้นในบริบทเดียวเพื่อจัดการอย่างชาญฉลาด
การรีแฟกเตอริงไปสู่ abstraction ที่ดี สร้างซอฟต์แวร์ที่ generalize ได้ดีกว่า และมีแนวโน้มถูกต้องไม่เฉพาะกับกรณีที่ทดสอบ แต่รวมถึงกรณีที่อนุมานแทรกและอนุมานขยายด้วย
มีทฤษฎีสารสนเทศและคณิตศาสตร์แบบเบย์สที่รองรับเรื่องนี้ และการที่ซอฟต์แวร์ซึ่งมีประสิทธิภาพทางเศรษฐกิจและพลังงานสูงขึ้นกลับแม่นยำขึ้นด้วยนั้น ดูเหมือนเป็นความบังเอิญที่ลงตัวอย่างยิ่ง
แก่นสำคัญคือ การลดเอนโทรปีของโค้ด
LLM อนุมานความหมายได้ค่อนข้างดีอยู่แล้ว แม้มีบริบทน้อย
น่าสนใจที่มีการนำเสนอข้อมูล และสอดคล้องกับความรู้สึกของผมว่า LLM ได้ประโยชน์มากจาก โค้ดที่แยกส่วนดี แต่ความสามารถในการสร้างโค้ดแบบนั้นเองยังไม่ได้ยอดเยี่ยมนัก
นักพัฒนามนุษย์ส่วนใหญ่ก็น่าจะคล้ายกัน
โดยรวมแล้วประโยชน์จาก AI นั้นมาก แต่ก็ต้องใช้เวลาจัดระเบียบ และยังคิดว่าต้องอ่านทุกบรรทัดอยู่ดี
เคยเลื่อนการตรวจทานบางส่วนออกไปเพื่อไม่ให้ขวางความคืบหน้าของทีมอื่น แล้วภายหลังต้องชำระหนี้เทคนิคมากกว่าปกติ แต่การปลดล็อกคอขวดก่อนก็คุ้มค่า
AI ทำให้การดึงหนี้เทคนิคมาใช้ล่วงหน้าง่ายขึ้นในทุกความหมาย และถ้านำทางดี ๆ ก็ช่วยปลดหนี้ได้ค่อนข้างดีด้วย เพียงแต่ผลลัพธ์แตกต่างกันไปตามแต่ละคน: https://news.ycombinator.com/item?id=49035455
การแสดงตัวอย่างโค้ดที่จัดโครงสร้างดี หรือใช้ repository โอเพนซอร์สให้ดูว่าอะไรควรทำและไม่ควรทำ ช่วยได้มาก
ประโยชน์ทางเศรษฐกิจส่วนใหญ่ของรีแฟกเตอริงมาจาก ความเข้าใจของมนุษย์ที่ดีขึ้น มากกว่าการประหยัดโทเค็น
ทำให้แก้ incident call ตอนตี 3 ได้เร็วขึ้น ลดบั๊กที่หลุดเข้า production และออกของได้เร็วกว่าคู่แข่ง
เหนือสิ่งอื่นใด เมื่อเข้าใจระบบ ก็จะเต็มใจรับผิดชอบและเป็นเจ้าของ ทำให้เมื่อเกิดปัญหาก็แก้ได้เร็วขึ้น และกระโจนเข้าไปปรับปรุงส่วนที่ควรปรับอย่างกระตือรือร้น
ประเด็นหลักคือ รีแฟกเตอริงลดการใช้โทเค็น และเป็นเรื่องดีที่บทความไม่ได้ถกในเชิงนามธรรม แต่ระบุผลเป็นตัวเลข
อย่างไรก็ตาม Fowler กล่าวไว้ใน 《Refactoring》 ว่าเงื่อนไขจำเป็นของรีแฟกเตอริงคือชุดทดสอบที่แข็งแรง และผมมองว่าประโยชน์จริง ๆ อยู่ตรงนี้ ไม่เกี่ยวกับ AI
การทดสอบที่ดีป้องกัน regression ที่มนุษย์หรือหุ่นยนต์สร้างขึ้น และทิ้ง specification ที่ทั้งสองฝ่ายอ่านได้ไว้ในรูปโค้ด: https://www.oreilly.com/library/view/refactoring-improving-the/9780134757681/
หากคำนวณราคา Sonnet 5 ที่ 3 ดอลลาร์ต่อ MTok จำนวนเงินที่ประหยัดได้จากการเปลี่ยนแปลงในอนาคตที่แตะเลเยอร์การเข้าถึงข้อมูลคือ 39.7 เซนต์
เมื่อพิจารณาการลดราคาของ OpenAI โมเดลเปิด และราคาต่อโทเค็นที่จะลดลงในระยะยาว อาจไม่คุ้มกับต้นทุนการให้ senior developer ที่คิดราวชั่วโมงละ 100 ดอลลาร์มาคอยนำทางรีแฟกเตอริง
โค้ดที่เอเจนต์สร้างกลายเป็นก้อนขนาดมหึมาที่มีแต่เอเจนต์เท่านั้นที่อ่านและเข้าใจได้ และสิ่งสำคัญในทางปฏิบัติคือความเป็นจริงนั้นเอง มากกว่าจะเป็นฟีเจอร์ บั๊ก หรือคุณสมบัติที่เกิดขึ้นเอง
เรากำลังกลับไป พึ่งพาเครื่องมือ AI อีกครั้งเพื่อจัดการกับโค้ดที่สร้างโดยเครื่องมือ AI
อย่างไรก็ตาม มนุษย์เองก็สร้างไฟล์ขนาดใหญ่จนน่ากลัวและ monorepo กันมาแล้ว และเพราะ LLM งานแก้ไขและรีแฟกเตอร์สิ่งเหล่านี้จึงเพิ่งเริ่มอยู่ในขอบเขตที่รับมือได้
ในสถานการณ์ที่ codebase ใหญ่และยุ่งเหยิงเกินกว่ามนุษย์จะเข้าใจ LLM อาจช่วยเราได้ และโดยส่วนตัวแม้จะเกลียดไฟล์ยักษ์ แต่ก็รู้สึกขมขื่นที่หลักการจัดระเบียบแบบ Fowler อาจไม่สำคัญอีกต่อไปแล้ว
แม้แต่พนักงานระดับปานกลางหรือคนที่ทำได้ดีเป็นครั้งคราว ก็อาจถูกทำให้ลงมือทำสิ่งที่เมื่อก่อนทำไม่ได้เพราะขั้นตอนของบริษัทได้รวดเร็วราวสายฟ้า จนกลับกลายเป็นพนักงานแย่ ๆ ได้
ถ้าเอเจนต์สร้างไฟล์หรือฟังก์ชันขนาดมหึมา แค่สั่งว่าอย่าทำแบบนั้นมันก็ทำตาม
ผมมองว่า การรีแฟกเตอร์เป็นหนึ่งในสัญญาณที่ดีที่สุดของทีมพัฒนาที่แข็งแรง
การรีแฟกเตอร์มีประโยชน์ในตัวเอง แต่เจ้าของผลิตภัณฑ์หรือบอร์ดงานฟีเจอร์มักมองไม่เห็นคุณค่านั้น
หากทีมรีแฟกเตอร์เพื่อสุขภาพโดยรวมของซอฟต์แวร์ นั่นหมายความว่านักพัฒนารู้สึกสบายใจที่จะเสนอแนวทางเพื่อซอฟต์แวร์ที่ดี และข้อเสนอนั้นได้รับการพิจารณาอย่างจริงจัง
ความเสื่อมของซอฟต์แวร์จะรุนแรงที่สุดเมื่อทีมไม่มีแรงจูงใจหรืออำนาจในการทำให้วิสัยทัศน์เรื่องซอฟต์แวร์คุณภาพสูงเป็นจริง และหากทีมสามารถทำตามดุลยพินิจของตนเกี่ยวกับความเป็นเลิศได้ โดยทั่วไปก็ถือเป็นสัญญาณที่ดี
แน่นอนว่ามีกรณีทำเกินไป เช่น เขียนใหม่ทั้งหมดจาก Ruby ไป Node ไป Rust แล้วกลับมาเป็นเทคโนโลยีที่เป็นมิตรกับเอเจนต์อีกครั้ง แต่ในสภาพแวดล้อมองค์กร ทีมที่รู้สึกว่าไม่ได้รับอนุญาตให้ปรับปรุงนั้นพบได้บ่อยกว่ามาก