寫出Go標(biāo)準(zhǔn)庫級別文檔注釋的十個細(xì)節(jié)
Go語言以其優(yōu)秀的工具鏈、“開箱即用”的標(biāo)準(zhǔn)庫和相對完善的文檔生態(tài)而聞名。Go通過代碼中的文檔注釋(Doc Comments)[1]生成相關(guān)包、類型、函數(shù)以及方法的說明文檔。Go標(biāo)準(zhǔn)庫中的文檔注釋不僅為使用者提供了清晰的指引,更為廣大Go開發(fā)人員樹立了高質(zhì)量技術(shù)文檔的標(biāo)桿。
然而,在日常開發(fā)中,很多Go開發(fā)者往往只注意到文檔注釋的基本格式要求,而忽略了一些能讓文檔質(zhì)量更上一層樓的細(xì)節(jié)。這些細(xì)節(jié)雖小,但卻是區(qū)分專業(yè)級別和業(yè)余水平代碼的重要標(biāo)志。
在這篇文章中,我就挑出十個在編寫Go文檔注釋時容易被忽視的關(guān)鍵細(xì)節(jié),和大家說說,希望能幫助大家提升Go代碼文檔注釋的level。
1. 注釋縮進(jìn)的陷阱
很多開發(fā)者在寫多行注釋時會不經(jīng)意產(chǎn)生縮進(jìn)問題,例如:
// TODO Revisit this design. It may make sense to walk those nodes
// only once. // 錯誤示例:第二行有縮進(jìn)
這種縮進(jìn)會使第二行被解析為代碼塊。正確的做法是保持未縮進(jìn):
// TODO Revisit this design. It may make sense to walk those nodes
// only once.
2. 并發(fā)安全性說明
對于類型(Type)的文檔注釋,默認(rèn)情況下讀者會認(rèn)為該類型僅適用于單個goroutine。如果類型支持并發(fā)訪問,應(yīng)該顯式地明確注釋說明:
// Regexp is the representation of a compiled regular expression.
// A Regexp is safe for concurrent use by multiple goroutines,
// except for configuration methods, such as Longest.
type Regexp struct {
...
}
3. 零值行為說明
如果類型支持零值可用或零值具有特殊含義,應(yīng)當(dāng)在注釋中顯式說明:
// Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
...
}
4. 避免實(shí)現(xiàn)細(xì)節(jié)
函數(shù)的文檔注釋應(yīng)該關(guān)注其行為和返回值,而不是實(shí)現(xiàn)細(xì)節(jié)。除非是性能關(guān)鍵的場景需要說明算法復(fù)雜度,否則應(yīng)該避免在注釋中描述算法實(shí)現(xiàn):
// 好的示例:關(guān)注行為
// Sort sorts data in ascending order as determined by the Less method.
// It makes O(n*log(n)) calls to data.Less and data.Swap.
// 不好的示例:暴露實(shí)現(xiàn)細(xì)節(jié)
// Sort uses quicksort algorithm to sort data...
5. 返回布爾值的函數(shù)注釋慣例
對于返回布爾值的函數(shù),按慣例最好使用"reports whether"的描述方式,避免使用"or not":
// HasPrefix reports whether the string s begins with prefix.
func HasPrefix(s, prefix string) bool
6. “構(gòu)造函數(shù)”在文檔中的位置
Go自身沒有構(gòu)造函數(shù)的專有語法,但當(dāng)一個包中包含返回類型T或指針*T(包括伴隨返回一個error的情況)的包頂層函數(shù)時,這些函數(shù)會被視為“構(gòu)造函數(shù)”。這些構(gòu)造函數(shù)在godoc中會被顯示在T類型的下面,看起來像是T類型的方法,這的確容易“誤導(dǎo)”一些Go新手:
$go doc time
... ...
type Duration int64
func ParseDuration(s string) (Duration, error)
func Since(t Time) Duration
func Until(t Time) Duration
... ...
在pkg.go.dev中,這些“構(gòu)造函數(shù)”在文檔中會自動顯示在類型T類型旁邊:
圖片
這意味著我們在寫“構(gòu)造函數(shù)”的文檔時應(yīng)當(dāng)注意與類型文檔的一致性:
// NewReader creates a new Reader reading from r.
// It is similar to NewReaderSize with the default buffer size.
func NewReader(r io.Reader) *Reader
// Reader implements buffering for an io.Reader object.
type Reader struct {
// ...
}
7. 頂層函數(shù)的并發(fā)安全性說明
對于頂層函數(shù)(包級別的導(dǎo)出函數(shù)),默認(rèn)情況下是假定它們是并發(fā)安全的,因此不需要顯式說明這一點(diǎn):
// 不必要的說明
// Parse parses the regular expression and returns a Regexp object.
// This function is safe for concurrent use. // 這行是多余的
func Parse(expr string) (*Regexp, error)
// 正確的做法
// Parse parses the regular expression and returns a Regexp object.
func Parse(expr string) (*Regexp, error)
8. 方法的并發(fā)安全性說明
與包的頂層函數(shù)不同,類型的方法則是默認(rèn)被認(rèn)為僅限單個goroutine使用,即不是并發(fā)安全的。如果某些方法支持并發(fā)調(diào)用,應(yīng)當(dāng)在方法的文檔注釋中顯式給予說明:
// Load returns the value stored in the map for a key.
// It is safe for concurrent use by multiple goroutines.
func (m *Map) Load(key interface{}) (value interface{}, ok bool)
9. 方法接收器命名一致對文檔展示的影響
在編寫類型的多個方法時,應(yīng)該使用統(tǒng)一的接收器(receiver)命名,這樣可以提高文檔的一致性和可讀性,避免不必要的命名變化:
// 不好的示例:接收器命名不一致
func (buffer *Buffer) Read(p []byte) (n int, err error)
func (b *Buffer) Write(p []byte) (n int, err error)
func (buf *Buffer) Cap() int
// 好的示例:統(tǒng)一使用b作為接收器名
func (b *Buffer) Read(p []byte) (n int, err error)
func (b *Buffer) Write(p []byte) (n int, err error)
func (b *Buffer) Cap() int
通過下圖,我們也可以看到一致的方法receiver參數(shù)命名在文檔中體現(xiàn)出的一致性,這種一致性不僅讓文檔看起來更專業(yè),也讓使用者在閱讀文檔時能更專注于方法的功能本身,而不是被不同的命名所分散注意力:
圖片
此外,選擇方法接收器名稱時的建議使用簡短的命名(通常是類型名的第一個小寫字母,比如上面的b),避免使用this、self等其他語言常用的命名。即使是單個方法,也要遵循這個命名約定,為后續(xù)可能的方法擴(kuò)展做準(zhǔn)備。
10. 廢棄標(biāo)記的使用
當(dāng)需要標(biāo)記某個API為廢棄時,應(yīng)該及時使用"Deprecated:"前綴予以標(biāo)記,并提供替代方案,如下面的strings.Title函數(shù):
// Title returns a copy of the string s with all Unicode letters that begin words
// mapped to their Unicode title case.
//
// Deprecated: The rule Title uses for word boundaries does not handle Unicode
// punctuation properly. Use golang.org/x/text/cases instead.
func Title(s string) string {
... ...
}
這些細(xì)節(jié)雖小,但都會影響到文檔的可讀性和代碼的可維護(hù)性。良好的文檔習(xí)慣需要在日常編碼中持續(xù)積累和保持。
Go團(tuán)隊將代碼的可讀性和可維護(hù)性放到至關(guān)重要的位置上,而編寫高質(zhì)量的文檔注釋就是提升代碼可讀可維護(hù)性的重要實(shí)踐。從注釋的縮進(jìn)、并發(fā)安全性說明,到零值行為、構(gòu)造函數(shù)文檔等細(xì)節(jié),這些看似微小的考量都在傳遞著重要的信息。通過遵循這些最佳實(shí)踐,我們不僅能讓文檔更加清晰易懂,還能幫助團(tuán)隊減少溝通成本,提高開發(fā)效率。更重要的是,這些實(shí)踐能幫助我們培養(yǎng)更專業(yè)的編碼習(xí)慣,寫出更加規(guī)范的代碼,讓我們在日常開發(fā)中持續(xù)積累這些好的習(xí)慣,讓代碼文檔更接近Go標(biāo)準(zhǔn)庫的專業(yè)水準(zhǔn)。
參考資料
[1] 文檔注釋(Doc Comments): https://go.dev/doc/comment
[2] Go Doc Comments: https://go.dev/doc/comment