ปรับโค้ด Go ให้ทันสมัยด้วย Go fix
(go.dev)- ใน Go 1.26 ได้มีการนำคำสั่ง
go fixที่เขียนขึ้นใหม่ทั้งหมด มาใช้ เพื่อปรับปรุงโค้ดโดยอัตโนมัติให้ใช้ความสามารถล่าสุดของภาษาและไลบรารีได้ - เครื่องมือนี้ใช้ analyzer หลายสิบตัว เพื่อตรวจจับแพตเทิร์นของโค้ด และใช้ modernizer หลากหลายแบบ เช่น
minmax,rangeint,stringscutเพื่อแปลงโค้ดที่ซ้ำซากหรือล้าสมัยให้เป็นรูปแบบสมัยใหม่ - มีการเพิ่ม analyzer
newexprเพื่อรองรับฟีเจอร์ใหม่new(expr)ทำให้สามารถย่อ helper function อย่างnewIntได้โดยอัตโนมัติ go fixให้ ผลแบบเสริมกันเมื่อรันหลายครั้ง โดย analyzer ต่าง ๆ สามารถเสนอการปรับปรุงต่อเนื่องกันได้ และยังมีการรวมการแก้ไขอัตโนมัติเมื่อเกิดการชนกัน รวมถึงการลบ import ที่ไม่จำเป็น- ทีม Go มีแผนขยายต่อไปด้วยพาราไดม์ Self-service เพื่อให้นักพัฒนาสามารถกำหนดและแจกจ่าย modernizer สำหรับ API ของตนเองได้
ภาพรวมของคำสั่ง go fix
- ใน Go 1.26
go fixถูก พัฒนาใหม่ทั้งหมด เพื่อให้สามารถแปลงโค้ดเบสเป็นสไตล์ Go สมัยใหม่ได้โดยอัตโนมัติ- ใช้คำสั่ง
go fix ./...เพื่อแก้ไขทุกแพ็กเกจภายใต้ไดเรกทอรีปัจจุบัน - ใช้ตัวเลือก
-diffเพื่อดูการเปลี่ยนแปลงล่วงหน้า
- ใช้คำสั่ง
- สามารถดูรายการ analyzer ที่ลงทะเบียนไว้ได้ด้วย
go tool fix helpซึ่งมีทั้งany,forvar,mapsloop,minmaxและกฎการแปลงอื่น ๆ - หากต้องการรันเฉพาะ analyzer บางตัว ให้ใช้แฟลกอย่าง
-anyและหากต้องการยกเว้นให้ระบุ-any=false - สามารถรันหลายรอบตามชุด
GOOS,GOARCHเพื่อคำนึงถึงความแตกต่างของโค้ดในแต่ละแพลตฟอร์ม
Modernizers — เครื่องมือทำโค้ดให้ทันสมัย
- หลังจาก Go 1.18 ที่มีการนำ generics มาใช้ ก็ยิ่งเปิดโอกาสให้ทำโค้ดให้เรียบง่ายขึ้นได้มาก
- ตัวอย่าง: ใช้
maps.Keysเพื่อรวบรวมคีย์ของ map, ใช้strings.Cutเพื่อแยกสตริง
- ตัวอย่าง: ใช้
- มีการเน้นย้ำถึงความจำเป็นในการอัปเดตโอเพนซอร์สโค้ดให้สะท้อน สำนวน Go ยุคใหม่ เพื่อแก้ปัญหาที่เครื่องมือสร้างโค้ดด้วย LLM มักยังคงใช้แพตเทิร์นแบบเก่า
- modernizer ที่รวมอยู่ใน
go fixและgoplsช่วยเพิ่มทั้งความอ่านง่ายของโค้ดและผลด้านการเรียนรู้ - ตัวอย่าง modernizer:
- minmax: แทนที่
ifstatement ด้วยฟังก์ชันmin/max - rangeint: แปลงลูป
forแบบ 3 ส่วนให้เป็นrange-over-int - stringscut: ย่อโค้ดที่อิง
strings.Indexให้เป็นstrings.Cut
- minmax: แทนที่
ฟีเจอร์ new(expr) ใน Go 1.26
- ฟังก์ชัน
newถูกขยายให้ รับอาร์กิวเมนต์ที่เป็นค่าได้ ทำให้สามารถกำหนดค่าเริ่มต้นในรูปแบบnew("go1.26")ได้ - analyzer
newexprจะค้นหา helper function อย่างnewIntแล้วทำให้เรียบง่ายเป็นreturn new(x)และแทนที่จุดเรียกใช้เป็นnew(expr) - จะถูกนำมาใช้เฉพาะเมื่อผ่านเงื่อนไขเวอร์ชัน Go ขั้นต่ำเท่านั้น เช่นมี directive
go 1.26 - สามารถนำไปใช้กับทั้งโค้ดเบสได้ด้วยคำสั่ง
$ go fix -newexpr ./... - หลังใช้งานแล้ว helper function ที่ไม่จำเป็นสามารถระบุได้ด้วยเครื่องมือ
deadcode
การทำงานเสริมกันและการจัดการความขัดแย้ง
- มี ผลแบบเสริมกัน ที่การแก้ไขหนึ่งครั้งจะสร้างโอกาสให้เกิดการแก้ไขอื่นต่อได้
- เช่น หลังใช้
minmaxแล้วอาจมีข้อเสนอการแปลงเพิ่มเติม - หรืออาจมีการปรับให้เหมาะสมต่อเนื่องจาก
stringsbuilderไปเป็นfmt.Fprintf
- เช่น หลังใช้
go fixใช้ อัลกอริทึม 3-way merge เพื่อรวมการแก้ไขที่ชนกันโดยอัตโนมัติ- หากชนกันในระดับไวยากรณ์ จะข้ามการแก้ไขนั้นและแสดงคำเตือน
- หากเป็นความขัดแย้งเชิงความหมาย เช่น การลบตัวแปรหรือ import ที่ไม่ถูกใช้งาน อาจต้องปรับด้วยตนเอง
- import ที่ไม่จำเป็นจะถูกลบออกโดยอัตโนมัติ
การผสานเข้ากับ Go analysis framework
go vetและgo fixถูกผสานให้ ใช้ analysis framework ร่วมกันvetเน้นการตรวจจับข้อผิดพลาด ส่วนfixเน้นการแก้ไขอัตโนมัติที่ปลอดภัย
- analyzer สามารถทำงานได้บนไดรเวอร์หลากหลาย เช่น
unitchecker,multichecker,gopls,staticcheck,Tricorder - สามารถแชร์ข้อมูลข้ามแพ็กเกจได้ผ่านระบบ fact
- เช่น อนุมานได้ว่า
log.Printfเป็น wrapper ของfmt.Printf
- เช่น อนุมานได้ว่า
goplsมีความสามารถในการวินิจฉัยแบบเรียลไทม์และเสนอการแก้ไขอัตโนมัติ
การปรับปรุงโครงสร้างพื้นฐานด้านการวิเคราะห์
- มีการขยาย แพ็กเกจ inspector เพื่อเพิ่มประสิทธิภาพการสำรวจ AST และรองรับการนำทางขึ้นลงซ้ายขวาด้วยชนิด
Cursor - การทำดัชนีการเรียกฟังก์ชันผ่าน typeindex ช่วยเพิ่มความเร็วในการวิเคราะห์ได้สูงสุดถึง 1000 เท่า
- การปรับปรุงเพิ่มเติม ได้แก่:
- การจัดเตรียม dependency graph ของ standard library
- รองรับ การ query เวอร์ชัน Go รายไฟล์
- ขยาย refactoring primitive เพื่อให้แก้ไขโค้ดอย่างปลอดภัยได้ เช่น การคอมเมนต์โค้ด
- modernizer บางตัวถูกตัดออกเนื่องจากมีการเปลี่ยนแปลงพฤติกรรมที่ละเอียดอ่อน (กรณี
append([]string{}, slice...)→slices.Clone(slice)) - ในอนาคตมีแผนพัฒนา pattern matching engine, automatic test harness, และ ไลบรารีตัวดำเนินการแก้ไขที่แม่นยำ
พาราไดม์ Self-service
- ตั้งแต่ Go 1.26 เป็นต้นไป มีการประกาศใช้ โมเดลการวิเคราะห์แบบ Self-service
- นักพัฒนาสามารถกำหนดและแจกจ่าย modernizer สำหรับ API ของตนเองได้
- สามารถรันได้ในระดับโปรเจกต์โดยไม่ต้องผ่านกระบวนการอนุมัติแบบรวมศูนย์
- ในก้าวแรก มีการรวมฟีเจอร์ annotation-driven inliner แบบพรีวิวไว้แล้ว
- แผนในอนาคต:
- รัน analyzer ที่ผู้ใช้กำหนดเองผ่าน dynamic loading (ภายใน
go fixหรือgopls) - ทำให้การตรวจสอบที่อิง control flow เป็นแบบทั่วไป เช่น ตรวจสอบเงื่อนไขคงที่อย่าง “open แล้วต้อง close”, “lock แล้วต้อง unlock”
- รัน analyzer ที่ผู้ใช้กำหนดเองผ่าน dynamic loading (ภายใน
- เป้าหมายคือเพิ่มประสิทธิภาพในการดูแลรักษา และช่วยให้นำฟีเจอร์ใหม่ของ Go มาใช้ได้รวดเร็วยิ่งขึ้น
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ช่วงปลายปี 2024 ตอนที่ผู้ช่วยเขียนโค้ด LLM แพร่หลายอย่างรวดเร็ว สิ่งที่น่าสนใจคือเครื่องมือพวกนี้มีแนวโน้มจะเลียนแบบ สไตล์โค้ด Go แบบเดิม จากข้อมูลฝึกมาแบบเดิมตามนั้น
ต่อให้สั่งให้ใช้ไวยากรณ์ใหม่ก็มักจะเมิน หรือบางครั้งถึงขั้นบอกว่าสิ่งนั้นไม่มีอยู่จริง
ถ้าอยากให้โมเดลในอนาคตสะท้อน แนวปฏิบัติแบบ Go 1.25 ล่าสุด โค้ดโอเพนซอร์สทั้งระบบก็คงต้องอัปเดตไปใช้สไตล์นั้นก่อน
แต่พอ LLM ได้ข้อมูลผิดเข้าไปครั้งหนึ่งแล้ว แทบจะไม่มีทางแก้ได้เลย
ตามรอยได้ยากว่าโมเดลสรุปออกมาจากอะไร สุดท้ายก็ได้แต่หวังว่าโมเดลรุ่นถัดไปจะดีขึ้น
มันดูเรียบง่ายจนรีวิวผ่านได้ แต่จริง ๆ แล้วกลับขาดการจัดการ error และ edge case
พอเอากลับเข้า LLM หลังรีวิว มันก็จะให้โค้ดที่ดูเหมือนแก้แล้วออกมา แต่ข้างในกลับมี data race หรือ deadlock แทรกอยู่
เป็นปัญหาที่เกิดซ้ำในแทบทุกโมเดล
Go รองรับย้อนหลังได้ดี เลยยังคอมไพล์ผ่าน แต่สไตล์โค้ดจะต่างกันมาก
ส่วน Python จะเจอ ความเข้ากันได้พังจริง ๆ จากการเปลี่ยน API
ถึงอย่างนั้น Go ก็ยังยอดเยี่ยมมากในฐานะ ภาษาสำหรับสร้างโค้ด เพราะตัวภาษาเสถียรและมี standard library ที่ดี
อย่างที่ Rob Pike เคยเตือนไว้ เทคโนโลยีแบบนี้คือ มลพิษของระบบนิเวศซอฟต์แวร์
หลายคนต้องการ slop ในชื่อของ ‘ความสะดวก’ แต่นั่นแหละคือแก่นของปัญหา
เครื่องมือที่ช่วย แปลงซอร์สโค้ดไปเป็นสไตล์ล่าสุด ได้อัตโนมัตินั้นยอดเยี่ยมมาก
ตัวอย่างเด่นคือ OpenRewrite ของ Java แต่พอนึกถึงภาษาอื่นกลับไม่ค่อยมีอะไรคล้ายกัน
ถ้าฟีเจอร์แบบนี้ฝังมาในภาษาเหมือน Go ก็ถือว่าเพิ่ม ความเป็นผู้ใหญ่ของภาษา ได้มาก
ต่อไปภาษารุ่นใหม่ ๆ น่าจะเอาแนวทางแบบบูรณาการของ Go ไปเป็นตัวอย่าง
IDE ของ JetBrains สามารถ refactor โค้ดหลายล้านบรรทัดพร้อมกัน หรือแปลงไปเป็นไวยากรณ์ใหม่แบบอัตโนมัติได้
มีฟีเจอร์อย่าง ConvertToPrimaryConstructor ด้วย
นอกจากนี้ Structural Search and Replace ก็ทำงานในระดับ ไวยากรณ์ของภาษา ไม่ใช่แค่ข้อความธรรมดา
ตัววิเคราะห์ Roslyn ของ .NET ก็ให้คำแนะนำการแก้โค้ดใน IDE ได้เช่นกัน
ลิงก์ tutorial
ทำให้โค้ดสะอาดขึ้นมาก
มันช่วยเปลี่ยน
concatกับmapเป็นconcatMapหรือทำให้ifที่ไม่จำเป็นง่ายลงได้เซิร์ฟเวอร์ LSP ยังมีความสามารถไม่พอ และยังไม่รองรับแม้แต่ refactor พื้นฐานอย่างการลบ argument
เลยกำลังคิดอยู่ว่าถ้าเอา jscodeshift มาจับคู่กับ Claude จะพอทำได้ไหม
Go เป็นภาษาที่ยอดเยี่ยมจริง ๆ ก็เพราะมี เครื่องมือแก้อัตโนมัติ (go fix) แบบนี้
ฟีเจอร์ใหม่อย่าง rangeint ก็มีแผนจะรองรับอัตโนมัติผ่าน go fix ด้วย เลยน่าคาดหวังมาก
ขอชื่นชมทีม Go
ความเร็วคอมไพล์ก็เร็วแบบน่าเหลือเชื่อ
forloop แล้วแก้เอง แต่ตอนนี้เครื่องมือนี้จัดการให้ได้อย่าง ประณีตกว่ามากในบทความไม่ได้พูดถึง แต่ฟีเจอร์ที่ฉันชอบที่สุดคือ directive
//go:fix inlineมันจะ inline ฟังก์ชันบรรทัดเดียวเข้าไปในจุดเรียกใช้
ทำให้คนเขียนไลบรารีสามารถ ย้ายเวอร์ชันอัตโนมัติ จากฟังก์ชันเก่าไปใหม่ได้อย่างลื่นไหล
ต่อให้ semver เปลี่ยนก็ยังอัปเกรดอัตโนมัติได้ด้วย
go fixในพอดแคสต์ของ Wes McKinney ที่เพิ่งดูมา
เขาบอกว่า Go เหมาะกับ coding agent มาก เพราะมี วงจรคอมไพล์-รันที่เร็ว, ระบบ type ที่แข็งแรง, และ ความปลอดภัยด้านมัลติเธรด
ฟังแล้วก็กลับมาสนใจ Go อีกครั้ง
ชุดเครื่องมือและธรรมเนียมปฏิบัติ ที่เป็นรูปเป็นร่างของ Go ช่วยงานพัฒนาแบบเอเจนต์ได้มาก
ใช้
go run main.goเพื่อยกสภาพแวดล้อมพัฒนาขึ้นมาได้ทันที และยังรองรับหลาย worktree, การตั้งค่ากลาง, รวมถึงฐานข้อมูลที่ migrate แล้วเครื่องมือแบบนี้ถูกแชร์ไว้ใน housecat-inc/cheetah
ตอนนี้กำลังคิดจะเพิ่ม
go fixเข้าไปในลูปที่เร็วอยู่แล้วซึ่งมีgo generate,go build,go test,go vetพอไปเรียน Python แล้วรู้สึกว่าวิธีทำสิ่งเดียวกันมี เยอะเกินไปและไม่ค่อยสม่ำเสมอ
เลยกลับคิดถึงภาษาแบบ C ที่มีวิธีหลัก ๆ แค่แบบเดียว
เลยสงสัยว่า Go ไปถึงจุดนั้นหรือยัง
อยากรู้ว่าเป็นภาษาที่ทำตาม best practice ได้โดยไม่ต้องพึ่ง LLM หรือเปล่า
ความพยายามในการไม่ทำให้มันซับซ้อนและคงความเรียบง่ายไว้นั้นน่าประทับใจมาก
ถ้าใครเริ่มเหนื่อยกับความสับสนของ Python ก็แนะนำเลย
แนวคิดเรื่อง self-service analyzer น่าสนใจมากจริง ๆ
น่าจะถูกใช้กันอย่างจริงจังในทีมไลบรารีขนาดใหญ่หรือทีมโครงสร้างพื้นฐาน
พอมีเครื่องมือแบบนี้ ก็ชวนให้คิดว่าแม้แต่ ภาษาที่ไม่รองรับ backward compatibility ก็น่าจะยังพอเป็นไปได้ไหม
ในโลกของ TypeScript มี biome ที่ทำหน้าที่แนวนี้
อย่างเช่นมันจะแนะนำให้ใช้
for...ofแทนforEachและถ้าใช้ร่วมกับ ultracite เวิร์กโฟลว์จะลื่นขึ้นมากฉันใส่ไว้ในไฟล์
agents.mdเลยว่า “หลังแก้แล้วให้รัน biome fix” ผลคือคุณภาพโค้ดถูกรักษาไว้แบบอัตโนมัติเป็นประสบการณ์ที่ เบาและมีประสิทธิภาพกว่า eslint มาก