LoginSignup
26
10

More than 3 years have passed since last update.

Pythonistaが伝える「ここがヘンだよGo言語」

Last updated at Posted at 2019-12-20

VISITS Advent Calendarも残りついに一週間を切ってしまいました。

見返してみると、とても幅広い分野の記事が書かれていて、社内の人間であっても「この人、こんな技術を持ってたんだ」のように驚かされることが多い毎日でした。

私にとっては2019年Advent Calendarの締めの記事となります!

TL; DR

弊社のいくつかのプロダクトで Go言語 でAPIサーバが実装されています。

昔からPythonばかり触ってきた筆者が、Go言語を一ヶ月半ほどですが業務でみっちり書いてわかった Go言語とPythonの違いについて記事にしてみることにします。

題して、

Pythonistaが伝える「ここがヘンだよGo言語」

とりあえず某番組のタイトルをパクt(ry リスペクトしてキャッチーなタイトルにしてみました(何

対象読者は次のように設定しています。

  • Pythonはよく使うけど、Go言語を書いたことがない人
  • Go言語を趣味で触っているけど、業務では書くor読む機会がない人

Go言語のことをよく知らなくてもPythonの文法を知っていたら読めるように書いているつもりです。

文法レベルの違いから、業務レベルで効いてくる言語特性までカバーできたらなーと思ってます。

記事全体としては、Go贔屓な感じになってしまいましたが、Pythonへの愛があるからこそですよ!

なんでPythonとGoの比較?全然違うだろうが!

確かにおっしゃる通りで、何から何まで違う言語です。

第一、Pythonは動的型付け言語かつインタプリタ上で実行される言語である一方、Goは静的型付け言語のコンパイル言語です。

これだけでも比較対象にするのは間違っていそうですね。

これぐらい違うと対比させると逆に面白いんじゃないかという筆者の淡い期待もあり、記事にしてみる価値があるかもしれないと思ったのが記事を書こうとした大きな動機です。

全然違う言語ではありますが、PythonとGoの大きな共通点は どちらの言語も可読性が高くなるように設計されている ことです。

様々なプログラミング言語がありますが、やっぱり可読性が高く、メンテナンス性の高い言語が(自然淘汰の結果)残っていくんだろうなぁという謎の雑感を持っています。

皆さんもやっぱり読みやすいコードが大好きですよね!

PythonとGoの違い

歴史は全然違う!

Pythonの歴史

Pythonの誕生は意外に古く、Guido Van Rossumによって1991年に最初のリリース(Python 0.90)がされました。
「Python」という名前の由来は空飛ぶモンティ・パイソン(Monty Python’s Flying Circus)というBBCのテレビ番組であることは有名ですね(なお観たことはない)。

History of Pythonによると、ヴァンロッサムさんはこの番組の大ファンだったらしく、当時名前のなかった発明した言語に短くてミステリアスな名前を付けたいということでこの名前になったそうです。

なんと、最初は クリスマスの暇つぶしに行っていた趣味開発だった らしいです。暇つぶしで作られた言語が世界中で使われるようになるのはなんとも夢があって面白いですね。

2000年にPython2.0がリリースされ、一躍メジャー言語として名が知れ渡ることとなりました(Python - Wikipedia

Python2.xからPython3.xへの後方互換性を失う大移行も今では少し過去のことに感じてしまいますが、今では、Python 3.8 がリリースされています(Python3.8の新機能 (まとめ))。

image.png
(History of Pythonから引用)

Pythonの思想 - Zen of Python

Pythonインタプリタ上で import this と打つとZen of Pythonが英語で出てきます!)

$ python
Python 3.8.0 (default, Oct 31 2019, 16:56:00)
[Clang 11.0.0 (clang-1100.0.33.8)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

英語辛い人は、プログラマが持つべき心構え (The Zen of Python)が参考になります。

Goの歴史

個人の趣味開発から始まったPythonとは異なり、Goは世界的大企業Googleから誕生しました。
新しいプログラミングを開発するという実験的プロジェクトのなかで、Googleのエンジニアである Robert Griesemer、Rob Pike、Ken Thompsonの三人によって設計されました(Go - en.wikipedia.org)。

2007年から設計が始まり、2012年にGo1.0がオープンリリースされたばかりという、プログラミング言語界ではまだまだ新入りという歴史の浅さですね。

Goはオープンソースなので、誰でもコードを読むことができます。

$ git clone https://github.com/golang/go.git # (注!) 200MBぐらいあります
$ git log --reverse

上記を実行すると、2008年頃のコミットが見れたりします。
(ちなみに最初のコミットは、仕様書でした。)

三人のうちの一人であるロブ・パイクさんが、まだリリースされていない2010年にStanford EE Computer Systems Colloquiumで発表したスライドをPDFで読むことができます(Another Go at Language Design)。

このPDFを眺めてみるとかなり興味深い内容が書かれているのですが、ざっくりいうと、JavaやC++が抱える問題(冗長なコード、コンパイルに時間がかかる、バイナリが大きいなどなど)を解決するためにGoが考え出されたようです。

The Target:
Go aims to combine the safety and performance of a
statically typed compiled language with the expressiveness
and convenience of a dynamically typed interpreted
language.

特にここの部分を訳すと、

Goが目指すのは、次の二つを結びつけること。

  • 静的型付けコンパイル言語の持つ安全性やパフォーマンス
  • 動的型付けインタプリタ言語の持つ表現能力や利便性

まさにいいとこ取りな話ですが、Goはそれを本気で実現しようとしている言語だと言えます。

また、PDFの中では、Goが他の言語の課題を解決する特徴についても挙げられています。

  • 高速なコンパイル
  • 表現性のある型システム
  • 並行性(並行プログラミングに強い)
  • ガベージコレクション
  • システムプログラミング言語(ハードウェアへの直接アクセスが可能)
  • 明瞭であること
  • 直交性(パッケージが他のパッケージと十分独立している)

直交性の部分の解釈はOrthogonality in Goを参考にしています。標準パッケージが充実していて、わかりやすく分割されているのもそのためでしょうか。

Goの思想 - Go Way

Goのお作法がまとめられており、それらをまとめて Go Way と呼びます。

Effective Go (英語)

「なんだ、お作法って・・・」

と気になる人も多いかもしれませんが、最初は奇妙に思えるお作法を守ることでめちゃくちゃ得することが多いのです。

「Go言語らしさ」とは何か? Simplicityの哲学を理解し、Go Wayに沿った開発を進めることの良さ

に非常に詳しくGo Wayの解説が書かれているのでオススメです。

文法が全然違う!

PythonもGoもプログラミング言語ですから、コードで見るのが手っ取り早いでしょう。

Pythonの"Hello, World"

hello.py
print('Hello, World')
$ python hello.py
Hello, World

もしくは、

$ python
Python 3.8.0 (default, Oct 31 2019, 16:56:00)
[Clang 11.0.0 (clang-1100.0.33.8)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>print('Hello, World')
Hello, World

Goの"Hello, World"

main.go
package main

import "fmt"

func main() {
    fmt.Println("Hello, World")
}
$ go run main.go
Hello, World

。。。うーん。

さすがにこれだけだと違いがよくわからないので、もうちょっと実用的な実装をしてみましょう。

PythonとGoで同じ機能を実装してみる

他のプログラマーに好きなプログラミング言語を布教活動するめんどくさいプログラマーをPythonとGoでそれぞれ実装してみましょう。

仕様としては、

  • プログラマーは名前と好きな言語を属性として持てる
  • 他のプログラマーを引数に与えると、そのプログラマーの好きな言語を自分の好きな言語に変える関数を持つ
  • クラス変数の中で名前はグローバルからは変更できないようにする

Python

まずは、Pythonで Programmer Classを作ってみます。

まずは、最小限の実装です。

main.py
class Programmer:
    def __init__(self, name, favorite_language):
        self.name = name
        self.favorite_language = favorite_language

    def teach_favorite_language(self, prog):
        prog.favorite_language = self.favorite_language


pythonista = Programmer("zawawahoge", "Python")
go_lover = Programmer("foobar", "Go")

pythonista.teach_favorite_language(go_lover)

@property デコレータを用いて、getter, setterを設定することができます。
これでプライベート変数(のようなもの)を実装することができますね。

name に関して、setterを実装しなかったので、外部からはアクセスできないようになります(できるけど)。

main.py
class Programmer:
    def __init__(self, name, favorite_language):
        self.__name = name
        self.__favorite_language = favorite_language

    @property
    def name(self):
        return self.__name

    @property
    def favorite_language(self):
        return self.__favorite_language

    @favorite_language.setter
    def favorite_language(self, favorite_language):
        self.__favorite_language = favorite_language

    def teach_favorite_language(self, prog):
        prog.favorite_language = self.favorite_language


pythonista = Programmer("zawawahoge", "Python")
go_lover = Programmer("go_lover", "Go")

pythonista.teach_favorite_language(go_lover)

Pythonでは、プライベート変数にしたいものには最初に __ を付けます。

Goの"struct"

Pythonで実装したものと全く同じ機能をGoでも実装してみました。
業務で書いているようなコードにできるだけ近づけています。

main.go
package main

type Language string

// Programmer はプログラマーのインターフェイス(グローバル変数)
type Programmer interface {
    TeachFavoriteLanguage(target Programmer) error
    SetFavoriteLanguage(lang Language) error
    FavoriteLanguage() Language
}

// programmer は Programmerインターフェイスの実装(パッケージ内からのみ参照可能)
type programmer struct {
    Programmer
    name             string
    favoriteLanguage Language
}

// NewProgrammer は新しい Programmer を返す
func NewProgrammer(name string, lang Language) Programmer {
    return &programmer{
        name:             name,
        favoriteLanguage: lang,
    }
}

// getter
func (p *programmer) FavoriteLanguage() Language {
    return p.favoriteLanguage
}

// setter
func (p *programmer) SetFavoriteLanguage(lang Language) error {
    p.favoriteLanguage = lang
    return nil
}

// TeachFavoriteLanguage の実装
func (p *programmer) TeachFavoriteLanguage(target Programmer) error {
    err := target.SetFavoriteLanguage(p.FavoriteLanguage())
    if err != nil {
        return err
    }
    return nil
}

// main関数内では、Programmerインターフェイスのみでやりとりができる
func main() {
    pythonista := NewProgrammer("zawawahoge", Language("Python"))
    go_lover := NewProgrammer("go_lover", Language("Go"))

    err := pythonista.TeachFavoriteLanguage(go_lover)

    if err != nil {
        panic("布教活動中にエラーが発生しました")
    }
}

一目見ただけでも、コード量がPythonに比べて圧倒的に多くなっていることがわかります。

何でこんなに増えてんの!?!?!?

Pythonと異なる特徴を上げると、

  • Language 型を明示的に定義( string
  • Programmer インターフェイスを定義
  • programmer structを定義
  • コンストラクタの代わりに、グローバルな NewProgrammer() を定義し、Programmerを生成する
  • インターフェイスを実装する(関数が作られていれば満たしていることになる)
  • エラーハンドリングを行う

Goでは、変数の最初の文字が大文字ならグローバル変数になり、小文字ならパッケージ変数(同一パッケージ内のみ参照可能)になります。

Pythonに比べてコード量自体は多くなってしまいますが、一つ一つに明確な意味があり、無駄な部分がありません

可読性はどちらもあるけど、特徴が全然違う!

Pythonの可読性

インデントが強制されているので、圧縮された .js ファイルのような読めないコードにはなり得ない言語仕様になっています。
初めてPythonに触った人がよく「インデントが気持ち悪い」と言うことがありますが、実はこのおかげで一目でスコープが理解しやすくなっているのです。

また、コード量が普通のコンパイル言語に比べて圧倒的に少なくすみます。Pythonがここまで科学技術計算で広まっていったのも、わかりやすいインターフェイスで様々なライブラリをラップすることができる特徴があったからでしょう。

ただ、動的型付け言語なので、IDEの補完などを活かしにくい欠点がある上、言語仕様的にはオブジェクトの中身を破壊することを前もって防ぐことが困難であるため、実装者の腕に強く依存した言語とも言えると思います(特にライブラリを作る時など)。

Goの可読性

Pythonは実はとんでもなく自由度が高いため、人によって実装が大きく違うことが多々起こります。
Goだと、言語の特性上、目的に対して実装手法がある程度定まるため、書いた人によらず、似たようなコードになります

このおかげで、複数の開発者で開発していても、実装が全く理解できない、ということが起こりにくいと言えます。

楽をしにくい言語ではあるものの、必要十分な実装を自然と求められるため、実装者に依存しないコードになるのだと思います。

エラー処理が全然違う!

Pythonのエラー処理 try ~ except

Pythonでは、 try ~ except 構文を使いますね。

try:
    do_something()
    res = get_something()
except Exception as e:
    print(f"e={e}, type(e)={type(e)}")

try ~ catch 系のエラーハンドリングはエラー検出のためのオーバーヘッドが大きく、エラーが起きない時でもパフォーマンスが落ちる可能性があります。
全部に try で囲むとオーバーヘッドが大変なので、エラーが起きそうな部分だけ try で囲ってエラー処理を書くわけです。
そうすると、 try してないところでエラー起きた時どうするの?という話になってきます。

Goのエラー処理

Goは他の言語と異なり、 try ~ catchtry ~ except によるエラーハンドリングを行わず、 error 型の戻り値によって成功かエラーかを判別します。

Goのコードには次のようなエラー処理が頻繁に出てきます。

見慣れていないと、おそらく 気持ち悪い かもしれません(初期の筆者)。

err := DoSomething()
if err != nil {
        return errors.Errorf("failed to do something; err=%#v", err)
}

res, err := GetSomething()
if err != nil {
        return errors.Errorf("failed to get something; err %#v", err)
}

Goのように errnil (Pythonでいう None )であるかどうかだけを調べれば、エラー検出が可能であるため、オーバーヘッドが非常に小さく、エラー検出のためのパフォーマンス低下がほとんどなくてすみます。

欠点としては、ほとんど全てのコードにエラー処理を書くことになるため、慣れないうちは なんとなくめんどくさいです。
ただ、慣れてきた頃には、全コードをエラー処理できていることの安心感の方が上回ってきます!

まとめ

Pythonistaが伝える「ここがヘンだよGo言語」いかがだったでしょうか?

題名とは違ってむしろGo贔屓ですいません。

記事的には、あとテストの項目も書きたかったですが、力尽きました。

この記事を書くにあたって、PythonとGoのどちらも調べることになったのですが、そのおかげでとても勉強になったので、題材として選んで良かったと思いました。

何か伝わるものがあった人はいいねしてくださると励みになります。

ありがとうございました。

参考URL

26
10
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
10