Categories
程式開發

從Python切換到Go的9個理由


切換到一種新的編程語言通常是一件大事,特別是當團隊成員對原始語言有豐富經驗時。今年年初,Stream將其主要編程語言從Python切換到了Go。本文將會解釋他們決定從Python切換到Go的一些原因。

使用Go的理由

理由1:性能

從Python切換到Go的9個理由 1

Go非常快。它的性能接近Java或C。 Go的速度比Python快30倍。

理由2:語言本身的性能很重要

對於許多應用程序而言,編程語言只是應用程序和數據庫之間的粘合劑。語言本身的性能通常並不重要。

Stream是一家API提供商,它為500家公司和超過2億的最終用戶提供了反饋基礎設施。多年來,我們一直在優化Cassandra、PostgreSQL、Redis等軟件的性能,但是現在我們已經達到了我們所使用編程語言的極限。

Python是一門偉大的語言,但是對於序列化/反序列化、排序和聚合等示例,它的性能非常差。我們經常會遇到性能問題,Cassandra花費1ms的時間來檢索數據,而Python將其轉換成對象則需要10ms的時間。

理由3:開發人員的效率,而無需太多創新

請看下“如何開始學習Go”教程中的如下Go代碼片段。

type openWeatherMap struct{}
func (w openWeatherMap) temperature(city string) (float64, error) {
    resp, err := http.Get("http://api.openweathermap.org/data/2.5/weather?APPID=YOUR_API_KEY&q=" + city)
    if err != nil {
        return 0, err
    }
    defer resp.Body.Close()
    var d struct {
        Main struct {
            Kelvin float64 `json:"temp"`
        } `json:"main"`
    }
    if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
        return 0, err
    }
    log.Printf("openWeatherMap: %s: %.2f", city, d.Main.Kelvin)
    return d.Main.Kelvin, nil
}

如果你剛開始學習Go,閱讀這段代碼不會有太多驚喜。它演示了賦值、數據結構、指針、格式化和內置的HTTP庫。

從我首次接觸編程開始,我總是喜歡使用Python的高級特性。 Python使我們能從正在編寫的代碼中獲得很好的想法。例如,我們可以:

  • 初始化代碼時,使用元類(MetaClasses)自己註冊類
  • 切換“True”和“False”
  • 將一個函數添加到內置函數列表中
  • 通過魔術方法(Magic Method)重載運算符

這些特性非常有趣,但是,大多數程序員都認為這會增加閱讀他人代碼的難度。

Go會迫使我們使用最基本的東西,這使得閱讀他人代碼變得更容易。

注:當然,“容易”取決於具體的項目。如果只是創建一個基本的CRUD API,我仍然建議使用Django&DRF或Rails。

理由4 :並發和通道

作為一門編程語言,Go總是盡可能地保持簡單。它沒有引入太多的新概念,因為它的目標是創建一門易於使用的編程語言。它唯一具有創新性的地方是Goroutines(go 協程)和Channels(通道)。 Goroutines是Go的輕量級線程解決方案,而Channels是與Goss交互的首選方式。

Goroutines非常輕量,僅需要幾千字節的額外內存。而且由於Goroutine如此輕量,因此可以同時運行數百甚至數千個Goroutine。

我們可以使用Channels在Goroutines之間進行通信。 Go運行時處理所有的內部複雜性。基於Goroutines和Channels的並發方案使應用程序能夠輕鬆使用所有可用的CPU內核並處理並發IoO,而無需進行複雜的開發。與Python/Java相比,在Goroutines上運行函數只需要很少的固定代碼。我們只需要使用關鍵字“go”調用函數即可:

package main
import (
    "fmt"
    "time"
)
func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}
func main() {
    go say("world")
    say("hello")
}

https://tour.golang.org/concurrency/1

Go的並發解決方案非常易於使用。與開發人員必須密切關注異步代碼處理方式的Node相比,這是一個非常有趣的方案。

Go並發的另一個關注點是競態檢測。它使應用程序能夠很容易地知道異步代碼中是否存在任何競態條件。

以下是一些學習Go和Channels的重要資源:

理由5:編譯速度快

用Go編寫的最大的微服務項目只需6秒就可以編譯完成。與Java和C等語言的龜速(turtle-speed)編譯相比,Go的極快編譯速度是它的主要生產力。

理由6:組件團隊的能力

讓我們從這些數據開始:Go的開發人員沒有C和Java的開發人員多。根據StackOverflow的統計,有38%的開發人員使用Java,19.3%的開發人員使用C,但只有4.6%的開發人員使用Go。 GitHub數據也顯示出了類似的趨勢:Go比Erlang、Scala和Elixir等語言使用得更廣泛,但不如Java和C那麼流行。

幸運的是,Go是一門非常簡單易學的語言。它只提供了我們需要的基本功能,而沒有提供其他附加功能。它引入了一些新概念,例如“defer”聲明和內置的“go routines”以及Channels並發管理等。團隊中的任何Python、Elixir、C、Scala或Java開發人員都可以在一個月內學習會怎麼使用Go編程,因為Go非常簡單。

與其他語言相比,我們發現建立Go開發團隊更加容易。如果我們在競爭激烈的環境中(例如在博爾德和阿姆斯特丹)招聘,這是一個非常重要的優勢。

理由7:強大的生態系統

生態系統對於我們這樣規模的團隊(大約20人)來說非常重要。如果你不得不重新設計所有的功能,你就不能為你的客戶創造價值。 Go為我們經常使用的工具提供了強大的支持。例如,Redis、RabbitMQ、PostgreSQL、模板解析、任務調度、表達式解析和DBRocks都可以使用現有的庫。

與其他新語言(例如Rust或Elixir)相比,Go具有巨大的生態系統優勢。儘管它不能與Java、Python或Node相提並論,但是我們是可以找到許多能夠滿足基本需求的高質量軟件包。

理由8:Gofmt,強制代碼格式化

Gofmt是一個優秀的命令行程序,它內置於Go編譯器中,可用於格式化代碼。在功能方面,它類似於Python的autopep 8。我們大多數人都不喜歡爭論製表符(tabs)和空格(spaces),但格式化的目標始終是一致的,實際的格式標準則無關緊要。 Gofmt以一種形式化的方式來格式化代碼,以避免所有這些爭論。

理由9:gRPC 以及Protocol Buffers

Go為Protocol Buffers和gRPC提供了一流的支持。它將這兩個工具完美地結合在一起,構建了一個通過RPC進行通信的微服務。我們只需編寫一個定義了RPC調用及其參數的清單文件,服務端和客戶端就可以據此自動生成適當的代碼了。這不僅速度快,而且網絡佔用空間小,使用起來更方便。

其他語言(如C、Java、Python和Ruby)中的客戶端代碼也可以基於相同的清單文件生成。這樣,就不會與內部REST接口發生衝突了,而且我們也不必每次都編寫幾乎相同的客戶端和服務端代碼。

使用Golang的缺點

缺點1 :缺乏框架

Go不像Ruby的Rails、Python或Django或PHP的Laravel,它沒有一個主要的框架。這個話題在Go社區引起了激烈的爭論,許多人認為不應該使用現有的框架來啟動項目。在某些情況下,我完全同意這一點。但是,如果我們想要構建一個簡單的CRUD API,那麼使用Django/DJRF、Rails Laravel或Phoenix則會更簡單。

缺點2:錯誤處理

Go通過簡單地從函數中返回錯誤的形式來處理錯誤。儘管這種方案是可行的,但是它很容易失去錯誤的範圍,從而很難向用戶提供有價值的錯誤信息。錯誤包可以通過返回錯誤的上下文和錯誤堆棧來解決該問題。

還有一個問題,那就是它很容易忘記去處理錯誤。儘管諸如errcheck和megacheck之類的靜態分析工具可以避免這些錯誤,但這始終並不完善。也許我們應該期待一種語言級別的錯誤處理方案。

缺點3:包管理

Go的包管理並不完善。默認情況下,它無法指定依賴項的特定版本,也無法創建可重用的構建方案。 Python、Node和Ruby都有更好的包管理系統。但是,如果能使用正確的工具,Go的包管理也可以變得更簡單。

我們可以使用Dep來管理指定固定版本的依賴項。此外,我們還提供了一個名為VirtualGo的開源工具,用於多項目管理。

Python vs Go

我們做了一個有趣的實驗,用Go重寫了原來由Python編寫的feed流。請看一下該排序方法的示例:

{
    "functions": {
        "simple_gauss": {
            "base": "decay_gauss",
            "scale": "5d",
            "offset": "1d",
            "decay": "0.3"
        },
        "popularity_gauss": {
            "base": "decay_gauss",
            "scale": "100",
            "offset": "5",
            "decay": "0.5"
        }
    },
    "defaults": {
        "popularity": 1
    },
    "score": "simple_gauss(time)*popularity"
}

Python和Go的代碼都需要執行如下操作來支持此排序方法:

  1. 解析分數表達式,將“simple_gauss”轉換為函數,輸入活動並輸出分數
  2. 通過JSON配置創建函數。例如,我們想要“simple_gauss”在scale為5天、offset為1天、factor為0.3時調用“decay_gauss”。
  3. 當字段沒有值時,解析“defaults”配置並採用默認值。
  4. 從步驟1開始使用該函數,對feed中的所有活動進行評分。

開發Python版的排序(Sort )代碼花了大約三天的時間,其中包括代碼編寫、單元測試和文檔編寫。接下來,我們花了大約2週的時間來優化代碼。其中一種優化方法是將分數表達式simple_gauss(time)*popularity 轉換為抽象語法樹。我們還實現了可用於預測分數的緩存邏輯。

相比之下,開發此代碼的Go版花了大約四天的時間,並且在後期不需要進一步地優化性能。因此,儘管Python最初的開發速度更快,但是Go版最終需要的工作量更少。另一個優勢是,Go代碼比我們高度優化的Python代碼還要快40倍。

當然,這只是說明我們切換到Go後性能提升的一個簡單示例:

  • 排序代碼是我用Go編寫的第一個項目。
  • Go代碼是在Python代碼之後編寫的,因此對項目的理解更加深入。
  • Go的表達式解析庫的質量更高

你的經歷可能會有所不同。與Python相比,使用Go構建系統中的某些其他組件需要花費更多的時間。通常,編寫Go代碼需要付出更多的努力。但是,優化代碼性能所需的時間會更少。

Elixir vs Go

我們想要評估的另一種語言是Elixir。 Elixir是一門建立在Erlang虛擬機上的引人入勝的語言。我之所以這麼說,是因為我們的一個項目團隊非常精通該語言。

出於這個原因,我們注意到Go的原始性能更好。 Go和Elixir都能支持數千個並發請求。但是,如果我們查看單個請求的性能,Go要快得多。我們選擇Go的另一個原因是它的生態系統。對於我們需要的組件來說,Go具有更成熟的庫,而Elixir尚不適合用於生產。同時,也很難招聘到Elixir開發人員或對開發人員進行Elixir培訓。

結論

Go是一種性能非常高的語言,並且它對並發的支持非常強大。它差不多與C和Java一樣快了。儘管Go的編譯速度比Python或Ruby慢,但我們可以節省出大量的優化代碼時間。

Go對於新手而言具有龐大的生態系統,它易於學習使用,具有超高的性能,並且對並發有強大的支持,此外,它還具有非常高效的開發環境。這些特性使Go成為開發人員最合適的選擇。

如果你想要了解更多關於Go的信息,請閱讀下面列出的文章。如果想了解更多關於Stream的信息,請瀏覽此交互式教程

相關閱讀:

Go學習資料:

原文鏈接:

Nine reasons to switch from Python to Go