ねぎ嫌い

始業前に学んだことを小出しに。最近はHacker Newsの人気記事をまとめてみたり。

2017-10-18 Why we switched from Python to Go

原文:getstream.io

PythonからGoを使い始めた経緯のお話。

既に経験のある言語から新しい言語に切り替えるのは大きなステップが必要になる。
streamでは主要なプログラミング言語PythonからGoに切り替えた。
本記事では、なぜPythonからGoに切り替えたのかの理由を述べている。

理由1. パフォーマンス

GoのパフォーマンスはJavaC++と同じくらい良い。
彼らのチームではGoのほうがPythonより30倍も早かった。

理由2. 言語のパフォーマンス

多くのアプリケーションにおいてプログラミング言語
ただアプリケーションとデータベースをつなぎ合わせるノリにすぎない。
言語自身のパフォーマンスはそんなに問題にならない。

彼らは500以上の会社と2億人以上のエンドユーザーにAPIを供給している。
Cassandra、PostgreSQL、Redisなどで最適化しているが、言語上の限界に達してしまった。

Pythonは素晴らしい言語であるが、serialization/deserialization、
ランキング、アグリゲーションといったユースケースでパフォーマンスが出なかった。
Cassandraは1msでデータを見つけるが、Pythonは10msでオブジェクトに詰めていた。

理由3. 開発者の生産性と創造性のなさ

Goのチュートリアルにあった以下のコードを見てほしい。

package main

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
}

このコード例には以下の要素が詰められている。

  • 複数割当(multiple assignments)
  • 構造体
  • ポインタ
  • フォーマッティング
  • ビルトインHTTPライブラリ

もしPythonで同じことをやろうとすると、

  • MetaClassesを使ってクラスの初期化をする
  • True/Falseをスワップアウトする
  • ビルトイン関数のリストに関数を追加する
  • 魔法のメソッド経由でオペレーターをオーバーロードする

これらの機能で遊ぶのは楽しいが、多くのプログラマが同意するように、
多くの場合これらのコードは理解するために読むのが難しくなる。

Goは基本に忠実にさせる。
誰が書いたコードであっても簡単に理解するために読めるようにしてくれている。

NOTE: どれだけ簡単か、はユースケースに因る。単純なCRUD APIであればDjangoDRFRailsを使うほうが良い。

理由4. 同期実行とチャネル

言語として、Goは物事を単純に保とうとする。
goroutineとchannelによって単純な言語を実現している。
goroutineは軽量のスレッド化手法で、
channelはgoroutine間のコミュニケーションを取る優れた方法である。

goroutineは非常に低コストで生成でき、メモリもわずかしか使用しない。
そのため、一度に何百、何千ものgoroutineを走らせることが出来る。

goroutine間の通信にはchannelを使う事ができる。
Goのランタイムが全ての複雑性をコントロールしてくれる。
goroutineとchannelベースの手法が同期実行を簡単に
使用可能な全てのCPUコアを使用させ、同期IOを扱う。
Python/Javaと比べ、Goは最低限の定型文のみが必要になるだけである。

理由5. 短いコンパイル時間

JavaC++に比べて非常に短い時間でコンパイルすることが出来る。

理由6. チームを構成する力がある

Goを書ける開発者はC++Javaといった古い言語の開発者よりも多くはない。
StackOverflowによると38%の開発者はJavaを書け、19%がC++を書けるが、
Goに関して言えば4.6%しかいない。
Githubのデータも似たような傾向を示している。

幸いにもGoは非常に簡単かつシンプルに学ぶことが出来る言語である。
基本的な機能を提供し、必要なものはそれ以外にない。
その簡潔さでPythonをかける開発者を1ヶ月以内に効率的に出来る。

そのため、他の言語に比べチームを構成するのに容易い。

理由7. 強力なエコシステムがある

20人以下の規模のチームにおいてエコシステムは問題になる。
小さな改革を起こさなければ、顧客に価値を提供することは出来ない。
Goは我々の使用しているツールに対してサポートをしている。
充実したライブラリは既に揃っていて、RedisやRabitMQ、PostgreSQL
テンプレートエンジンやタスクスケジューリング、文法解析などがある。

GoのエコシステムはRustやElixirといった他の新しい言語に概ね勝っている。
Javapython、Nodeなどには負けているが、
必要としている基本的なライブラリは既に使用可能である。

理由8. コードの強制フォーマッティング

Pythonにおけるautopop8のような強制フォーマッティングツールが備わっている。
タブ - スペース論争のようなものはもう必要ない。
Gofmtが全てを解決してくれる。

理由9. gRPCとプロトコルバッファ

GoはプロトコルバッファとgRPCを快適にサポートしている。
これらの2つのツールはRPC経由でのマイクロサービスを立てるのに適している。
マニフェストを書くだけで、RPC呼び出しを使える。
サーバーとクライアントのコードはマニフェストから自動生成される。

同じマニフェストから、クライアントのコードをJava, C++, PythonRubyで生成できる。
これ以上曖昧なRESTのエンドポイントは必要ない。

Goが優れていない点

不利1. フレームワークの不足

Goはまだ支配的なフレームワークを有していない。
これはGoコミュニティ内で白熱した論争のトピックとなっているが、
今のところフレームワークを使うべきではない、という。
もしシンプルなCRUD APIを立てたかったら、 DjangoRailsを使うほうが良い。

不利2. エラーハンドリング

Goでは、呼び出し元のコードでエラーをハンドリングする必要がある。
そのため、エンドユーザーに対して意味のあるエラーを返すのを簡単に忘れてしまう。

エラーのパッケージはこの問題をエラーにコンテキストとスタックトレースを追加することで解決している。

他の問題として、簡単にアクシデントによるエラー処理を忘れてしまう。
errcheckやmegacheckのような静的解析ツールはこのミスを便利に防いでくれる。

不利3. パッケージ管理

Goのパッケージ管理は決して完璧ではない。
デフォルトでは依存するライブラリのバージョンを指定する方法はなく、
再現性のあるビルドをする方法もない。
Python, Node, Rubyのいずれも優れたパッケージ管理システムを持っている。
しかし、正しいツールを使えばGoのパッケージ管理もかなり良い。

Depを使って依存性を予定したバージョンで管理することが出来る。
それとは別にVirtual Goというオープンソースツールもある。