10 คะแนน โดย GN⁺ 2024-12-16 | 5 ความคิดเห็น | แชร์ทาง WhatsApp
  • บทความนี้อธิบายข้อจำกัดและปัญหาของภาษา Go ที่ผู้เขียนรู้สึกหลังจากใช้งานมาหลายปีแล้วเปลี่ยนไปใช้ Java
  • นำเสนอมุมมองว่าคุณลักษณะที่ Go เป็นภาษาที่เรียบง่ายและน่าเบื่อ (boring) อาจไม่ใช่ข้อดีเสมอไป แต่อาจกลายเป็นข้อเสียได้
  • ปรัชญาของ Go: ทีมออกแบบ Go ของ Google เน้นความเรียบง่ายและข้อจำกัด แต่สิ่งนี้ทำให้เกิดงานซ้ำ ๆ ที่ผู้ใช้ต้องแก้เอง

1. การที่ Go "ไม่น่าสนุก" อาจเป็นข้อเสียได้

  • ข้ออ้างของ Russ Cox:
    • ย้ำว่าการที่ Go "น่าเบื่อ (boring)" เป็นข้อดี
    • มีลูปเพียง for แบบเดียว และไม่มีฟังก์ชันอย่าง filter, map, reduce ให้มาในตัว
    • การไม่มีฟีเจอร์ขั้นสูงหลากหลายแบบที่ภาษาส่วนใหญ่อื่น ๆ มี ถือเป็นส่วนหนึ่งของความเรียบง่าย
  • ความเห็นของผู้ใช้ Reddit:
    • เส้นแบ่งระหว่าง "น่าเบื่อ" กับ "ทรงพลัง" นั้นคลุมเครือ
    • มองว่าการขาดฟังก์ชันพื้นฐานของ Go มีโอกาสสูงที่จะถูกเพิ่มเข้ามาในภาษาในอนาคต
  • การพึ่งพาแพ็กเกจจากภายนอก:
    • แพ็กเกจ samber/lo ที่มักใช้เพื่อชดเชยฟังก์ชันที่ขาดไป:
      • มีความสามารถสำคัญอย่าง filter, map, search
      • ได้ 18.1k ดาวบน GitHub และถูกใช้ในมากกว่า 12.6k โปรเจกต์
    • บางฟังก์ชันถูกเพิ่มเข้ามาในแพ็กเกจ slices แล้ว แต่ก็ยังขาดความสามารถอยู่
  • ความไม่พอใจของผู้เขียน:
    • ถูกบังคับให้เขียนลูปซ้ำ ๆ
    • จัดการงานอย่าง filter และ map ให้กระชับได้ยาก
    • แม้จะแยกออกเป็น receiver method ได้ แต่ก็ทำให้โค้ดดูไม่สะอาดนัก
  • ความเรียบง่ายของ Go เป็นข้อดีในหลายกรณี แต่การขาดฟังก์ชันอำนวยความสะดวกพื้นฐานก็อาจเป็นข้อเสียที่ลดทั้งประสิทธิภาพการทำงานและความอ่านง่ายของโค้ดได้

2. ขัดขวางหลักการ Clean Code

  • ปัญหาการจัดการ error:
    • ฟังก์ชันส่วนใหญ่มักรวม error ไว้ในค่าที่คืนกลับมาด้วย:
      • ต้องใช้แพตเทิร์น if err != nil ซ้ำไปซ้ำมา
      • พอพยายามจัดระเบียบโค้ดกลับยิ่งทำให้โค้ดซับซ้อนขึ้น
    • โค้ด HTTP handler ในโปรเจกต์เล็ก ๆ ก็อาจยาวเกิน 20 บรรทัด
      • ทั้งที่เป้าหมายเดิมคืออยากให้คงไว้ราว 4 บรรทัด
    • ถึงขั้นหงุดหงิดกับแนวทางจัดการ error จนคิดจะใช้ panic() และ recovery middleware
  • การแนะนำให้ใช้ชื่อสั้น:
    • มีการแนะนำให้ใช้ชื่อตัวแปร เมธอด และฟังก์ชันแบบสั้น ๆ:
      • ชื่ออย่าง c, a ไม่ชัดว่าแปลว่าอะไร
      • เช่น c หมายถึง Command, Controller, Argument หรือ Amendment กันแน่?
      • แม้การใช้ชื่อยาวจะช่วยให้ชัดขึ้น แต่ปรัชญาของ Go กลับโน้มเอียงไปทางชื่อสั้น
    • สิ่งนี้นำไปสู่การถกเถียงไม่รู้จบใน code review ของทีม เช่น เรื่องชื่อเมธอดทดสอบ
  • แม้ปรัชญาของ Go จะเน้นความกระชับและเรียบง่ายของโค้ด แต่ผลลัพธ์อาจกลับกลายเป็นความซับซ้อนและความไม่มีประสิทธิภาพที่ขัดกับหลักการ clean code

3. ปรัชญาภาษาเล็กโดยเจตนาและวัฒนธรรม DIY

  • ขาดความสามารถพื้นฐาน:
    • การทำ HTTP handler แบบง่ายนั้นไม่ยาก แต่ถ้าต้องการ middleware พื้นฐาน (เช่น exponential backoff, การตั้งค่า cross-site ฯลฯ) ก็ต้องไปไล่หาแพ็กเกจหลายตัว
    • และยากจะมั่นใจว่าแพ็กเกจเหล่านั้น (1) ยังมีการดูแลอยู่หรือไม่ และ (2) ทำงานได้ตามที่คาดหวังจริงหรือไม่
  • งานซ้ำเพิ่มขึ้น:
    • ปรัชญาการออกแบบของ Go ที่พยายามรักษาความเรียบง่าย กลับกลายเป็นการผลักให้นักพัฒนาต้อง "ประดิษฐ์ล้อขึ้นมาใหม่"
    • ตัวอย่างเช่น แม้แต่ฟังก์ชัน filter ง่าย ๆ ก็ยังต้องลงมือทำเอง
  • ระบบนิเวศของแพ็กเกจที่ยังไม่สุกงอม:
    • โปรเจกต์บน GitHub จำนวนมากถูกปล่อยทิ้งไว้ หรือออกมาเพียงไม่กี่เวอร์ชัน
    • แม้จะอาจไม่ยุติธรรมที่จะเทียบกับ .NET/Java เพราะ Go ยังเป็นภาษาที่อายุน้อยกว่า แต่ในทางปฏิบัติก็ยังขาดทั้งเสถียรภาพและความสุกงอมของแพ็กเกจ
  • ข้อจำกัดของ ORM:
    • แพ็กเกจ ORM หลักของ Go อย่าง Gorm ยังตามหลัง Hibernate หรือ Entity Framework ในด้านความสามารถ
    • มีทั้งพฤติกรรมแปลก ๆ และปัญหาด้านเอกสารประกอบที่ไม่เพียงพอ
    • ปฏิกิริยาจากชุมชน Go: "Go ไม่ต้องมี ORM ก็ได้, Do It Yourself!"
  • ความเรียบง่ายของ Go อาจเป็นข้อดีได้ ขึ้นอยู่กับโปรเจกต์และทีม แต่การขาดความสามารถที่ควรมีมาให้ตั้งแต่ต้นอาจส่งผลลบต่อทั้งประสิทธิภาพการทำงานและประสบการณ์ของนักพัฒนา

4. ใน Go ไม่ได้มีเพียงวิธีเดียวเสมอไป

  • ความเข้าใจผิดเรื่องความสม่ำเสมอและความเป็นหนึ่งเดียว:
    • Table Test
      • ใช้ test suite อย่าง stretchr/testify (ถูกใช้ใน 557k โปรเจกต์)
      • เขียน custom subtest ภายใน table test
    • สิ่งนี้ทำให้เห็นช่องว่างระหว่างปรัชญาแบบ "มีวิธีที่เป็นหนึ่งเดียว" ของ Go กับความเป็นจริง
  • ก่อให้เกิดความขัดแย้งภายในทีม:
    • การถกเถียงเรื่องสไตล์การทดสอบและวิธี implementation ระหว่างทีมกลับเพิ่มมากขึ้น
    • แม้แต่ปรัชญาและทีมออกแบบของ Go เองก็ยังขาดความสม่ำเสมอ:
      • เช่น ความไม่สอดคล้องกันเรื่องการตั้งชื่อ getter method
  • การปฏิเสธฟีเจอร์และการพึ่งพาแพ็กเกจ:
    • ทีม Go ปฏิเสธการเพิ่มฟังก์ชัน assertion และถูกวิจารณ์ว่ามีท่าทีโทษผู้เขียนโปรแกรมแทนที่จะแก้ปัญหา
    • สุดท้ายถ้าต้องการใช้ฟีเจอร์ที่จำเป็น ก็ต้องติดตั้งแพ็กเกจเพิ่มอีกอยู่ดี (go get)
  • แม้ Go จะมุ่งสู่ความเรียบง่ายและความเป็นหนึ่งเดียว แต่ในความเป็นจริงกลับมีวิธีทำหลากหลายแบบและข้อถกเถียงตามมา ขณะที่ความกำกวมของปรัชญาการออกแบบภาษายิ่งทำให้ปัญหาแย่ลง

5. การดีบักใน Go ไม่น่าสนุก

  • ไม่สามารถประเมิน expression ระหว่างดีบักได้:
    • ระหว่างเซสชันดีบัก ไม่สามารถประเมิน expression หรือดูการแสดงผลข้อความแบบกำหนดเองของอ็อบเจ็กต์ได้
    • ทำให้เข้าใจสถานะของอ็อบเจ็กต์ขณะรันได้ยาก
  • stack trace และ log ที่ไม่เป็นมิตร:
    • เมื่อการทดสอบขนาดใหญ่ล้มเหลว (เช่น รันการทดสอบหลายพันรายการใน CI) จะได้ stack trace และ log ที่ชวนสับสน
    • ส่งผลให้การดีบักยากขึ้นและประสิทธิภาพการทำงานลดลง
  • ประสบการณ์ดีบักสไตล์ C:
    • toolchain สำหรับดีบักของ Go ทำงานบนฐานแบบ C:
      • ให้ประสบการณ์ดีบักที่ดิบและคล้าย C
      • ไม่เป็นมิตรกับนักพัฒนา
  • เปรียบเทียบกับ Rust:
    • Rust ปรับปรุงข้อจำกัดของ Go ได้ดีกว่า:
      • ให้ข้อมูล error ที่ชัดเจนและมีประโยชน์
      • มีคำแนะนำการแก้ไขที่แม่นยำอยู่ในข้อความ error
  • ประสบการณ์การดีบักของ Go ตั้งอยู่บนปรัชญาการออกแบบที่เน้นการสร้างไบนารีที่เหมาะสมต่อการใช้งานจริง แต่ต้องแลกมาด้วยประสบการณ์ของนักพัฒนา ในสภาพแวดล้อมที่ให้ความสำคัญกับประสิทธิภาพการดีบัก การพิจารณาภาษาอื่นอาจเหมาะสมกว่า

สรุป: ความเหมาะสมและข้อจำกัดของ Go

  • ข้อดีของเครื่องมือในตัวของ Go:
    • มี toolchain พื้นฐานสำหรับ package management, การทดสอบ และ performance monitoring
    • ใช้งานได้โดยไม่ต้องตั้งค่าเพิ่ม ช่วยให้การตั้งค่าสภาพแวดล้อมเริ่มต้นง่ายขึ้น
  • ข้อจำกัด:
    • "โค้ดน่าเบื่อ" และงานซ้ำ ๆ:
      • toolchain ของ Go ใช้งานได้จริง แต่บังคับให้เขียนงานซ้ำ ๆ (Plumbing Code) เวลาลงมือเขียนโค้ด
      • เช่น ไวยากรณ์ที่จำเจและความสามารถที่จำกัดทำให้ความน่าสนใจของงานลดลง
    • "import cycle not allowed":
      • ไม่อนุญาตให้มี circular dependency (import cycle) ในการทดสอบ
      • เมื่อทำงานแบบ domain-driven design (DDD) จึงเพิ่มความซับซ้อนจากข้อจำกัดเชิงโครงสร้าง
    • การพึ่งพาเทคนิค struct embedding:
      • กลไก struct embedding ที่แปลกและมีข้อจำกัด ทำให้ใช้งานได้อย่างลำบาก
  • ขอบเขตที่เหมาะสม:
    • เหมาะกับงานพัฒนา infrastructure:
      • เครื่องมือระดับระบบอย่าง Docker, Drone, Hugo ถูกเขียนด้วย Go
    • มีประโยชน์สำหรับการพัฒนาเซิร์ฟเวอร์ขนาดเบาและแอปพลิเคชัน CLI
  • ขอบเขตที่ไม่เหมาะสม:
    • การพัฒนาแอปพลิเคชันองค์กรที่ซับซ้อน (เช่น ระบบ ERP):
      • ปรัชญาภาษาที่จำกัดและเครื่องมือที่มีอยู่ทำให้การจัดการ business logic ขนาดใหญ่ไม่มีประสิทธิภาพ
  • Go มีประสิทธิภาพยอดเยี่ยมสำหรับงานบางประเภท โดยเฉพาะงานด้าน infrastructure แต่ไม่ใช่เครื่องมือที่เหมาะกับแอปพลิเคชันที่มี business domain ซับซ้อน แม้ CTO จะเอนเอียงไปทางเทคสแต็กของ Google ก็ควรระมัดระวังในการเลือกเทคโนโลยี

5 ความคิดเห็น

 
secret3056 2024-12-17

ถ้ามีแค่ ? ของ Rust ก็คงดีกว่าตอนนี้มากแล้วไม่ใช่หรือ...

 
bbulbum 2024-12-17

พอได้ใช้ Go แล้วก็ทำให้คิดขึ้นมาว่า ที่ผ่านมาผมจัดการข้อผิดพลาดแบบปริยายมาเยอะแค่ไหน
แน่นอนว่าการจัดการข้อผิดพลาดไว้ที่จุดเดียวอาจดูสะอาดตาในเชิงโครงสร้าง แต่ผมรู้สึกว่าการทำให้เห็นอย่างชัดเจนว่านี่คือการทำงานที่อาจตอบกลับด้วยข้อผิดพลาดได้ กลับช่วยให้เขียนโค้ดได้อย่างปลอดภัยมากขึ้น

 
tsboard 2024-12-16

if err != nil {} นี่พูดตรง ๆ ว่าก็น่ารำคาญอยู่เหมือนกันครับ และก็เห็นด้วยกับข้อเสียที่ถูกชี้ไว้ด้วย ถึงอย่างนั้น ถ้าเข้าใจให้ชัดเจนว่าภาษานี้มุ่งไปทางไหน และคิดว่าจะดึงจุดเด่นอะไรมาใช้เพิ่มเติมได้บ้าง ก็น่าจะใช้งานมันได้ดีขึ้นแม้จะมีข้อเสียตามที่ถูกพูดถึงก็ตาม

คล้ายกับ C แต่มี GC รองรับ แถมยังรองรับ generics แบบมีข้อจำกัด และยัง cross-compile ได้อีก! มองแบบนี้แล้วมันก็เป็นภาษาที่คุ้มค่ามากภาษาหนึ่งไม่ใช่หรือครับ?

 
riki3 2024-12-16

ตอนย้ายจาก Java มาใช้ Go ช่วงแรกผมก็น่าจะรู้สึกว่ามันคล้ายกันอยู่บ้างครับ
ตอนนี้ผมสนุกกับ Go มากจนถึงขั้นรู้สึกเสียดายเวลาที่เคยใช้กับ Java ไปเลย การที่บอกว่าไม่เหมาะกับแอปพลิเคชันธุรกิจที่ซับซ้อน กลับทำให้ผมคิดว่าแอปพลิเคชันนั้นคงไม่ได้คิดมากพอที่จะทำให้ระบบเรียบง่ายลง

 
GN⁺ 2024-12-16
ความคิดเห็นจาก Hacker News
  • จะเกิดปัญหาเมื่อโปรแกรมเมอร์ Java พยายามบังคับสไตล์แบบ Java ให้กับ Go

    • ปรัชญาของ Go อาจมีประโยชน์น้อยกว่าในระยะสั้น แต่สร้างความแตกต่างอย่างมากในระยะยาว
    • คนที่จมลึกอยู่ใน ecosystem ของ JVM มักจะสนุกกับ Go ได้ยาก
  • นักพัฒนาจำนวนมากพยายามทำ abstraction เร็วเกินไป

    • สร้าง abstraction ที่ไม่จำเป็น ทั้งที่การทำซ้ำแบบง่าย ๆ ก็เพียงพอแล้ว
  • standard library ของ Go มีขนาดใหญ่ แต่ก็ไม่ได้ใหญ่พอที่จะทำได้ทุกอย่าง

    • มีแนวโน้มที่จะประดิษฐ์วงล้อขึ้นมาใหม่ในแต่ละโปรเจกต์
    • Go เหมาะอย่างยิ่งกับแอปพลิเคชันเซิร์ฟเวอร์/CLI
  • มีความท้าทายที่ใหญ่กว่าการเลือกภาษาโปรแกรมมิง

    • สิ่งสำคัญคือการปรับตัวให้เข้ากับกลไกและปรัชญาของ Go
  • ยากที่จะเข้าใจว่าทำไมคนถึงชอบ Go

    • สิ่งที่ชอบคือ toolchain และความง่ายในการ deploy มากกว่าตัวภาษาเอง
  • การที่ทีมหลักของ Go ย้อนกลับการตัดสินใจที่ผิดพลาดทำให้รู้สึกหงุดหงิด

    • มีระบบแพ็กเกจและเครื่องมือที่ดีอยู่แล้ว
    • สำหรับระบบ ERP ที่ซับซ้อน Java อาจเป็นตัวเลือกที่ดีกว่า
  • Go มีปัญหาแบบเดียวกับ UNIX

    • มีแนวโน้มจะผลักภาระความซับซ้อนไปให้ผู้ใช้
    • runtime ของ Java กำลังพัฒนาอย่างรวดเร็วเมื่อเทียบกับ Go