คุยกันเรื่อง Writing Better Go: Lessons from 10 Code Reviews

Published: (January 9, 2026 at 03:37 AM EST)
2 min read
Source: Dev.to

Source: Dev.to

Handle Errors

  • อย่าเมิน error ไปแบบเงียบ ๆ (เช่นใช้ _ รับค่า error)
  • อย่าคิดว่า error นี้ “รับได้” เช่น if err != nil { return nil }
  • เมื่อเจอ error ให้ตรวจสอบและจัดการทันที เช่น log แล้วถือว่า error นั้นจัดการแล้ว ไม่ต้องส่งต่อให้คนอื่น
  • อย่าโวยซ้ำ — ถ้า log แล้วยัง return ให้คนอื่นไป log อีกครั้ง จะทำให้ log ซ้ำซ้อน

ตัวอย่าง (bad vs. good)

// Bad
if err != nil {
    slog.Error(err)
    return err
}

// Good
if err != nil {
    slog.Error(err)
    return nil // หรือจัดการ error ที่นี่แล้วไม่ต้องส่งต่อ
}

ลดภาระให้ฝั่งที่เรียกใช้

  • return result, nil – คืนผลลัพธ์และไม่มี error ทำให้ผู้เรียกใช้เข้าใจง่าย
  • return nil, err – มี error ให้จัดการในขณะที่ไม่มีผลลัพธ์
  • return nil, nil – แย่ เพราะผู้เรียกต้องตีความว่าผลลัพธ์หรือ error ใด ๆ ควรหลีกเลี่ยง
  • return result, err – แย่ เพราะผู้เรียกต้องตรวจสอบทั้งสองค่าเพื่อทราบว่าใช้งานได้หรือไม่

การใช้ Interface

  • อย่าเริ่มด้วยการสร้าง interface ก่อน (มักมาจากภาษาอื่นเช่น Java) หรือเพื่อให้ mock ใน test เท่านั้น
  • ใช้ accept interfaces, return concrete types – สื่อสารผ่าน interface แต่คืนค่าเป็น concrete type
  • เริ่มจากการใช้ concrete type จนถึงจุดที่จำเป็นต้องใช้ interface จริง ๆ
  • Litmus Test: ถ้าทดสอบได้โดยไม่ใช้ interface ก็ไม่จำเป็นต้องสร้าง
  • อย่าสร้าง interface เพียงเพื่อให้เทสได้ – พยายามเขียน test ที่ไม่ต้องพึ่ง interface

Mutexes Before Channels

  • การใช้ channel ทำให้โค้ดซับซ้อนและเสี่ยงต่อ panic (เช่น close channel ที่เปิดอยู่, ส่งไปยัง channel ที่ปิดแล้ว) หรือ deadlock
  • เริ่มด้วยวิธีง่าย ๆ ก่อน: ใช้ sync.Mutex, sync.WaitGroup จัดการ shared state
  • เพิ่ม goroutine หรือ channel เฉพาะเมื่อ profiling แสดงว่ามีคอขวดจริง ๆ
  • ใช้ channel เฉพาะเมื่อจัดการที่ซับซ้อนจริง ๆ ไม่ใช่สำหรับงานง่าย ๆ

การประกาศ (Declare) ใกล้จุดใช้

  • ประกาศ constants, variables, functions, types ใกล้กับที่ใช้จริงในไฟล์เดียวกัน
  • Export (ชื่อขึ้นต้นด้วย Capital Letter) เฉพาะเมื่อจำเป็นต้องใช้จากภายนอก package
  • ภายในฟังก์ชัน ให้ประกาศตัวแปรใกล้กับจุดที่ใช้ที่สุด
  • จำกัดขอบเขตให้แคบที่สุด เช่น ทำ 1 statement ใน if

อย่าให้เกิด Runtime Panics

  • ตรวจสอบ input จากภายนอกก่อนนำไปใช้
  • อย่าใช้ if x == nil หากคุณควบคุมการไหลของข้อมูลแล้ว – ให้เชื่อการจัดการ error ที่จุดนั้น
  • เมื่อ dereference pointer (เช่น *p = 10) ต้องตรวจสอบ nil ก่อนเสมอ
  • วิธีที่ปลอดภัยที่สุดคือ หลีกเลี่ยงการใช้ pointer หากออกแบบได้

การจัดรูปแบบ (Spacing)

  • อย่าห่อโลจิกหลายชั้น (nested if, for)
  • ใช้ Return Early และ Flatter Structure: จัดการ error ก่อน แล้วค่อยทำงานต่อ หรือกำจัดเงื่อนไขที่ไม่ต้องทำต่อทันที

การตั้งชื่อไฟล์และแพ็กเกจ

  • หลีกเลี่ยงชื่อคลุมเครือ เช่น util.go, misc.go, constants.go, interfaces/interfaces.go
  • ให้ชื่อสื่อความหมายของสิ่งที่ทำ ไม่ใช่ตำแหน่งในโครงสร้าง
  • จัดไฟล์ให้อยู่ใกล้กับโค้ดที่เกี่ยวข้อง ไม่แยกกันไปคนละที่

การจัดกลุ่มและลำดับการประกาศ

  • จัดกลุ่มตามความหมาย ไม่ใช่ตาม type (เช่น ไม่จัดกลุ่ม controller แบบแยกจากกัน)
  • เรียงลำดับ declarations ตามความสำคัญ:
    1. Exported API‑facing functions/structs (ต้องการให้ผู้ใช้เห็นก่อน)
    2. Helper functions ที่อธิบายหรือสนับสนุนส่วนบน

การตั้งชื่อตัวแปร

  • อย่าเติม type ลงท้ายชื่อ (เช่น userMap, idStr, injectFn) – ชื่อตัวแปรควรบอกว่าเก็บอะไร
  • ความยาวของชื่อควรสอดคล้องกับ scope: ตัวแปร scoped สั้นอาจใช้ชื่อสั้น ๆ, แต่ global ควรใช้ชื่อที่อธิบายได้ชัดเจน

Documentation

  • Document ควรตอบ “ทำไม” ไม่ใช่แค่ “ทำอะไร”
  • ให้เหตุผลว่าฟีเจอร์หรือโค้ดมีไว้ทำไม และทำไมต้องใช้วิธีนั้น
  • คอมเมนต์ควรสื่อถึงจุดประสงค์หรือประโยชน์ของโค้ด ไม่ใช่แค่อธิบายการทำงานซ้ำ ๆ
  • Documentation คือเจตนา ไม่ใช่วิธีการ – ช่วยให้ผู้อ่านเข้าใจแรงจูงใจของการออกแบบ

สรุปจาก “Writing Better Go: Lessons from 10 Code Reviews” ของ Konrad Reiche (โดย Asleep‑Actuary‑4428)

Back to Blog

Related posts

Read more »

The Secret Life of Go: Error Handling

Chapter 12: The Sound of Breaking Glass Monday morning arrived with a heavy gray mist clinging to the city. Inside the archive, the silence was absolute, broke...