經(jīng)驗(yàn)之談:學(xué)習(xí)Go語(yǔ)言的利與弊
在這個(gè)競(jìng)爭(zhēng)越來越烈的社會(huì),掌握一門新語(yǔ)言或新技能,意味著你能比別人多一個(gè)機(jī)會(huì)。
但萬事開頭難,學(xué)習(xí)新東西亦如此。如果開發(fā)員想學(xué)一門新的編程語(yǔ)言,該選擇什么呢?
Go語(yǔ)言學(xué)起來簡(jiǎn)單得令人驚訝
當(dāng)我第一次開始學(xué)習(xí)Go語(yǔ)言時(shí),我正著手開發(fā)一個(gè)個(gè)人項(xiàng)目,為此我不得不掌握新的語(yǔ)法(我總是在學(xué)習(xí)一門新的編程語(yǔ)言時(shí)想出一個(gè)項(xiàng)目)。
我決定創(chuàng)建一個(gè)命令行應(yīng)用程序來枚舉子域,以輔助尋找資產(chǎn)中存在的漏洞獎(jiǎng)金計(jì)劃。為實(shí)現(xiàn)這一功能,與gobuster相似,該應(yīng)用程序必須并行地發(fā)出多個(gè)HTTP請(qǐng)求,但我想通過增加一些功能(例如抓取HTML響應(yīng)以獲取與安全相關(guān)的有趣信息)來重新構(gòu)建特定循環(huán)。
我嘗試用go-routine來解決此問題,其中很具挑戰(zhàn)性的一點(diǎn)是程序發(fā)出的HTTP請(qǐng)求數(shù)量未知,因此需要學(xué)習(xí)如何有效處理這些請(qǐng)求。
第一印象
很快,我發(fā)現(xiàn)語(yǔ)法異常熟悉,盡管我之前從未閱讀過相關(guān)文檔。在我看來,這些概念很直觀(其他人可能不贊成)。Defer的使用直接明了。用于格式化字符串的fmt包好像解決了我之前未發(fā)現(xiàn)的問題。我開始認(rèn)識(shí)到Go作為新興編程語(yǔ)言近年來得到快速發(fā)展的原因。因此,我決定更深入地研究Go語(yǔ)言的初衷,以確定它是否值得花時(shí)間學(xué)習(xí)。
為什么開發(fā)Go語(yǔ)言
目的
Go語(yǔ)言由谷歌開發(fā),目的是使多進(jìn)程開發(fā)更加高效和安全,以提高服務(wù)器長(zhǎng)期運(yùn)行的可維護(hù)性、可靠性和有效性。對(duì)谷歌來說,該語(yǔ)言可解決其當(dāng)前面臨的編譯時(shí)間過長(zhǎng)和當(dāng)今已在生產(chǎn)中取得普遍應(yīng)用的大規(guī)模數(shù)據(jù)處理問題。谷歌希望開發(fā)出一種注重于可伸縮性、可讀性和并發(fā)性的語(yǔ)言,而其他語(yǔ)言無法滿足這些要求,因此誕生了Go語(yǔ)言。谷歌開發(fā)人員從現(xiàn)有的語(yǔ)言中提取了最簡(jiǎn)單明了的概念,并將這些概念改進(jìn)和組合,最終形成了Go。以處理字符串的高效數(shù)據(jù)庫(kù)——fmt數(shù)據(jù)包為例:
“fmt包使用類似于C的printf和scanf的函數(shù),用來實(shí)現(xiàn)格式化的I/O。動(dòng)詞形式源自C,但更簡(jiǎn)單。”
這就是從一種成功且通用的語(yǔ)言(在本例中是C語(yǔ)言)中提取功能并對(duì)其進(jìn)行改進(jìn)的例子。
Go語(yǔ)言的并發(fā)機(jī)制基于CSP建模;使用通道可避免共享數(shù)據(jù)出現(xiàn)同步錯(cuò)誤,這種信息交互方式更簡(jiǎn)單也更安全。
Go語(yǔ)言關(guān)注的另一個(gè)重點(diǎn)是簡(jiǎn)潔化。使用Go語(yǔ)言需要在其框架下形成一種公認(rèn)的特有代碼風(fēng)格,并在開發(fā)不同項(xiàng)目時(shí)保持一致,以減少配置linting規(guī)則和在開發(fā)過程中學(xué)習(xí)不同的代碼風(fēng)格的時(shí)間;而時(shí)間,是在團(tuán)隊(duì)中工作的一個(gè)要素。
從理論上講,這將減少開發(fā)人員在代碼風(fēng)格和編程方法上的差異,正如包含了許多Eslint規(guī)則的JavaScript語(yǔ)言。
方法
Go語(yǔ)言所采用的方法將解釋型動(dòng)態(tài)型語(yǔ)言的編程簡(jiǎn)便性與編譯性靜態(tài)型語(yǔ)言的效率和安全性相結(jié)合。其內(nèi)置映射定義了int、byte和string等基本類型。有指針。除此之外,在使用Go語(yǔ)言進(jìn)行開發(fā)時(shí)還應(yīng)注意的一個(gè)重要的原則就是正交性,該原則也是函數(shù)方法的基礎(chǔ)。
Go使用結(jié)構(gòu)(struct)表示數(shù)據(jù),用戶接口表示抽象。關(guān)于Go語(yǔ)言是否面向?qū)ο笠恢贝嬖跔?zhēng)議,Java開發(fā)人員起初很難理解為什么對(duì)此會(huì)存在爭(zhēng)議。爭(zhēng)議的焦點(diǎn)在于Go中沒有類型層次,而普遍判斷是否面向?qū)ο蟮囊罁?jù)是類型層次。有些結(jié)構(gòu)不能繼承,但確實(shí)符合對(duì)象樣式。Go更傾向于組合而不是繼承。多態(tài)性可以通過接口來實(shí)現(xiàn)。滿足該接口的任何類型對(duì)象都可與其對(duì)接。
除了這些核心概念之外,Go還通過多核處理實(shí)現(xiàn)了對(duì)并發(fā)的現(xiàn)代需求。強(qiáng)并發(fā)性以goroutines和channels的形式實(shí)現(xiàn)。在大型并發(fā)程序中,自動(dòng)垃圾回收作為一種有效的內(nèi)存管理手段非常重要。單元測(cè)試簡(jiǎn)單到只需使用前綴_test.go即可,該前綴在與源文件相同的目錄中聲明。
學(xué)習(xí)Go語(yǔ)言的理由
1、簡(jiǎn)潔性
Go語(yǔ)言采用極簡(jiǎn)方法開發(fā)。沒有類或繼承。流行語(yǔ)言(如Java和Python)中的這部分功能在Go中被結(jié)構(gòu)取代了。Go是強(qiáng)靜態(tài)類型且鼓勵(lì)在各種情況下使用接口。靜態(tài)類型旨在減少編譯錯(cuò)誤,也使Go更易學(xué)。
在使用其他語(yǔ)言如JavaScript時(shí),多種固有方法、范例和公約令人為難,而Go提供了一種方法作為通用樣式指南。從團(tuán)隊(duì)的角度出發(fā),個(gè)人代碼的分析和推理更容易,集成也更順暢。
盡管沒有隱式轉(zhuǎn)換,但是花費(fèi)在語(yǔ)法上的工作仍然非常少。這使代碼可讀性更強(qiáng)、更簡(jiǎn)單。
2、快速性
靜態(tài)連接的編譯器通過編譯生成二進(jìn)制可執(zhí)行文件,而無需處理外部依賴項(xiàng)??蓤?zhí)行的二進(jìn)制文件已編譯為本機(jī)代碼,無需使用虛擬機(jī),盡管其數(shù)據(jù)量有所增加,但編譯速度更快、可移植性更強(qiáng)。
此外,如前文所述,Go的編譯時(shí)間和生產(chǎn)時(shí)間也很快。由于其簡(jiǎn)潔性,在使用Go語(yǔ)言時(shí),開發(fā)者的工作效率得到了關(guān)注,即從最初的概念/想法到產(chǎn)成品的過程更快。
3、并發(fā)性
在Go語(yǔ)言中,并發(fā)性是核心概念,具有較高優(yōu)先級(jí),就像使用go關(guān)鍵字為函數(shù)添加前綴一樣容易。Goroutines是簡(jiǎn)單輕量級(jí)的執(zhí)行線程。在Go中實(shí)現(xiàn)并發(fā)非常容易。使用go關(guān)鍵字產(chǎn)生一個(gè)新線程,該線程在一組線程的多個(gè)核心之間共享。Goroutines只有幾千字節(jié),由Go運(yùn)行時(shí)處理,Go運(yùn)行時(shí)將go-routines移動(dòng)到不同的可運(yùn)行線程上,以避免通道被阻塞。這種方法使得異步執(zhí)行速度幾乎和C/ C++一樣快。您可以使用channel來控制goroutine的數(shù)量,各channel看似同步,但實(shí)質(zhì)上是異步的。
Go語(yǔ)言的運(yùn)行時(shí)使用可調(diào)整大小的有界堆棧,從而使堆棧變小。運(yùn)行時(shí)會(huì)更改內(nèi)存大小以自動(dòng)存儲(chǔ)堆棧。數(shù)十萬個(gè)goroutine可以在同一地址空間上運(yùn)行。
存在的問題
沒有范型
此問題存在爭(zhēng)議。在Java這樣的語(yǔ)言中,范型的使用提高了代碼的可重用性,同時(shí)確保了類型安全。Go的使用者們已經(jīng)提出了這個(gè)“問題”,并對(duì)此進(jìn)行了思考。這里的建議可參考。然而,主流意見是使用范型的好處不會(huì)超過簡(jiǎn)單性和可讀性(沒有范型)的好處。
競(jìng)爭(zhēng)形勢(shì)
“不要通過分享記憶來交流;相反,通過交流來共享記憶。”
這一理念帶來了優(yōu)勢(shì),也使Go容易受到競(jìng)爭(zhēng)條件的影響。
由于go結(jié)構(gòu)的可變性(以及缺少不可變的數(shù)據(jù)結(jié)構(gòu)),共享可變數(shù)據(jù)被迫要跨越多個(gè)并發(fā)進(jìn)程實(shí)現(xiàn)。例如在沒有深度復(fù)制的情況下沿通道發(fā)送指針,本質(zhì)上可變特性引發(fā)了競(jìng)爭(zhēng)形勢(shì)。通道可能會(huì)改進(jìn)并發(fā)編程,但確實(shí)存在競(jìng)爭(zhēng)風(fēng)險(xiǎn),這種情況channel無能為力。
然而,Go CLI中內(nèi)置了一個(gè)競(jìng)態(tài)檢測(cè)器來幫助檢測(cè)競(jìng)態(tài)條件。
錯(cuò)誤檢查
錯(cuò)誤檢查非常明確,沒有try…catch語(yǔ)句。在處理錯(cuò)誤時(shí),必須改變?cè)蟹椒ê退季S方式,尤其是已習(xí)慣于其他語(yǔ)言的處理方式。Go開發(fā)團(tuán)隊(duì)認(rèn)為,減少異常可以防止代碼復(fù)雜化和返回值重載。這與其簡(jiǎn)潔性需求一致。但是,在真正異常的情況下,可使用panic和recover來處理異常并進(jìn)行恢復(fù)。Go還有一個(gè)標(biāo)準(zhǔn)的error接口類型,它返回一個(gè)帶有error()的錯(cuò)誤字符串。
Go開發(fā)人員使用多值返回檢查錯(cuò)誤值來處理錯(cuò)誤??梢詮念A(yù)設(shè)產(chǎn)生錯(cuò)誤的函數(shù)中返回錯(cuò)誤。通常用if err != nil來從代碼庫(kù)中識(shí)別錯(cuò)誤。
對(duì)某些問題而言太簡(jiǎn)單
Go語(yǔ)言的簡(jiǎn)潔性是有代價(jià)的。Go不如JavaScript富有表現(xiàn)力。沒有默認(rèn)值。缺少抽象和范式使得實(shí)現(xiàn)DRY原理更加困難、復(fù)雜,不直觀。
值得注意的一點(diǎn)是Go還很年輕。開發(fā)團(tuán)隊(duì)正在考慮使用范型,隨著Go的成熟還有很大改進(jìn)的空間。該團(tuán)隊(duì)非常努力地不斷開發(fā)和改進(jìn)Go。和任何一種語(yǔ)言一樣,Go也有其長(zhǎng)處和短處。可以確定的是,如果足夠多的Gophers(Go程序員)覺得需要某種功能,該功能將得以實(shí)現(xiàn)。
盡管看上去某些功能缺失了,但換個(gè)角度看待可以了解到如何在Go中實(shí)現(xiàn)看似缺少的功能。
通常可以通過不同的方法來實(shí)現(xiàn)同一件事,即更加友好的Go方法。
何時(shí)使用
可以說在當(dāng)前階段,Go并不能解決所有問題;特別是與需要大量抽象的GUI和復(fù)雜系統(tǒng)相關(guān)時(shí)。
但是,又有哪種語(yǔ)言可以解決所有問題呢?
利用Go的優(yōu)勢(shì)。如果覺得該語(yǔ)言過于簡(jiǎn)單,并且很難以一種簡(jiǎn)明的方式增加復(fù)雜性,則可以用Go來構(gòu)建簡(jiǎn)單的微服務(wù)而不是復(fù)雜系統(tǒng)。將Go作為構(gòu)建網(wǎng)絡(luò)和系統(tǒng)工具,而不是替代一種更適合當(dāng)前任務(wù)的語(yǔ)言。
因此最重要的是使用正確的工具完成工作。如果這個(gè)工具是Go,那么Go應(yīng)擅長(zhǎng)解決該問題。
切記不要張冠李戴,病急亂投醫(yī)。
Go作為一種開源編程語(yǔ)言,可輕松構(gòu)建簡(jiǎn)單,可靠和高效的軟件。

















