บทที่ 1 · Part 1 — Foundations and Judgment

Go Conventions and Engineering Judgment

เข้าใจว่า Go กำหนดอะไรไว้แล้ว convention เกิดตรงไหน และตัดสินอย่างไรเมื่อไม่มีคำตอบเดียว

สมมติว่าคุณเปิด repository ของ Go service 2 ระบบที่ทำงานคล้ายกัน ระบบแรกใช้ net/http กับ concrete types เกือบทั้งหมด ส่วนอีกระบบมี framework, dependency injection container และ interface อยู่ทุก package ทั้ง 2 ระบบผ่าน gofmt, compile ได้ และมี test คำถามที่คนเพิ่งเข้าทีมมักถามคือ แล้วแบบไหนคือ Go ที่ถูกต้อง?

คำตอบคือทั้งคู่อาจถูกตามกติกาของภาษา แต่มี readability, testability, dependency cost และ operational risk ต่างกัน gofmt ทำให้หน้าตาโค้ดสม่ำเสมอ แต่มันไม่ได้บอกว่าเราควรแบ่ง package อย่างไร ควรสร้าง interface เมื่อไร หรือควรเลือก Chi, Fiber หรือ standard library

จบบทนี้คุณจะ

  • อธิบายได้ว่า convention เข้ามาช่วยตรงไหนที่ภาษาไม่ได้ตัดสินให้
  • แยก Guarantee, Idiom, Convention, Recommended default และ Trade-off ได้
  • ใช้ Effective Go, Google, Uber และ production evidence โดยไม่ยก guide ใดเป็นกฎของภาษา
  • บันทึกคำแนะนำของทีมพร้อมเหตุผล ข้อยกเว้น และเงื่อนไขที่ต้องกลับมาทบทวน

ทำไม Go Code ที่ถูกต้องจึงต่างกันมาก

Go มี opinion ชัดเจนในแกนเล็ก ๆ ของภาษาและเครื่องมือ เช่น syntax, type system, memory model, compatibility promise, package contract และการ format ด้วย gofmt สิ่งเหล่านี้ช่วยตัดตัวเลือกที่ไม่จำเป็นออก แต่ Go ตั้งใจเปิดพื้นที่อีกมากให้ programmer และทีมตัดสิน เช่น architecture, package boundaries, error taxonomy, dependency injection, HTTP router, database library และวิธีจัด lifecycle ของ service

ดังนั้นประโยคว่า “Go ไม่มี opinion” จึงกว้างเกินไป ประโยคที่แม่นกว่าคือ Go มี opinion สูงกับ แกนของภาษาและ tooling แต่ไม่ได้ออกแบบ application architecture แทนเรา ช่องว่างนี้ไม่ใช่ ข้อบกพร่อง เพราะงานแต่ละชนิดมี constraint ต่างกัน แต่ถ้าทีมไม่ตกลง convention ช่องว่างเดียวกัน จะกลายเป็นข้อถกเถียงซ้ำ ๆ ใน code review

Convention คืออะไร

Convention คือข้อตกลงร่วมของทีมสำหรับตัวเลือกที่ compiler หรือ package contract ไม่ได้ ตัดสินให้ เช่น package ที่รับ HTTP request จะตั้งชื่อ handler หรือ transport ก็ได้ ภาษาไม่ห้าม ทั้งคู่ แต่การเลือก 1 แบบให้สม่ำเสมอช่วยให้คนหาโค้ดเจอเร็วขึ้นและ review เฉพาะสาระได้มากขึ้น

convention ที่ดีต้องมีขอบเขตและเหตุผล ไม่ใช่ “ทำเพราะเคยเห็นที่บริษัทใหญ่” อย่างน้อยควรตอบได้ว่า

  • ใช้กับ code ส่วนใด
  • ปัญหาอะไรที่กำลังลด
  • มีข้อยกเว้นเมื่อใด
  • หลักฐานหรือเหตุการณ์ใดจะทำให้กลับมาทบทวน

ตัวอย่างเช่น “service นี้รับ context.Context เป็น parameter แรกของ operation ที่ทำ I/O เพื่อให้ deadline และ cancellation เดินทางข้าม boundary ได้; ไม่เก็บ context ไว้ใน struct ยกเว้น API ที่ package contract ออกแบบไว้เช่นนั้น” นี่นำไปใช้ได้ชัดกว่า “ทุก function ต้องรับ context”

คำ 5 คำที่ต้องแยกให้ออก

ระดับหมายถึงตัวอย่าง
Guaranteeสิ่งที่ language หรือ package contract รับรองmap iteration ไม่มีลำดับที่รับรอง
Idiomรูปแบบที่เข้ากับกลไกและความคาดหวังของ Goตรวจ error ใกล้จุดเรียกแล้ว return ให้ flow ตรงไปตรงมา
Conventionข้อตกลงที่ลดตัวเลือกภายในขอบเขตหนึ่งชื่อ package, error code และ project layout ของทีม
Recommended defaultจุดเริ่มที่คอร์สแนะนำ พร้อมเหตุผลและเงื่อนไขเปลี่ยนproducer return concrete type; consumer สร้าง interface เท่าที่ใช้
Trade-offคำตอบที่ขึ้นกับ workload หรือ constraintmutex เทียบกับ channel, Chi เทียบกับ Fiber

คำว่า best practice ในคอร์สนี้หมายถึง Recommended default ไม่ได้หมายถึงคำตอบนิรันดร์ เราจะเขียนให้ครบว่า “เริ่มด้วย X เพราะ Y และพิจารณา Z เมื่อเจอเงื่อนไขนี้” ถ้าคำแนะนำมีแต่ X แต่ไม่มี Y หรือ Z มันยังไม่ช่วยให้ผู้อ่านตัดสินใจในสถานการณ์ใหม่

เราควรฟังใครเมื่อคำแนะนำไม่ตรงกัน

แหล่งข้อมูลแต่ละชนิดตอบคำถามคนละระดับ ให้ไล่จากฐานที่แข็งที่สุดขึ้นมา:

  1. Language specification, memory model และ package documentation บอกว่าโปรแกรมพึ่งพาอะไรได้
  2. Official documentation และ release notes บอก behavior, compatibility และเครื่องมือปัจจุบัน
  3. Effective Go และ Go Code Review Comments อธิบาย idiom และปัญหาที่พบซ้ำ
  4. Google, Uber และ production guides ให้ convention ที่ผ่านบริบทขององค์กรนั้นมาแล้ว
  5. Team convention เลือก default ให้ codebase นี้โดยระบุเหตุผลและข้อยกเว้น
  6. Tests, benchmark, profile และ production metrics ตัดสินเรื่องที่ขึ้นกับ workload จริง

Google หรือ Uber guide จึงมีประโยชน์มาก แต่เป็น production witness ไม่ใช่ language authority เราสามารถเลือกคำแนะนำของ Uber มาเป็น default ได้เมื่อเหตุผลตรงกับระบบของเรา และปฏิเสธได้เมื่อ constraint ต่างกัน หลักเดียวกันใช้กับ library: “standard library first” เป็นจุดเริ่ม ไม่ใช่คำสั่งห้าม dependency ถ้า library ทำให้ intent, ownership หรือ lifecycle ชัดขึ้นและคุ้มกับ cost ก็ควรแนะนำ

Effective Go อยู่ตรงไหนในปี 2026

Effective Go ยังอธิบาย formatting, naming, zero value, methods, interfaces, defer, errors และ concurrency ได้ดี แต่หน้าเอกสารระบุเองว่าเนื้อหาเขียนขึ้น ในปี 2009 และไม่ได้อัปเดตอย่าง active จึงไม่ครอบคลุม modules, generics, fuzzing, error chains, context, modern collection APIs และ production tooling รุ่นปัจจุบัน

คอร์สนี้จึงใช้ Effective Go เป็น หนึ่งในรากของ idiom ไม่ใช่ชื่อบทหรือโครง syllabus ตัวอย่างเช่นแนวคิดเรื่อง interface ขนาดเล็กยังสำคัญ แต่ต้องประกบด้วยคำแนะนำสมัยใหม่ว่า producer มัก return concrete type และ consumer ควรเป็นเจ้าของ interface เท่าที่ตัวเองใช้ เราจะลงมือทำใน Packages as APIs

Version เป็นส่วนหนึ่งของคำแนะนำ

style guide ทุกฉบับต้องอ่านพร้อมวันที่และ Go version เพราะ workaround ที่ถูกต้องเมื่อหลายปีก่อน อาจกลายเป็น noise วันนี้ ตัวอย่างเช่น semantics ของ loop variable เปลี่ยนตั้งแต่ Go 1.22 และ typed atomic types มีใน sync/atomic ตั้งแต่ Go 1.19

module example.com/settlement

go 1.25.0

toolchain go1.26.5

go directive กำหนด minimum Go version และมีผลต่อ language semantics ส่วน toolchain เสนอ toolchain ที่ควรใช้เมื่อ environment มีรุ่นเก่ากว่า ก่อนใช้คำแนะนำจาก guide ให้ตรวจ baseline ของ module, CI image และ deployment build ให้เล่าเรื่องเดียวกัน

go version
go env GOTOOLCHAIN GOFLAGS
go test ./...

วิธีตัดสินเมื่อไม่มีคำตอบเดียว

เมื่อ review มี comment ว่า “ห้าม return nil slice” อย่าเริ่มจากการโหวต ให้เดินตามลำดับนี้:

  1. หา guarantee ก่อน: nil slice ใช้ len, range และ append ได้
  2. หา boundary contract: JSON response อาจต้องเป็น [] ไม่ใช่ null
  3. เลือก default ที่ง่ายและอ่านชัด: internal function ไม่ต้อง allocate ถ้า caller ไม่แยก 2 ค่า
  4. ใช้ tool หรือ test พิสูจน์จุดที่สำคัญ: เพิ่ม contract test ที่ HTTP boundary
  5. บันทึก convention: “กำหนด nil/empty ที่ boundary; ภายในใช้ค่าที่ contract อนุญาต”

reasoning loop นี้ใช้ได้ตลอดคอร์ส: guarantee → contract → readable default → evidence → documented convention มันช่วยให้ทีมย้ายจาก “guide ไหนชนะ” ไปสู่ “ระบบนี้ต้องการอะไร”

แบบฝึกทบทวน

ลองจัดแต่ละข้อความเป็น Guarantee, Idiom, Convention, Recommended default หรือ Trade-off แล้วระบุหลักฐานที่ต้องใช้ก่อนเปิดคำตอบจากแหล่งอ้างอิง:

  1. “ผลจากการวน map จะเรียงเหมือนเดิมทุกครั้ง”
  2. “ชื่อ package ของทีมต้องยาวไม่เกิน 1 คำ”
  3. “operation ที่ข้าม process รับ context.Context เป็น parameter แรก”
  4. “Fiber เร็วกว่า Chi จึงเหมาะกับทุก service”
  5. “HTTP response ของ endpoint นี้ต้อง encode collection ว่างเป็น []”

ข้อ 1 ต้องตรวจ specification และพบว่าเป็นข้อความที่พึ่งพาไม่ได้ ข้อ 2 เป็น convention ข้อ 3 เป็น recommended default ที่อิง idiom และ contract ข้อ 4 เป็น trade-off ที่ต้อง benchmark กับ workload จริง ส่วนข้อ 5 เป็น boundary convention ซึ่งต้องยืนยันด้วย API contract และ test

Checklist ก่อนใช้คำว่า Idiomatic

  • ระบุได้หรือไม่ว่ากำลังพูดถึง guarantee, idiom, convention, default หรือ trade-off
  • ตรวจ Go version และวันที่ของ source แล้วหรือยัง
  • คำแนะนำทำให้ contract, ownership หรือ lifecycle ชัดขึ้นจริงหรือไม่
  • รู้ข้อยกเว้นและ trigger ที่ต้องกลับมาทบทวนหรือยัง
  • ถ้าเลือก library รู้หรือไม่ว่ามันทดแทนโค้ดอะไรและเพิ่ม dependency cost เท่าใด
  • เรื่องที่ขึ้นกับ workload มี test, benchmark, profile หรือ production evidence รองรับหรือไม่

อ่านต่อจากแหล่งต้นทาง: Go specification, Go toolchains, Go 1 compatibility, Go Code Review Comments, Google Go Style และ Uber Go Style Guide