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