คำโกหกที่เราบอกตัวเองเพื่อใช้ Golang ต่อไป (2022)
(fasterthanli.me)- ข้อโต้แย้งที่พบซ้ำๆ ต่อคำวิจารณ์ Go เช่น “บริษัทใหญ่ก็ใช้”, “มันก็มีข้อดี”, “ความถูกต้องมีต้นทุนสูง” อาจบดบังปัญหาที่ ข้อบกพร่องในการออกแบบภาษา สะสมเป็นต้นทุนระยะยาว
- Go แข็งแกร่งในด้าน async runtime, GC, เครื่องมือจัดการแพ็กเกจ·refactoring·cross compile แต่ก็ต้องยอมรับ custom toolchain และระบบ build, calling convention, GC เดียว, รวมถึง ecosystem ไปพร้อมกัน
- เพราะไม่มี sum type, ควบคุมความเปลี่ยนแปลงได้ไม่เพียงพอ, zero value, และพฤติกรรมที่ต่างกันของ
nilslice·map·channel ทำให้ compiler ไม่สามารถช่วยรักษา invariant จำนวนมากได้ - หากไม่มี cgo ก็ยากที่จะผสานกับภาษา·เครื่องมืออื่น และถ้าใช้ cgo ก็จะมีภาระเรื่อง GC และกฎ pointer, การรวม runtime, static library ขนาดใหญ่ ทำให้ ขอบเขตเครือข่าย กลายเป็นจุดผสานที่สมจริงที่สุด
- ความคาดหวังว่า “ทำ prototype ด้วย Go ก่อน แล้วค่อย rewrite ทีหลังได้” ในทางปฏิบัติอาจเพิ่ม ต้นทุนการดำเนินงาน เพราะมักเลี่ยงการ rewrite, ผูกติดกับโค้ด Go เดิม, และต้องแบกรับภาระ linter·เอกสาร·การตรวจสอบด้วยมือ
ตรรกะป้องกันที่เกิดซ้ำต่อคำวิจารณ์ Go
- หลังบทความปี 2020 I want off Mr Golang’s Wild Ride ปฏิกิริยาต่อคำวิจารณ์ Go ยังคงวนซ้ำเดิมๆ
- ปฏิกิริยาว่ากรณีของ Windows ไม่ใช่ขอบเขตที่ Go ทำได้ดี
- ปฏิกิริยาว่าไม่พูดถึงข้อดีของ Go
- ปฏิกิริยาว่าไม่เข้าใจการประนีประนอมที่ Go เลือก
- ปฏิกิริยาว่าบริษัทใหญ่ใช้ Go ดังนั้นคงไม่ได้แย่มาก
- ปฏิกิริยาว่าต้นทุนในการ model ปัญหาให้ “ถูกต้อง” นั้นสูง จึงไม่มีความหมายที่จะถกเรื่องความถูกต้อง
- ปฏิกิริยาว่า Rust ก็มีข้อเสีย ดังนั้นคำวิจารณ์ Go จึงใช้ไม่ได้
- ประเด็นหลักไม่ใช่ว่า Go มีข้อดีหรือไม่ แต่อยู่ที่ ตรรกะป้องกัน ที่ถูกใช้ซ้ำเพื่อใช้ Go ต่อไปนั้นกำลังบดบังต้นทุนจริงหรือไม่
วิธีโจมตีคุณสมบัติของผู้วิจารณ์ก่อน
- เมื่อมี feedback ที่ไม่อยากได้ยิน หากสรุปไปก่อนว่าคนพูดไร้ความสามารถหรือมีแรงจูงใจแอบแฝง ก็ไม่จำเป็นต้องตรวจสอบเนื้อหา
- การพบปัญหาไม่ได้จำเป็นต้องใช้ความเชี่ยวชาญสูงเสมอไป และ developer ระดับ senior อาจกลับมองข้ามความไม่สะดวกที่เคยชินไปแล้วได้
- developer ระดับ junior ยังไม่คุ้นกับสิ่งแปลกๆ จึงอาจรู้สึกไม่สะดวกและตั้งคำถามได้เร็วกว่า
- คุณค่าของ feedback ควรถูกพิจารณาจากว่ามีปัญหาจริงหรือไม่ มากกว่าจากว่าใครเป็นคนพูด
ข้อจำกัดของเหตุผลว่า “บริษัทอื่นก็ใช้”
- ในการเลือกเทคโนโลยี การดูว่าบริษัทอื่นใช้อะไรมีประโยชน์ต่อการ ค้นพบ แต่ยังไม่เพียงพอเป็นเหตุผลในการนำมาใช้
- บล็อกเทคโนโลยีของบริษัทมักเปิดเผยต้นทุนการนำมาใช้ได้ไม่ครบถ้วน
- มักเขียนหลังจากมีการตัดสินใจไปแล้ว
- มีเป้าหมายให้ภาพลักษณ์บริษัทดูดีและช่วยเรื่องการรับสมัครงาน
- คำวิจารณ์ทางเทคนิคที่รุนแรงมักมาจากตัวบุคคลมากกว่าจากบริษัท
- การใช้ Go ของ Tailscale ใกล้เคียงกับกรณีของทีมผู้เชี่ยวชาญ Go ที่รู้ต้นทุนและรับมือได้
- netaddr.IP: a new IP address type for Go เผยให้เห็นข้อจำกัดของ Go ในกระบวนการแสดงที่อยู่ IPv4 หรือ IPv6
- Hey linker, can you spare a meg? พูดถึงปัญหาที่ต้องมีความรู้ลึกเกี่ยวกับ linker ของ Go
- ที่มาของความเจ็บปวดที่ปรากฏใน
netaddr.IPเกี่ยวพันกับข้อจำกัดหลายอย่างของ Go- Go ไม่มี sum type ทำให้แสดง type ที่เป็น “IPv4 หรือ IPv6” ได้อย่างฝืนๆ
- slice ถูกใช้เป็นโครงสร้างข้อมูลทั่วไปที่มีต้นทุน 24 ไบต์บนเครื่อง 64 บิต
- ไม่มี operator overloading จึงเกิดการแยกอย่าง
a == bกับa.equals(b) - ไม่รองรับ immutable data จึงต้องส่งสำเนาและระวังการแก้ไขภายใน
- สร้าง
newtypeแบบ opaque ได้ยาก จึงต้องใช้แพ็กเกจแยกและอ้อมผ่าน interface
- ทีมอย่าง Tailscale สามารถรับมือรายละเอียดภายในและกรณีขอบของ Go ได้ลึก แต่ไม่ใช่ทุกองค์กรที่จะมีผู้เชี่ยวชาญ Go และต้นทุนทางวิศวกรรมแบบ Google หรือ Tailscale
สิ่งที่ Go ทำได้ดีจริงๆ
- Go มี async runtime ที่ค่อนข้างดี, ค่า default ที่มีจุดยืนชัดเจน, GC สมัยใหม่ที่มี knob ปรับได้สองตัว และ toolchain ที่ดี
- เพราะ async runtime อยู่เป็นศูนย์กลางของภาษา จึงมีเครื่องมือบางอย่างที่ developer Rust อาจอิจฉา
- dump backtrace ของ goroutine ทั้งหมดที่กำลังรันได้ง่าย
- ตรวจจับ deadlock ได้
- มี profiler ของตัวเอง
- ทำให้ไม่ต้องกังวลเรื่อง function coloring มากนัก
- เครื่องมือจัดการแพ็กเกจ, refactoring, cross compile เมื่อเทียบกับ
pkg-config, autotools, CMake ใน ecosystem ของ C แล้ว ตอนแรกอาจรู้สึกว่าเป็นการปรับปรุง - อย่างไรก็ตาม เราไม่ได้เลือกใช้เฉพาะ runtime ที่สะดวกของ Go เท่านั้น
- custom toolchain
- ระบบ build
- calling convention
- GC เดียว
- standard component และ ecosystem ที่รวมมาให้
- รวมถึงการออกแบบภาษาที่เกิดขึ้นโดยบังเอิญ ก็ถูกนำมาใช้พร้อมกันด้วย
ช่องโหว่ของ type system และการออกแบบภาษา
- Go วาง runtime สำหรับ TCP, HTTP, TLS, HTTP/2, DNS และการสร้าง web service ไว้เป็นแกนกลาง และมีรูปแบบที่คุ้นเคยสำหรับ developer ของ Google ที่เรียน Java/C/C++/Python มา
- เช่นเดียวกับ C มันไม่ได้จัดการ error handling อย่างจริงจังนัก และ developer ต้องจัดการ state ที่เปลี่ยนแปลงได้จำนวนมากด้วย
ifและelseเอง - เช่นเดียวกับ Java มันทำให้เส้นแบ่งระหว่าง value กับ reference คลุมเครือ และยากที่จะรู้จาก call site เพียงอย่างเดียวว่าค่าจะถูกเปลี่ยนหรือไม่
- ขึ้นอยู่กับว่า method receiver เป็น
Aหรือ*Alocal variableaในmainอาจถูกเปลี่ยนหรือไม่ก็ได้
- ขึ้นอยู่กับว่า method receiver เป็น
- การจำกัด mutability และ immutability ให้ชัดเจนด้วย type ทำได้ยาก เมื่อส่ง reference จึงมีความเสี่ยงว่าค่าภายในจะถูกเปลี่ยน หรือถูกเก็บไว้นานจนไม่ถูกปล่อย
- Go ป้องกันกลุ่มข้อผิดพลาดต่อไปนี้ได้ไม่เพียงพอ
- เผลอ copy mutex จนทำให้มันไร้ผล
- ไม่ initialize field ของ struct ทำให้มี zero value เข้าไป
nil, zero, empty string ที่ผิดพลาดแพร่กระจายลึกเข้าไปในระบบ
zero value และข้อจำกัดของ “แค่ระวังก็พอ”
- แม้เพิ่ม field ใหม่ใน struct จนรูปแบบ input ของ function เปลี่ยนไปในทางปฏิบัติ แต่ถ้าลืม field ใหม่ใน composite literal compiler ก็ไม่กันไว้
- ในตัวอย่าง เพิ่ม
b int32เข้าไปในParamsแต่ถ้า call site ส่งแค่a: 47ก็จะรันด้วยb=0 - ในทางกลับกัน หากเพิ่ม argument ให้ function โดยตรง จะเกิด compile error เพราะจำนวน argument ไม่พอ
- ในตัวอย่าง เพิ่ม
- หนึ่งในหลักการของ Go คือ zero value มีประโยชน์ แต่ความหมายจริงแตกต่างกันไปตาม type
- เรียก
lenบนnilslice ได้ 0 จึงทำงานเหมือน slice ว่าง - ใส่ค่าลงใน
nilmap จะเกิดpanic: assignment to entry in nil map
- เรียก
- channel ก็มี rule แยกต่างหาก
- send ไปยัง
nilchannel จะ block ตลอดไป - receive จาก
nilchannel จะ block ตลอดไป - send ไปยัง channel ที่ปิดแล้วจะเกิด panic
- receive จาก channel ที่ปิดแล้วจะคืน zero value ทันที
- send ไปยัง
- Go ไม่มี sum type จึงยากที่จะแสดง input ที่ “อนุญาตเพียงหนึ่งในตัวเลือกที่รู้จัก” ด้วย type และใกล้เคียงกับแนวทางแบบ C ที่ว่า “ก็แค่ระวังเอา”
- ท่าทีที่ว่าเพราะป้องกันทุกปัญหาไม่ได้ จึงไม่จำเป็นต้องป้องกันแม้แต่บางปัญหา เป็น false dichotomy และทำให้ปัญหาที่ type system ช่วยได้ถูกโยนไปให้ความระมัดระวังด้วยมือ
การเทียบกับ Rust ไม่ใช่ข้อถกเถียงเรื่อง “ตัวแทนที่สมบูรณ์แบบ”
- ไม่ได้ถือว่า Rust สมบูรณ์แบบ และ Go กับ Rust สามารถมองได้ว่าเป็นกรณีความสำเร็จคนละแบบ
- ความสำเร็จของ Go พึ่งพา component ที่รวมมาให้และ default ที่มีจุดยืนชัดเจนเป็นอย่างมาก
- ความสำเร็จของ Rust พึ่งพาการนำมาใช้แบบค่อยเป็นค่อยไปและความง่ายในการผสานกับภาษาอื่นเป็นอย่างมาก
- กรณีการนำ Rust มาใช้จริงใกล้เคียงกับ การย้ายบางส่วนแบบค่อยเป็นค่อยไป มากกว่าการแทนที่ทั้งหมด
- Firefox มี component Rust สำคัญหลายส่วนอยู่ใน codebase C++ ขนาดใหญ่
- Android implement Bluetooth stack ใหม่ด้วย Rust
- โค้ด cryptography ของ Rust เข้าไปอยู่ใน Python และโค้ด HTTP ของ Rust เข้าไปเป็นหนึ่งใน backend หลายตัวของ curl
- patch Rust สำหรับ Linux kernel กำลังได้รับการปรับปรุงซ้ำๆ
- ผู้ใช้ Rust เองก็พูดถึง build time, การขาด feature ของภาษา และข้อเสียอื่นๆ อย่างจริงจัง
- ประเด็นไม่ใช่การต้องเลือก Go หรือ Rust อย่างเด็ดขาด แต่อยู่ที่เครื่องมือที่สามารถแสดง mutability·lifetime·abstraction ด้วย type ช่วยลดปัญหาแบบใดได้บ้าง
- แม้ใน Rust ก็อาจไม่ model ทุกกรณีอย่างเข้มงวด
Go ใกล้เคียงกับเกาะที่แยกจาก ecosystem อื่น
- หากไม่ใช้ cgo Go ก็ห่างจาก assembly, linker, debugger, memory checker, calling convention ทั่วไป
- Go ใกล้เคียงกับภาษาแบบ closed-world มากกว่า C/C++ และเป็นมิตรกับ FFI น้อยกว่า Node.js·Python·Ruby
- ความแตกต่างนี้ก็มีข้อดีเช่นกัน
- สามารถดูภายใน TLS และ HTTP stack ด้วย profiler ในแบบเดียวกับ business logic ได้
- แตกต่างจากภาษา dynamic ที่ stack trace หยุดอยู่ที่ OpenSSL
- runtime สามารถจัดการ non-blocking I/O และ scheduling ได้
- แต่ใช้เครื่องมือเดิมและความรู้เชิงสถาบันที่มีอยู่จำนวนมากได้ยาก และการผสาน Go กับภาษาอื่นก็ยุ่งยากเช่นกัน
- วิธีผสาน Go แต่ละแบบมีต้นทุนของมัน
- หากเรียก C จาก Go นอกจากต้นทุนข้าม FFI boundary แล้ว ยังต้องติดตาม descriptor ด้วยมือเพื่อไม่ให้ GC พัง
- หากเรียก Go จากภาษาอื่น ต้องใส่ Go runtime ทั้งหมดรวมถึง GC เข้าไปด้วย ทำให้เกิด static library ขนาดใหญ่และภาระด้าน operation
- ขอบเขตที่ดีที่สุดสำหรับการเชื่อมกับ Go ใกล้เคียงกับ ขอบเขตเครือข่าย
- RPC over TCP เช่น REST-ish HTTP/1, JSON-RPC, gRPC ค่อนข้างเจ็บปวดน้อยกว่า หากรับต้นทุน latency ได้
- ถึงอย่างนั้นก็ยังต้องรักษา invariant ทั้งสองฝั่ง และในฝั่ง Go หากไม่ใช้แพ็กเกจ validation อย่างสม่ำเสมอ compiler ก็ไม่รับประกันให้
กับดักของความคาดหวังว่า Go เหมาะสำหรับ prototype
- มีความคิดว่าเพราะ Go เรียนง่ายและหาคนได้ง่าย จึงสร้าง prototype ก่อน แล้วหากภายหลังยากขึ้นก็ค่อย rewrite หรือดึงผู้เชี่ยวชาญเข้ามา
- แต่ “โค้ดที่จะทิ้ง” แทบไม่มีอยู่จริงในทางปฏิบัติ
- การ rewrite ใช้เวลา
- การประสานการเปลี่ยนผ่านให้ราบรื่นทำได้ยาก
- รายละเอียดอาจหายไป
- การพัฒนา feature ใหม่หยุดชะงัก
- ต้อง retrain คนให้เข้ากับเทคโนโลยีใหม่
- ยิ่ง component Go เพิ่มขึ้น ก็ยิ่งมีเหตุผลเพิ่มขึ้นที่จะเลือก Go ต่อไปเพื่อให้โต้ตอบกับโค้ด Go เดิมได้
- linter ช่วยได้บางส่วน แต่ทำได้ไม่เท่า compiler ของภาษาที่จัดการปัญหานั้นอย่างจริงจังตั้งแต่แรก และยังทำให้ความเร็วในการพัฒนาช้าลงด้วย
- ความซับซ้อนที่ไม่มีในภาษา จะย้ายไปอยู่ใน codebase
- invariant ที่ไม่ได้แสดงด้วย type ต้องเขียนเป็นโค้ดเอง
- จาก function signature เพียงอย่างเดียว ยากที่จะรู้ว่าข้อมูลจะถูกแก้ไขหรือไม่, ถูกเก็บไว้หรือไม่, อนุญาต zero value หรือไม่, จะเริ่ม goroutine หรือไม่, มีความเป็นไปได้ของ
nilchannel หรือไม่, และจริงๆ แล้ว type ใดส่งเข้าinterface{}ได้บ้าง - ต้องพึ่งพาเอกสาร และเอกสารมีต้นทุนสูงทั้งการอัปเดตและการไม่อัปเดต
“คำโกหก” ที่สรุปในท้ายที่สุด
- สิ่งที่เราบอกตัวเองเพื่อใช้ Go ต่อไปสรุปได้ดังนี้
- คนอื่นใช้กัน ดังนั้นมันก็น่าจะดีสำหรับเรา
- คนที่พูดถึงข้อกังวลเป็นพวก elitist หรือคนเสียมารยาท
- async runtime และ GC ที่น่าดึงดูดชดเชยข้อเสียอื่นทั้งหมด
- ข้อบกพร่องในการออกแบบภาษาแต่ละอย่าง ไม่ว่าจะเดี่ยวๆ หรือรวมกัน ก็ไม่เป็นไร
- สามารถเอาชนะได้ด้วย “การระวัง”, linter, และ reviewer ที่มากขึ้น
- เพราะเขียนง่าย การพัฒนา production software ก็เลยง่าย
- เพราะภาษาง่าย สิ่งอื่นๆ ก็เลยง่ายด้วย
- ใช้แค่นิดเดียวหรือใช้แค่ตอนแรกก็พอ และออกจากมันได้ง่าย
- rewrite ทีหลังเมื่อไรก็ได้
- หากเครื่องมือที่เลือกไม่สามารถกำจัดความซับซ้อนได้ ความซับซ้อนนั้นจะย้ายไปยัง developer, operator และลูกค้าคนอื่น
- สถานการณ์ที่ค่า
nilเข้าไปอยู่ในที่ที่ไม่ควรเข้าและทำให้ต้องตื่นกลางดึกระหว่าง operation จะนำไปสู่การทำซ้ำ Billion Dollar Mistake
2 ความคิดเห็น
ผมก็ไม่แน่ใจเหมือนกันว่าคนสมัครเล่นที่เพิ่งได้คลุกคลีกับภาษา Go แบบเข้มข้นมาเพียงช่วงเวลาสั้นแสนสั้นจะเขียนความเห็นได้ไหม... แต่ดูเหมือนว่าภาษา Go จะมีทั้งข้อดีและข้อเสียที่ชัดเจนมาก จนทั้งคนที่เลือกใช้และคนที่หลีกเลี่ยงต่างก็มีเหตุผลที่ชัดเจนของตัวเอง สำหรับผมคงไม่ค่อยเหมาะที่จะเอาไปเทียบกับ Rust และน่าจะเทียบกับ Kotlin(Java) มากกว่า
goroutine ของ Go นั้นยอดเยี่ยมจริง แต่ก็ไม่ใช่เวทมนตร์ โดยเฉพาะในงานแบ็กเอนด์ที่เป็นโปรเจ็กต์เล็กและใช้ MySQL แค่ตัวเดียว เรื่อง concurrency นี่จัดการยากมาก ปัญหาทรัพยากร MySQL หมดหรือการจัดการ pool ที่ใน JS/TS runtime แทบไม่ต้องกังวล กลับยากกว่าที่คิด สุดท้ายในสถานการณ์แบบนี้ DB จะกลายเป็นคอขวด ทำให้ข้อดีด้าน concurrency ของภาษา Go ลดทอนลงไปบางส่วน (async I/O หรือ event loop ของ JS/TS runtime อาจจะเหมาะกว่าด้วยซ้ำ) ลองยิงด้วยเครื่องมืออย่าง
heyแบบ-c 100ดูก็จะเข้าใจทันทีแล้วถึงจะมี GC ที่ยอดเยี่ยม ก็ไม่ได้หมายความว่าจะใช้วัตถุแบบส่งต่อแต่ pointer อย่างเดียวได้ตามใจ แล้วไม่สนใจการจัดการภายหลัง ทุกอย่างมี trade-off และสำหรับภาษา Go ถ้าเป็นวัตถุขนาดเล็กก็ควรส่งแบบคัดลอกค่าไปใช้ แล้วให้มันถูกจัดการทันทีเมื่อฟังก์ชันจบจะดีกว่า ผมอาจจะติดกรอบความคิดเก่าอยู่ก็ได้ แต่คงไม่ควรเข้าหา pointer แบบง่าย ๆ ด้วยมุมมองเรื่องประสิทธิภาพเหมือนในภาษา C/C++
การที่เวลารีเทิร์นจากฟังก์ชันต้องรีเทิร์น
errorแทบทุกครั้ง และต้องตรวจด้วยif err != nil {}ทุกครั้งนั้นน่ารำคาญมาก แต่สิ่งนี้ก็เป็นข้อดี เพราะมีต้นทุนต่ำกว่าtry catchและคีย์เวิร์ดdeferที่ทำหน้าที่คล้ายfinally {}ก็ยอดเยี่ยมมากเช่นกัน เพราะไม่ต้องกังวลว่าจะปล่อยทรัพยากรเมื่อไร นอกจากนี้การที่สามารถประกอบแบ็กเอนด์เซิร์ฟเวอร์ที่ดีได้ทันทีด้วย standard library เพียงอย่างเดียวก็ดีมากเช่นกัน (1.23 ขึ้นไป) และที่ดีที่สุดคือถ้าบิลด์ให้ตรงกับ target OS ก็ไม่จำเป็นต้องมี runtime อื่นหรือการติดตั้งล่วงหน้าเพิ่มเติมผมยังใช้ภาษา Go มาไม่นาน แต่เหมือนจะเขียนความเห็นส่วนตัวเสียยาวเกินไปแล้ว ขอยุติแค่นี้ครับ 555 ผมชอบทั้งภาษา Go และภาษาอื่น ๆ เหมือนกัน!
ความคิดเห็นจาก Hacker News
บทความนี้ชี้ข้อเสียของ Go ได้ดีหลายจุด แต่ผมไม่คิดว่า การจัดการข้อผิดพลาดแบบชัดเจน เป็นหนึ่งในนั้น
ผมไม่ชอบการจัดการข้อยกเว้น เพราะมันเพิ่มชั้นของ “เวทมนตร์” ที่ทำพังได้ง่ายเกินไป และแม้จะไม่ใช่ข้อโต้แย้งที่สมบูรณ์แบบในเชิงเทคนิค แต่จากประสบการณ์หลายสิบปี ผมชอบแนวทางที่จัดการข้อผิดพลาดตรงจุดที่มันเกิดขึ้นมากกว่า
จะบอกว่า Rust ทำได้สง่างามกว่าก็ได้ และในโปรเจกต์ส่วนตัวผมก็ชอบ Rust มากกว่า แต่ในโปรเจกต์ใหญ่ที่มีนักพัฒนาหลากหลายระดับเข้ามาออกไป ผมมองว่าปรัชญาของ Go ค่อนข้างสมเหตุสมผลในบรรดาวิธีจัดการข้อผิดพลาดสมัยใหม่
ในพื้นที่ของผม Go ถูกนำไปใช้มากกว่าภาษา “ใหม่” อื่น ๆ มาก เพราะความเรียบง่ายของมัน มันไม่ใช่ภาษาที่ดีที่สุด แต่มีวิจารณญาณในตัวหลายอย่างที่ช่วยกันไม่ให้เราทำร้ายตัวเอง จึงมักเป็นตัวเลือกที่ดีที่สุดสำหรับภาษาเอนกประสงค์
ไม่มี stack trace และต้อง wrap ข้อผิดพลาด ทำให้ต้องสร้างไม่เพียงข้อความข้อผิดพลาดที่ไม่ซ้ำกัน แต่ยังต้องมีข้อความ wrap ที่ไม่ซ้ำในแต่ละจุดเรียกด้วย เพื่อจะได้ใช้
grepข้อความแล้วประมาณสิ่งที่คล้าย stack trace ได้“ทูเพิลที่คืนค่า” เป็นกลไกประหลาดที่มีอยู่เพราะข้อผิดพลาดเท่านั้น และในส่วนอื่นของภาษาก็ใช้ทูเพิลไม่ได้ด้วยซ้ำ ด้วยกฎการกำหนดค่าเริ่มต้นตัวแปรที่ขัด ๆ สักวันหนึ่งคุณจะเผลอใช้ตัวแปร
errผิดตัวGo ควรมี enum หรือไม่ก็ควรใส่ syntactic sugar ที่ดีกว่าอย่าง
?ของ Rust แทน “ทูเพิลที่คืนค่า” แปลก ๆ ที่มีไว้เฉพาะข้อผิดพลาด วิธีปัจจุบันน่าเบื่อและเกิดข้อผิดพลาดได้ง่ายGo มีความดื้อรั้น แต่บ่อยเกินไปที่มันดูเหมือนความดื้อรั้นเพื่อปกป้องภาษาไม่ให้ถูกวิจารณ์ น่าเสียดายที่การที่ภาษาโปรแกรมจะได้รับความนิยม การตลาด หรือก็คือคำโกหก ก็สำคัญ และวิธีนี้ก็ได้ผล
exception คือ
gotoอันเลวร้ายที่ไลบรารีใด ๆ ก็โยนใส่โค้ดของผมได้ และแทบไม่เคยถูกจัดการอย่างถูกต้อง แม้หลังจาก exception ถูก “จัดการ” แล้ว แอปพลิเคชันก็มักอยู่ในสถานะที่ไม่รู้แน่ชัดและไม่สามารถให้เหตุผลตามได้ข้อผิดพลาดของ Go โดยทั่วไปแม้แต่วิศวกรจูเนียร์ก็ debug ได้ง่าย แต่สาเหตุแท้จริงของ exception ใน Java แม้แต่ principal ที่มีประสบการณ์ก็ยังจับต้นตอได้ยาก
ในทางกลับกัน ผมยังไม่เคยเห็นคนที่คุ้นกับ sum type อย่าง Maybe, Option, Result แล้วมองว่าแนวทางของ Go พอใช้ได้แม้แต่นิดเดียว
ถ้าคุณพยายามอ่าน logic ของเมธอดหนึ่ง แต่ 75% เป็นเสียงรบกวนเรื่องข้อผิดพลาด และมีเพียง 25% ของบรรทัดที่จำเป็นต่อการเข้าใจพฤติกรรมจริง มันก็อ่านยาก
เมื่อดู codebase ครั้งแรก ผมอยากรู้ก่อนว่ามันพยายามทำอะไรให้สำเร็จ มากกว่าดู edge case ของข้อผิดพลาด แล้วค่อยดูว่ามันรับประกันความถูกต้องอย่างไร
ในจุดนี้ การจัดการข้อผิดพลาดของ Go ล้มเหลวอย่างมาก ไม่ใช่ว่าการจัดการข้อผิดพลาดแบบชัดเจนไม่ดี แต่ปัญหาคือ การยืนกรานว่าต้องเอาเส้นทางสำเร็จกับการจัดการข้อผิดพลาดมาสลับปนกันทีละบรรทัด ทั้งที่ยังสามารถจัดการอย่างชัดเจนได้ในส่วนเฉพาะสำหรับจัดการข้อผิดพลาด
ส่วนที่เห็นด้วยคือมันบังคับให้คิดถึงความเป็นไปได้ทั้งหมดที่โค้ดสามารถสร้างขึ้นได้ เพียงแต่นี่เป็นคำถามที่ใกล้กับ C มากกว่าภาษาอื่น
เวลาทำ abstraction ให้โค้ด บางครั้งเราไม่จำเป็นต้องจัดการข้อผิดพลาดทันที หรืออยากจัดการมันในระดับล่างของ call stack
เท่ากับว่าภาษายอมสละ ความอ่านง่าย ไปมาก เพื่อบังคับให้ใช้แนวทางใดแนวทางหนึ่ง
Rust กับ Go แตกต่างกันมาก และดูเหมือนว่าผู้คนต้องการ จุดกึ่งกลาง ที่ตอนนี้ยังไม่มีอยู่จริง
หมายถึงภาษาที่มี garbage collection ค่อนข้างเรียบง่าย คอมไพล์เป็นไบนารีแบบ static link ได้ แต่มี type system คล้าย Rust และสิ่งอย่าง remainder type
ในเชิงไวยากรณ์ Gleam กับ Kotlin ใกล้เคียงอยู่บ้าง แต่ก็ยังไม่ใช่ทั้งหมด ผมชอบ Rust แต่คิดว่ามันซับซ้อนเกินไปสำหรับคนจำนวนมากที่แม้จะมีความสามารถในการสร้างอะไรบางอย่างได้ แต่ไม่ได้จบวิทยาการคอมพิวเตอร์และไม่ได้ทำงานเป็นโปรแกรมเมอร์
ถ้าใช้ภาษาแค่เป็นครั้งคราว ก็จะจำไม่ได้ว่า vtable คืออะไร หรือค่าถูกปล่อยเมื่อไรและอย่างไร ไม่มี “ภาษาที่สมบูรณ์แบบ” แต่ทั้ง Go และ Rust ต่างก็นำสิ่งดี ๆ มาให้ และหวังว่าจะมีใครสักคนได้แรงบันดาลใจจากทั้งสอง แล้วสร้างภาษาที่เรียบง่ายและใช้งานได้แพร่หลายขึ้นมา
Kotlin จริง ๆ แล้วซับซ้อนมาก และทำให้เสียเวลาไปกับการพยายามทำให้ฉลาดเกินไป มีวิธีทำงานอยู่เป็นพันแบบ ทั้ง operator overloading, proxy, property, companion object, exception และ result type ครบหมด
ถ้าสมมติว่าใช้ Gradle ระบบ build ก็แทบเป็นการทรมานสำหรับคนที่คุ้นกับ
go buildCoroutine API รู้สึกทั้งซับซ้อนกว่าและจำกัดกว่า Goroutine ในเวลาเดียวกัน มันมีโครงสร้างมากกว่า แต่ยืดหยุ่นน้อยกว่า และต้องใช้ความพยายามมากกว่าในการใช้งาน
access control ก็ชวนอึดอัด ไม่มีวิธีทำให้ type ใช้ได้เฉพาะใน package ได้ มีแต่เฉพาะไฟล์ หรือเปิดให้ทั้ง module เลย ดังนั้นผิวหน้า API จึงขยายใหญ่ขึ้นโดยไม่ต้องการ หรือไม่ก็แบ่งความซับซ้อนไปหลายไฟล์ไม่ได้
Kotlin/Native กับ Kotlin/JVM แทบเป็นคนละสิ่งกัน Kotlin/JVM โตเต็มที่แล้ว แต่เพราะรันบน JVM จึงตัดกลุ่มงานบางส่วนที่น่าจะต้องหยิบ Rust มาใช้ออกไป
ecosystem ของ Kotlin/Native อ่อนมาก และเอกสารก็มีน้อย ใน issue tracker ยังมีบั๊กน่ากลัว ๆ อยู่ด้วย
Kotlin/Native แม้แต่การแจกจ่ายไลบรารีแบบมีแต่ซอร์สก็ยังทำไม่ได้ จึงต้องมี CI ที่ซับซ้อนเพื่อ build ไบนารีสำหรับทุกระบบปฏิบัติการและสถาปัตยกรรม ไม่อย่างนั้นก็จะไม่แจกจ่ายไลบรารีไปเลย ซึ่งดูเหมือนยิ่งทำให้ ecosystem ที่อ่อนอยู่แล้วแย่ลงอีก
เหมือนตอนเพิ่ม generics แค่เพิ่มอีกสามอย่างก็พอ คือ Result ซึ่งเป็น generic container สำหรับ error, Optional สำหรับจัดการ nil ให้ดีขึ้น และ syntactic sugar เล็ก ๆ อย่าง Elvis operator
ข้อได้เปรียบใหญ่ที่แทบอธิบายความสำเร็จของ Go ได้ด้วยตัวมันเอง คือมันถูกสร้างโดยบริษัทขนาดใหญ่ที่ใช้งานเอง และมีทรัพยากรพอจะสร้างเครื่องมือที่ยอดเยี่ยมกับไลบรารีโอเพนซอร์ส ความจริงที่ว่ามีคนใช้อยู่แล้วและมีไลบรารี ดูเหมือนจะเป็นปัจจัยชี้ขาดที่ใหญ่ที่สุดต่อความสำเร็จของภาษา
ไวยากรณ์พื้นฐานตรงไปตรงมามาก การยอมรับใช้งานก็สูงมากอยู่แล้ว ecosystem ใหญ่ runtime ดีขึ้นเรื่อย ๆ และยังคอมไพล์เป็น WASM ได้ด้วย
จากนั้นเอาส่วนแปลก ๆ ออก เช่น ความยืดหยุ่นในการนิยามใหม่ที่มากเกินไป การใช้ global scope อย่างพร่ำเพรื่อ ความประหลาดเกี่ยวกับตัวเลข แล้วเพิ่ม type, sum type/product type, Result, Maybe, decimal ฯลฯ เข้าไป
แล้วถ้าใส่ฟีเจอร์เชิง functional อย่างสไตล์ที่ทุกอย่างเป็น expression, pattern matching, currying รวมถึง standard library ที่ครอบคลุม ก็อาจได้ภาษาที่ใกล้เคียงกับสิ่งที่คนจำนวนมากต้องการมากที่สุด
อีกความก้าวหน้าครั้งใหญ่ที่อยากเห็นในเครื่องมือนักพัฒนา คือการปรับปรุง HTML ให้มีคอนโทรลขั้นสูงที่ดีในตัว และทำให้ style ได้ง่ายด้วย inline CSS แบบนั้นปริมาณ JS ที่จำเป็นบน client ก็จะลดลง
ผมคิดว่าสองอย่างนี้จะเพิ่มผลิตภาพการเขียนโปรแกรมสำหรับการพัฒนาเกี่ยวกับเว็บได้อย่างมาก
สามารถคอมไพล์เป็นไบนารี native แบบ static ได้ด้วย
dotnet publish -p:PublishAot=trueใน F# ต้องใช้เมธอดConsole.*แทนprint*เพราะprintมี reflection แบบไม่จำกัดอยู่ภายใน เนื่องจากการพิมพ์โครงสร้างของตัวระบุรูปแบบ%Aซึ่งส่งผลเสียต่อขนาดไบนารี และ compiler จะบ่นโดยเฉพาะ F# แนะนำได้ในฐานะ “ทางเลือกแทน Rust ที่ง่ายกว่าและเน้นธุรกิจมากกว่า” มันเน้น expression มี discriminated union, การอนุมานชนิดแบบ HM เต็มรูปแบบ และการใช้ type แบบค่อยเป็นค่อยไปก็เข้ากันดี การวิเคราะห์ข้อมูลและ domain modeling ก็สบายมาก
สำหรับ system programming C# เป็นตัวเลือกหนึ่ง มี primitive ด้าน concurrency ที่ยอดเยี่ยม การทำงานร่วมกับ native ที่เร็วและบางครั้งแทบไม่มีต้นทุน ไบนารี native ที่เล็กกว่า Go และ API ระดับต่ำจำนวนมาก รวมถึง SIMD แบบพกพาได้
Go มักไม่เหมาะกับงานแบบนี้ หรือทำไม่ได้เลยหากไม่มีทางเลี่ยง .NET กำลังได้รับความนิยมในชุมชนที่ไม่ใช่เกมในพื้นที่นี้ และมีไลบรารีประสิทธิภาพสูงเกิดขึ้นมากมาย อีกทั้งต่างจากภาษาเฉพาะทางตามที่คอมเมนต์พี่น้องเสนอไว้ มันได้ประโยชน์จาก ecosystem เดิมขนาดใหญ่ จึงไม่จำเป็นต้องทำงานพื้นฐานทั้งหมดเอง
Go: “ฉันเป็นภาษาที่เรียบง่ายนะ!”
ผู้ใช้ใช้ Go ไปสักพัก
ผู้ใช้: “ฉันเกลียดแก แกมันก็แค่ภาษาที่เรียบง่ายนี่!”
ไม่รู้เพราะผมอายุเกิน 50 แล้วหรือเปล่า แต่ผมชอบ ภาษาที่เรียบง่าย
“คำวิจารณ์” นี้ดูไม่ค่อยสมดุลนัก และทุกอย่างในเทคโนโลยีล้วนเป็นการประนีประนอม ผมจึงคิดว่าการตัดสินที่ไม่สมดุลนั้นอ่อน
แน่นอนว่าข้อสรุปของผมต่างออกไป: https://www.inkmi.com/blog/why-we-chose-go-over-rust-for-our...
ในบทความมีตัวอย่างมากมายที่ชี้ให้เห็นความไม่สอดคล้องน่ารำคาญของภาษา และสิ่งเหล่านั้นคือ ตรงข้ามกับความเรียบง่าย
ผมชอบการนำเสนอ Go ของ Rob Pike และบางส่วนก็ทำให้ผมเปิดหูเปิดตา เพียงแต่ผมอยากให้ Go ที่เห็นในทางปฏิบัติใกล้เคียงกับ Go ที่แฟน ๆ Go บรรยายในเชิงนามธรรมมากกว่านี้มาก ๆ
ตรงข้ามกับภาพภายนอก พอเริ่มใช้ Go ไม่นานก็จะเห็นว่า มันไม่เรียบง่าย มีความซับซ้อนและกับดักที่ซ่อนอยู่มากมาย เช่น: https://archive.ph/WcyF4
ถึงอย่างนั้นมันก็เป็นภาษาที่มีประโยชน์และใช้กันค่อนข้างมาก แต่ไม่ใช่ “เรียบง่าย”
ผมคิดว่าถ้าเป็นภาษาที่เรียบง่าย ก็ควรมีความสอดคล้องสูง กฎควรมีน้อย แต่ใช้ได้ทั่วไปและสอดคล้องกันทุกที่ แต่ใน Go มีความแปลก ความไม่สอดคล้อง และทางอ้อมอยู่ทั่วไป
ตัวอย่างที่ดีคือการละเว้นชนิดข้อมูล ทำได้ในการประกาศ array, slice, map แต่ทำกับ struct ไม่ได้ ถ้าอนุญาตกับ struct ด้วย ก็จะมีข้อดีหลายอย่างด้านความอ่านง่าย และยังทำให้มี named parameters ผ่านอาร์กิวเมนต์ struct แบบ anonymous ได้ ซึ่งอาจทำให้การออกแบบดีกว่าทางอ้อมหน้าตาน่าเกลียดที่ใช้อยู่ตอนนี้มาก
แน่นอนว่าทางเลือกที่สมเหตุสมผลส่วนใหญ่ของ Go ก็แย่กว่าในมุมมองนี้ เช่น Python, Ruby, JS
ผมสงสัยว่าอะไรเป็นแรงจูงใจให้ใครสักคนโจมตีภาษาใดภาษาหนึ่งได้ยืดยาวขนาดนี้ แม้บางประเด็นในบทความจะมีเหตุผล แต่ก็ไม่ได้เขียนด้วยน้ำเสียงเชิงสร้างสรรค์ ผมจึงขอเรียกว่า การกล่าวโทษ
มีภาษาไหนบ้างที่วิจารณ์ไม่ได้?
นี่เป็นบทความที่เขียนขึ้นเพื่อให้รู้สึกดีขึ้น โดยโยนความล้มเหลวในโปรเจกต์นั้นไปให้ภาษาใช่ไหม? หรือเป็นเพราะไม่เข้าใจว่าทุกคนไม่ได้คิดและทำงานแบบเดียวกัน และสิ่งที่ทนไม่ได้สำหรับบางคน แทบไม่สะดุดตาสำหรับอีกคนหนึ่ง?
ภาษาที่ตอบสนองความต้องการจริงไม่ได้ มักหายไปเองอยู่แล้ว และแทบไม่ต้องมีใครช่วย
Go แม้จะต่างจากภาษาที่ “ประณีตกว่า” แต่ในโปรเจกต์ที่ผมเจอมันก็ทำงานได้ยอดเยี่ยม หวังว่าผู้เขียนจะไม่ถูกบังคับให้ทำงานด้วย Go ส่วนชุมชนที่เหลือก็จะยังสร้างบริการต่อไป ให้ฟีดแบ็กกับทีม Go และเฝ้าดูภาษาค่อย ๆ พัฒนาโดยไม่ทำให้ production พัง
ความซับซ้อนโดยเนื้อแท้ ไม่ได้หายไปเพียงเพราะหลับตา
หากคุณเลือกที่จะไม่ใส่ใจกับความซับซ้อนนั้น คุณก็แค่ผลักมันไปให้คนอื่นในองค์กร ไม่ว่าจะเป็นนักพัฒนาคนอื่น ฝ่ายปฏิบัติการ ลูกค้า หรือใครสักคน ตอนนี้พวกเขาต้องคอยอ้อมผ่านสมมติฐานของคุณเพื่อให้ทุกอย่างเดินได้ราบรื่น
ช่วงหลัง ๆ ผมมักเป็น “ใครสักคน” คนนั้น และตอนนี้ผมเหนื่อยแล้ว
แน่นอนว่า Go ทำงานได้ยอดเยี่ยมในโปรเจกต์ที่คุณเจอ C, C++, PHP, JavaScript ก็ทำงานได้ ในโลกของเรา มีสิ่งมากมายเหลือเกินที่ “ทำงานได้” การเพิ่มเลนถนนอีกเลนก็ทำงานได้ รัฐตำรวจก็ทำงานได้
แต่เป็นไปได้ไหมว่าสิ่งอื่นบางอย่างอาจทำงานได้ยอดเยี่ยมกว่านั้นมาก?
เหตุผลที่เขาเขียนก็คงเพราะผู้เขียนต้นฉบับรู้สึกว่าความคิดของตัวเองไม่ได้ถูกแทนเสียงใน “โลก” มากพอ ผมเองก็ไม่ใช่แฟน Go ดังนั้นในปี 2022 ความนิยมของ Go ทำให้ผมสับสน เพราะดูเหมือนข้อเสียที่ค่อนข้างชัดเจนหลายอย่างถูกมองข้าม
เดี๋ยวนี้คนไม่ค่อยเขียนบล็อกยาว ๆ ว่า PHP เป็นภาษาที่แย่แล้ว เพราะเรื่องนั้นถูกพูดถึงมากเกินไปแล้ว
การที่บล็อกโพสต์นี้ไม่ได้เขียนในแบบที่คุณชอบ ไม่ได้แปลว่ามันไม่มีคุณค่าสำหรับคนอื่น
สิ่งที่ Go ทำ ไม่ว่ามันจะเป็น implementation ขั้นพื้นฐานที่สุด หรือเป็นสิ่งที่ภาษาอื่นทำได้ดีกว่าอยู่แล้วก็ตาม ถูกนำเสนอว่าเป็นสิ่งจำเป็น ดีที่สุด และเป็นวิธีเดียวในการแก้ปัญหานั้น
สิ่งที่ Go ไม่ทำทั้งหมดถูกปฏิบัติราวกับว่าไม่จำเป็น และเป็นแผนสมคบคิดของ Big Complexity ที่พยายามทำให้พวกเราผู้ใช้ที่ไม่ใช่ Go และยังไม่รู้แจ้ง เสพติด syntax sugar ฟีเจอร์ต่าง ๆ เช่น การจัดการ null ที่เหมาะสม การจัดการ error โค้ด boilerplate น้อยลง generics หรือการสร้างทางออกที่สอดคล้องกันโดยไม่ต้องทำ special case ให้ทุกอย่าง ก็ถูกมองแบบนั้น
แม้แต่ในเธรดนี้ก็ยังมีการตีหุ่นฟางต่อไป พอพูดถึงการจัดการ error แทนที่จะยอมรับข้อเท็จจริงชัดเจนว่า error handling ของ Go ทำให้ดีขึ้นได้มาก กลับยกสิ่งอย่าง JavaScript ขึ้นมา ราวกับว่าคนที่วิจารณ์ Go คิดว่า JavaScript คือจุดสูงสุดของการจัดการ error
ทุกครั้งที่ทำงานด้วยภาษาอื่น แม้ Go จะไม่ใช่ภาษาที่สมบูรณ์แบบ ก็ยังทำให้อยากกลับมาใช้เสมอ
ชอบตรงที่มัน แค่ทำงานได้เลย อย่างแท้จริง ติดตั้ง Go, ดึงโค้ดลงมา, เขียนโค้ด แล้วก็จบ
ไม่ต้องไปหาว่าควรใช้เวอร์ชันไหน, runtime อะไร, ตั้งค่าอย่างไร, build tool ตัวไหน, หรือ package manager อะไร แค่ติดตั้งแล้วใช้ Go ก็พอ
ภาษาอื่นที่ให้ประสบการณ์แบบเดียวกันได้ก็น่าจะมีแค่ Rust ประมาณนั้น
อาจเป็นคำโกหกที่บอกตัวเองก็ได้ แต่ทุกครั้งที่ใช้ Python, TypeScript, Java ทั้งที่แค่อยากเขียนโค้ด กลับต้องไป debug หรือทำความเข้าใจปัญหาเรื่องการตั้งค่า, package manager, build tool, เวอร์ชัน จนเริ่มกลัวการเขียนโปรแกรม
ทุกครั้งที่อ่านคำวิจารณ์ Go ก็คิดแบบเดิม ถึงอย่างนั้นก็ยังจะใช้ Go ต่อไป
เข้าใจว่ามีปัญหาหลายอย่างที่ในทางทฤษฎีควรอธิบายเป็นเอกสารได้ง่าย และในความเป็นจริงผู้คนก็เจอบ่อย แต่ในงานจริงก็ยังรู้สึกว่ามันเป็นหนึ่งในภาษาโปรแกรมมิงที่ดีกว่าอยู่ดี
ในสิ่งที่มักถูกลิสต์เป็นข้อเสีย มีบางอย่างที่ไม่เห็นด้วย เช่น ผมชอบวิธีจัดการ error อย่าง explicit ทุกที่ ส่วนอื่น ๆ ก็เห็นด้วย แต่ก็ไม่ได้รำคาญมากไปกว่าข้อเสียของภาษาโปรแกรมมิงอื่น ๆ มากนัก
ก็รู้สึกเสียดายที่คนซึ่งไวต่อพื้นที่ที่ Go ทำได้ไม่ดีเป็นพิเศษ คงต้องบ่นเรื่องนั้นไปตลอดชีวิต
แต่เวลาจะเลือกว่าจะใช้ภาษาอะไรในโปรเจกต์ ก็ไม่ได้เอาตรรกะเชิงเหตุผลเรื่อง “ภาษาไหนดีที่สุด” มาถกกันมากมาย ผมเลือกภาษาที่เข้ากับตัวเอง รู้สึกว่าสามารถเขียนซอฟต์แวร์ที่ดีได้อย่างต่อเนื่อง และทำงานด้วยแล้วดี
ในฐานะคนที่ให้ความสำคัญกับความถูกต้อง ก็อยากให้ภาษานี้ใกล้เคียง Rust มากกว่านี้ แต่ตอนนี้มันไม่ใช่แบบนั้น ไม่ได้เกลียด Rust เช่นกัน แต่เวลาเร่ง ๆ มันไม่ใช่ภาษาที่มือจะเอื้อมไปหา
ตอนนี้มีบทความเรื่อง Go แย่แค่ไหนเขียนออกมามากพอแล้ว อย่างน้อยตอนนี้ก็รู้แล้วว่าการเป็นแฟน PHP เมื่อหลายปีก่อนรู้สึกอย่างไร อาจจะพูดเกินจริง แต่ในความเห็นผมก็ไม่ได้เกินจริงมากนัก
คุณจะใช้ Go
แล้วคุณจะเสียใจ
แต่ตอนนั้นมันก็สายเกินไปแล้ว และคุณถูกผูกติดกับมันไปแล้ว
ผู้เขียนยกเหตุผลได้ค่อนข้างน่าเชื่อ และโต้แย้งได้ยากมาก ในหลายกรณีที่ระบุไว้อย่างชัดเจน และในสถานการณ์การทำงานจำนวนมากที่บริษัทไม่ได้เชี่ยวชาญทางเทคนิคแต่ใช้เทคโนโลยีที่ไม่เหมาะสม Go เป็นตัวเลือกที่แย่ และคุณจะเสียใจที่เอามันเข้าระบบ production
บริษัทควรยึดติดกับ เครื่องมือที่น่าเบื่อ ส่วนโปรเจกต์ส่วนตัว แน่นอนว่าจะทำอะไรก็ได้ตามใจ
นอกนั้นมองว่าความเรียบง่ายเป็นจุดแข็ง ใช้งานรู้สึกดี เรียนรู้ง่าย และโดยรวม performance ก็ค่อนข้างดี ไม่ได้จะเลือกใช้กับทุกสถานการณ์ แต่มีหลายสถานการณ์ที่มันเป็นเครื่องมือที่ดี
เช่นเดียวกัน ผมก็ชอบ Java และ Python อยู่พอสมควร และใช้ทั้งคู่ได้ดีพอ ภาษาเหล่านี้เป็นเครื่องมือที่ดีตามสิ่งที่มันตั้งใจจะเป็น
ไม่เข้าใจว่าทำไมผู้คนถึงยึดติดกับภาษาโปรแกรมมิงอย่างเร่าร้อนขนาดนั้น ภาษาเป็นเครื่องมือ คุณอาจชอบเครื่องมือบางอย่างมากกว่าได้ แต่ไม่ได้แปลว่าเครื่องมือที่คุณไม่ชอบจะใช้ไม่ได้
if err != nilมาตบหน้ามีข้อเสนอที่ดีมากมายเพื่อแก้ปัญหานี้ แต่บางส่วนของชุมชนที่อนุรักษนิยมสุดขั้วและเสียงดัง ฆ่าทุกข้อเสนอด้วย feedback ที่แทบจะเป็นการด่าทอ
ตอนนี้ทีม Go กลัวคนเหล่านั้นในเชิงจิตวิทยา จนยอมแพ้ในการสู้เพื่อ ปรับปรุงการจัดการ error ไปแล้ว
ในแบบสำรวจนักพัฒนา Go ทุกครั้ง ชุมชนโหวตอย่างท่วมท้นให้ปรับปรุงการจัดการ error แต่คนหัวรุนแรงส่วนน้อยทำให้ความคืบหน้าทั้งหมดตกราง
Go มีท่าทีหยิ่งผยองว่าจะตัดสินใจให้เองว่าคุณต้องการฟีเจอร์อะไร และให้สิ่งที่ใช้ทำงานได้น้อยมาก
การอภิปรายในตอนนั้น:
(130 คะแนน, 148 ความคิดเห็น) https://news.ycombinator.com/item?id=34188528
(748 คะแนน, 544 ความคิดเห็น) https://news.ycombinator.com/item?id=31205072
มีส่วนที่บอกว่า “เริ่มใส่ใจ semantics มากกว่า syntax มาก จึงไม่ได้ดู Zig, Nim, Odin ฯลฯ อีกต่อไป ไม่สนใจ ‘C ที่ดีกว่า’ แล้ว”
ผมว่าบทความค่อนข้างยืดยาว ส่วนที่บอกว่า Go เป็นสิ่งที่ “บังเอิญ” นั้นไร้สาระ เพราะ Rust ก็ “บังเอิญ” พอ ๆ กันในแง่ที่มาและการออกแบบ
สิ่งหนึ่งที่สะดุดตาคือ Nim ไม่ใช่ “C ที่ดีกว่า” อย่างแน่นอน มันมี garbage collection และสามารถใช้ reference counting ได้ ถ้าต้องการก็เขียนให้เหมือน C ที่ดีกว่าได้
type system ของ Nim หลีกเลี่ยงปัญหาหลายอย่างที่ Go มีได้ แต่ไม่ได้เข้มงวดเท่า Rust
สุดท้ายก็มีซอฟต์แวร์จำนวนมากที่เขียนและ deploy ด้วย Go, ทำงานเร็ว, downtime น้อย, โดยรวมมีประสิทธิผล และดูเหมือนมีปัญหาความปลอดภัยน้อยกว่า ผมมองว่าโปรเจกต์ซอฟต์แวร์ที่ deploy ด้วย Rust มีน้อยกว่ามาก และ Firefox ก็ยังเป็น C++ มากกว่า 95%
ผมไม่มีส่วนได้ส่วนเสียในศึกนี้ และในที่ทำงานก็ใช้ Python กับ C++ อย่างพอใจอยู่ แม้ซอฟต์แวร์ที่เขียนด้วย Rust จะน้อยกว่ามาก แต่ก็ยังถือได้ว่า มีซอฟต์แวร์จำนวนมากที่เขียนและ deploy ด้วย Rust และจะมองข้ามการลงทุนจากบริษัทใหญ่ ๆ ก็ไม่ได้
0: https://x.com/m_ou_se/status/1599173341117435905
ไม่เข้าใจว่าทำไม Go ถึงถูกนำไปเทียบกับ Rust อยู่เสมอ คู่เปรียบเทียบที่เหมาะที่สุดคือ Java
เหตุผลที่การเปรียบเทียบ Go กับ Rust พบได้บ่อยกว่าแต่มีประโยชน์น้อยกว่า น่าจะเป็นเพราะทั้งคู่เป็นส่วนหนึ่งของคลื่นภาษาใหม่ และทั้งคู่โดยพื้นฐานแล้วสร้างไบนารีแบบลิงก์สแตติก
ในสถานการณ์ที่ Java เป็นตัวเลือกที่ดี Go เป็นตัวเลือกที่แย่มาก ไม่ใช่แค่ตัวภาษาที่มีข้อจำกัด แต่ ecosystem ก็จำกัดมากเมื่อเทียบกับ Java
ผมใช้ Go, C#, TypeScript เป็นภาษาหลักอยู่ และชุดนี้ครอบคลุมได้ดีมาก ถ้ามีช่วง 6 เดือนที่ยอมรับการลดลงของ productivity ได้ หรือมีโปรเจกต์ที่ข้อกำหนดด้านคุณภาพต่ำ ผมจะเพิ่ม Rust เข้าไป
การเรียนภาษาใช้เวลา 1–2 สัปดาห์ก็พอ แต่การเรียนรู้วิธีทำงานวิศวกรรมภายใน ecosystem นั้นต้องใช้เวลา
บริษัทส่วนใหญ่ที่ไม่ได้กังวลเรื่อง performance มากนักไม่ใช้ C++ สำหรับ network service ถ้า Rust โตพอแล้วก็คงเป็นตัวเลือกที่ดี แต่ compile time ซึ่งเป็นหนึ่งในปัญหาหลักตั้งแต่แรก อาจยิ่งแย่กว่าใน codebase ของ Rust
ในความเป็นจริงก็เคยเห็นกรณีที่ใช้ Go แทน Rust และเหตุผลใหญ่ที่สุดคือ Go compile ได้เร็วกว่ามาก
ถ้าถามว่า Rust กับ Go แตกต่างกันพอจนควรถูกเลือกใช้คนละกรณีไหม คำตอบคือใช่ และนั่นแหละคือเหตุผลที่พิจารณาทั้งสองพร้อมกันแล้วเลือกหนึ่งในนั้น
ครั้งหนึ่งผมเคยมองหา Python ที่ “ดีกว่า” อยากได้ภาษาที่เรียบง่ายและปลอดภัยกว่า C/C++ แต่เร็วกว่า Python และเหนือสิ่งอื่นใดคือสร้าง ไบนารีเดี่ยว ได้
เลยดูตั้งแต่ Rust ไปจนถึงภาษาที่ไม่ค่อยเป็นที่รู้จักอย่าง Hare
Go ควรจะเป็นตัวเลือกที่ชัดเจน แต่ไม่รู้ทำไมถึงไม่ชอบไวยากรณ์ของมัน Rust ยังพอเข้าใจได้ มันใช้สัญลักษณ์พิเศษเยอะที่จำยากและพิมพ์ยากบนคีย์บอร์ดที่ไม่ใช่ QWERTY และยังมีจุดเจ็บปวดอื่น ๆ อีก Lisp หลายตัวมีปัญหาเรื่องวงเล็บกับ reverse Polish notation แต่สำหรับ Go ผมหาเหตุผลมาอธิบายไม่ได้ว่าทำไมถึงไม่ชอบ
สุดท้ายเลยตัดสินใจ compile โค้ด Python ด้วย Nuitka ไม่แน่ใจว่าเร็วขึ้นไหม แต่ตอนนี้แจกจ่ายเป็นไบนารีได้แล้ว
กำลังดู C# มากขึ้นด้วย เพราะ AOT compilation กำลังพัฒนาอยู่ อย่างไรก็ตาม ไม่ชอบความยืดยาวในโหมดพื้นฐานและความรู้สึกว่าถูกผูกกับ Windows มากเกินไป
Nim กับ Crystal ชอบมาก แต่ชุมชนขนาดเล็กเป็นอุปสรรค ถึงอย่างนั้นก็ประทับใจจริง ๆ กับสิ่งที่ Nim ทำได้ด้วยชุมชนเล็กขนาดนั้น และทำให้คิดว่าเป็นภาษาที่ยอดเยี่ยม
ตั้งใจว่าจะลองสร้างแรงจูงใจให้ตัวเองเรียนหนึ่งในภาษาที่กล่าวถึง
ทุกวันนี้ใช้งานจริงได้ค่อนข้างดี
ยังมีแนวทางอื่นที่ละทิ้ง C-API แล้วสร้างสะพานไปยังภาษาอย่าง Go หรือ Rust
ผมทำงานกับ py2many มาหลายปี อยากได้ feedback เกี่ยวกับแนวทางนี้