ภาษา Go: สิ่งที่เราทำถูกและสิ่งที่เราทำผิด
(commandcenter.blogspot.com)- หลังจาก Go เปิดเป็นโอเพนซอร์สเมื่อวันที่ 10 พฤศจิกายน 2009 ตลอด 14 ปีที่ผ่านมา Go พัฒนาขึ้นโดยมีเป้าหมายไม่ใช่แค่ตัวภาษาใหม่ แต่คือ วิธีทำให้การสร้างซอฟต์แวร์สำหรับโปรดักชันง่ายขึ้น
- สเปกแบบเป็นทางการ, การมีคอมไพเลอร์หลายชุด, การ cross compile ที่ง่าย, การรับประกันความเข้ากันได้ หลัง Go 1.0, ไลบรารีมาตรฐาน และ gofmt ล้วนช่วยสนับสนุนการยอมรับใช้งาน
- Concurrency สำหรับจัดโครงสร้างซอฟต์แวร์เซิร์ฟเวอร์เป็นจุดแข็ง แต่ช่วงแรกอธิบายความต่างจาก parallelism และกรณีใช้งานที่เหมาะสมได้ไม่เพียงพอ จึงทำให้เกิดความสับสน
- Interface กลายเป็นรากฐานของไลบรารี การทดสอบ การจัดการ dependency และการออกแบบที่เน้น composition ส่วนการไม่มี generics ทำให้การออกแบบ polymorphism ยากอยู่กว่าสิบปีหลังจากนั้น
- ด้านการจัดการแพ็กเกจ เอกสาร และการทำงานร่วมกับคอมมูนิตี้ มีการลองผิดลองถูกมาก แต่ Go มุ่งสร้างสภาพแวดล้อมที่โค้ดดูคล้ายกันไม่ว่าใครหรือเมื่อไรเป็นผู้เขียน และยัง build ได้ต่อเนื่อง
เป้าหมายของ Go: วิธีพัฒนาซอฟต์แวร์มากกว่าตัวภาษา
- วันที่ 10 พฤศจิกายน 2023 เป็นวันครบรอบ 14 ปี นับตั้งแต่ Go เปิดตัวเป็นโปรเจกต์โอเพนซอร์ส
- ตอนเปิดตัวในปี 2009 มี Ken Thompson, Robert Griesemer, Russ Cox, Ian Taylor, Adam Langley, Jini Kim และคนอื่น ๆ ร่วมเฝ้าดูการเปิดโปรเจกต์
- เป้าหมายดั้งเดิมของ Go ไม่ใช่แค่ภาษาโปรแกรมมิงใหม่ แต่คือการ เขียนซอฟต์แวร์คุณภาพสูง ด้วยวิธีที่ดีกว่า
- คอขวดในเวลานั้นไม่ได้อยู่ที่ฟีเจอร์ของภาษาเองเท่าไร แต่อยู่ที่ความซับซ้อนของกระบวนการสร้างซอฟต์แวร์เซิร์ฟเวอร์สมัยใหม่ที่ Google
- การควบคุม dependency
- ทีมขนาดใหญ่และโครงสร้างบุคลากรที่เปลี่ยนแปลง
- ความง่ายในการบำรุงรักษา
- การทดสอบที่มีประสิทธิภาพ
- การใช้ประโยชน์จาก CPU แบบมัลติคอร์และเครือข่าย
- จนถึงวันนี้ Go ยังนิยามตัวเองว่าเป็นโปรเจกต์ที่ทำให้การสร้างซอฟต์แวร์สำหรับโปรดักชันง่ายขึ้นและมีผลิตภาพมากขึ้น
อัตลักษณ์ของชุมชนที่มาสคอต Gopher สร้างขึ้น
- Go gopher เป็นหนึ่งในปัจจัยช่วงแรกที่ช่วยให้ Go ประสบความสำเร็จ
- gopher ที่ Renee French สร้างขึ้นเป็น สัญลักษณ์ ที่โปรแกรมเมอร์ Go ใช้ร่วมกัน และทำหน้าที่เหมือนธงของคอมมูนิตี้
- เช่นเดียวกับชื่อคอนเฟอเรนซ์ GopherCon, gopher ช่วยสร้างบรรยากาศของโปรเจกต์ที่มีทั้งความเป็นเลิศทางเทคนิคและความสนุก
- การเลือกเผยแพร่ภายใต้ไลเซนส์ Creative Commons Attribution มีทั้งข้อดีและข้อเสีย
- ส่งเสริมการรีมิกซ์และดัดแปลง ช่วยหล่อเลี้ยงจิตวิญญาณของคอมมูนิตี้
- เงื่อนไข attribution ทำให้เกิดข้อถกเถียง หรือเกิดปัญหาให้เครดิตผิดกับงานที่ Renee French ไม่ได้สร้าง
- การดูแลให้มาสคอตคงจิตวิญญาณเดิมไว้ยังคงเป็นโจทย์ที่ยาก
ทางเลือกทางเทคนิคที่ Go ทำได้ดี
- เขียน สเปกแบบเป็นทางการ ก่อน เพื่อกำหนดพฤติกรรมของการ implement คอมไพเลอร์ให้ชัดเจน และทำให้หลาย implementation เห็นพ้องกับพฤติกรรมเดียวกันได้
- มีการตัดสินใจว่าคอมไพเลอร์เพียงตัวเดียวไม่สามารถเป็นสเปกได้
- ร่างสเปกฉบับแรกเขียนขึ้นบนชั้น 18 ของอาคารที่ Sydney Darling Harbour
- การมีคอมไพเลอร์หลาย implementation ช่วยขัดเกลาภาษาและสเปก
- Ian Taylor ส่งอีเมลเมื่อวันที่ 7 มิถุนายน 2008 ว่าเขาได้อ่านร่างสเปกของ Go และสร้าง gcc frontend แล้ว
- หลังจากนั้นก็มี implementation อื่น ๆ ที่เข้ากันได้เกิดขึ้นอีกหลายแบบ
- การทำให้ cross compile ง่ายช่วยให้นักพัฒนาทำงานบนแพลตฟอร์มที่ต้องการ และ deploy ไปยังแพลตฟอร์มที่จำเป็นได้
- หลังจากจัดระเบียบภาษาใน Go 1.0 แล้ว การเลือกนำ การรับประกันความเข้ากันได้ มาใช้ส่งผลอย่างมากต่อการยอมรับ Go
- แม้การรักษาความเข้ากันได้อย่างเข้มงวดจะมีต้นทุน แต่ช่วยลดความกังวลว่า Go release ใหม่จะทำให้โปรเจกต์พัง
- ไลบรารีมาตรฐานให้ฟังก์ชันส่วนใหญ่ที่จำเป็นต่อการเขียนโค้ดเซิร์ฟเวอร์ในศตวรรษที่ 21 และทำให้คอมมูนิตี้ทำงานบนกล่องเครื่องมือเดียวกัน
- ช่วงแรกไลบรารีขยายใหญ่ขึ้นส่วนหนึ่งเพราะยังไม่มีที่อื่นให้ติดตั้งโค้ด Go
- ช่วยลดการแตกแขนงของไลบรารีหลายแบบ และช่วยรวมคอมมูนิตี้เข้าด้วยกัน
ประสบการณ์พัฒนาที่เครื่องมือและ gofmt สร้างขึ้น
- Go ถูกออกแบบให้เป็นภาษาที่ parse ได้ง่าย จึงสร้างเครื่องมือได้ง่าย
- ช่วงแรกมองว่าจำเป็นต้องมี IDE แต่เมื่อการทำเครื่องมือทำได้ง่ายขึ้น เวลาผ่านไป IDE ต่าง ๆ ก็รองรับ Go และ gopls ก็เกิดขึ้น
- นอกจากคอมไพเลอร์ ยังมีเครื่องมือเสริมอย่างการทดสอบอัตโนมัติ coverage และ code vetting ให้มาด้วย
- คำสั่ง go รวมกระบวนการ build ทั้งหมดเข้าด้วยกัน และหลายโปรเจกต์สามารถใช้คำสั่งนี้จัดการสิ่งส่วนใหญ่ที่จำเป็นต่อการ build และบำรุงรักษาโค้ด Go ได้
- gofmt กลายเป็นเครื่องมือที่มีอิทธิพลไม่ใช่เฉพาะกับ Go แต่กับคอมมูนิตี้โปรแกรมมิงโดยรวม
- Robert Griesemer ยืนกรานตั้งแต่แรกว่าควรสร้าง gofmt
- ก่อน gofmt คุณภาพของ auto formatter ยังไม่สูง จึงแทบไม่มีใครใช้
- เวลาที่เสียไปกับการถกเถียงเรื่องเว้นวรรคและขึ้นบรรทัดใหม่ลดลง
- เมื่อภายในของ gofmt กลายเป็นไลบรารี ก็สามารถ parse และแก้ไข AST แล้วสร้างผลลัพธ์ที่มนุษย์อ่านได้กลับมาอีกครั้ง
- เครื่องมืออื่นอย่าง simplifier, analyzer และเครื่องมือ code coverage ก็เป็นไปได้ด้วย
Concurrency: ฟีเจอร์ที่ทรงพลัง แต่อธิบายช้าไป
- ในปี 2002 ซอฟต์แวร์ของ Google แทบหลีกเลี่ยงการใช้ thread และมีบรรยากาศที่อ้างแนวคิดของ John Ousterhout ว่า “threads were bad” เพื่อห้ามการใช้งาน
- Go เริ่มจากประสบการณ์ว่ามีวิธีใช้ concurrency ที่ดีกว่าแพ็กเกจระดับต่ำอย่าง pthread
- Go มีบทบาทสำคัญในการทำให้โลกโปรแกรมมิงยอมรับ concurrency เป็นเครื่องมือที่ทรงพลัง
- โดยเฉพาะในสภาพแวดล้อมมัลติคอร์และเครือข่ายที่ให้ผลมาก
- ปัจจุบันภาษา mainstream ส่วนใหญ่มีการรองรับ concurrency
- Go ไม่ใช้ชื่ออย่าง coroutine, task หรือ thread แต่สร้างคำใหม่ว่า goroutine
- async/await เป็นการพัฒนาที่เหนือกว่า pthread มาก แต่เมื่อเทียบกับ goroutine, channel และ select แม้จะง่ายกว่าและเล็กกว่าสำหรับผู้ implement ก็อาจผลักความซับซ้อนบางส่วนกลับไปให้โปรแกรมเมอร์
- ปัญหา “What Color is Your Function?” ของ Bob Nystrom ถูกใช้เป็นตัวอย่าง
- โมเดล CSP ของ Go เข้ากับภาษาเชิง procedural ได้ดี แต่ถ้าจะ implement ให้ถูกต้องต้องมีความซับซ้อนใน runtime
- หากมี implementation ของ concurrency หลายแบบใน environment เดียวกันอาจเป็นปัญหาได้ Go จึงใส่สิ่งนี้ไว้ในภาษา ไม่ใช่ในไลบรารี
สองสิ่งที่ทำผิดเกี่ยวกับ concurrency
- ทีม Go ไม่ได้ให้แนวทางเพียงพอในช่วงแรกว่าควรใช้ concurrency ที่ไหน
- กรณีใช้งานหลักคือการทำให้ซอฟต์แวร์เซิร์ฟเวอร์ง่ายขึ้นในไลบรารีแกนหลักอย่าง
net/http - มันไม่ใช่ฟีเจอร์ที่ควรใช้ทุกที่ในทุกโปรแกรม
- กรณีใช้งานหลักคือการทำให้ซอฟต์แวร์เซิร์ฟเวอร์ง่ายขึ้นในไลบรารีแกนหลักอย่าง
- การอธิบายความต่างระหว่าง parallelism กับ concurrency ช้าไปก็เป็นปัญหาเช่นกัน
- นักพัฒนาหลายคนพยายามใช้ goroutine เพื่อ parallelize โค้ดให้เร็วขึ้น แต่กลับช้าลงและสับสน
- โค้ด concurrent จะเร็วขึ้นจาก parallelization ได้ก็ต่อเมื่อปัญหานั้นมีลักษณะ parallel โดยตัวมันเอง เช่น การประมวลผลคำขอ HTTP
- เพื่อชดเชยเรื่องนี้ จึงมีการบรรยาย “Concurrency is not Parallelism” ที่ Waza ในปี 2012
- คำอธิบายนี้ควรเกิดขึ้นเร็วกว่านั้น
- ถึงอย่างนั้น Go ก็มีส่วนช่วยทำให้การใช้ concurrency เพื่อจัดโครงสร้างซอฟต์แวร์เซิร์ฟเวอร์เป็นที่นิยม
Interface และการออกแบบที่เน้น composition
- Interface เป็นไอเดียหลักที่ทำให้ Go แตกต่างควบคู่กับ concurrency
- interface ของ Go ใกล้เคียงกับคำตอบของ Go ต่อการออกแบบเชิงวัตถุแบบดั้งเดิมที่เน้นพฤติกรรม
- วิธีแบบ dynamic ที่ไม่ต้องประกาศล่วงหน้าว่า type ใด implement interface ใด ถูกวิจารณ์ในช่วงแรก แต่สำคัญต่อการเขียนโปรแกรมสไตล์ Go
- ไลบรารีมาตรฐาน การทดสอบ และการจัดการ dependency พึ่งพาธรรมชาติที่เปิดกว้างของ interface อย่างมาก
sortของ Go ถูก implement เป็นฟังก์ชันที่ทำงานบน interface- ต่างจากสไตล์ object-oriented ที่หลายคนคุ้นเคย แต่เป็นไอเดียที่ทรงพลัง
- interface ขนาดเล็กอย่าง
Reader,Writerและ empty interface กลายเป็นรากฐานของไลบรารี Go- interface ชื่อดังทั้งสามนี้มีจำนวนเมธอดเฉลี่ยอย่างละ 2/3 เมธอด
- ชุดเมธอดขนาดเล็กกลายเป็น idiom แบบ Go
ทางอ้อมอันยาวนานเกี่ยวกับ generics
- ทีม Go เห็นว่า generic programming มีแนวโน้มทำให้คิดถึง type ก่อน algorithm
- ชอบการออกแบบที่เติบโตอย่างเป็นธรรมชาติมากกว่าการ abstract ตั้งแต่ต้น
- ให้ความสำคัญกับฟังก์ชันมากกว่า container
- Go ใส่ generic container อย่าง map, slice, array และ channel ไว้ในภาษา แต่ไม่ได้มอบความ generic นั้นให้ผู้ใช้โดยตรง
- มีการประเมินว่างานโปรแกรมมิงง่าย ๆ ส่วนใหญ่เพียงพอกับ type เหล่านี้แล้ว แต่ก็มีกรณีที่ไม่พอ และเส้นแบ่งนี้ทำให้ผู้ใช้บางส่วนไม่สะดวก
- เพราะ interface เป็นรากฐานของการเขียนโปรแกรม Go โมเดล polymorphism ใหม่จึงต้องเข้ากับ interface ได้ดี
- มีความพยายามหลายครั้งและ implementation ที่หยุดไปกลางทาง
- หลังการถกเถียงจำนวนมาก ยังได้รับความช่วยเหลือจากนักทฤษฎี type ที่นำโดย Phil Wadler
- ท้ายที่สุด Go ออกแบบ generics ด้วยการ generalize interface จาก “ชุดเมธอด” เป็น “ชุด type”
- คำว่า “generics” มาจาก polymorphism ที่เน้น data structure จึงไม่ได้ชี้ตรง ๆ ถึง parametric polymorphism ที่ Go ให้มา แต่ในทางปฏิบัติก็ใช้คำว่า generics
คอมไพเลอร์: ตอนแรกเป็น C ต่อมาจึงเป็น Go
- การที่คอมไพเลอร์ Go ช่วงแรกเขียนด้วย C ทำให้บางส่วนของคอมมูนิตี้ภาษาไม่สบายใจ
- มีความเห็นว่าควรใช้ toolkit อย่าง LLVM หรือควร self-hosting ด้วย Go เอง
- เหตุผลที่เขียนคอมไพเลอร์แรกด้วย C เป็นเรื่องเชิงปฏิบัติ
- การ bootstrap ภาษาใหม่จำเป็นต้องมีภาษาที่มีอยู่ก่อน
- Ken Thompson เขียนคอมไพเลอร์ C ไว้แล้ว และโครงสร้างภายในของมันสามารถเป็นพื้นฐานของคอมไพเลอร์ Go ได้
- หากออกแบบภาษาไปพร้อมกับเขียนคอมไพเลอร์ด้วยภาษานั้นเอง ภาษาอาจเอนเอียงไปเป็นภาษาที่เหมาะกับการเขียนคอมไพเลอร์
- คอมไพเลอร์ช่วงแรกสร้างโค้ดได้ธรรมดา และภายในก็ไม่ได้สะอาดนัก แต่มีขนาดเล็ก คุ้นเคย และแก้ไขได้รวดเร็ว
- การเพิ่ม segmented stack ที่ขยายได้อัตโนมัติแสดงให้เห็นข้อดีของทางเลือกนี้
- หากใช้ toolkit อย่าง LLVM คงต้องรวมถึงการเปลี่ยน ABI และการรองรับ garbage collector ทำให้ผสานรวมได้ยาก
- โครงสร้างที่มาจากแนวทางของตระกูลคอมไพเลอร์ Plan 9 เดิมยังเข้ากับ cross compile ได้ดี
- ใน Go 1.5 Russ Cox เขียนเครื่องมือที่แปลคอมไพเลอร์จาก C เป็น Go แบบกึ่งอัตโนมัติ
- ณ จุดนี้ภาษาเสร็จสมบูรณ์แล้ว ความกังวลเรื่องการออกแบบภาษาที่เอนเข้าหาคอมไพเลอร์จึงหมดไป
- หลังย้ายมาเป็น Go ก็สามารถนำจุดแข็งของ Go เช่น การทดสอบ เครื่องมือ การ rewrite อัตโนมัติ และการวิเคราะห์ประสิทธิภาพ มาใช้กับการพัฒนาคอมไพเลอร์ได้ด้วย
- ปัจจุบันมีทั้งคอมไพเลอร์ Go ที่ใช้ LLVM และคอมไพเลอร์อื่นอีกหลายตัว
การดำเนินโปรเจกต์โอเพนซอร์สและคอมมูนิตี้
- Go ตัดสินใจตั้งแต่แรกว่าหากจะประสบความสำเร็จ ต้องเป็นโปรเจกต์โอเพนซอร์ส
- อย่างไรก็ตาม มองว่าการพัฒนาแบบปิดจะมีประสิทธิผลกว่า จนกว่าจะจัดระเบียบไอเดียหลักและ implementation ของพฤติกรรมให้ชัดเจน
- 2 ปีแรกก่อนเปิดเผยมีความสำคัญต่อการทำให้เป้าหมายชัดเจน
- หลังเปลี่ยนเป็นโอเพนซอร์ส input จากคอมมูนิตี้มีปริมาณมหาศาล โดยเฉพาะ Ian Taylor ใช้เวลามากในการตอบคำถาม
- พอร์ต Windows ทำโดยคอมมูนิตี้ทั้งหมดภายใต้การนำทางของ Alex Brainman
- ทีม Go ใช้เวลานานกว่าจะเข้าใจวิธีที่ดีที่สุดในการทำงานร่วมกับคอมมูนิตี้
- คิดว่าสื่อสารได้ดีแล้ว แต่ความเข้าใจผิดและความคาดหวังที่ไม่ตรงกันทำให้เสียเวลา
- โปรเจกต์ Go เลือกวิธีรักษา คุณภาพโค้ดสูง ก่อน merge แทนการรับโค้ดเข้ามาอย่างรวดเร็วแล้วค่อยจัดระเบียบทีหลัง
- code review ที่บังคับใช้
- การใส่ใจรายละเอียดอย่างถี่ถ้วน
- วิธีนี้เพิ่มภาระให้คอมมูนิตี้ ดังนั้นหากผู้ร่วม योगदानไม่เข้าใจคุณค่านั้น อาจรู้สึกว่าไม่ได้รับการต้อนรับ
- ประวัติ repository ของโปรเจกต์ Go ผ่าน SVN, Perforce, Mercurial และ Git แต่ Russ Cox รักษาประวัติไว้ ทำให้ใน Git repository ปัจจุบันยังมีบันทึกการเปลี่ยนแปลงยุค SVN ตอนต้นอยู่
- Google สนับสนุน Go แต่ไม่ได้กำหนดวาระของทีม Go
- codebase Go ขนาดใหญ่ภายใน Google ใช้สำหรับทดสอบและตรวจสอบ release
- ทิศทางคือการนำจาก repository สาธารณะเข้า Google ไม่ใช่กลับกัน
- ทีม Go หลักได้รับเงินเดือนจาก Google แต่ทำงานอย่างเป็นอิสระ
การลองผิดลองถูกด้านการจัดการแพ็กเกจ
- การออกแบบ package ของภาษา Go เองยอดเยี่ยม แต่กระบวนการพัฒนาการจัดการแพ็กเกจไม่ได้ราบรื่น
- การกำหนด path ในคำสั่ง
importเป็น string ธรรมดาเป็นทางเลือกที่สำคัญและยืดหยุ่น - ปัญหาคือการเปลี่ยนผ่านจากสภาพที่มีเพียงไลบรารีมาตรฐาน ไปสู่การดึงโค้ดจากเว็บนั้นขรุขระ
- ทีม Go ช่วงแรกคุ้นเคยกับ monorepo ของ Google และการ build จาก head
- มีประสบการณ์ไม่เพียงพอกับ package manager ที่มีแพ็กเกจหลายเวอร์ชัน
- ไม่ได้จัดการความซับซ้อนทางเทคนิคของการแก้ dependency graph ตั้งแต่ต้น
- งาน deps.dev ถูกมองเหมือนเป็นการชดเชยปัญหานี้ในทางหนึ่ง
- ความพยายามแก้ปัญหาการจัดการ dependency ร่วมกับคอมมูนิตี้เกิดจากเจตนาดี แต่เมื่อดีไซน์สุดท้ายออกมา สมาชิกคอมมูนิตี้จำนวนมากรู้สึกถูกกันออกไป
- ประสบการณ์นี้กลายเป็นบทเรียนว่าการมีส่วนร่วมของคอมมูนิตี้ควรเกิดขึ้นจริงอย่างไร และต่อมาก็ดีขึ้นมาก
- ปัจจุบันดีไซน์เสถียรแล้ว ยอดเยี่ยมในทางเทคนิค และดูเหมือนทำงานได้ดีสำหรับผู้ใช้ส่วนใหญ่
เอกสารและตัวอย่างที่รันได้
- เอกสารช่วงแรกเขียนไว้มาก แต่ยังไม่ตรงกับระดับที่คอมมูนิตี้ต้องการ
- ส่วนที่ขาดมากที่สุดคือแม้แต่ฟังก์ชันที่เรียบง่ายที่สุดก็ต้องมี ตัวอย่างการใช้งาน
- การแสดงให้เห็นว่าใช้อย่างไรมีคุณค่ามากกว่าการบอกว่ามันทำอะไร
- ปัจจุบันเอกสารมีตัวอย่างจำนวนมาก และส่วนใหญ่จัดทำโดยผู้ร่วม योगदानโอเพนซอร์ส
- Go ทำให้ตัวอย่างรันบนเว็บได้ตั้งแต่ช่วงแรก
- ในงานนำเสนอ Google I/O ปี 2012 ทำให้โค้ดสั้น ๆ ที่แสดง concurrency สามารถรันในเบราว์เซอร์ได้
- Andrew Gerrand เขียนเทคโนโลยีเว็บที่ทำให้สิ่งนี้เป็นไปได้
- ต่อมาถูกนำไปใช้กับบล็อกและเอกสารแพ็กเกจออนไลน์ด้วย
- The Go playground ถูกให้บริการเป็น sandbox สาธารณะที่ผู้คนใช้ทดลองและพัฒนาโค้ดได้อย่างอิสระ
ตำแหน่งที่ Go ไปถึงหลังผ่านไป 14 ปี
- ด้วยการตัดสินใจต่าง ๆ ในการออกแบบภาษาและกระบวนการพัฒนา Go ได้กลายเป็นมากกว่าภาษาโปรแกรมมิงธรรมดา แต่เป็นวิธีการเขียนซอฟต์แวร์
- องค์ประกอบต่อไปนี้มีส่วนช่วยให้ Go มาถึงจุดนี้
- ไลบรารีมาตรฐานที่แข็งแกร่ง ซึ่ง implement ฟังก์ชันพื้นฐานส่วนใหญ่ที่จำเป็นต่อโค้ดเซิร์ฟเวอร์
- concurrency ในฐานะองค์ประกอบชั้นหนึ่งของภาษา
- แนวทางที่อิงกับ composition มากกว่าการสืบทอด
- โมเดล packaging ที่ทำให้การจัดการ dependency ชัดเจน
- เครื่องมือ build และ test ที่รวดเร็วและรวมเป็นหนึ่ง
- การจัดรูปแบบที่เข้มงวดและสม่ำเสมอ
- ทัศนคติที่ให้ความสำคัญกับความอ่านง่ายมากกว่าความฉลาดแพรวพราว
- การรับประกันความเข้ากันได้
- คอมมูนิตี้ Go ประกอบด้วย Gopher ที่หลากหลายและกระตือรือร้น และมีบทบาทสำคัญต่อความสำเร็จของโปรเจกต์
- ผลลัพธ์คือโค้ด Go ดูและทำงานคล้ายกันไม่ว่าใครเป็นผู้เขียน มีฝักฝ่ายที่ใช้ subset ของภาษาต่างกันค่อนข้างน้อย และรับประกันว่าจะยัง compile และ run ได้แม้เวลาผ่านไป
- คุณลักษณะเหล่านี้ถือเป็นผลลัพธ์ที่พบได้ยากในภาษาโปรแกรมมิงหลัก ๆ
1 ความคิดเห็น
ความเห็นบน Hacker News
น่าขอบคุณมากที่บุคคลสำคัญสละเวลามาเขียน บททบทวนย้อนหลัง และมันช่วยคนที่อยากสร้างการเปลี่ยนแปลงจริง ๆ ในตอนนี้ได้มาก
แต่ก็รู้สึกว่า Rob Pike ยังไม่ได้อธิบายให้ชัดพอว่า Go ทำอะไรได้ถูกต้องบ้าง Go จัดการได้ดีไม่ใช่แค่ตัวภาษา แต่รวมถึงแรงกดดันที่มีต่อโปรเจ็กต์ด้วย: จำกัดขอบเขตการใช้งานเป้าหมายไว้ที่ระบบโปรแกรมมิง, นิยามภาษาและหลักการให้ชัดเจน, ยึดท่าทีให้ความสำคัญกับคุณภาพโดยแก้ปัญหาก่อนปล่อยใช้งาน, และควบคุมภาษา การออกรีลีส และสารหลักอย่างเข้มงวด ขณะเดียวกันก็เปิดพื้นที่ให้ชุมชนเป็นผู้นำในส่วนย่อย
อีกจุดที่คนประเมินค่าน้อยเกินไปคือ Google เองไม่ได้เข้ามาแทรกแซงมากนัก อาจเป็นเพราะ Go มีส่วนช่วยเป้าหมายของ Google จริงและมีความสำคัญต่อบริษัท เลยไม่แน่ใจว่าวันนี้โปรเจ็กต์ใหม่ ๆ จะยังเป็นแบบนั้นได้ไหม
Dart แทบไม่ได้รับการยอมรับนอก Flutter ซึ่งก็น่าสนใจเมื่อเทียบกับความจริงที่ว่าโค้ดแอปพลิเคชันมีมากกว่าโค้ดระบบมหาศาล Go เป็นเทคโนโลยีหลักที่ย้ายซอฟต์แวร์ฝั่งเซิร์ฟเวอร์ออกจากระบบนิเวศ Java อันเทอะทะไปสู่คอนเทนเนอร์แบบเนทีฟ และกลายเป็นฐานของโครงสร้างพื้นฐานเว็บแอปส่วนใหญ่ตลอด 10 ปีที่ผ่านมา แม้จะเติบโตเร็วขนาดนั้น ทีมก็ยังเล็กและคนแกนหลักก็ยังอยู่ต่อ แต่ไม่รู้ว่าจะเป็นแบบนั้นต่อไปหรือไม่
ดังนั้นตามการออกแบบแล้วจึงไม่เหมาะจะเป็นภาษาสำหรับ system programming การที่ช่วงแรกเคยเรียกตัวเองแบบนั้นทำให้เกิดความเชื่อแปลก ๆ ค้างอยู่นาน แต่ดูเหมือนว่าข้อความนั้นจะถูกลบไปเมื่อหลายปีก่อนแล้ว
Go ใกล้เคียงกับการเป็นคู่แข่งของสาย Java มากกว่า C หรือ Rust และก็ดูออกจากขอบเขตการใช้งานจริง เพียงแต่ด้วยระบบ type ที่จำกัดและการรองรับ concurrency ที่ยอดเยี่ยม มันจึงมักไปอยู่ในชั้นที่ต่ำลงมาอีกนิดของสแตก
โดยเฉพาะเรื่องการจัดการเวอร์ชัน ตอนแรก Go ไม่ยอมรับด้วยซ้ำว่ามีปัญหา แต่พอฉันทามติในชุมชนเริ่มชัดขึ้น ก็เหมือนลืมการถกเถียงก่อนหน้าไปแล้วประกาศว่าจะไปทาง modules แทน
ยังจำได้ด้วยว่าก่อน Cloudflare จะเจอเหตุขัดข้องจากเวลาออมแสง พวกเขาไม่อยากทำ monotonic time ให้เป็น public API
ในโลก Java มีความสนุกจากการใช้ฟีเจอร์ใหม่ ๆ เช่น streams ที่ยอดเยี่ยม เพื่อรีแฟกเตอร์โค้ดเบส แต่ใน Go ความสนุกมาจากการค้นหานามธรรมที่เรียบง่ายและธรรมดาที่สุด
ทุกภาษาต้องมี ไลบรารีและเฟรมเวิร์ก ที่เหมาะกับโดเมนเฉพาะ Flutter คือเฟรมเวิร์กสำหรับเขียนแอปฝั่งไคลเอนต์ด้วย Dart และถ้าไม่มี Flutter ต่อให้ชอบภาษา Dart มากแค่ไหน คุณก็ยังต้องเสียเวลามหาศาลกับเรื่องพื้นฐานอย่างการวาดพิกเซลขึ้นจอบน Android และ iOS
ทุกคนที่เขียนแอปต่างก็สร้างบนกองของไลบรารีและเฟรมเวิร์ก ความต่างระหว่าง Go กับ Dart ที่จับคู่กับ Flutter ก็แค่ไลบรารีด้านโดเมนที่จำเป็นกับเซิร์ฟเวอร์ เช่น networking, serialization, cryptography นั้นถูกรวมอยู่ใน standard library ของ Go
Dart มีองค์ประกอบพื้นฐานอย่าง collections และ async ในไลบรารีมาตรฐาน แต่ความสามารถเฉพาะโดเมนจะพึ่งพาแพ็กเกจภายนอกอย่าง Flutter เป็นหลัก เรื่องนี้ส่วนใหญ่เป็นเพราะ Dart มีระบบจัดการแพ็กเกจที่แข็งแรงมาตั้งแต่ช่วงแรกมาก ๆ ไลบรารีแกนกลางจำนวนมากที่ทีม Dart เป็นคนเขียนและดูแลก็ยังแจกจ่ายผ่านตัวจัดการแพ็กเกจแทนการฝังมาในภาษา เพราะพัฒนาได้ง่ายกว่ามาก
โดยทั่วไป UI framework มักมีอายุสั้นกว่าภาษา ดังนั้นผมจึงคิดว่าการที่ Flutter ไม่ได้ถูกฝังแน่นอยู่ใน standard library ของ Dart เป็นเรื่องที่ดีกว่า Flutter ยอดเยี่ยมมาก แต่ถ้าอีก 20 ปีข้างหน้ามีอะไรที่ดีกว่าออกมาก็ไม่น่าแปลกใจ และถ้าตอนนั้น Flutter ไม่ได้เป็นของฝังมากับภาษา ผู้ใช้ Dart ก็จะย้ายไปใช้สิ่งใหม่ได้ง่ายกว่า
ของอย่าง Android ซึ่งเป็น FOSS ปลอม ๆ ยังพอยอมรับได้เพราะมีคุณค่าทางยุทธศาสตร์ แต่นอกนั้นถ้าไม่พาไปสู่การคลิกโฆษณา ก็บรรยากาศเหมือนถูกมองว่าไม่มีประโยชน์
ในปี 2012 ตอนเป็นนักพัฒนา Python ผมต้องทำงานจัดการบิตสำหรับ Modbus เลยหยิบ Go มาใช้ แม้โค้ดนั้นจะไม่ได้เอาไป deploy แต่ก็ช็อกมากที่งานจับบิตจับไบต์มันง่ายและ ใช้งานได้เลย
ผ่านมา 10 ปี และหลังจากได้ทำงาน Go แบบเกือบเต็มตัวอยู่หลายครั้ง ก็ยังประหลาดใจอยู่ดีที่ส่วนใหญ่แล้วมัน ใช้งานได้เลย
ผมชอบภาษา Go และชุมชน Go ขอบคุณ Rob, Ian, Russ และคนอื่น ๆ ที่สร้าง Go สำหรับงานที่ทำ และก็ขอบคุณที่การนำเสนอ/บทความนี้พูดถึง “ช่วงหนัก ๆ” กับชุมชนอย่างตรงไปตรงมา
แต่ในฐานะคนที่ผ่านช่วงนั้นมา โดยเฉพาะความวุ่นวายเรื่องการจัดการแพ็กเกจ มันยากมากที่จะเชื่อว่าพวกเขาไม่รู้จริง ๆ ว่าตัวเองกำลังทำอะไรอยู่ ถึงอย่างนั้น การประเมินในบทความที่ว่าตอนนี้มาถึงทางออกที่ดีแล้วก็น่าจะถูกต้อง
หวังว่าอีก 10 ปีข้างหน้าของ Go และภาษาแนวเดียวกันที่ได้รับแรงบันดาลใจอย่าง Zig และ Deno จะยังเติบโตต่อไปพร้อมชุมชนที่แข็งแรง
ผมเป็นนักชีววิทยาเชิงคำนวณมากกว่าโปรแกรมเมอร์ เลยใช้ Go แบบ ๆ หยุด ๆ แต่พอกลับมาใช้ Go อีกครั้ง โค้ดเก่าก็ยังคอมไพล์ได้ และภาษาก็ยังทำงานตามที่คาดหวัง
ขณะเดียวกันก็ชอบที่ Rob Pike ยอมรับความผิดพลาดระหว่างที่เรียนรู้การเปิดรับการมีส่วนร่วมจากชุมชน โดยที่ไม่ได้รับเอาฟีเจอร์หวือหวาทุกอย่างเข้ามาหมด
บางตัวใช้งานได้ทันที บางตัวต้องแก้นิดหน่อยเพราะโมเดล AI รู้ข้อมูล API แบบเก่าไปหน่อย
แต่ถึงอย่างนั้น แค่
scpโปรแกรมไปที่เซิร์ฟเวอร์แล้วมันก็ทำงานได้เลย ก็ยังน่าทึ่งเมื่อเทียบกับตอนอยู่ Python ที่ต้องเต้นตามกระแส “รอบนี้ระบบ venv ตัวไหนกำลังฮิตนะ” อยู่ตลอดเป็นบทความที่ดี แต่ก็ดูเหมือนชื่นชมตัวเองพอสมควร ผมนึกว่าจะมีการยอมรับถึงปัญหาเชิงลึกของภาษานี้บ้าง แต่ดูเหมือนใจความสำคัญคือ สำหรับพวกเขาแล้วภาษานี้สมบูรณ์แบบ และคนที่มีปัญหาคือผมเอง
เพราะแบบนี้เลยแนะนำ Go ได้ยาก มีปัญหาทั้งเรื่องระบบชนิดข้อมูล, การจัดการข้อผิดพลาด, concurrency ที่ไม่ปลอดภัย, ไวยากรณ์ที่เรียบง่ายเกินไป,
nil, ค่าเริ่มต้นเป็นศูนย์ และแพ็กเกจหลักจำนวนมากที่ถูกปล่อยปละละเลยตอนนี้ผมใช้ Rust เป็นภาษาหลัก ecosystem คึกคักและมีวิสัยทัศน์ในหลายด้านที่ Go ไม่มี
ถ้าจะพูดให้คมขึ้นหน่อย ก็ดูเหมือน Go เคยมีช่วงเวลาของมันตอนที่แข่งขันกับ PHP ในฐานะภาษาแบ็กเอนด์
ย่อหน้าแรกคือ “ไม่ชอบ”, ย่อหน้าที่สองคือประชด, ย่อหน้าที่สามก็ “ไม่ชอบ”, ย่อหน้าที่สี่คือ “Rust ดีกว่า”, และย่อหน้าที่ห้าก็ยังดูประชดอีก
deps.devหายไปหลายปีต่อมา ที่บริษัทผมพยายามนำ Go เข้ามาให้ทีมเพื่อให้งานเสร็จ แต่มีวิศวกรอีกคนใช้เวลาศึกษาภาษาอย่างจริงจังแล้วไล่เหตุผลว่าทำไมมันไม่ค่อยดี และเขาก็พูดถูก
แก่นของปัญหาคือ มันอาจจะเรียบง่าย แต่เรื่องเล็กน้อยอย่างการจัดการข้อผิดพลาดและ imports ที่ไม่ได้ใช้ กลับทำให้การใช้งานเจ็บปวด ส่วนตัวผมชอบการจัดการข้อผิดพลาด แต่ไม่ชอบระบบชนิดข้อมูล
ก่อนหน้านั้นผมเคยใช้ OCaml เลยรู้อยู่แล้วว่าประเภท
OptionและResultมีคุณค่ามากกว่า try/catch หรือnilแต่ผมใช้ Go เพราะมันง่าย อย่างไรก็ตาม ความง่ายนั้นมาพร้อมต้นทุนการบำรุงรักษาระยะยาว ควรทำให้ถูกตั้งแต่แรกดีกว่า แล้วจะได้ไม่ต้องไปเจอความยากทีหลังไม่ได้จะพูดเล่น ๆ นะ แต่ผมได้ยินคำอธิบายบ่อยมากว่า Go คือภาษาที่เอาความก้าวหน้าของภาษาการเขียนโปรแกรมตลอด 50 ปีที่ผ่านมาโยนทิ้งไป
ไม่ได้หมายความในแง่ลบ Rob Pike ไม่ได้สนใจว่า concurrency ไม่ปลอดภัย หรือว่า
nilคือความผิดพลาดมูลค่าพันล้านดอลลาร์ และผู้ใช้ Go ส่วนใหญ่ก็คงไม่สนใจเหมือนกันแต่ก็ไม่ได้แปลว่า Go กลายเป็นภาษาที่ดี ประเด็นไม่ได้อยู่ตรงนั้น Go เป็นภาษาที่สะดวกและดีพอสมควร เมื่อจับคู่กับเครื่องมือที่น่าเชื่อถือซึ่งทำให้ใช้งานได้ง่าย มันเหมือน Crocs แห่งวงการภาษาการเขียนโปรแกรม
ถ้าต้องการความถูกต้องสมบูรณ์และความปลอดภัยระดับสูง ผมจะใช้ Ada หรือ Ada/SPARK ที่มี formal verification นอกนั้นแล้ว Go ให้ผลิตภาพสูงกว่า Rust มาก
สำหรับผม Rust เป็นตัวอย่างคลาสสิกของการออกแบบเกินความจำเป็นเหมือน C++
ฟังดูอาจจะขมขื่น แต่เมื่อ 10 ปีก่อนผมเคยถูกเยาะเย้ยใน
go-nutsและยังได้รับคำตอบแบบเมินเฉยจาก Rob Pike ด้วย เพราะผมกล้าพูดว่ารูปแบบที่go getกับการ import โมดูลผ่านเครือข่ายทำงานอยู่ ซึ่งถูกโปรโมตในเอกสารสำหรับมือใหม่แทบทั้งหมดและถูกใช้กันทั้งชุมชน สุดท้ายแล้วเป็นแนวทางที่เป็นโทษและมองสั้นโครงสร้าง namespace ทั่วโลกแบบประหลาดนั้นพออยู่ในนั้นก็สมเหตุสมผล และการเน้นโค้ดที่ generate แล้วก็เข้าใจได้ ถ้ามองว่า Google ใส่แม้แต่ผลลัพธ์การ build ไว้ใน monorepo ด้วย
แน่นอนว่ามีปัญหา
v2และปัญหาv0ที่เหมือนไม่มีวันจบ แต่ก็ยังดีกว่าการใช้ npm หรือไปยุ่งกับอะไรบางอย่างใน Python ที่ทำเอาอยากทำเครื่องหมายกางเขนใส่บทความนี้ดูเหมือนเป็นเวอร์ชันที่เป็นกันเองมากขึ้นของบทความ [1] ที่ลงใน ACM ทั้งสองบทความต่างยอมรับว่า สิ่งที่สร้างขึ้นไม่ใช่ภาษาโปรแกรมใหม่ที่ยอดเยี่ยมในมุมมองของข้อกำหนดภาษา แต่เป็นการสร้างสิ่งรอบ ๆ ภาษาการเขียนโปรแกรมได้ดีมาก และนั่นอาจสำคัญยิ่งกว่า
ในบทความที่ส่งมานี้ ผู้เขียนบอกว่าตนคิดค้นแนวทางการใช้อินเทอร์เฟซและแนวทางด้าน concurrency แต่ goroutine ก็เหมือนกับ thread ของ Haskell และอินเทอร์เฟซก็คล้ายกับ typeclass ของ Haskell มาก หากรองรับ generic parameters แบบทุกวันนี้ เมื่อดูว่า Haskell มาก่อน Go ก็ชวนให้สนใจว่าพวกโปรแกรมเมอร์สาย procedural ไปค้นพบพลังของแนวคิดจาก functional programming ได้ด้วยตัวเองอย่างไร
นวัตกรรมด้านภาษาของ Go อย่างหนึ่งคือ การ implement อินเทอร์เฟซไม่จำเป็นต้องประกาศก่อนว่าตัวเอง implement อินเทอร์เฟซอะไร ในมุมความปลอดภัยนี่แย่มาก แต่ในทางปฏิบัติแทบไม่เป็นปัญหาเลย และยังช่วยกำจัดปัญหา circular dependency ที่เลวร้ายใน Haskell และ Rust ได้ด้วย
[1] https://cacm.acm.org/magazines/2022/5/260357-the-go-programm...
ถ้าชนิดข้อมูลต้องประกาศล่วงหน้าว่าตัวเอง implement อินเทอร์เฟซใดบ้าง Go ก็คงเป็นคนละภาษาไปเลย นี่เป็นหนึ่งในจุดต่างหลักของ Go อย่างน้อยก็จนกว่า TypeScript จะออกอินเทอร์เฟซแบบ structural typing มาใช้ด้วยเหตุผลอีกแบบหนึ่ง
พอคุณนิยามอินเทอร์เฟซแล้ว มันมีภาษาโปรแกรมไหนบ้างที่เราควรใส่ใจ ซึ่งบังคับให้ต้องมีการ implement อินเทอร์เฟซนั้นโดยเฉพาะ?
ดูเหมือนว่าปัญหาด้านการเข้ารหัสลับของ Go ยังไม่ได้ถูกพูดถึงมากนัก หลังจาก OpenSSH เลิกใช้ SHA1 ทีม Go ใช้เวลาถึง 1 ปีกว่าจะเพิ่มการรองรับ SHA2 ให้กับ
x/crypto/ssh[1] และ Gitea ก็เป็นผู้ได้รับผลกระทบที่เป็นที่รู้จักรายหนึ่ง [2]นอกจากนี้ การที่ผู้ดูแลส่วนการเข้ารหัสลับโจมตี GnuPG [3] และพยายามบั่นทอนความน่าเชื่อถือของ Dan Bernstein [4] ก็ไม่ได้ช่วยสร้างความเชื่อมั่น
[1]: https://github.com/golang/go/issues/49269
[2]: https://github.com/go-gitea/gitea/issues/17798
[3]: https://twitter.com/FiloSottile/status/1127643698676797441
[4]: https://twitter.com/FiloSottile/status/1555669786826244096
x/หลายตัวมักถูกปล่อยปละละเลย และใช้รูปแบบการพัฒนาที่ทำให้ผู้มีส่วนร่วมหน้าใหม่ส่งแพตช์ให้สำเร็จได้ยากในทางกลับกัน ผมมองว่าแพ็กเกจ crypto ใน standard library เป็นหนึ่งในความสำเร็จครั้งใหญ่ของ Go โดย OpenSSL เป็นหายนะมาอย่างยาวนาน ส่วน Go และโดยเฉพาะ agl ได้สร้างและเผยแพร่ implementation ทางเลือกที่ถูกใช้งานอย่างกว้างขวางและมีคุณภาพเฉลี่ยสูงกว่า โดยเฉพาะในแง่ที่มี footgun ใน public API น้อยกว่ามาก
บทความนี้ยก
gofmtเป็นสิ่งที่ “ทำได้ถูกต้อง” ซึ่งควรเน้นเป็นพิเศษสำหรับนักออกแบบภาษาและผู้สนับสนุนหลายคน มันอาจดูเป็นเรื่องเล็กน้อย แต่ตั้งแต่วันแรกของโครงการ Go มันได้กำจัดหนึ่งในข้อถกเถียงเรื่องจุกจิกที่พบบ่อยไปอย่างสิ้นเชิง จึงมีคุณค่าอย่างมาก
ผมเห็นภาษารุ่นใหม่หลายภาษาจงใจลอกแนวคิดนี้ หรือยอมรับมันเพราะคิดว่าเมื่อ Go มี เราก็ควรมีด้วย
ผมก็เคยเห็นกฎการจัดรูปแบบที่ค่อนข้างแปลกอยู่บ้าง แต่แทบไม่เคยมีกฎไหนที่ปรับตัวให้คุ้นเคยไม่ได้ ตรงกันข้าม ในโค้ดเบสที่ไม่มีกฎการจัดรูปแบบแบบบังคับ หลายครั้งผลลัพธ์กลับทำให้โค้ดเข้าใจยากกว่าเดิมมาก
go fmtทำให้เรื่องนั้นแทบไม่มีความหมายไปเลยเป็นส่วนใหญ่เห็นด้วยกับคำพูดที่ว่า “การมีไลบรารีที่แข็งแรง สร้างมาอย่างดี และมีเกือบทุกอย่างที่จำเป็นสำหรับการเขียนโค้ดเซิร์ฟเวอร์ในศตวรรษที่ 21 เป็นทรัพย์สินชิ้นใหญ่”
Go ได้รับเงินสนับสนุนจาก Google เพราะ Google มีปัญหาที่ต้องแก้ ต้องเขียนโค้ดแอปพลิเคชันฝั่งเซิร์ฟเวอร์จำนวนมาก และ Python ก็ช้าเกินไป ขณะที่ C++ ก็เปราะบางเกินไป Go จึงเหมาะกับช่องว่างนั้นมาก
โบนัสก้อนใหญ่คือคนของ Google เขียนไลบรารีที่จำเป็นสำหรับงานแบบนั้นและใช้งานเองภายในด้วย ดังนั้นเมื่อใช้ไลบรารีเหล่านั้น แม้แต่เส้นทางข้อผิดพลาดก็เป็นโค้ดที่ถูกใช้งานจริงมาเยอะแล้ว
มีข้อเห็นต่างเชิงเทคนิคเกี่ยวกับตัวภาษาอยู่บ้าง โดยเฉพาะการเน้นในช่วงแรกที่พยายามใช้คิวกับทุกอย่าง ถึงขั้นสร้าง mutex จากคิวด้วยซ้ำ แต่พวกเขาจับประเด็นเรื่อง green thread ที่เหมาะกับกรณีใช้งานได้ถูกต้อง ปัญหา function coloring เป็นปัญหาจริง และลูกเล่นยุ่งยากใน Rust ที่บังคับให้ใช้ async กับ thread ร่วมกันนั้นมากเกินไป Go ยอมแลกประสิทธิภาพเล็กน้อยเพื่อความเรียบง่ายที่สูงมาก
ความเกลียดชัง thread ในยุคก่อนก็น่าสนใจเหมือนกัน ผมเริ่มจากเมนเฟรม UNIVAC ซึ่งมี thread มาตั้งแต่ปี 1967 และตอนนั้นเรียกว่า “activities” แล้ว ในปี 1972 ก็ไม่เพียงมี user-space thread แต่ยังรันบน symmetric multiprocessor ได้ด้วย ยังมี asynchronous I/O, user-space completion function, คำสั่งในตัวสำหรับ lock และระบบปฏิบัติการที่ภายในทำงานแบบ thread อีกต่างหาก
Thread และ multiprocessor เคยถูกมองว่าเป็นเรื่องปกติ และพอย้ายมาสู่ UNIX ก็เหมือนสูญเสียสิ่งเหล่านั้นไป รออีกหลายสิบปีกว่า UNIX/Linux จะตามทันด้านนี้ และโปรแกรมเมอร์หลายรุ่นก็ไม่รู้วิธีใช้ shared-memory multiprocessor
ทฤษฎี concurrency ยุคแรกของ Dijkstra ถูกนำไปคิดใหม่บ่อยครั้งด้วยคำศัพท์คนละชุดและมักเป็นดีไซน์ที่แย่กว่า คนของ Go เข้าใจ bounded buffer ของ Dijkstra และเข้าใจว่าทำไม bounded buffer ที่มีความยาว 0 และ 1 จึงมีประโยชน์ การได้เห็นสิ่งนั้นกลับมาอีกครั้งเป็นเรื่องดี ถ้ามี primitive ที่ถูกต้อง concurrency ก็ไม่ได้ยากขนาดนั้น แต่ถ้าพยายามแก้แบบแนวคิด pthreads คลาสสิกที่หว่าน lock ไปทั่ว มันจะไม่ทำงานดีนัก และในยุคแรกของ UNIX/Linux ตัวจัดตาราง CPU ก็แย่มากจนการปลด block ของ thread ทำงานได้อย่างเลวร้าย ซึ่งยิ่งไม่ช่วยอะไรเลย
Go ถูกสร้างขึ้นเพราะ Rob Pike มีปัญหา ต่างหาก เขาไม่ชอบ C++, Java, Python และเพราะเขาเป็น Rob Pike นี่เอง Google จึงยอมให้เขาใช้เวลาหลายปีทำภาษาใหม่ ไม่ใช่ว่า Larry, Sergey, Sundar หรือ Eric เดินมาหา Rob แล้วขอให้ทำอะไรบางอย่าง
โดยรวมแล้ว Go ได้รับเงินสนับสนุนเพราะ Rob Pike ขอแรงสนับสนุน และ Google ก็อยากรั้ง Rob Pike ไว้ เพราะมองว่าเขาแทบจะแน่นอนว่าจะทำอะไรที่น่าสนใจออกมาได้ สำหรับ Google Engineering คำว่าเงินบริษัทในทางปฏิบัติก็แทบจะหมายถึงการอนุญาตให้คนได้ลงมือทำงานนั่นเอง
เพราะ Rob Pike เป็นวิศวกรที่ยอดเยี่ยม Go จึงออกมาดีพอสมควรในหลายด้าน และตอบโจทย์ปัญหาเดิมของเขาที่การเขียนแอปพลิเคชันปฏิบัติการของ Google ต้องใช้ C++, Python, Java คนภายใน Google บางส่วนก็เห็นด้วยจึงเริ่มใช้ Go และในกลุ่มนั้นก็มีคนที่มีอิทธิพลซึ่ง “สนับสนุน” ให้คนอื่นใช้ Go แทน Python
ใน Google ปี 2009 ถ้าไม่นับกรณีจากการเข้าซื้อ YouTube แล้ว Python แทบไม่ได้ถูกใช้กับเซิร์ฟเวอร์เลยนอกจากยูทิลิตีภายในไม่กี่ตัว ดังนั้นแนวคิดนี้ตัดทิ้งได้ทันที เซิร์ฟเวอร์ Python ที่ใหญ่ที่สุดที่ Google สร้างเองคือเครื่องมือ code review ชื่อ Mondrian ที่ Guido van Rossum เขียน และต่อมาก็ถูกแทนที่ด้วย Critique ที่เขียนด้วย Java
ตอนนั้น Google มี นโยบายสามภาษา ที่ค่อนข้างเข้มงวดเพื่อหลีกเลี่ยงสงครามภาษาโปรแกรม ใช้ได้แค่ C++, Java และ Python โดย Python ใช้สำหรับสคริปต์, C++ ใช้กับอินฟราสตรักเจอร์ และ Java ใช้กับเว็บเซิร์ฟเวอร์
ก็มีส่วนที่ทับซ้อนกันอยู่ Java ถูกนำเข้ามาทีหลัง C++ และเว็บเซิร์ฟเวอร์บางตัวก็ยังคงเป็น C++ เพราะเขียนใหม่ได้ยาก มีความพยายามย้ายแบบค่อยเป็นค่อยไปไปยัง Java ด้วย แต่ไม่ได้ลงหลักปักฐานมากนัก อินฟราสตรักเจอร์เซิร์ฟเวอร์บางตัวอย่าง Megastore เขียนด้วย Java และไลบรารีแกนหลักส่วนใหญ่เป็น C++ พร้อม JNI และ Python binding
นโยบายนี้ก็มีช่องโหว่อยู่บ้าง บางทีมจึงสร้างภาษาของตัวเองสำหรับอินฟราสตรักเจอร์ภายใน และใน codebase จริงก็มีทั้งภาษา config แบบกำหนดเองและ Sawzall ที่ Rob Pike สร้างขึ้น ซึ่งไวยากรณ์คล้าย Go อยู่เล็กน้อย
โดยรวมแล้วผู้คนมีอิสระในการเลือกสูง เซิร์ฟเวอร์เว็บใหม่ ๆ มักเขียนด้วย Java ส่วนสิ่งอย่างเอนจินฐานข้อมูลใหม่ ๆ ก็มักเขียนด้วย C++
การเขียนโค้ด async นั้นยากจริง และ Go ก็แก้ปัญหาส่วนนั้นได้ถูกทางจริงเช่นกัน แต่ Go ไม่ได้เป็นโปรเจกต์ที่ผู้บริหารระดับสูงเริ่มขึ้นเพื่อแก้ปัญหาของ Google ไม่ใช่เพราะผู้บริหารกลัวโปรเจกต์แบบนั้น ตรงกันข้าม ตอนนั้นมีโปรเจกต์อินฟราสตรักเจอร์ภายในจำนวนมหาศาลที่ทีมเกี่ยวข้องสร้างขึ้นและจัดคนมาทำโดยตรงเพื่อแก้ความลำบากของนักพัฒนา ตัวอย่างเช่นการอัปเกรดเครือข่ายขนาดใหญ่ที่ผลักดันในช่วงนั้น โปรเจกต์พวกนี้มักมีการแจ้งล่วงหน้า
ผมไม่จำได้ว่ามีความไม่พอใจอย่างหนักกับสภาพตอนนั้น ปัญหาอยู่ที่เวลา build จนกระทั่ง Bazel/Blaze ทำงานร่วมกับคลัสเตอร์ build ระยะไกล หลังจากนั้นทุกอย่างก็ถูกแคชระยะไกล ทำให้แม้ codebase ขนาดมหึมาก็คอมไพล์ได้ภายในไม่กี่วินาที ความเร็วของคอมไพเลอร์บนเครื่องจึงแทบไม่สำคัญ และ
javacเองก็เร็วมากการประกาศเปิดตัว Go เป็นเรื่องน่าประหลาดใจพอสมควร ถ้ามันถูกพัฒนาขึ้นมาเพื่อแก้ปัญหาของ Google จริง ตามปกติแบบ Google ก็ควรปล่อยใช้ภายในก่อน แล้วพัฒนาร่วมกับผู้ใช้ภายในไปอีกหลายปีก่อนเปิดเผยสู่ภายนอก แต่ Go กลับเปิดสู่สาธารณะก่อน คนรอบตัวผมต่างสงสัยว่านี่สร้างมาทำอะไร ซึ่งเป็นปฏิกิริยาที่ไม่น่าจะเกิดถ้าความต้องการภายในเป็นตัวขับเคลื่อนจริง ๆ
ส่วนที่ว่าด้วยการตัดสินใจเลือกใช้คอมไพเลอร์ C ของ Ken Thompson แทน LLVM นั้นน่าสนใจ ผู้คนบ่นกัน และโดยเฉพาะในเวอร์ชันแรก ๆ ก็ทำให้โค้ดที่สร้างออกมาถูกปรับแต่งประสิทธิภาพได้น้อยกว่า
แต่ข้อแลกเปลี่ยนคือสามารถทำให้ segmented stacks ใช้งานได้อย่างรวดเร็ว ถ้าต้องไปทำบน LLVM และต้องให้เข้ากับ LLVM ABI ก็อาจทำไม่ได้เลยด้วยซ้ำ
ในบทความยกเรื่องนี้ขึ้นมาไม่ใช่ในฐานะข้อดีเพียงอย่างเดียวของการตัดสินใจนี้ แต่เป็นเพียงตัวอย่างหนึ่งของข้อดี
มีการรองรับอยู่แล้วใน X86FrameLowering และมีอยู่ตั้งแต่ตอนที่ Go เปิดตัวด้วยซ้ำ เมื่อเปิดใช้ stack splitting แล้ว LLVM จะปล่อยการตรวจสอบไว้ใน function prologue และถ้าจำเป็นก็จะเรียก
__morestackเพื่อจัดสรรสแตกเพิ่ม โค้ดที่คล้ายกันมากยังจำเป็นสำหรับ Windows MSVC ABI เพื่อรองรับ_chkstkอยู่แล้ว จึงเข้ากันได้อย่างเป็นธรรมชาติกับการรองรับ__morestackกลับกัน การทำให้ GDB เข้าใจ segmented stacks นั้นยากกว่าส่วนใด ๆ ของการทำคอมไพเลอร์เสียอีก และเรื่องนี้ไม่เกี่ยวกับแบ็กเอนด์
ผู้เขียนอาจสับสนกับ relocatable stacks มากกว่า ในเวลานั้นการทำให้ใช้งานได้ยากเพราะต้องอาศัย precise garbage collection แต่ตอนนี้ Azul ได้ทำไว้บน LLVM แล้ว ตอนนั้นวิธีที่ง่ายที่สุดในการทำ precise garbage collection คือบังคับ spill รีจิสเตอร์ทั้งหมดข้ามการเรียกฟังก์ชัน ซึ่งต้องใช้ความพยายามในการพัฒนาเพิ่มขึ้น แต่ก็ไม่ได้มากเกินไป ดูเหมือนว่า Plan 9 compiler ก็ทำแบบนั้นอยู่แล้ว ดังนั้นก็คงไม่ใช่การถดถอยด้านประสิทธิภาพเมื่อเทียบกับ 6g/8g ไม่ว่าอย่างไร ตอนนี้การรองรับ garbage collection ของ Azul ก็มีการติดตั้งใช้งานที่ถูกต้องซึ่งสามารถเก็บ root ไว้ในรีจิสเตอร์ได้แล้ว
ดังนั้นความสามารถในการทดลองจึงมีคุณค่าอยู่จริง แต่ตัวอย่างเฉพาะนี้ไม่ใช่กรณีตัวอย่างที่สมบูรณ์แบบนัก
[1]: https://go.dev/doc/go1.3#:~:text=Go%201.3%20has%20changed%20...
ระหว่างที่กำลังคิดภาษางานอดิเรกแบบ static type ผมก็พยายามทำอะไรบางอย่างที่จัดการทั้ง interface และ generics ไปพร้อมกัน และสัมผัสได้ด้วยตัวเองว่า Rob พูดถูก การทำให้สองอย่างนี้ทำงานเข้ากันได้ดีนั้นยากมาก
ถึงอย่างนั้นผมก็ยังคิดว่ามันคุ้มค่า โดยส่วนตัวแล้วคงไม่ค่อยรู้สึกว่าการใช้ภาษาที่เป็น static type แต่ไม่เปิดให้ผู้ใช้กำหนด generic type เองได้นั้นมีคุณค่าเท่าไร
เมื่อก่อนผมเคยเขียนโปรแกรมในสภาพแวดล้อม BASIC ที่มี
GOSUBสำหรับซับรูทีน แต่ไม่มีทางเขียนซับรูทีนที่รับอาร์กิวเมนต์ได้ ผมไม่อยากย้อนกลับไปเจอประสบการณ์แบบนั้นอีกในระดับ type systemเพราะอย่างนั้นจึงเข้าใจได้ว่าทีม Go ใช้เวลานานในการหาแบบออกแบบที่ดี การออกแบบภาษาที่ดีนั้นยาก และการออกแบบภาษาที่ดีที่มี type system นั้นยากขึ้น 10 เท่า ส่วนการออกแบบ type system ที่มี generics ก็ยากกว่านั้นไปอีก 10 เท่า
เรียกได้ว่า Go เลือกทำส่วนที่ง่ายที่สุดของ generics เหตุผลที่ไม่ได้ทำก่อนหน้านี้ไม่ใช่เรื่องเทคนิค แต่เป็นเรื่องอุดมการณ์ และตั้งอยู่บนความไม่รู้ล้วน ๆ เพราะผู้ออกแบบ Go ส่วนใหญ่ไม่ได้ติดตามแวดวง ทฤษฎีภาษาการเขียนโปรแกรม มาตั้งแต่ปลายยุค 90 แล้ว