位元詩人 [技術雜談] 從 Go 語言 (Golang) 來看程式設計的精簡哲學

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

許多程式語言以豐富的語法特性和表達力著稱,但是也有像 Go 語言 (golang) 反其道而行,抱著少就是多 (less is more) 的精簡哲學。本文從一些 Go 語言的設計來看如何實踐精簡 (simplicity) 哲學。

使用內隱知識來簡化專案

在程式碼專案中,Go 語言的專案是數一數二簡單的,因為 Go 專案不使用專案設定檔,直接使用專案資料夾的資訊作為專案的內隱設定;像是 Go 應用程式專案編譯時預設會直接以目錄名稱作為執行檔的名稱。

引入專案時也是以目錄作為命名空間的一部分,如下例:

import "github.com/gotk3/gotk3/gtk"

這個專案託管在 github.com 上,使用者是 gotk3 ,專案根目錄是 gotk3 ,實際的函式庫位於 gtk 子目錄中。

我們在建立有一定規模的程式時,本來就會將專案集中在同一個目錄下。Go 語言重複使用這項內隱規則,讓專案設定精簡化。

重複使用關鍵字

不同迴圈 (loop) 使用不同的迭代方式,可以用計數器 (counter)、條件句 (conditional)、迭代器 (iterator) 等,像 Ruby 用 for 表示計數器迴圈,while 表示條件句迴圈,each 表示迭代器迴圈,使用了三個不同的語法。

註:each 在 Ruby 中是方法 (method)。

但在 Go 語言中,重複使用 for 表達不同的概念。像是以下的迴圈使用計數器:

for i := 0; i < 10; i++ {
    // Repeat something here.
}

以下的迴圈使用條件句:

// `isDone` is a bool.

for !isDone {
    // Repeat something here.
}

以下的迴圈使用迭代器:

// `arr` is an array.

for _, e := range arr {
    // Repeat something here.
}

這算不算 Go 語言的優點呢?其實是有爭議性的。喜歡這項特性的程式人會覺得這樣的程式碼很精簡,但也有些程式人覺得用不同的語法比較能區分意圖。

只給予必要的功能

C 或 C++ 的前置處理器 (preprocessor) 其實本身是一個小型巨集語言,可以用來做很多事,像是引入函式庫的頭文字檔 (header)、宣告和呼叫巨集 (macro)、條件編譯等,甚至有程式人用前置處理器模擬泛型 (generics) (參考這裡) 和程式語法 (像這個模擬 try ... catch ... 的例子)。

但前置處理器本質上是字串代換,既沒有型別安全,更難以除錯。Go 語言沒有放入巨集這類的特性,而是利用 build constraint 來滿足條件編譯的需求,這就是一個少即是多的實例。

善用現有的線上資源

當初 Perl 或 Python 在發展時,還沒有 GitHub 或 Bitbucket 這類程式碼專案托管網站,所以會架設網站以集中托管套件。在 Go 問世時,GitHub 等網站已經相當普遍了,所以 Go 語言沒有重造輪子,而直接利用這些現成的網站,以分散式的策略存放在現有的資源上,Go 工具再直接從這些網站直接存取 Go 套件。

不過,這個模式並非完美無缺,像是對 Go 套件的開發過於理想化,忽略了對第三方套件進行版本控制的需求。近年來陸續出現數個管理 Go 套件的專案,像是 glidegovendordep 等;最近又出現 Go module 等用於管理套件的新特性。目前這個問題尚未完全解決。

大神也會犯錯

Go 語言是由 Ken ThompsonRob Pike 等大神級人物所設計,不過,Go 語言有時精簡過頭了。以下是一個排序 (sorting) 的例子 (摘自 Go by Example):

package main

import "sort"
import "fmt"

type byLength []string

func (s byLength) Len() int {
    return len(s)
}
func (s byLength) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}
func (s byLength) Less(i, j int) bool {
    return len(s[i]) < len(s[j])
}

func main() {
    fruits := []string{"peach", "banana", "kiwi"}
    sort.Sort(byLength(fruits))
    fmt.Println(fruits)
}

在這裡,我們需要實作 Len()Swap()Less() 三個函式,以滿足 sort.Sort 的介面;但對於有泛型 (generics) 的語言來說,這個問題可用更簡單的語法來完成。這個例子在概念上精簡,但在語法上卻冗餘。

結語

由 Go 語言的特性,我們可以看到 KISS (keep it simple, stupid) 原則的實踐;當然,沒有完美無缺的程式語言,Go 語言在某些地方的確有問題。精簡的軟體相對會易於使用和推廣,但在精簡的大原則下仍能保持充足的功能則需要在設計上權衡一番。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。