Michelle Chen [Golang] 程式設計教學:基本概念

Golang
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

在本文中,我們假定讀者沒寫過 golang 程式或是僅寫過少量 golang 程式。會帶著讀者實際寫簡單的 golang 程式並且執行該程式。在這個過程中,熟悉 golang 程式的撰寫流程。

Golang 程式碼的檔名和副檔名

Golang 程式碼的副檔名為 .go 。檔名則沒有特別限制,只要符合檔案系統的規範即可。

撰寫第一個 Golang 程式。

Hello World 是用來熟悉開發工具的簡短程式,這個經典程式的文化來自於 The C Programming Language 。此處會撰寫 Go 語言的版本。

打開編輯器,新增 hello.go 檔案,加入以下內容:

package main

import "fmt"

func main() {
    fmt.Println("Hello World")
}

我們現在不急著了解這個程式的內容,重點在於快速建立一個可執行的程式碼。後文會再說明這個程式的內容。

對於簡短的 Go 程式碼,可用 go run 指令直接執行,如以下指令:

$ go run hello.go
Hello World

使用 go run 執行 Go 程式碼,Go 編譯器會偷偷在系統暫存區建立相對應的執行檔;在未修改程式碼時,第二次執行的速度會比較快,因為執行檔已經編譯好了。整個編譯執行的過程是自動的,不需使用者介入;我們可以當成 Go 編譯器可直接執行 Go 程式碼,把 Go 語言當另類腳本語言來用。

也可以先將 Go 程式碼以 go build 指令編譯成執行檔後再執行:

$ go build hello.go
$ ./hello
Hello World

對於簡短的 Golang 程式碼來說,使用單一檔案就足夠了。這時,按照本節的方法執行 Go 程式即可。後文會說明對於多檔案 Go 程式碼的編譯和執行的方式。

Golang 程式碼的組成

本節帶著讀者閱讀一次 Hello World 程式,藉此熟悉 Go 程式的組成。

我們重新看一次 Hello World 程式,這次加上註解 (comments)。程式碼如下:

/* Set the package of a program. */
package main

/* Import `fmt` package for formatted strings. */
import "fmt"

/* The main function */
func main() {
    /* Print out some string to a console. */
    fmt.Println("Hello World")
}

Golang 程式碼的編碼 (Encoding)

Golang 原始碼使用 UTF-8 來儲存。實務上會用英文來撰寫原始碼,因為大部分程式設計者能夠閱讀英文。

註解 (Comments)

Golang 使用兩種註解:

  • 單行註解:在 // 後內的單行文字視為註解。承襲自 C++
  • 多行註解:使用一對 /**/ 包住的文字視為註解,可跨越多行。承襲自 C

註解不會計入真正的程式碼,所以用一般文字敘述來撰寫即可。一般來說,註解是用來說明某段程式碼的意圖。像是程式設計師在寫了某段比較不直覺的程式碼,會用註解來補充說明為什麼要這樣寫,以免團隊成員或未來的自己忘了為什麼當初要這樣寫。

在教學用的程式碼中,註解是在不影響程式運作的前提下,說明程式的用途等。像是讀者可以把本範例程式碼直接貼到 Go Playground 中,該範例程式仍可順利執行,如同一般旳 Hello World 程式。

設置套件 (Package) 名稱

每個 Go 程式都要在一開始時設置套件 (package) 名稱,透過 package 這個保留字 (keyword) 來設置套件名稱。

套件名稱實際上是命名空間 (namespace) 的概念,其用意在於避免套件間名稱的衝突。主流的程式語言不會只有內建的功能,一定會保留加入第三方套件或模組的能力,讓全世界的開發者都可以為該語言貢獻新的套件。透過這樣的方式,該語言的社群才會逐漸壯大。 package 則是 Go 語言中用來處理命名空間的保留字。

若該 Go 程式碼是應用程式 (application),套件名稱一律用 main。若該 Go 程式碼是函式庫 (library),則由套件開發者自行為套件命名。目前我們的目標放在撰寫可以自用的小型程式,故套件名稱皆使用 main 即可。

引入 (import) 套件

我們在撰寫電腦程式時,不會所有的功能都自行從頭實作,而會藉由引入他人已經實作好的套件來節省撰寫程式的時間。標準函式庫 (standard library) 是由程式語言的開發團隊預寫好的套件,用意在於提供一些常用的功能。社群函式庫 (community library) 或第三方函式庫 (third-party library) 則是由程式語言的開發團隊以外的開發者所寫好的套件,用意在補充內建語法及標準函式庫不足的功能。

通常我們會優先使用標準函式庫,因為標準函式庫會隨著該語言共生共滅,通常會維護得最長久。對於標準函式庫缺乏的功能或是社群函式庫顯著優於相對應的標準函式庫時,我們才會使用社群函式庫。比起標準函式庫,社群函式庫相對有斷頭的危機。在原開發者不願繼續維護該套件後,若沒有其他開發者願意接手,該套件就變成孤兒套件,即使沒有完全消失,往往也不會再新增功能 (feature) 或修復臭蟲 (bug)。

在 Go 語言中,透過 import 保留字引入其他套件。在本例中,我們引入標準函式庫的 fmt 套件,該套件用於命令列環境的格式化輸出入 (formatted input/output)。

主函式 (Main Function)

主函式 (main function) 是一個特殊的函式,用於應用程式的起始點或進入點。每個 Go 應用程式只會有一個主函式。由於主函式的寫法是固定的,讀者不用擔心自己目前還不會寫函式,就把主函式當成制式的樣板程式碼即可。

一開始程式碼很短時,全部寫在主函式也無妨。隨著程式規模變大,我們會將一些程式碼切出來,這時候我們就會撰寫其他函式。我們將原本的 Hello World 程式改寫如下:

package main

import "fmt"

func main() {
    /* Call the function `sayHello` */
    sayHello()
}

func sayHello() {
    fmt.Println("Hello World")
}

在這個例子中,我們只是把原本寫在主函式的程式碼搬到另一個函式 sayHello 而已,執行起來和原本的 Hello World 程式是等效的。我們會在後文介紹 Go 函式的寫法。

在命令列環境印出文字

fmt 套件用於處理 Go 程式在命令列環境的格式化輸出入,是 Go 語言的標準函式庫之一。由於 Go 語言不支援函式重載 (function overloading),我們會在 Go 套件中看到很多功能相近但不同名稱的函式。不用因函式名稱較多就覺得很難,程式設計師很少在背函式,而是在長期撰寫程式的過程中自然記住常用的函式,對於不常用的函式在需要時再查閱即可。

在本範例程式中,我們使用 fmt 套件的 Println 函式來輸出字串到命令列環境中。我們一起來看一下 fmt 套件的說明。

每個 Go 套件會分為

  • Overview:對該套件整體的說明
  • Index:指向特定函式的超連結
  • Examples:展示套件用法的簡短範例
  • 各函式的說明:個別函式的說明

我們找到 Println 的說明,可以看到 Println 的公開界面如下:

func Println(a ...interface{}) (n int, err error)

由這個公開界面可知,Println 接收任意數量的參數,參數為任意型別 (interface{})。該函式會回傳兩個參數,分別是整數 nerr 物件。n 表示寫入的字串長度而 err 回傳錯誤物件,當程式沒有發生錯誤時,err 為空值。

在我們的範例程式中,我們知道程式不會發生錯誤,而且也不需要知道字串的長度,故我們忽略程式的回傳值。

完全沒寫過 Go 程式的讀者,應該無法馬上閱讀套件的說明文件。每個程式的初學者都是都透過教程和範例來學的,但我們要學著直接閱讀套件的文件,不依賴他人寫好的範例。套件文件算是套件的第一手文件,他人寫的教程和範例只能算是第二手資訊。程式人要培養查閱第一手資料的能力,不應過度依賴二手訊息。

Golang 專案的命名模式

當撰寫由多個 Go 程式碼所組成的 Go 程式時,我們需要將這些 Go 程式碼以專案 (project) 的方式組織起來。專案這個詞聽起來好像很專業,但其實只是以單一資料夾為中心,將 Go 程式碼組織起來的方式而已。

比起其他程式語言的專案,Go 專案算是相對簡單的。因為 Go 專案不需要專案設定檔,完全依靠專案的內隱知識來自動設置專案。例如,由於 Go 語言缺乏真正的泛型 (generics),cheekybits/genny 是一個用程式碼生成 (code generation) 來模擬泛型的命令列工具。這個專案的根目錄為 genny ,編譯出來的命令列程式也稱為 genny。在此處,Go 編譯器利用根目錄的名稱來自動命名編譯出來的命令列工具,故可省略專案設定檔。

在建立函式庫套件時,我們會讓專案目錄名稱和套件名稱一致,套件使用者才不會搞混。像是 julienschmidt/httprouter 這個輕量級的網頁伺服器路徑套件,專案目錄為 httprouter ,套件名稱也是 httprouter

如果專案比較龐大,我們則會將專案拆分成多個子專案。這時,各個子專案可以使用自己的套件名稱,不需要勉強共用同一個套件名稱。gotk3/gotk3 是 GTK+3 的 Go 語言綁定,該專案實際上是由五個子專案所組成,頂層專案並沒有 Go 程式碼,只是用來統合該專案的命名空間。

Golang 專案的架構

在撰寫 Go 專案時,得將 Go 專案放在系統中特定的位置;此外,專案放置的位置會影響套件引入的路徑。這和主流的程式語言略有不同,我們在本節說明 Go 語言的設置方式。

Go 語言內含兩個和專案位置相關的路徑,分別以下列環境變數 (environment variable) 為準:

  • GOROOT:Go 編譯器、Go 相關工具、標準函式庫所在的位置
  • GOPATH:第三方應用程式和函式庫所在的位置

GOROOT 路徑內存放的是 Go 語言內建的工具,我們不應該去動該路徑內的東西,以免造成 Go 開發軟體的毀損。

GOPATH 路徑內存放的第三方應用程式和函式庫。我們自己寫的 Go 程式也可以放在裡面。GOPATH 內再分為三個子目錄:

  • bin :存放執行檔
  • lib :存放二進位函式庫檔
  • src :存放 Go 專案原始碼

binlib 都是存放編譯出來的二進位檔,而 src 則是存放 Go 專案原始碼的實際的目錄。

假定我們寫了一個 Go 程式 myapp,該專案的位置可能是 GOPATH/src/myapp/ ,而編譯出來的執行檔則位於 GOPATH/bin/myapp

專案在 src 內的相對位置會影響引入的路徑。像是 julienschmidt/httprouter 下載在本地端的位置會在 GOPATH/src/github.com/julienschmidt/httprouter/ 中。我們目前沒有要實作函式庫套件,暫時不用關注路徑位置的細節。

在舊版的 Go 軟體中,若沒有設置 GOPATH,在使用 Go 軟體時,可能會引發錯誤。新版的 Go 軟體為了改善這個缺點,會自動設置預設的 GOPATH。像是在 Windows 上的預設 GOPATH 位在 C:\Users\user\go 。可在 Windows 終端機中透過 echo 指令來查詢:

> echo %GOPATH%
C:\Users\user\go

在類 Unix 系統上,預設的 GOPATH 位於 $HOME/go/ 。可在終端機輸入 echo 指令來查詢:

$ echo $GOPATH
/Users/user/go

結語

本文帶著讀者實際撰寫一個簡單的 Go 程式,並編譯及執行該程式。雖然這個 Go 程式相當簡單,請讀者還是要實地做一次,因為我們的目的不是在寫程式,而是在熟悉開發工具。