Verification tools
output ทุกอันด้านล่างเป็นของจริง จากการรันแต่ละเครื่องมือกับ pattern ใน AI Bug Catalog จริงๆ ไม่มีเครื่องมือไหน overlap กันสมบูรณ์ และไม่มีตัวไหนใกล้เคียงคำว่าครบถ้วนเลย — ประเด็นของบทนี้คือให้รู้ชัดเจนว่าแต่ละตัวครอบคลุมถึงตรงไหน เพื่อให้รู้ว่าส่วนที่เหลือยังเป็นหน้าที่ของคุณอยู่
go vet — มากับ go test เลย จับ bug เชิงโครงสร้างได้ฟรี
หัวข้อที่มีชื่อว่า “go vet — มากับ go test เลย จับ bug เชิงโครงสร้างได้ฟรี”go vet มากับ Go toolchain และรันอัตโนมัติก่อน go test มันรายงานเฉพาะสิ่งที่พิสูจน์ได้จาก type system กับชุด analyzer ที่กำหนดตายตัวเท่านั้น — ไม่มีความเห็นเรื่อง style, false positive น้อยมาก และไม่ต้อง config เลย
จับได้แบบไม่มีเงื่อนไข ใน catalog นี้:
copylocks: Inc passes lock by value: main.Counter contains sync.Mutexwaitgroup: WaitGroup.Add called from inside new goroutineerrorsas: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface typemain.go:10:8: using resp before checking for errorsจับได้เฉพาะกับ go.mod directive ที่มันใช้: analyzer loopclosure จับ goroutine ที่ capture loop variable — แต่เฉพาะภายใต้ go directive ที่เก่ากว่า 1.22 เท่านั้น เพราะตัว bug เองก็มีอยู่แค่ในกรณีนั้น
พลาด: ทุกอย่างที่ต้องพิสูจน์เรื่องพฤติกรรมของโปรแกรม ไม่ใช่โครงสร้าง — leak, การเขียน nil map ที่พิสูจน์ไม่ได้ว่า nil เสมอ, ไม่เช็ก status code, ไม่มี Timeout field, err shadowing (analyzer shadow มีอยู่จริงแต่ไม่รันโดย default — เปิดเองด้วย go vet -vettool=$(which shadow) หรือ setting govet.enable: [shadow] ของ golangci-lint)
staticcheck — static analysis ที่ลึกกว่า ไม่มีเรื่องพลาดที่น่าอาย
หัวข้อที่มีชื่อว่า “staticcheck — static analysis ที่ลึกกว่า ไม่มีเรื่องพลาดที่น่าอาย”staticcheck ครอบคลุม check ของ go vet ทั้งหมดแล้วเพิ่มอีกหลายร้อยตัว จัดเป็น SA* (bug), ST* (style), และ prefix อื่นๆ มันช้ากว่าและบางครั้ง flag สิ่งที่ตั้งใจไว้ แต่มันจับ bug จริงที่ go vet จับไม่ได้เชิงโครงสร้าง
จับได้ ใน catalog นี้:
SA4023: this comparison is always true (typed-nil error, when provable)SA2000: should call wg.Add(1) before starting the goroutine to avoid a raceSA5000: assignment to nil map (only when provably always-nil)SA4011: ineffective break statement. Did you mean to break out of the outer loop?SA1004: sleeping for 5 nanoseconds is probably a bug; be explicit if it isn'tST1006: receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"พลาด: หมวดที่ต้องรันจริงเหมือนที่ go vet พลาด บวกกับอะไรก็ตามที่ต้องรู้ทั้ง codebase — ที่เด่นที่สุดคือ interface มี implementation กี่ตัว
golangci-lint v2 — รัน go vet, staticcheck, และอีกหลายสิบตัวในครั้งเดียว
หัวข้อที่มีชื่อว่า “golangci-lint v2 — รัน go vet, staticcheck, และอีกหลายสิบตัวในครั้งเดียว”golangci-lint เป็น meta-linter: linters.default: standard ใน config รันชุดที่คัดมาแล้วซึ่งมี go vet (ในชื่อ govet) และ staticcheck รวมอยู่ พร้อม errcheck, unused, และอื่นๆ คำสั่งเดียวแทนที่การรันแต่ละเครื่องมือเอง
version: "2"linters: default: standardจับทุกอย่างที่ go vet และ staticcheck จับด้านบน โดยรายงานภายใต้ linter ที่เจอมัน — golangci-lint run บนไฟล์ errors.As ที่ขาด & รายงานว่า:
main.go:19:5: errorsas: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type (govet)จับได้ด้วย ผ่าน errcheck (อยู่ใน standard) — ค่า return ที่ไม่ถูกเช็ก ซึ่งเป็นคนละ bug กับตัวที่มันบังเอิญอยู่ข้างๆ ใน catalog นี้:
main.go:10:23: Error return value of `resp.Body.Close` is not checked (errcheck)ต้อง opt-in เอง แม้ analyzer จะมีอยู่ใน toolbox ของ golangci-lint แล้ว:
shadow(check เรื่องerrshadowing) — เปิดด้วยlinters.settings.govet.enable: [shadow]modernize(flaginterface{}ในจุดที่anyอ่านง่ายกว่า และการ rewrite อื่นๆ) — เปิดด้วยlinters.enable: [modernize]
พลาด: จุดบอดเดียวกับ go vet + staticcheck รวมกัน เพราะนั่นคือสิ่งที่มันรัน
-race — เครื่องมือเดียวในลิสต์นี้ที่เฝ้าดูโปรแกรมตอนรันจริง
หัวข้อที่มีชื่อว่า “-race — เครื่องมือเดียวในลิสต์นี้ที่เฝ้าดูโปรแกรมตอนรันจริง”go run -race และ go test -race ใส่ instrumentation ให้ทุก memory access และตรวจจับการเข้าถึง memory เดียวกันแบบ concurrent ที่ไม่ sync กันจริงๆระหว่างการรันครั้งนั้น — ไม่ใช่ความเป็นไปได้ทางทฤษฎี แต่เป็นสิ่งที่สังเกตเห็นจริง
จับได้ ใน catalog นี้ — pattern wg.Add ข้างใน goroutine รันด้วย -race:
WARNING: DATA RACEWrite at 0x00c00013e000 by goroutine 7: main.main.func1() .../main.go:18 +0xf0Previous read at 0x00c00013e000 by main goroutine: main.main() .../main.go:24 +0x23cFound 1 data race(s)พลาด: race ไหนก็ตามที่บังเอิญไม่เกิดขึ้นในการรันครั้งที่คุณทดสอบ — race detector พิสูจน์ได้แค่ว่ามี race ที่มันสังเกตเห็น ไม่เคยพิสูจน์ว่าไม่มี race ที่มันไม่เห็น มันก็ไม่มีอะไรจะพูดเกี่ยวกับ goroutine leak, resource leak, หรือ bug ทาง logic แบบ single-threaded ด้วย — พวกนั้นไม่ใช่ race
govulncheck — CVE ที่รู้จักแล้ว กรองเหลือแค่โค้ดที่คุณเรียกใช้จริง
หัวข้อที่มีชื่อว่า “govulncheck — CVE ที่รู้จักแล้ว กรองเหลือแค่โค้ดที่คุณเรียกใช้จริง”govulncheck เทียบ dependency ของคุณกับ Go vulnerability database แล้วกรองผลลัพธ์ให้เหลือเฉพาะ vulnerability ที่ call graph ของโค้ดคุณเข้าถึงได้จริง — ไม่ใช่แค่ “มี version ที่มีช่องโหว่อยู่ใน go.sum ที่ไหนสักแห่ง”
output จริง module ที่ depend on golang.org/x/[email protected] และเรียก language.Parse:
Vulnerability #1: GO-2021-0113 Out-of-bounds read in golang.org/x/text/language More info: https://pkg.go.dev/vuln/GO-2021-0113 Module: golang.org/x/text Found in: golang.org/x/[email protected] Fixed in: golang.org/x/[email protected] Example traces found: #1: main.go:10:26: vulndemo.main calls language.Parse
Your code is affected by 1 vulnerability from 1 module.This scan also found 1 vulnerability in packages you import and 2vulnerabilities in modules you require, but your code doesn't appear to callthese vulnerabilities.พลาด: ทุกอย่างใน catalog นี้ govulncheck ตอบคำถามเดียวเท่านั้น — “มี CVE ที่รู้จักแล้วส่งผลกับโค้ดที่คุณเรียกใช้จริงไหม” — และไม่มีอะไรจะพูดเกี่ยวกับ bug ทาง logic, bug ทาง concurrency, หรือการละเมิด API contract เลย ไม่ว่าจะเป็นของคุณเองหรือของ dependency ที่ยังไม่มีใครค้นพบ
gopls — analysis แบบเดียวกับที่ editor ของคุณรันอยู่แล้ว แต่จาก command line
หัวข้อที่มีชื่อว่า “gopls — analysis แบบเดียวกับที่ editor ของคุณรันอยู่แล้ว แต่จาก command line”gopls check <file> รัน diagnostic แบบเดียวกับที่ editor ของคุณแสดง inline โดยไม่ต้องเปิดมันขึ้นมา — มีประโยชน์ใน CI หรือผ่าน SSH มันมี analyzer ของ go vet รวมอยู่และเพิ่มของตัวเองอีกนิดหน่อย เช่น nilness analysis
output จริง บนไฟล์ errors.As ที่ขาด & — gopls เจอทั้งการใช้ errors.As ผิดและ bug ที่เป็นผลตามมาอีกตัวในไฟล์เดียวกัน ที่ไม่มีเครื่องมือไหนในลิสต์นี้รายงาน:
main.go:19:5-23: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface typemain.go:20:32: nil dereference in field selectionบรรทัดที่สองคือ nilness analyzer ของ gopls เอง: เนื่องจาก errors.As จะ fail (ตามที่ diagnostic แรกบอก) ve จึงยังเป็น nil และบรรทัดถัดไปที่เข้าถึง ve.Field คือ nil pointer dereference ที่เกิดขึ้นแน่นอน — ผลลัพธ์ที่ go vet และ staticcheck ไม่ได้เชื่อมโยงให้
พลาด: หมวดเดียวกับ go vet เพราะมันสร้างบน analysis framework เดียวกัน บวกกับอะไรก็ตามที่ต้องรู้ทั้ง codebase หรือข้อมูลตอน runtime
go test -cover — บอกว่าอะไรไม่เคยถูกรัน ไม่ใช่บอกว่าอะไรพัง
หัวข้อที่มีชื่อว่า “go test -cover — บอกว่าอะไรไม่เคยถูกรัน ไม่ใช่บอกว่าอะไรพัง”go test -cover รายงานเปอร์เซ็นต์ของ statement ที่ถูก execute ระหว่างรัน test มันไม่ใช่ bug finder มันคือแผนที่บอกว่าเครื่องมืออื่นๆ — และการอ่านของคุณเอง — ไม่เคยดูตรงไหนเลย
output จริง method Store.Set ที่ test มีแค่ทดสอบ Get:
ok scratch/covdemo 0.474s coverage: 66.7% of statements33.3% ที่ไม่ถูกทดสอบคือ Set เป๊ะๆ — method ที่ panic บน nil map coverage ไม่ได้เจอ bug นั้น มันแค่เจอว่าไม่มีใครดูตรงนั้นเลย
พลาด: ความถูกต้องทั้งหมด coverage 100% พิสูจน์แค่ว่าทุกบรรทัดถูกรันอย่างน้อยหนึ่งครั้งระหว่าง test บางตัว มันไม่พิสูจน์อะไรเลยว่า assertion ใน test นั้นถูกต้องหรือเปล่า หรือ input ที่ทำให้เกิด bug เคยถูกลองหรือยัง
รันทั้งชุดในคำสั่งเดียว
หัวข้อที่มีชื่อว่า “รันทั้งชุดในคำสั่งเดียว”golangci-lint run ครอบคลุม go vet และ staticcheck พร้อมกันอยู่แล้ว เพิ่ม govulncheck สำหรับ CVE ของ dependency และ go test -race -cover สำหรับ race ตอน runtime กับ coverage แล้วบรรทัดเดียวครอบคลุมทุกอย่างในหน้านี้ ยกเว้น gopls (ซึ่งเป็นแบบ interactive/editor-oriented ไม่ใช่ CI step):
golangci-lint run && govulncheck ./... && go test -race -cover ./...Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”flowchart TD
subgraph Static["Static (source only)"]
Vet["go vet<br/>structural bugs (type-provable):<br/>copylocks, waitgroup, errorsas, loopclosure"]
SC["staticcheck (SA*, ST*)<br/>deeper static bugs"]
Vuln["govulncheck<br/>known CVEs reached by your call graph"]
end
subgraph Dynamic["Dynamic (must run)"]
Race["-race<br/>concurrency bugs that actually occur (only what ran)"]
Cover["go test -cover<br/>what ran vs. didn't"]
end
GCL["golangci-lint runs the whole Static box (go vet + staticcheck + more) in one command"] --> Vet
GCL --> SC