わたしがGoを好きな理由

こんにちは!バックエンドチームの上原(https://twitter.com/_uhzz_)です!

この記事は、連載記事の1本目になります。

はじめに

フクロウラボでは、Goをコアな技術スタックとして定義しています。
そのため、社内では毎週Go勉強会を開催するなど、メンバーのGopher化が進んでいる最中です。

今回、中のメンバーがGoの好きなところをピックアップしてお話できればと思います。

理由1:豊富なツールチェイン

Goをインストールした後、コマンドラインで「go」と打ってみると以下のようにヘルプが表示されます。

# go
Go is a tool for managing Go source code.

Usage:

       go <command> [arguments]

The commands are:

       bug         start a bug report
       build       compile packages and dependencies
       clean       remove object files and cached files
       doc         show documentation for package or symbol
       env         print Go environment information
       fix         update packages to use new APIs
       fmt         gofmt (reformat) package sources
       generate    generate Go files by processing source
       get         add dependencies to current module and install them
       install     compile and install packages and dependencies
       list        list packages or modules
       mod         module maintenance
       work        workspace maintenance
       run         compile and run Go program
       test        test packages
       tool        run specified go tool
       version     print Go version
       vet         report likely mistakes in packages

Use "go help <command>" for more information about a command.

goコマンドには、プログラムのビルドを行うものやドキュメントの作成と参照、コードのフォーマット、依存管理、テスト実行、テスト実行前に疑わしいコードの検出など、拾いきれないほど様々な機能がツールチェーンとして提供されています。

開発環境を構築する際に、なにかとプログラミング言語の実行環境のほかに周辺ツールを揃えるのは日常茶飯事かと思いますが、Goにおいてはそのステップがコンパクトになっています。

周辺ツールのインストールに疲弊することなく、モチベーションを維持したまま開発に取り組めるのは大事だと筆者は思います笑

ヘルプの始めに書いてあるこの言葉がすべてを語っていますね。

Go is a tool for managing Go source code.

理由2:ビルドの速さ

Goが速いと言われる理由の中に、ビルドの速さも含まれているかと思います。

tenntennさんのスライドで言及されているGoが開発された理由の中にも、ビルドの速さを目指して作られたことが書かれています。

ビルドの速い理由としては、以下の要因が挙げられるそうです。

循環インポートを禁止

循環インポート(circular imports)を禁止することによってコンパイラの負担を減らしているようです。

循環インポートがコンパイラの負担となる原因としては、 ソースコードの規模が大きくなってきたときに、コンパイラが大きなソースファイルを一度に処理しようとすることで時間がかかるということに加え、バイナリを肥大化させて初期化、テスト、リファクタリング、リリース、およびソフトウェア開発の他のタスクを複雑にしてしまう、ということだそうです。

そのため、Goでは循環インポートを禁止することに加えて、使用していないパッケージのimportを禁止することで、コンパイラの負担を軽減しているようです。

また、使用しないパッケージを含まないので、ビルドされたバイナリの肥大化を防ぐというメリットもあるとのことです。

パッケージの依存解決アプローチ

GoはCやC++の「インクルードしているファイルのインクルード」というアプローチではなく、「オブジェクトファイルと呼ばれるファイルを正確に1つだけ開く」という方法を取っているため、ビルドにかかる時間が指数関数的に短くなる、とのことです。

記事内の例を翻訳した内容によると、以下のように説明されています。

パッケージAがパッケージBをインポートしている状態で、
パッケージAをコンパイルするとき、コンパイラはパッケージBのソースコードではなく、オブジェクトファイルを読みます。Bのオブジェクトファイルには、コンパイラがBを実行するために必要なすべての型情報が含まれています。

つまり、オブジェクトファイルには依存する情報がメタデータとして記載されているため、それを使うパッケージはオブジェクトファイルを読み込むだけで済むようなアプローチを取っているとのことです。

上記の内容は、以下の記事に記載されていたものを独自に解釈したものになるので、ソースからより正確な情報を汲み取っていただければと思います笑


理由3:継承がないところ

Goは継承ではなく、委譲をつかいます。

Goが継承ではなく委譲である例は、以下の記事に詳しく説明されていますがここでも少し紹介させていただきます。

// 記事中のプログラムを参考にしています
// FYI: https://qiita.com/Maki-Daisuke/items/511b8989e528f7c70f80#%E7%BD%A0%EF%BC%91-%E3%83%95%E3%83%8F%E3%83%8F%E3%83%8F%E3%83%8F%E4%BB%A3%E5%85%A5%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%A8%E8%AA%B0%E3%81%8C%E8%A8%80%E3%81%A3%E3%81%9F
package main

import "fmt"

type Parent struct{ name string }

func (p Parent) Hello() string { return fmt.Sprintf("parent method: %s", p.name) }

type Child struct {
	Parent
	nickname string
}

func newChild(name, nickname string) *Child {
	c := new(Child)
	c.name = name // c.Parent.name の省略形
	c.nickname = nickname

	return c
}

func Example() {
	p := Parent{name: "hoge"}
	fmt.Println(p.Hello()) // parent method: Parent.name 

	c := newChild("fuga", "fugafuga")
	fmt.Println(c.Hello()) // parent method: Child.Parent.name 

	// Output:
	// parent method: hoge
	// parent method: fuga 
}

上記のプログラムからも分かるとおり、Parent型の構造体はHello()メソッドを持っていますが、Child型の構造体はHello()メソッドをもっていません。しかしながら、c.Hello()というかたちでメソッドを呼び出せています。

これは、Child型の構造体に埋め込まれたParent型のフィールドを経由することで、Parent型のHello()メソッドを呼ぶようになっています。

つまり、c.Hello() -> c.Parent.Hello()というかたちで呼び出しているということです。(この変換はGoがやってくれています)

この「埋め込まれた」という点がポイントで、

構造体Aに埋め込まれた構造体Bのメソッドは、構造体Aのメソッドになる(構造体A is 構造体B)

ではなく、

構造体Aに埋め込まれた構造体Bのメソッドを委譲(ほかのオブジェクトに操作をまかせる)する(構造体A has 構造体B)

という関係になっていることです。

個人的に好きなのは、継承の場合、「オーバーライドするのを忘れて意図した挙動にならない」というようなところを、GoではInterfaceをつかってメソッドを定義するので、「実装していないメソッドがあれば、ビルドが通らないし、参照されないけどなぜか実装されてる謎メソッドがある」ということが発生しないことです。

さいごに

Goの好きな理由をうまく言語化してこなかったこともあり、この記事を執筆しながら、初めて理由づけができたように思います。

あらためて好きなこと、主に技術ですが、ただ好きなだけに留めずに言語化することの大事さに気づきました。

捕捉

きちんとした理由を述べてきましたが、インターネットで言われるように「Gopherくんがかわいい」ことも主な理由の1つです。


バックエンドチームでは、サーバレスやGoを使った開発に興味のある方を絶賛募集しています!


この記事が気に入ったらサポートをしてみませんか?