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