- การตัดสินใจด้านการออกแบบหลายอย่างของภาษา Go ถูกทำขึ้นอย่างไม่จำเป็นหรือโดยละเลยประสบการณ์เดิมที่มีอยู่แล้ว
- ปัญหาเรื่อง การจัดการขอบเขตของตัวแปร error ทำให้การอ่านโค้ดและการตามหาบั๊กทำได้ยากขึ้น
- มีการออกแบบที่ ไม่เป็นธรรมชาติและไม่สอดคล้องกับการใช้งานจริง ในหลายส่วน เช่น ความเป็นสองแบบของ nil, การใช้หน่วยความจำ, และความสามารถในการพกพาโค้ด
- ข้อจำกัดของคำสั่ง defer และวิธีจัดการข้อยกเว้นในไลบรารีมาตรฐาน ทำให้การรับประกันความปลอดภัยจากข้อยกเว้นทำได้ยาก
- การจัดการหน่วยความจำและการรองรับ UTF-8 ที่ไม่เพียงพอ รวมถึงปัญหาสะสมอื่น ๆ กำลังส่งผลเสียต่อคุณภาพของโค้ดเบส Go ในระยะยาว
คำวิจารณ์ระยะยาวต่อภาษา Go
ความไม่เป็นธรรมชาติของขอบเขตตัวแปร error
- ไวยากรณ์ของ Go ทำให้ ขอบเขตของตัวแปร error (
err) กว้างเกินความจำเป็น และเพิ่มโอกาสเกิดความผิดพลาด
- ในโค้ดตัวอย่าง ตัวแปร
err ยังคงอยู่ตลอดทั้งฟังก์ชันและถูกนำกลับมาใช้ซ้ำ ซึ่งทำให้ ความอ่านง่ายและการบำรุงรักษาโค้ด ลดลง
- แม้แต่นักพัฒนาที่มีประสบการณ์ก็อาจเกิดความเข้าใจผิดและเสียเวลาในการตามหาบั๊กเพราะปัญหาของขอบเขตตัวแปรเช่นนี้
- ไวยากรณ์ของภาษาไม่เปิดทางให้จำกัดขอบเขตตัวแปรให้แคบลงอย่างเหมาะสม
nil สองรูปแบบ
- ใน Go มีความสับสนจากการที่ nil ทำงานต่างกันในชนิด
interface และชนิด pointer
- ตามตัวอย่างด้านล่าง แม้
s (pointer) และ i (interface) จะถูกกำหนดเป็น nil แต่ s==i กลับถูกประเมินต่างกัน แสดงถึง พฤติกรรมที่ไม่สอดคล้องกัน
- นี่เป็นปัญหาประเภทที่โดยทั่วไปแล้วควรหลีกเลี่ยงในการจัดการ null และสะท้อนร่องรอยของการออกแบบที่คิดมาไม่รอบด้านพอ
ข้อจำกัดด้านความสามารถในการพกพาของโค้ด
- การใช้คอมเมนต์เพื่อทำ conditional compilation ไม่มีประสิทธิภาพอย่างชัดเจนในแง่การบำรุงรักษาและความสามารถในการพกพา
- หากเคยมีประสบการณ์สร้างซอฟต์แวร์ที่พกพาได้จริง จะรู้ว่าวิธีนี้ทั้งยุ่งยากและก่อให้เกิดข้อผิดพลาดได้ง่าย
- ประสบการณ์ที่สั่งสมมาในอดีต ทั้งเรื่องความสามารถในการพกพาของโค้ดและกรณีใช้งานจริง ถูกมองข้ามไป
- รายละเอียดเพิ่มเติมดูได้ที่ Go programs are not portable
ความไม่ชัดเจนของ ownership ใน append
- ความสัมพันธ์ด้าน ownership ระหว่างฟังก์ชัน
append กับ slice ไม่ชัดเจน ทำให้คาดเดาพฤติกรรมของโค้ดได้ยาก
- จากตัวอย่าง เมื่อฟังก์ชัน
foo ทำ append กับ slice ก็ยากที่จะรู้ล่วงหน้าว่าจะส่งผลต่อข้อมูลต้นฉบับอย่างไรจริง ๆ
- ทำให้ภาษาเต็มไปด้วย “quirk” ที่ต้องจำเพิ่มขึ้น และนำไปสู่ความผิดพลาดได้
การออกแบบคำสั่ง defer ที่ยังไม่ดีพอ
- Go ไม่ได้รองรับการปลดปล่อยทรัพยากรอย่างชัดเจนแบบหลักการ RAII(Resource Acquisition Is Initialization)
- เมื่อเทียบกับโครงสร้างจัดการทรัพยากรแบบมีโครงสร้างใน Java และ Python แล้ว ใน Go ไม่ชัดเจนว่าทรัพยากรใดควรถูกปล่อยด้วย
defer
- ดังตัวอย่างการทำงานกับไฟล์ ผู้พัฒนาต้องจัดการปัญหา double-close เอง และลำดับกับวิธีการปล่อยทรัพยากรที่ถูกต้องก็ไม่ชัดเจน
การจัดการข้อยกเว้นในไลบรารีมาตรฐาน
- แม้ Go จะมีโครงสร้างที่ ไม่รองรับ exception แบบชัดเจน แต่สถานการณ์ผิดปกติอย่าง
panic ก็ยังเกิดขึ้นได้
- ในบางกรณี
panic ไม่ได้ทำให้โปรแกรมหยุดทั้งหมด แต่กลับถูกกลืนหายไป
- มีรูปแบบในไลบรารีมาตรฐาน (
fmt.Print, HTTP server ฯลฯ) ที่เพิกเฉยต่อข้อยกเว้น ทำให้ ไม่สามารถรับประกัน exception safety ที่แท้จริงได้
- สุดท้ายแล้วการเขียนโค้ดให้ปลอดภัยจากข้อยกเว้นยังเป็นสิ่งจำเป็น แต่กลับไม่สามารถใช้ข้อยกเว้นได้โดยตรง
UTF-8 และสตริง
- แม้จะใส่ ข้อมูลไบนารีตามอำเภอใจลงในชนิด
string Go ก็ยังทำงานต่อไปโดยไม่มีการตรวจสอบพิเศษ
- อาจเกิดกรณีที่ชื่อไฟล์ซึ่งสร้างขึ้นก่อนยุคการเข้ารหัส UTF-8 ถูกละทิ้งไปอย่างเงียบ ๆ
- ข้อมูลสำคัญอาจสูญหายได้ เช่น ในงานสำรองข้อมูล และเป็นแนวทางจัดการที่เรียบง่ายเกินไปจนไม่สะท้อนสภาพงานจริง
ข้อจำกัดของการจัดการหน่วยความจำ
- ควบคุมการใช้ RAM ได้โดยตรงยาก และความน่าเชื่อถือของ GC(garbage collection) ก็มีข้อจำกัด
- การใช้หน่วยความจำของ Go เพิ่มขึ้นและเชื่อมโยงไปสู่ปัญหาด้านต้นทุนและประสิทธิภาพในระยะยาว
- ในสภาพแวดล้อมแบบหลายอินสแตนซ์หรือคอนเทนเนอร์ ปัญหาด้านต้นทุนและการขยายระบบเกิดขึ้นจริง
บทสรุป: มีเส้นทางที่ดีกว่านี้
- แม้จะมี แนวทางการออกแบบภาษาที่พิสูจน์ประสิทธิภาพมาแล้ว อยู่ก่อน แต่ Go กลับเมินสิ่งเหล่านั้นในหลายด้าน
- ต่างจากปัญหาในข้อเสนอแรกเริ่มของ Java เพราะในช่วงที่ Go ออกสู่ตลาดนั้น มีแนวทางที่ดีกว่าให้เลือกใช้อยู่แล้ว
เอกสารอ้างอิง
ยังไม่มีความคิดเห็น