Go:我有注解,Java:不,你沒(méi)有!
大家好,我是煎魚(yú)。
作為一位 Go 程序員,你會(huì)發(fā)現(xiàn)身邊的同事大多都擁有其他語(yǔ)言的編寫(xiě)經(jīng)驗(yàn)。那勢(shì)必就會(huì)遇到一點(diǎn),要把新學(xué)到的知識(shí)和以前的知識(shí)建立連接。
圖來(lái)自網(wǎng)絡(luò)
特殊在于,Go 有些特性是其他語(yǔ)言有,他沒(méi)有的。最經(jīng)典的就是 N 位 Java 同學(xué)尋找 Go 語(yǔ)言的注解在哪里,總要解釋。
為此,今天煎魚(yú)就帶大家了解一下 Go 語(yǔ)言的注解的使用和情況。
什么是注解
了解歷史
注解(Annotation)最早出現(xiàn)自何處,翻了一圈并沒(méi)有找到。但可以明確,在注解的使用中,Java 注解最為經(jīng)典,為了便于理解,因此我們基于 Java 做初步的注解理解。
在 2002 年,JSR-175 提出了 《A Metadata Facility for the Java Programming Language》,也就是為 Java 編程語(yǔ)言提供元數(shù)據(jù)工具。
這就是現(xiàn)在使用最廣泛地注解(Annotation)的來(lái)源。示例如下:
- // @annotation1
- // @annotation2
- func Hello() string {
- return ""
- }
在格式上均以 “@” 作為注解標(biāo)識(shí)來(lái)使用。
注解例子
摘抄自 @wikipedia 的一個(gè)注解例子:
- //等同于 @Edible(value = true)
- @Edible(true)
- Item item = new Carrot();
- public @interface Edible {
- boolean value() default false;
- }
- @Author(first = "Oompah", last = "Loompah")
- Book book = new Book();
- public @interface Author {
- String first();
- String last();
- }
- // 該標(biāo)注可以在運(yùn)行時(shí)通過(guò)反射訪問(wèn)。
- @Retention(RetentionPolicy.RUNTIME)
- // 該標(biāo)注只用于類(lèi)內(nèi)方法。
- @Target({ElementType.METHOD})
- public @interface Tweezable {
- }
在上述例子中,通過(guò)注解去做了一系列的定義、聲明、賦值等。若是對(duì)語(yǔ)言既有注解不熟,或是做的比較復(fù)雜的注解,就會(huì)有一定的理解成本。
在業(yè)內(nèi)也常常會(huì)說(shuō),注解就是 “在源碼上進(jìn)行編碼”,注解的存在,有著明確的優(yōu)缺點(diǎn)。你覺(jué)得呢?
注解的作用
在注解的的作用上,分為如下幾點(diǎn):
為編譯器提供信息:注釋可以被編譯器用來(lái)檢測(cè)錯(cuò)誤或支持警告。
編譯時(shí)和部署時(shí)處理:軟件工具可以處理注釋信息以生成代碼、XML文件等。
運(yùn)行時(shí)處理:有些注解可以在運(yùn)行時(shí)檢查,并用于其他用途。
Go 注解在哪里
現(xiàn)狀
Go 語(yǔ)言本身并沒(méi)有原生支持強(qiáng)大的注解,僅限于以下兩種:
- 編譯時(shí)生成:go:generate
- 編譯時(shí)約束:go:build
但這先按不足以作為一個(gè)函數(shù)注解來(lái)使用,也無(wú)法形成像 Python 那樣的裝飾器行為。
為什么不支持
Go issues 上有人提過(guò)類(lèi)似的提案:
Go Contributor @ianlancetaylor 給出了明確的答復(fù),Go 在設(shè)計(jì)上更傾向于明確的、顯式的編程風(fēng)格。
思考的優(yōu)缺點(diǎn)如下:
- 優(yōu)勢(shì):不知道 Go 能從添加裝飾器中得到什么好處,沒(méi)能在 issues 上明確論證。
- 缺點(diǎn):是明確的,會(huì)存在意外設(shè)置的情況。
因如下原因,沒(méi)有接受注解:
- 對(duì)比現(xiàn)有代碼方法,這種裝飾器的新的方法沒(méi)有提供比現(xiàn)有方法更多的優(yōu)勢(shì),大到足矣推翻原有的設(shè)計(jì)思路。
- 社區(qū)內(nèi)的投票,支持的也很少(基于表情符號(hào)的投票),用戶(hù)反饋不多。
可能有小伙伴會(huì)說(shuō)了,有注解做裝飾器了,代碼會(huì)簡(jiǎn)潔不少。
對(duì)此 Go 團(tuán)隊(duì)的態(tài)度很明確:
Go 認(rèn)為可讀性更重要,如果只是額外多寫(xiě)一點(diǎn)代碼,在權(quán)衡后,還是可以接受的。
用 Go 實(shí)現(xiàn)注解
雖然 Go 語(yǔ)言官方?jīng)]有原生的完整支持,但開(kāi)源社區(qū)中也有小伙伴已經(jīng)放出了大招,借助各項(xiàng)周邊工具和庫(kù)來(lái)實(shí)現(xiàn)特定的函數(shù)注解功能。
GitHub 項(xiàng)目分別如下:
- MarcGrol/golangAnnotations
- u2takey/go-annotation
使用示例如下:
- package tourdefrance
- //go:generate golangAnnotations -input-dir .
- // @RestService( path = "/api/tour" )
- type TourService struct{}
- type EtappeResult struct{ ... }
- // @RestOperation( method = "PUT", path = "/{year}/etappe/{etappeUid}" )
- func (ts *TourService) addEtappeResults(c context.Context, year int, etappeUid string, results EtappeResult) error {
- return nil
- }
對(duì) Go 注解的使用感興趣的小伙伴可以自行查閱使用手冊(cè)。
我們更多的關(guān)心,Go 原生都沒(méi)支持,那么開(kāi)源庫(kù)都是如何實(shí)現(xiàn)的呢?在此我們借助 MarcGrol/golangAnnotations 項(xiàng)目所提供的思路來(lái)講解。
分為三個(gè)步驟:
- 解析代碼。
- 模板處理。
- 生成代碼。
解析 AST
首先,我們需要用用 go/ast 標(biāo)準(zhǔn)庫(kù)獲取代碼所生成的 AST Tree 中需要的內(nèi)容和結(jié)構(gòu)。
示例代碼如下:
- parsedSources := ParsedSources{
- PackageName: "tourdefrance",
- Structs: []model.Struct{
- {
- DocLines: []string{"// @RestService( path = "/api/tour" )"},
- Name: "TourService",
- Operations: []model.Operation{
- {
- DocLines: []string{"// @RestOperation( method = "PUT", path = "/{year}/etappe/{etappeUid}"},
- ...
- },
- },
- },
- },
- }
我們可以看到,在 AST Tree 中能夠獲取到在示例代碼中所定義的注解內(nèi)容,我們就可以依據(jù)此去做很多奇奇怪怪的事情了。
模板生成
緊接著,在知道了注解的輸入是什么后,我們只需要根據(jù)實(shí)際情況,編寫(xiě)對(duì)應(yīng)的模板生成器 code-generator 就可以了。
我們會(huì)基于 text/template 標(biāo)準(zhǔn)庫(kù)來(lái)實(shí)現(xiàn),比較經(jīng)典的像是 kubernetes/code-generator 是一個(gè)可以參考的實(shí)現(xiàn)。
代碼實(shí)現(xiàn)完畢后,將其編譯成 go plugin,便于我們?cè)谙乱徊秸{(diào)用就可以了。
代碼生成
最后,萬(wàn)事俱備只欠東風(fēng)。差的就是告訴工具,哪些 Go 文件中包含注解,需要我們?nèi)ド傻摹?/p>
這時(shí)候我們可以使用 //go:generate 在 Go 文件聲明。就像前面的項(xiàng)目中所說(shuō)的:
- //go:generate golangAnnotations -input-dir .
聲明該 Go 文件需要生成,并調(diào)用前面編寫(xiě)好的 golangAnnotations 二進(jìn)制文件,就可以實(shí)現(xiàn)基本的 Go 注解生成了。
總結(jié)
今天在這篇文章中,我們介紹了注解(Annotation)的歷史背景。同時(shí)我們針對(duì) Go 語(yǔ)言目前原生的注解支持情況進(jìn)行了說(shuō)明。
也面向?yàn)槭裁?Go 沒(méi)有像 Java 那樣支持強(qiáng)大的注解進(jìn)行了基于 Go 官方團(tuán)隊(duì)的原因解釋。如果希望在 Go 實(shí)現(xiàn)注解的,也提供了相應(yīng)的開(kāi)源技術(shù)方案。
你覺(jué)得 Go 語(yǔ)言是否需要像和 Java 一樣的注解支持呢?