重慶分公司,新征程啟航
為企業(yè)提供網(wǎng)站建設、域名注冊、服務器等服務
為企業(yè)提供網(wǎng)站建設、域名注冊、服務器等服務
英文原文鏈接【Go, the unwritten parts】 發(fā)表于2017/05/22 作者JBD是Go語言開發(fā)小組成員
創(chuàng)新互聯(lián)公司網(wǎng)站建設公司,提供網(wǎng)站建設、成都做網(wǎng)站,網(wǎng)頁設計,建網(wǎng)站,PHP網(wǎng)站建設等專業(yè)做網(wǎng)站服務;可快速的進行網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,是專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!
檢查程序的執(zhí)行路徑和當前狀態(tài)是非常有用的調試手段。核心文件(core file)包含了一個運行進程的內存轉儲和狀態(tài)。它主要是用來作為事后調試程序用的。它也可以被用來查看一個運行中的程序的狀態(tài)。這兩個使用場景使調試文件轉儲成為一個非常好的診斷手段。我們可以用這個方法來做事后診斷和分析線上的服務(production services)。
在這篇文章中,我們將用一個簡單的hello world網(wǎng)站服務作為例子。在現(xiàn)實中,我們的程序很容易就會變得很復雜。分析核心轉儲給我們提供了一個機會去重構程序的狀態(tài)并且查看只有在某些條件/環(huán)境下才能重現(xiàn)的案例。
作者注 : 這個調試流程只在Linux上可行。我不是很確定它是否在其它Unixs系統(tǒng)上工作。macOS對此還不支持。Windows現(xiàn)在也不支持。
在我們開始前,需要確保核心轉儲的ulimit設置在合適的范圍。它的缺省值是0,意味著最大的核心文件大小是0。我通常在我的開發(fā)機器上將它設置成unlimited。使用以下命令:
接下來,你需要在你的機器上安裝 delve 。
下面我們使用的 main.go 文件。它注冊了一個簡單的請求處理函數(shù)(handler)然后啟動了HTTP服務。
讓我們編譯并生產(chǎn)二進制文件。
現(xiàn)在讓我們假設,這個服務器出了些問題,但是我們并不是很確定問題的根源。你可能已經(jīng)在程序里加了很多輔助信息,但還是無法從這些調試信息中找出線索。通常在這種情況下,當前進程的快照會非常有用。我們可以用這個快照深入查看程序的當前狀態(tài)。
有幾個方式來獲取核心文件。你可能已經(jīng)熟悉了奔潰轉儲(crash dumps)。它們是在一個程序奔潰的時候寫入磁盤的核心轉儲。Go語言在缺省設置下不會生產(chǎn)奔潰轉儲。但是當你把 GOTRACEBACK 環(huán)境變量設置成“crash”,你就可以用 Ctrl+backslash 才觸發(fā)奔潰轉儲。如下圖所示:
上面的操作會使程序終止,將堆棧跟蹤(stack trace)打印出來,并把核心轉儲文件寫入磁盤。
另外個方法可以從一個運行的程序獲得核心轉儲而不需要終止相應的進程。 gcore 可以生產(chǎn)核心文件而無需使運行中的程序退出。
根據(jù)上面的操作,我們獲得了轉儲而沒有終止對應的進程。下一步就是把核心文件加載進delve并開始分析。
差不多就這些。delve的常用操作都可以使用。你可以backtrace,list,查看變量等等。有些功能不可用因為我們使用的核心轉儲是一個快照而不是正在運行的進程。但是程序執(zhí)行路徑和狀態(tài)全部可以訪問。
可以去DELVE官網(wǎng)進行下載。
關于delve工具的介紹,這里簡單給大家介紹一下。
delve在go項目及應用的開發(fā)中可以用來追蹤程序中的異常代碼,也可以通過打日志的方式追查問題,但是更重要也是非常厲害的一點,就是delve可以直接分析程序執(zhí)行的情況。這一點在后期或線上的問題排查中無疑是提供了一個非常大的便捷。
Go(又稱?Golang)是?Google?的 Robert Griesemer,Rob Pike 及 Ken Thompson 開發(fā)的一種靜態(tài)強類型、編譯型語言。
Go 語言語法與?C?相近,但功能上有:內存安全,GC(垃圾回收),結構形態(tài)及 CSP-style?并發(fā)計算。
Go的語法接近C語言,但對于變量的聲明有所不同。Go支持垃圾回收功能。Go的并行模型是以東尼·霍爾的通信順序進程(CSP)為基礎。
采取類似模型的其他語言包括Occam和Limbo,但它也具有Pi運算的特征,比如通道傳輸。在1.8版本中開放插件(Plugin)的支持,這意味著現(xiàn)在能從Go中動態(tài)加載部分函數(shù)。
Delve常用命令
命令功能:
dlv attach后面跟 pid,用來Debug編譯好的Golang程序。
dlv core用于 coredump。
dlv debug后面跟要調試的 go 文件,進入 Debug。
dlv testDebug test 函數(shù)。
云和安全管理服務專家新鈦云服 張春翻譯
這種方法有幾個缺點。首先,它可以對程序員隱藏錯誤處理路徑,特別是在捕獲異常不是強制性的情況下,例如在 Python 中。即使在具有必須處理的 Java 風格的檢查異常的語言中,如果在與原始調用不同的級別上處理錯誤,也并不總是很明顯錯誤是從哪里引發(fā)的。
我們都見過長長的代碼塊包裝在一個 try-catch 塊中。在這種情況下,catch 塊實際上充當 goto 語句,這通常被認為是有害的(奇怪的是,C 中的關鍵字被認為可以接受的少數(shù)用例之一是錯誤后清理,因為該語言沒有 Golang- 樣式延遲語句)。
如果你確實從源頭捕獲異常,你會得到一個不太優(yōu)雅的 Go 錯誤模式版本。這可能會解決混淆代碼的問題,但會遇到另一個問題:性能。在諸如 Java 之類的語言中,拋出異常可能比函數(shù)的常規(guī)返回慢數(shù)百倍。
Java 中最大的性能成本是由打印異常的堆棧跟蹤造成的,這是昂貴的,因為運行的程序必須檢查編譯它的源代碼 。僅僅進入一個 try 塊也不是空閑的,因為需要保存 CPU 內存寄存器的先前狀態(tài),因為它們可能需要在拋出異常的情況下恢復。
如果您將異常視為通常不會發(fā)生的異常情況,那么異常的缺點并不重要。這可能是傳統(tǒng)的單體應用程序的情況,其中大部分代碼庫不必進行網(wǎng)絡調用——一個操作格式良好的數(shù)據(jù)的函數(shù)不太可能遇到錯誤(除了錯誤的情況)。一旦您在代碼中添加 I/O,無錯誤代碼的夢想就會破滅:您可以忽略錯誤,但不能假裝它們不存在!
try {
doSometing()
} catch (IOException e) {
// ignore it
}
與大多數(shù)其他編程語言不同,Golang 接受錯誤是不可避免的。 如果在單體架構時代還不是這樣,那么在今天的模塊化后端服務中,服務通常和外部 API 調用、數(shù)據(jù)庫讀取和寫入以及與其他服務通信 。
以上所有方法都可能失敗,解析或驗證從它們接收到的數(shù)據(jù)(通常在無模式 JSON 中)也可能失敗。Golang 使可以從這些調用返回的錯誤顯式化,與普通返回值的等級相同。從函數(shù)調用返回多個值的能力支持這一點,這在大多數(shù)語言中通常是不可能的。Golang 的錯誤處理系統(tǒng)不僅僅是一種語言怪癖,它是一種將錯誤視為替代返回值的完全不同的方式!
重復 if err != nil
對 Go 錯誤處理的一個常見批評是被迫重復以下代碼塊:
res, err := doSomething()
if err != nil {
// Handle error
}
對于新用戶來說,這可能會覺得沒用而且浪費行數(shù):在其他語言中需要 3 行的函數(shù)很可能會增長到 12 行 :
這么多行代碼!這么低效!如果您認為上述內容不優(yōu)雅或浪費代碼,您可能忽略了我們檢查代碼中的錯誤的全部原因:我們需要能夠以不同的方式處理它們!對 API 或數(shù)據(jù)庫的調用可能會被重試。
有時事件的順序很重要:調用外部 API 之前發(fā)生的錯誤可能不是什么大問題(因為數(shù)據(jù)從未通過發(fā)送),而 API 調用和寫入本地數(shù)據(jù)庫之間的錯誤可能需要立即注意,因為 這可能意味著系統(tǒng)最終處于不一致的狀態(tài)。即使我們只想將錯誤傳播給調用者,我們也可能希望用失敗的解釋來包裝它們,或者為每個錯誤返回一個自定義錯誤類型。
并非所有錯誤都是相同的,并且向調用者返回適當?shù)腻e誤是 API 設計的重要部分,無論是對于內部包還是 REST API 。
不必擔心在你的代碼中重復 if err != nil ——這就是 Go 中的代碼應該看起來的樣子。
自定義錯誤類型和錯誤包裝
從導出的方法返回錯誤時,請考慮指定自定義錯誤類型,而不是單獨使用錯誤字符串。字符串在意外代碼中是可以的,但在導出的函數(shù)中,它們成為函數(shù)公共 API 的一部分。更改錯誤字符串將是一項重大更改——如果沒有明確的錯誤類型,需要檢查返回錯誤類型的單元測試將不得不依賴原始字符串值!事實上,基于字符串的錯誤也使得在私有方法中測試不同的錯誤案例變得困難,因此您也應該考慮在包中使用它們。回到錯誤與異常的爭論,返回錯誤也使代碼比拋出異常更容易測試,因為錯誤只是要檢查的返回值。不需要測試框架或在測試中捕獲異常 。
可以在 database/sql 包中找到簡單自定義錯誤類型的一個很好的示例。它定義了一個導出常量列表,表示包可以返回的錯誤類型,最著名的是 sql.ErrNoRows。雖然從 API 設計的角度來看,這種特定的錯誤類型有點問題(您可能會爭辯說 API 應該返回一個空結構而不是錯誤),但任何需要檢查空行的應用程序都可以導入該常量并在代碼中使用它不必擔心錯誤消息本身會改變和破壞代碼。
對于更復雜的錯誤處理,您可以通過實現(xiàn)返回錯誤字符串的 Error() 方法來定義自定義錯誤類型。自定義錯誤可以包括元數(shù)據(jù),例如錯誤代碼或原始請求參數(shù)。如果您想表示錯誤類別,它們很有用。DigitalOcean 的本教程展示了如何使用自定義錯誤類型來表示可以重試的一類臨時錯誤。
通常,錯誤會通過將低級錯誤與更高級別的解釋包裝起來,從而在程序的調用堆棧中傳播。例如,數(shù)據(jù)庫錯誤可能會以下列格式記錄在 API 調用處理程序中:調用 CreateUser 端點時出錯:查詢數(shù)據(jù)庫時出錯:pq:檢測到死鎖。這很有用,因為它可以幫助我們跟蹤錯誤在系統(tǒng)中傳播的過程,向我們展示根本原因(數(shù)據(jù)庫事務引擎中的死鎖)以及它對更廣泛系統(tǒng)的影響(調用者無法創(chuàng)建新用戶)。
自 Go 1.13 以來,此模式具有特殊的語言支持,并帶有錯誤包裝。通過在創(chuàng)建字符串錯誤時使用 %w 動詞,可以使用 Unwrap() 方法訪問底層錯誤。除了比較錯誤相等性的函數(shù) errors.Is() 和 errors.As() 外,程序還可以獲取包裝錯誤的原始類型或標識。這在某些情況下可能很有用,盡管我認為在確定如何處理所述錯誤時最好使用頂級錯誤的類型。
Panics
不要 panic()!長時間運行的應用程序應該優(yōu)雅地處理錯誤而不是panic。即使在無法恢復的情況下(例如在啟動時驗證配置),最好記錄一個錯誤并優(yōu)雅地退出。panic比錯誤消息更難診斷,并且可能會跳過被推遲的重要關閉代碼。
Logging
我還想簡要介紹一下日志記錄,因為它是處理錯誤的關鍵部分。通常你能做的最好的事情就是記錄收到的錯誤并繼續(xù)下一個請求。
除非您正在構建簡單的命令行工具或個人項目,否則您的應用程序應該使用結構化的日志庫,該庫可以為日志添加時間戳,并提供對日志級別的控制。最后一部分特別重要,因為它將允許您突出顯示應用程序記錄的所有錯誤和警告。通過幫助將它們與信息級日志分開,這將為您節(jié)省無數(shù)時間。
微服務架構還應該在日志行中包含服務的名稱以及機器實例的名稱。默認情況下記錄這些時,程序代碼不必擔心包含它們。您也可以在日志的結構化部分中記錄其他字段,例如收到的錯誤(如果您不想將其嵌入日志消息本身)或有問題的請求或響應。只需確保您的日志沒有泄露任何敏感數(shù)據(jù),例如密碼、API 密鑰或用戶的個人數(shù)據(jù)!
對于日志庫,我過去使用過 logrus 和 zerolog,但您也可以選擇其他結構化日志庫。如果您想了解更多信息,互聯(lián)網(wǎng)上有許多關于如何使用這些的指南。如果您將應用程序部署到云中,您可能需要日志庫上的適配器來根據(jù)您的云平臺的日志 API 格式化日志 - 沒有它,云平臺可能無法檢測到日志級別等某些功能。
如果您在應用程序中使用調試級別日志(默認情況下通常不記錄),請確保您的應用程序可以輕松更改日志級別,而無需更改代碼。更改日志級別還可以暫時使信息級別甚至警告級別的日志靜音,以防它們突然變得過于嘈雜并開始淹沒錯誤。您可以使用在啟動時檢查以設置日志級別的環(huán)境變量來實現(xiàn)這一點。
原文:
本教程介紹了 Go 中模糊測試的基礎知識。通過模糊測試,隨機數(shù)據(jù)會針對您的測試運行,以嘗試找出漏洞或導致崩潰的輸入。可以通過模糊測試發(fā)現(xiàn)的一些漏洞示例包括 SQL 注入、緩沖區(qū)溢出、拒絕服務和跨站點腳本攻擊。
在本教程中,您將為一個簡單的函數(shù)編寫一個模糊測試,運行 go 命令,并調試和修復代碼中的問題。
首先,為您要編寫的代碼創(chuàng)建一個文件夾。
1、打開命令提示符并切換到您的主目錄。
在 Linux 或 Mac 上:
在 Windows 上:
2、在命令提示符下,為您的代碼創(chuàng)建一個名為 fuzz 的目錄。
3、創(chuàng)建一個模塊來保存您的代碼。
運行go mod init命令,為其提供新代碼的模塊路徑。
接下來,您將添加一些簡單的代碼來反轉字符串,稍后我們將對其進行模糊測試。
在此步驟中,您將添加一個函數(shù)來反轉字符串。
a.使用您的文本編輯器,在 fuzz 目錄中創(chuàng)建一個名為 main.go 的文件。
獨立程序(與庫相反)始終位于 package 中main。
此函數(shù)將接受string,使用byte進行循環(huán) ,并在最后返回反轉的字符串。
此函數(shù)將運行一些Reverse操作,然后將輸出打印到命令行。這有助于查看運行中的代碼,并可能有助于調試。
e.該main函數(shù)使用 fmt 包,因此您需要導入它。
第一行代碼應如下所示:
從包含 main.go 的目錄中的命令行,運行代碼。
可以看到原來的字符串,反轉它的結果,然后再反轉它的結果,就相當于原來的了。
現(xiàn)在代碼正在運行,是時候測試它了。
在這一步中,您將為Reverse函數(shù)編寫一個基本的單元測試。
a.使用您的文本編輯器,在 fuzz 目錄中創(chuàng)建一個名為 reverse_test.go 的文件。
b.將以下代碼粘貼到 reverse_test.go 中。
這個簡單的測試將斷言列出的輸入字符串將被正確反轉。
使用運行單元測試go test
接下來,您將單元測試更改為模糊測試。
單元測試有局限性,即每個輸入都必須由開發(fā)人員添加到測試中。模糊測試的一個好處是它可以為您的代碼提供輸入,并且可以識別您提出的測試用例沒有達到的邊緣用例。
在本節(jié)中,您將單元測試轉換為模糊測試,這樣您就可以用更少的工作生成更多的輸入!
請注意,您可以將單元測試、基準測試和模糊測試保存在同一個 *_test.go 文件中,但對于本示例,您將單元測試轉換為模糊測試。
在您的文本編輯器中,將 reverse_test.go 中的單元測試替換為以下模糊測試。
Fuzzing 也有一些限制。在您的單元測試中,您可以預測Reverse函數(shù)的預期輸出,并驗證實際輸出是否滿足這些預期。
例如,在測試用例Reverse("Hello, world")中,單元測試將返回指定為"dlrow ,olleH".
模糊測試時,您無法預測預期輸出,因為您無法控制輸入。
但是,Reverse您可以在模糊測試中驗證函數(shù)的一些屬性。在這個模糊測試中檢查的兩個屬性是:
(1)將字符串反轉兩次保留原始值
(2)反轉的字符串將其狀態(tài)保留為有效的 UTF-8。
注意單元測試和模糊測試之間的語法差異:
(3)確保新包unicode/utf8已導入。
隨著單元測試轉換為模糊測試,是時候再次運行測試了。
a.在不進行模糊測試的情況下運行模糊測試,以確保種子輸入通過。
如果您在該文件中有其他測試,您也可以運行go test -run=FuzzReverse,并且您只想運行模糊測試。
b.運行FuzzReverse模糊測試,查看是否有任何隨機生成的字符串輸入會導致失敗。這是使用go test新標志-fuzz執(zhí)行的。
模糊測試時發(fā)生故障,導致問題的輸入被寫入將在下次運行的種子語料庫文件中go test,即使沒有-fuzz標志也是如此。要查看導致失敗的輸入,請在文本編輯器中打開寫入 testdata/fuzz/FuzzReverse 目錄的語料庫文件。您的種子語料庫文件可能包含不同的字符串,但格式相同。
語料庫文件的第一行表示編碼版本。以下每一行代表構成語料庫條目的每種類型的值。由于 fuzz target 只需要 1 個輸入,因此版本之后只有 1 個值。
c.運行沒有-fuzz標志的go test; 新的失敗種子語料庫條目將被使用:
由于我們的測試失敗,是時候調試了。
具體步驟:
1、去官網(wǎng)下載go1.1.2的tarball,一般下載到tem目錄
2、打開終端cd /usr/local, tar -zxvf go1.1.2.linux-386.tar.gz
將源碼文件解壓縮到/usr/local目錄,如果解壓到其他目錄,需要自己設置GOROOT
3、安裝gcc工具,因為golang有些功能是使用c寫
sudo apt-get install bison gawk gcc libc6-dev make
4、$ cd go/src,$ ./all.bash
運行bash腳本,如果運行正常會獲得你的操作系統(tǒng)和cpu信息,自動編譯安裝
5、將export PATH=$PATH:/usr/local/go/bin 寫入$HOME/.profile
最后進行測試輸入go version 會顯示go1.1.2 linux/386
GO是編譯性語言,所以函數(shù)的順序是無關緊要的,為了方便閱讀,建議入口函數(shù) main 寫在最前面,其余函數(shù)按照功能需要進行排列
GO的函數(shù) 不支持嵌套,重載和默認參數(shù)
GO的函數(shù) 支持 無需聲明變量,可變長度,多返回值,匿名,閉包等
GO的函數(shù)用 func 來聲明,且左大括號 { 不能另起一行
一個簡單的示例:
輸出為:
參數(shù):可以傳0個或多個值來供自己用
返回:通過用 return 來進行返回
輸出為:
上面就是一個典型的多參數(shù)傳遞與多返回值
對例子的說明:
按值傳遞:是對某個變量進行復制,不能更改原變量的值
引用傳遞:相當于按指針傳遞,可以同時改變原來的值,并且消耗的內存會更少,只有4或8個字節(jié)的消耗
在上例中,返回值 (d int, e int, f int) { 是進行了命名,如果不想命名可以寫成 (int,int,int){ ,返回的結果都是一樣的,但要注意:
當返回了多個值,我們某些變量不想要,或實際用不到,我們可以使用 _ 來補位,例如上例的返回我們可以寫成 d,_,f := test(a,b,c) ,我們不想要中間的返回值,可以以這種形式來舍棄掉
在參數(shù)后面以 變量 ... type 這種形式的,我們就要以判斷出這是一個可變長度的參數(shù)
輸出為:
在上例中, strs ...string 中, strs 的實際值是b,c,d,e,這就是一個最簡單的傳遞可變長度的參數(shù)的例子,更多一些演變的形式,都非常類似
在GO中 defer 關鍵字非常重要,相當于面相對像中的析構函數(shù),也就是在某個函數(shù)執(zhí)行完成后,GO會自動這個;
如果在多層循環(huán)中函數(shù)里,都定義了 defer ,那么它的執(zhí)行順序是先進后出;
當某個函數(shù)出現(xiàn)嚴重錯誤時, defer 也會被調用
輸出為
這是一個最簡單的測試了,當然還有更復雜的調用,比如調試程序時,判斷是哪個函數(shù)出了問題,完全可以根據(jù) defer 打印出來的內容來進行判斷,非常快速,這種留給你們去實現(xiàn)
一個函數(shù)在函數(shù)體內自己調用自己我們稱之為遞歸函數(shù),在做遞歸調用時,經(jīng)常會將內存給占滿,這是非常要注意的,常用的比如,快速排序就是用的遞歸調用
本篇重點介紹了GO函數(shù)(func)的聲明與使用,下一篇將介紹GO的結構 struct