2017年1月24日火曜日

Go言語 (golang)の良い点

勉強の為に引用しました。


結論としてはGo言語には以下のようないくつかの長所があり、現実路線で非常にバランスがとれた言語だと思います。 これらの長所のために失われたメリットも当然いくつもありますが、一定程度以上の規模のプロジェクトで利用する言語の選択肢としては現存するプログラミング言語の中では一番か二番目によいのではないかと思います。

  • コンパイルが速い (vs. C++)
  • GCとメモリ安全性 (vs. C++)
  • 妥当で現実的なレベルの型安全性 (vs. Python/Ruby)
  • 実行時パフォーマンスが良さ (vs. Python/Ruby)
    • 現実問題、ある程度の規模と期間のプロジェクトになると型検証があるとリファクタリングなどがだいぶ楽になるのでありがたい。
    • 型があるので自然と実行時パフォーマンスも良い
  • シンプルでバランスの取れた言語仕様。習得が比較的容易 (vs. C++, Haskell)
  • 標準ライブラリが整備されている (vs. C++)
    • というか標準ライブラリにjsonのパースすら存在しないC++がおかしい…
  • スレッドモデルによる並行プログラミング (vs. Node.js)
    • 一時期Node.jsなどでイベントモデルによるプログラミングが何故かもてはやされていましたが、人類にはイベントモデルは無意味に難しすぎます
    • 言語標準の方法としてgoroutineを使った同期モデルによる並行処理が推奨されているのは素晴らしいデザインだと思います。
  • Dockerなどの素晴らしい大規模プロジェクトでの実績がある。

シンプルでバランスの取れた言語仕様

  • 仕様は比較的シンプルである
    • すべて読むのにそれほど多くの時間は必要ない。
  • C++やHaskellに比べれば遥かに習得が容易である。 すでに手続き型言語を1つでも使いこなせるプログラマならば軽く仕様を眺めればすぐに何となく使えるようになるであろう。 ただしそれでもはまりどころはいくつもあるので
  • いくつかの"クール"な言語機能がGo言語 (golang) ではサポートされていない。代表的なものは 「例外」、「Generics (template)」、「継承」

なぜGoは"悪い"言語なのか

Goに対する批判は数多く存在します。それのどれにもきちんとした理由はあると思います。主な批判は大きく

  • Generics (template) がない
  • 継承がない
  • 例外がない。まるで1970年代に設計されたかのようである。
  • 非知的なプログラマのためにデザインされている。

に集約されるように思います。

Generics (template)がない

Go言語(golang)にはGenericsはありません。 JavaのGeneric TypesとかC++のテンプレートで書けるようなことはGoでは書けません。 ただ配列(スライス), mapについては特別に言語でサポートされているのでJavaやC++で総称型を使うケースの9割ぐらいはカバーされるとは思います。Java1.5より前のJavaのように連想配列や可変長配列を使うのに常にキャストが必要になったりはしません。そのためGenericsがサポートされていないことはそれほど大きな問題にはなりません。

上述のFAQにかかれている通り、GenericsをサポートしないことによってGo(golang)の型システムはシンプルに保たれています。 C++のテンプレートの意味不明なコンパイルエラーに悩まされることはGo言語(golang)ではありません。 またGenericsも継承と同じように本来使われるべきでない場所で乱用される言語機能の1つだと思います。Go/Javaのinterfaceに相当するもので十分なものに無意味にGenerics (template)が使われていて保守性・可読性の低いコードは特にC++でよく見かけます。そのためカスタムのコンテナを定義するのにGenericsがないのは不便だけれど、Genericsが存在することで生じる複雑性や乱用による不利益を避ける方を選んだのは悪いことではなかったと思います。

一方で、mapと配列以外のカスタムのコンテナが使いたい時にはGenericsがないのは不便なのも確かなので、将来うまい落とし所がみつかることを期待したいです(Generics may well be added at some point.)

継承がない

Goには継承はありません。 そもそも継承はプログラミング言語にあまり必要ない機能だと思います。 継承が本当に有益なこともありますが、経験上大半のケースでは設計を手抜きするために継承が使われていて、結果長い目で見た際のreadabilityやmaintainabilityが著しく劣化してしまっていることが多いと思います。Composition over inheritanceリスコフの置換原則のような基本的な原則が守られておらず(そもそも多くの人は名前すら知らない)、単に一部のコードをクラス間で共有するために継承が使われていて可読性が著しく低いコードもよく目にします。

そのため、そもそもプログラミング言語が継承をサポートしないというのは良いデザインだなと思います。継承が非常に有益な場合があるもの分かりますが、正しく使われない害のほうが確実に大きいと思います。

Errors as values (例外が推奨されない)

Go言語のFAQにあるように、Goには例外がありません。panic, recoverで例外と同じようなことはできますが、Javaの例外のように気軽に使ってはなりません。個人的にはこのFAQにかかれていることには概ね同意します。 例外で返されたエラーを try {...} catch (Exception e) {...} みたいに処理しないといけないのは無意味に複雑なように思います。 それだけならよいですが、例外が発生するとコードが想定外の順序で実行されて困ったり、何故かこのコードが実行されないなと思ったら、その前に例外で大域脱出していて、しかもその例外が予想外のところでcatchされ握りつぶされていたり、と例外を大規模なプロジェクトの中で正しく扱うのは中々に困難だと思います。 Goの例外は極力使わず、エラーを値として扱うポリシーはよいもの(特に大規模なプロジェクトで、エラーハンドリングが大切なプロジェクトでは)だと思います。

ただ一方で、ちょっとした使い捨ての便利ツールを書く場合や、とりあえずプロトタイプで正常系だけ書きたい時、 あるいは異常が発生したらプログラムを停止してしまって良いような起動時の初期化処理を書く時には、正直Goのエラーハンドリングはかなり面倒くさいです。 こういうタイプのコードでは外部ライブラリの呼び出しやファイル、データベースなどの外部リソースへのアクセスが大きな割合を占めます。そして、そうした処理はほとんどの場合 error が発生しうるのでそれぞれの処理に対してエラーハンドリングを行う必要があります。 場合によってはコードのかなりの割合の行が

if err != nil {
	return nil, err
}

の繰り返しで占められてしまうこともあるでしょう。これに対しては根本的な解決策はないように思います。エラーが発生した場合はエラーメッセージを出力して処理を中断してしまって問題ない

  • 使い捨てあるいは内部ツールで開発者・利用者の数が数人でエラーハンドリングがあまり重要ではない時
  • 正常系だけとりあえずプロトタイプしたい時

には例外の方が便利であり、正直Go言語ではあまり効率的にコードが書けないような気がします。個人的にはそういう用途にはPythonなどを使うのが正しい解決策のように思えます。何でもGoで書く必要はないのですから。 型の存在すら簡単なプロトタイピングをするのには少し鬱陶しいですしどんな言語も適材適所だと思います。

非知的なプログラマのためにデザインされている (Designed for non-intelligent programmers)

Goは知的でないプログラマのためにデザインされていると批判されることがあります。そして「知的でないプログラマ」のためにデザインされているというのは指摘自体は正しいと思います。実際Rob PikeもFrom Parallel to Concurrentの20:40~で次のように述べています

The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt.

要するにGoogle社員のような研究者ではなく、大学を出たばかりでC++, Java, Pythonの経験しかないような、「素晴らしいプログラミング言語」を理解する能力が欠けているエンジニアでも簡単に理解し、大きなシステムを構築するのに使えるようにGo言語はデザインされているのです。

Goは言語デザインとして正しく使うのが難しい、乱用するとプログラムを無意味に複雑にしてしまう機能が排除されています(高度な型システム・継承・Generics・例外・イベントモデルによる並行処理など)。 そのため、ある意味ではGoは「知的な」プログラマにとっては面白みに欠く言語だと思います。実際、Goを勉強しても概念的には目新しいものはなくて勉強することそのもので何かが学べるとは僕は全く思いません。 一方でプログラミング言語を習得することのゴールは、それを使って何か大規模なソフトウェアをチームで作ることであるのだと思います。 チームで大規模なソフトウェアを開発することはそれ自体が非常に難しいことです。そのゴールのためにプログラミング言語自体はシンプルに保ち、それ以外の部分により多くの労力をさけるようにするというのは僕は正しい判断だと思います。

0 コメント:

コメントを投稿