假如你是一名建築工,也許你會需要鋼筋、水泥、板模、磁磚,還有各式各樣的建築材料。這些材料總是從特定的廠商買回來,而不是由你自己來製造的。平常寫程式的時候也是一樣,只要遇上了稍微複雜一點的需求,我們通常都不會希望所有的環節都僅僅憑著一己之力從頭做起。
比如說,我們想要用 Golang 寫一個讀取網頁內容的工具(網路爬蟲),那麼我們除了向目標網址送出 HTTP request、接收對方回傳的 response 以外,我們還會需要專門用來解析 HTML 格式內容的工具,才能快速地拆解並得到我們需要的內容。這種時候我們就可以引用網路上各個熱心的神人製作並提供的套件,也就是所謂的「第三方函式庫」。
不過畢竟網路爬蟲會花費比較多時間,有機會再專門寫一篇。今天就先來講講 Golang 的 package 機制,然後弄個簡單的小程式來讀取電腦裡現有圖片的 QR code 吧。
? Golang package
在 Golang,我們在撰寫任何程式碼之前都必須事先規定好它們所屬的 package(包)。上次示範 Hello, world! 的時候,你有沒有注意到程式碼的第一行寫著「package main」呢?沒有錯,它的意義就代表了這個檔案的程式碼屬於「main」這個包。
首先建立一個簡單的專案吧!隨意新增一個資料夾,用 VS Code 打開資料夾,並且新增兩個檔案:
main.go
my_math.go
接著簡單寫個把兩數相加的函式,試著執行:
可以從這邊觀察到,即便它們被寫在不同檔案裡,只要是屬於同一個包的函式,我們仍然可以直接引用。換句話說,我們可以為求乾淨而把同一個 package 的東西分類成好幾個不同的部分,分別寫在不同的檔案裡面,只要確保它們屬於同一個包就可以了。
由於 Golang 本身規定,當我們在編譯、執行程式碼的時候會找 main 包裡面的 main 函式來執行,所以我們這種小專案裡面一定都是先定義 package main,至於 package 的其他用法,我們就留到以後再慢慢講。
? 建立 go module
剛才隨手寫的 my_math.go 可以砍了。接下來我們使用 go module 的 init 功能來替這個專案建立第三方函式庫的引用列表,執行指令:
go mod init go_example
你會注意到系統幫你產生了 go.mod 檔案,負責記錄你引用了哪些第三方的 package(以及它們本身的版本)。這裡的 go_example 你可以粗略地理解成專案的名稱,以現在而言這個名字不算重要,想取什麼都可以。建立好 go module 以後,我們就可以下載別人做的 package 來用了。
執行指令:
go get "github.com/makiuchi-d/gozxing"
我們就成功從 makiuchi-d 這個網友的 GitHub 下載了他提供的 QR code 函式庫。你通常可以在 GOPATH 底下的 pkg\mod\ 或者是 GOPATH 底下的 src 資料夾找到你剛利用 go get 從別人的遠端儲存庫下載下來的 package 原始碼內容。
? 照著說明文件做
下一步就是直接到他的 repository 頁面:
照著作者提供的說明文件或範例做做看:
func main() {
// open and decode image file
file, _ := os.Open("qrcode.jpg")
img, _, _ := image.Decode(file)
// prepare BinaryBitmap
bmp, _ := gozxing.NewBinaryBitmapFromImage(img)
// decode image
qrReader := qrcode.NewQRCodeReader()
result, _ := qrReader.Decode(bmp, nil)
fmt.Println(result)
}
這裡要注意,go module 管理的是「整個專案」所用到的第三方 package,而我們除了把 package 利用 go get 安裝、整理進去以外,還要在程式原始碼檔案裡再經過一步 import,才能正式地被引用。
你的開發環境偶爾笨一點,它也許沒辦法辨認程式碼裡面的 gozxing 和 qrcode 是分別來自於:
github.com/makiuchi-d/gozxing
github.com/makiuchi-d/gozxing/qrcode
這導致它不會在你 Ctrl+S 的時候自動把引用到的函式庫完全加到 main.go 的 import 列表裡,我們就需要手動添加上去:
package main
import (
"fmt"
"image"
"os"
_ "image/jpeg"
"github.com/makiuchi-d/gozxing"
"github.com/makiuchi-d/gozxing/qrcode"
)
func main() {
// open and decode image file
file, _ := os.Open("qrcode.jpg")
img, _, _ := image.Decode(file)
// prepare BinaryBitmap
bmp, _ := gozxing.NewBinaryBitmapFromImage(img)
// decode image
qrReader := qrcode.NewQRCodeReader()
result, _ := qrReader.Decode(bmp, nil)
fmt.Println(result)
}
除此之外,使用任何函式庫的時候,也要稍微瞭解一下函式庫本身的特性,才能避免踩到不必要的坑。像是 Golang 官方提供的 image 包本身只是一個圖片解析的框架,我們需要再利用 image/jpeg 來讓程式有能力去解析 jpeg 格式(即 jpg 檔案)的圖片。
當我們 import 函式庫的時候,如果 package 名稱前面加上底線,代表我們只是想執行這個 package 裡面的 init() 函數而已,沒有要完全引入。以 image/jpeg 本身的性質來說,我們只要讓程式執行過它裡面的 init() 就可以讓 image 的包有能力去解析 jpg 圖片了,如果有需要用到 image/jpeg 裡其他的功能,才需要把底線拿掉。
如果這個情境下沒有加上底線,而你又沒有真的用到 image/jpeg 裡的其他函式,那系統就有可能會在 Ctrl+S 的時候誤認為你沒有要使用這個 package 而把它從 import 列表拿掉,所以底線在這個範例裡是必要的。如果仍然沒能搞懂的話,先照著別人的範例,跟著做就對了。
? 實際執行
我們先隨便準備一個 jpeg 格式的 QR code:
儲存下來取名為 qrcode.jpg,放到專案的資料夾裡面,接著執行:
我們就成功地利用第三方函式庫把一個 QR code 圖片裡的內容解析出來了。
? Go module 小技巧
有的時候我們可能不是像這樣一步一步來新增,而是直接複製別人現有的程式碼。
當我們使用 go mod init 建立第三方函式庫依賴列表之後,只要再接著執行指令:
go mod tidy
Go module 工具就會自動幫你把專案資料夾底下把程式碼需要用到但還沒 get 的包通通都 get 進來,並且把目前函式庫列表裡多餘的包都拿掉,替你省掉很多步。假如你的開發環境告訴你 go.mod 有問題,tidy 就對了。
? 碎碎唸
其實早期的 Golang 版本,官方根本沒有提供像 go module 套件管理的功能,這導致我們當時還得用 govendor 或是 godep 來管理專案引用的 package 列表。我記得那時候用 govender 遇上的坑還一堆,至於是什麼樣的問題我則是連想都不敢回想了,對我這種菜雞來說要排除那些問題簡直是地獄。在 go module 逐漸成熟之後,起碼使用體驗比以前還要好上許多,不必再搞一堆麻煩的參數。
Golang 的社群還算活躍,只要不是太冷門的功能,應該不太容易有遇上問題卻求助無門的情形。基本上只要有了 go module 的基本概念,那麼下一步大概就只剩下怎麼把 code 寫得漂亮了。
HackMD 好讀版:https://hackmd.io/@upk1997/go-qrcode
縮圖素材原作者:Renée French(CC BY-SA 3.0)