25 เทคนิค Go ที่นักพัฒนามืออาชีพใช้จริงในงาน Production
25 เทคนิค Go ที่นักพัฒนามืออาชีพใช้จริงในงาน Production
Go มีชื่อเสียงเรื่องความเรียบง่าย syntax ไม่ซับซ้อน เรียนรู้ได้เร็ว — แต่ความเรียบง่ายนี้ซ่อน pattern ที่สำคัญมากสำหรับงาน production ไว้เยอะ
บทความนี้รวบรวม 25 เทคนิค ที่นักพัฒนา Go มืออาชีพใช้กันจริงในการสร้าง API, background workers, distributed systems และ infrastructure ต่าง ๆ
หมายเหตุเรื่อง Go version: ตัวอย่างทั้งหมดในบทความนี้ทดสอบกับ Go 1.26 (stable release ล่าสุด ณ มีนาคม 2026) ฟีเจอร์ที่เพิ่มมาใน Go 1.25 หรือ 1.26 จะระบุไว้ชัดเจนในแต่ละหัวข้อ
1) go run — เพื่อนที่ดีที่สุดในช่วง Prototype
ในขั้นตอน prototype หลายทีมสร้าง Makefile, shell script มากมายแค่เพื่อรันโปรแกรม แต่จริง ๆ แค่:
go run main.go
// หรือใน module:
go run .
มันเร็ว เรียบง่าย และทำให้คุณ focus กับ logic ได้ ค่อยเพิ่ม build step ทีหลังเมื่อจำเป็นจริง ๆ
2) Goroutines — แค่ใส่ go แล้วมันก็ทำงานพร้อมกัน
Goroutine คือ lightweight thread ที่ Go จัดการเอง ใช้ memory น้อยมาก (เริ่มต้นแค่ ~2-8 KB stack) สร้างง่ายมาก:
package main
import (
"fmt"
"time"
)
func worker(id int) {
fmt.Println("worker", id, "started")
time.Sleep(time.Second)
fmt.Println("worker", id, "finished")
}
func main() {
for i := 1; i <= 3; i++ {
go worker(i)
}
time.Sleep(2 * time.Second)
}
Goroutine ถูกมาก — แต่ในระบบจริงต้องใช้ร่วมกับ channel, context และ WaitGroup
3) Worker Pool — ป้องกัน Goroutine ระเบิด
Goroutine ถูก แต่ไม่ฟรี ถ้า spawn ไม่จำกัดตอน load สูง server อาจล่ม ทางออกคือ worker pool:
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Printf("worker %d processing job %d\n", id, j)
}
}
func main() {
jobs := make(chan int, 100)
var wg sync.WaitGroup
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, &wg)
}
for j := 1; j <= 10; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
}
Pattern นี้เจอได้ทุกที่: consumer, indexer, ETL pipeline, webhook processor
4) Context — หัวใจของการควบคุม Lifecycle
ถ้าเขียน Go service ที่ไม่มี context เท่ากับสร้าง goroutine ที่หยุดไม่ได้ ไม่มี timeout ไม่มี cancellation:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case result := <-taskChan:
fmt.Println(result)
case <-ctx.Done():
fmt.Println("timed out:", ctx.Err())
}
ถ้าทำ API, background job, distributed system — context คือเครื่องมือป้องกัน resource leak
5) Error Handling — ไม่มี Exception, มีแต่ความชัดเจน
Go ไม่มี try/catch แต่ใช้ return error แทน ดูซ้ำซาก แต่ทำให้ failure ชัดเจน:
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero: a=%d b=%d", a, b)
}
return a / b, nil
}
result, err := divide(10, 0)
if err != nil {
log.Printf("error: %v", err)
return
}
fmt.Println(result)
กฎทอง: ถ้าเห็น _ = err ใน production code นั่นคือ incident report ในอนาคต
6) errors.Is / errors.As / errors.AsType — Error Matching ที่ถูกวิธี
ตั้งแต่ Go 1.13 มี error wrapping ด้วย %w และ matching ด้วย errors.Is / errors.As และ Go 1.26 เพิ่ม errors.AsType ที่ใช้ generics ทำให้สะดวกขึ้น:
import "errors"
var ErrNotFound = errors.New("not found")
func getUser(id int) (*User, error) {
return nil, fmt.Errorf("getUser(%d): %w", id, ErrNotFound)
}
// ตรวจสอบ sentinel error
user, err := getUser(42)
if errors.Is(err, ErrNotFound) {
// handle not found
}
// ดึง specific error type (แบบเดิม)
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("path:", pathErr.Path)
}
// Go 1.26: errors.AsType — generic version ที่สะดวกกว่า
if pe, ok := errors.AsType[*os.PathError](err); ok {
fmt.Println("path:", pe.Path)
}
Go 1.26:
errors.AsType[T]เป็น generic function ที่ type-safe กว่าerrors.Asไม่ต้องประกาศตัวแปรก่อน และเร็วกว่าด้วย
7) select — Traffic Controller ของ Channels
เมื่อมีหลาย channel ที่ต้อง listen พร้อมกัน select คือคำตอบ:
select {
case msg := <-dataChan:
process(msg)
case err := <-errChan:
log.Println("error:", err)
case <-time.After(5 * time.Second):
log.Println("timeout!")
case <-ctx.Done():
log.Println("cancelled")
}
ใช้บ่อยสำหรับ: timeout, graceful shutdown, non-blocking read, fan-in pattern
8) sync.WaitGroup + .Go() — รอให้ Goroutine ทำงานเสร็จ
ปัญหาคลาสสิก: สร้าง goroutine แล้ว main() จบก่อน — WaitGroup แก้เรื่องนี้:
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println("task", i)
}(i)
}
wg.Wait() // รอจนทุก goroutine เสร็จ
Go 1.25: เพิ่ม method
WaitGroup.Go()ที่รวมAdd(1)+go func()+Done()ในคำสั่งเดียว:
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Go(func() {
fmt.Println("task", i)
})
}
wg.Wait()
wg.Go() ลด boilerplate และป้องกัน bug จากการลืม Add(1) หรือ Done()
9) Struct Tags — ทำให้ JSON API เป็นเรื่องง่าย
Struct tags ควบคุมการ marshal/unmarshal โดยไม่ต้องเขียน custom code:
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"`
CreatedAt time.Time `json:"created_at"`
Password string `json:"-"` // ไม่ส่งออก JSON
}
// Encode
json.NewEncoder(w).Encode(user)
// Decode
json.NewDecoder(r.Body).Decode(&user)
Go 1.25: มี
encoding/json/v2(experimental, เปิดด้วยGOEXPERIMENT=jsonv2) ที่ decode เร็วขึ้นมาก และมี option ใหม่ ๆ
10) defer — ป้องกัน Resource Leak
defer ทำให้ cleanup อยู่ติดกับ setup เสมอ ไม่ว่าจะ return ด้วยเหตุผลอะไร:
file, err := os.Open("data.txt")
if err != nil {
return err
}
defer file.Close() // ปิดแน่นอน ไม่ว่าจะเกิดอะไร
mu.Lock()
defer mu.Unlock()
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
หลักการ: เปิดอะไร → defer ปิดทันที ล็อคอะไร → defer ปลดล็อคทันที
11) Go Modules — จัดการ Dependency อย่างมืออาชีพ
Go Modules เป็นมาตรฐานตั้งแต่ Go 1.11 และเป็น default ตั้งแต่ Go 1.16:
go mod init myapp # สร้าง module
go get github.com/gin-gonic/gin # เพิ่ม dependency
go mod tidy # ลบ dependency ที่ไม่ใช้
go mod vendor # copy dependency ลง vendor/
Go 1.25: เพิ่ม
ignoredirective ในgo.modสำหรับ exclude directory จาก package pattern matchingGo 1.26:
go mod initจะ default ไปที่ Go version ต่ำกว่าเพื่อ compatibility กับ release ที่ยังรองรับอยู่
12) Generics — เขียน Type-Safe Code ที่ Reuse ได้ (Go 1.18+)
Generics มาตั้งแต่ Go 1.18 ทำให้เขียน function/type ที่ทำงานกับหลาย type ได้โดยไม่ต้อง interface{}:
func Map[T any, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
// ใช้งาน
names := Map(users, func(u User) string { return u.Name })
doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 })
// Type constraint
type Number interface {
~int | ~float64 | ~int64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
Go 1.26: Generic types สามารถ self-reference ใน type parameter constraints ได้แล้ว:
type Adder[A Adder[A]] interface { Add(A) A }
13) Table-Driven Tests — เขียนเทสต์แบบ Go Idiom
Pattern ที่ใช้กันมากที่สุดใน Go testing:
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b int
want int
wantErr bool
}{
{"normal", 10, 2, 5, false},
{"divide by zero", 10, 0, 0, true},
{"negative", -10, 2, -5, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := divide(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("got %d, want %d", got, tt.want)
}
})
}
}
Go 1.25: เพิ่ม
testing/synctestสำหรับทดสอบ concurrent code ด้วย virtualized time:synctest.Test(t, func(ctx context.Context) { // time.Sleep() เลื่อน fake clock ทันที ไม่ต้องรอจริง })
14) Interfaces — Implicit Implementation สุด Flexible
Go interfaces เป็น implicit — ไม่ต้อง implements keyword:
type Logger interface {
Log(msg string)
}
// แค่มี method ตรง ก็ implement แล้ว
type ConsoleLogger struct{}
func (c ConsoleLogger) Log(msg string) {
fmt.Println(msg)
}
type FileLogger struct{ file *os.File }
func (f FileLogger) Log(msg string) {
fmt.Fprintln(f.file, msg)
}
// ใช้งาน
func process(l Logger) {
l.Log("processing...")
}
หลักการ: “Accept interfaces, return structs” — รับ interface เป็น parameter, return concrete type
Interface เล็ก ๆ ดีกว่า interface ใหญ่ — ประกอบกันได้:
type Reader interface { Read(p []byte) (n int, err error) }
type Writer interface { Write(p []byte) (n int, err error) }
type ReadWriter interface {
Reader
Writer
}
15) Embedding — Composition over Inheritance
Go ไม่มี inheritance แต่ใช้ embedding เพื่อ compose behavior:
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
type Dog struct {
Animal // embed Animal
Breed string
}
d := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
fmt.Println(d.Speak()) // "Buddy makes a sound"
fmt.Println(d.Name) // "Buddy" — promoted field
16) Graceful Shutdown — ปิดระบบอย่างสง่างาม
Production service ต้องรับ signal แล้ว drain connection ก่อนปิด:
func main() {
srv := &http.Server{Addr: ":8080", Handler: mux}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// รอ interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("forced shutdown:", err)
}
log.Println("server stopped gracefully")
}
ไม่ตัด request ที่กำลังทำอยู่ ไม่ทำให้ client เจอ connection reset
17) slog — Structured Logging (Go 1.21+)
ตั้งแต่ Go 1.21 มี log/slog ใน standard library สำหรับ structured logging:
import "log/slog"
// JSON output สำหรับ production
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("user login",
slog.String("user_id", "u-123"),
slog.String("ip", r.RemoteAddr),
slog.Duration("latency", elapsed),
)
// Output: {"time":"...","level":"INFO","msg":"user login","user_id":"u-123","ip":"...","latency":"..."}
Go 1.25: เพิ่ม
slog.GroupAttrs()สำหรับจัดกลุ่ม attributes และRecord.Source()methodGo 1.26: เพิ่ม
slog.NewMultiHandler()สำหรับส่ง log ไปหลาย destination พร้อมกัน:
// Go 1.26: ส่ง log ไปทั้ง stdout และ file พร้อมกัน
jsonHandler := slog.NewJSONHandler(os.Stdout, nil)
fileHandler := slog.NewJSONHandler(logFile, nil)
logger := slog.New(slog.NewMultiHandler(jsonHandler, fileHandler))
18) sync.Once — ทำครั้งเดียว ไม่ว่า Goroutine กี่ตัว
เหมาะสำหรับ lazy initialization ที่ต้อง thread-safe:
var (
instance *Database
once sync.Once
)
func GetDB() *Database {
once.Do(func() {
instance = connectToDatabase()
log.Println("database connected")
})
return instance
}
// เรียกจากหลาย goroutine ได้ — connectToDatabase() ทำงานแค่ครั้งเดียว
19) Channel Patterns — Fan-Out / Fan-In
Pattern สำคัญสำหรับ parallel processing:
// Fan-out: กระจายงานไปหลาย goroutine
func fanOut(input <-chan int, workers int) []<-chan int {
channels := make([]<-chan int, workers)
for i := 0; i < workers; i++ {
ch := make(chan int)
channels[i] = ch
go func() {
defer close(ch)
for v := range input {
ch <- v * v // process
}
}()
}
return channels
}
// Fan-in: รวม output จากหลาย channel เป็น channel เดียว
func fanIn(channels ...<-chan int) <-chan int {
var wg sync.WaitGroup
merged := make(chan int)
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for v := range c {
merged <- v
}
}(ch)
}
go func() {
wg.Wait()
close(merged)
}()
return merged
}
20) errgroup — Goroutine Group พร้อม Error Propagation
golang.org/x/sync/errgroup ดีกว่า WaitGroup เมื่อต้องจัดการ error:
import "golang.org/x/sync/errgroup"
func fetchAll(ctx context.Context, urls []string) ([]string, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([]string, len(urls))
for i, url := range urls {
i, url := i, url
g.Go(func() error {
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
results[i] = string(body)
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
Go 1.26:
io.ReadAllเร็วขึ้น ~2x และใช้ memory น้อยลง ~50%
21) Middleware Pattern — สำหรับ HTTP Handlers
Pattern ที่ใช้กันมากใน Go web frameworks:
type Middleware func(http.Handler) http.Handler
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("request",
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
slog.Duration("duration", time.Since(start)),
)
})
}
func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// Chain middlewares
mux := http.NewServeMux()
mux.HandleFunc("/api/data", handleData)
handler := Logging(Auth(mux))
22) Enhanced HTTP Routing — ServeMux ที่ทรงพลังขึ้น (Go 1.22+)
ตั้งแต่ Go 1.22 http.ServeMux รองรับ method matching และ path parameters:
mux := http.NewServeMux()
// Method matching
mux.HandleFunc("GET /api/users", listUsers)
mux.HandleFunc("POST /api/users", createUser)
// Path parameters
mux.HandleFunc("GET /api/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// ...
})
// Wildcard
mux.HandleFunc("GET /files/{path...}", serveFile)
Go 1.26: trailing slash redirects ใน ServeMux เปลี่ยนจาก HTTP 301 เป็น 307 (รักษา method ของ request)
ทำให้หลายโปรเจกต์ไม่ต้องใช้ router library ภายนอกแล้ว
23) Modern Iteration — range over Integers & Functions
Range over Integers (Go 1.22+)
for i := range 10 {
fmt.Println(i) // 0, 1, 2, ..., 9
}
// แทนที่: for i := 0; i < 10; i++ { ... }
Range over Function Iterators (Go 1.23+)
// Custom iterator ด้วย iter.Seq
func Fibonacci(n int) iter.Seq[int] {
return func(yield func(int) bool) {
a, b := 0, 1
for i := 0; i < n; i++ {
if !yield(a) {
return
}
a, b = b, a+b
}
}
}
// ใช้งาน — เหมือน range ปกติ
for v := range Fibonacci(10) {
fmt.Println(v)
}
// ใช้กับ slices package
all := slices.Collect(Fibonacci(10))
Go 1.26:
reflect.Typeมี iterator methods ใหม่:Fields(),Methods(),Ins(),Outs()ที่ใช้กับ range ได้เลย
24) embed — ฝังไฟล์ลงใน Binary
ตั้งแต่ Go 1.16 ฝัง static files, templates, migrations ลงใน binary ได้เลย:
import "embed"
//go:embed templates/*
var templates embed.FS
//go:embed version.txt
var version string
Deploy แค่ binary ตัวเดียว ไม่ต้องจัดการ file path หรือ Docker COPY เพิ่ม
25) Benchmarking — วัด Performance ด้วย testing.B
Go มี benchmarking tool built-in ไม่ต้องติดตั้งอะไรเพิ่ม:
func BenchmarkConcat(b *testing.B) {
for b.Loop() {
s := ""
for j := 0; j < 100; j++ {
s += "x"
}
}
}
func BenchmarkBuilder(b *testing.B) {
for b.Loop() {
var sb strings.Builder
for j := 0; j < 100; j++ {
sb.WriteString("x")
}
_ = sb.String()
}
}
Go 1.26:
B.Loop()ไม่บล็อค inlining ใน loop body อีกต่อไป ทำให้ benchmark แม่นยำขึ้นGo 1.26: เพิ่ม
T.ArtifactDir()/B.ArtifactDir()สำหรับเก็บ artifacts จาก test
รัน:
go test -bench=. -benchmem ./...
Output จะบอก ns/op, B/op, allocs/op ทำให้เห็นชัดว่า strings.Builder เร็วกว่า += มาก
สรุป: Go Version Compatibility
| เทคนิค | Go version ขั้นต่ำ |
|---|---|
| 1-5: พื้นฐาน (go run, goroutines, worker pool, context, errors) | Go 1.16+ |
6: errors.AsType | Go 1.26+ (Is/As ใช้ได้ตั้งแต่ 1.13) |
| 7-11: select, WaitGroup, struct tags, defer, modules | Go 1.16+ |
8: WaitGroup.Go() | Go 1.25+ |
| 12: Generics | Go 1.18+ |
13: Table-driven tests / testing/synctest | Go 1.16+ / Go 1.25+ |
| 14-16: Interfaces, embedding, graceful shutdown | Go 1.16+ |
17: slog / NewMultiHandler | Go 1.21+ / Go 1.26+ |
| 18-21: sync.Once, channels, errgroup, middleware | Go 1.16+ |
| 22: Enhanced ServeMux routing | Go 1.22+ |
| 23: Range over int / Range over func | Go 1.22+ / Go 1.23+ |
24: embed | Go 1.16+ |
25: Benchmarking / B.Loop improvements | Go 1.16+ / Go 1.26+ |
Go 1.25/1.26 Highlights ที่ Production Dev ควรรู้
Runtime: Green Tea Garbage Collector (Go 1.26 — default)
GC ตัวใหม่ที่ปรับปรุง marking/scanning สำหรับ small objects — ลด GC overhead 10-40% ในงานจริง:
# ปิด Green Tea GC (ถ้ามีปัญหา)
GOEXPERIMENT=nogreenteagc go build .
Runtime: Container-Aware GOMAXPROCS (Go 1.25)
Go runtime ปรับ GOMAXPROCS ตาม cgroup CPU limits อัตโนมัติ — ไม่ต้องใช้ uber-go/automaxprocs อีกต่อไป:
# ปิดถ้าต้องการ
GODEBUG=containermaxprocs=0,updatemaxprocs=0
Tooling: go fix Modernizers (Go 1.26)
go fix เวอร์ชันใหม่มี “modernizers” ช่วยอัปเดตโค้ดให้ใช้ idiom ใหม่ ๆ อัตโนมัติ:
go fix ./...
Experimental: Goroutine Leak Profile (Go 1.26)
ตรวจจับ goroutine ที่ blocked บน concurrency primitive ที่ไม่มีใคร reference ถึง:
GOEXPERIMENT=goroutineleakprofile go build .
# แล้วเข้า /debug/pprof/goroutineleak
เคล็ดลับด้าน Security
- Validate inputs ทุกครั้ง อย่าเชื่อ client
- หลีกเลี่ยง
unsafeถ้าไม่จำเป็นจริง ๆ - อย่า ignore errors — hidden failure คือ failure ที่แย่ที่สุด
- ใช้ context + timeout ทุก network call และ long-running task
- ใช้
crypto/randแทนmath/randสำหรับ security-sensitive values - ใช้
go vet+staticcheckใน CI — จับ bug ก่อนถึง production
Go เหมาะกับงานแบบไหน?
Go เป็นตัวเลือกที่ดีเมื่อต้องการ: fast APIs, distributed services, CLI tools, streaming pipelines และ always-on backend systems ที่ต้องการ concurrency + networking + reliability
เมื่อใช้ patterns ที่ถูกต้องตั้งแต่แรก — goroutines with control, contexts everywhere, explicit errors, clean APIs — ระบบจะ scale ได้ดีและ maintain ง่าย
Go productive เพราะ straightforward แต่ speed ที่แท้จริงมาจากการใช้ patterns ที่ Go ออกแบบมาให้ใช้