はじめに
個人開発でバックエンドにGoを使いたかったので、Goの勉強を始めました。
疑問をその都度解決していく勉強法なため、横道にそれまくりますが、Goを勉強し始目た時に同じ壁にぶつかった人の助けになればと思っております。コードは本家様をご参照ください。
Goとは
Goとは、Googleがサポートするオープンソースのプログラミング言語です。
環境構築編
早速環境構築を始めます。
まず、Go公式からGoのインストーラーをダウンロードします。homebrewを使わないのはbrew upgrade
で意図せずバージョンアップさせそうだからです。
インスターラーでのインストールが完了したら、.zshrcに下記(パス)を記述します。
export PATH=$PATH:/usr/local/go/bin
次は、Goのバージョンを確認します。
% go version
go version go1.22.2 darwin/arm64
問題なくインストールされていますね。
ついでに、VScodeにGoの拡張機能をインストールします。
チュートリアル Get started with Go
環境構築が終わったら、Go公式のチュートリアルに取り組みます。
# 適当なディレクトリに移動
% cd example
# 作業ディレクトリの作成
% mkdir hello
# 作業ディレクトリに移動
% cd hello
# チュートリアル用のファイルを作成
% touch hello.go
# hello.goを編集
% vim hello.go
# go.modファイルを生成
% go mod init example/hello
go: creating new go.mod: module example/hello
疑問1: go.modって何?
go.modファイルは使用されるモジュールの依存関係を管理するファイルです。とのこと。つまり、Pythonでいうpip install requirements.txt
のrequirements.txt
にあたるファイルのことです。
※go.modはgo本体のバージョンや設定を含むため、厳密には違います。また、go.modはGOPATHの外側で開発を行う際には必須なファイルなので、絶対作成しましょう。
結論が出たところでチュートリアルに戻ります。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
goはgo run
で実行できます。
% go run hello.go
Hello, World!
恒例の"Hello,World"はうまくいきました。ですが、ここで一つ疑問が生まれます。package main
とは何でしょうか?
疑問2 : packageとは?
パッケージはGoのコードをまとめる基本単位です。Goでは全てのコードがパッケージの中に含まれています。
以下のようにパッケージを読み込むことで、他パッケージ内にある識別子(関数,変数,etc)にアクセスすることができます。 この時、以下の要件を満たすようにします。
-
mainパッケージとmain()を準備する
- Goではリポジトリ内に必ずmainパッケージが存在し、その中にmain()が必要です。このmian()がエントリーポイントになるからです。
-
パッケージはディレクトリの中に入れる
- Goのコードはパッケージで纏められており、同一のパッケージはディレクトリで纏めて管理します。
-
パッケージ名とファイル名は同一のものにする
- 理由は後述します。
-
外部で使用する識別子の頭は大文字
- Goは頭文字が大文字であることをキーにエクスポートします。
package main
import {
"fmt"
"/test/print"
}
func main(){
fmt.Println(hello.PrintHello("World"))
}
package hello
func PrintHello(name string) string{
return fmt.Sprintf("Hello, %s!!",name)
}
% go run main.go
Hello, World!!
import {path}
で他パッケージを読み込むことができます。しかし、実際に使用する際はファイル名ではなくパッケージ名が使用されるため、ファイル名とパッケージ名は同一のものにすることが推奨されています。よって上記のコードは推奨されていない書き方と言えます。
ここまでパッケージの話をしてきましたが、多くのソースコードで以下のような記述を見かけます。
import (
"fmt"
"github.com/{}/{}"
)
私は思いました。なぜ、ここでgithub?
疑問3 : packageに現れるgithub
Go言語ではgithubといったリモートからパッケージをインポート使用することが、しばしばあります。他者が作成したパッケージを気軽に使用できるのは利点かもしれません。しかし、疑問が残ります。
別にローカルにコピーして使っても良いのではなだろうか?
特に、開発中に自分で作ったパッケージをgithubからインポートする意味がわかりません。「同じものローカルにあるじゃん!」叫びました。
しかし、物事には理由があるわけで、これにもしっかり理由がありました。
-
理由1:常に最新版のパッケージを使用できる
- チーム開発を行う際、誰かがgithubのパッケージを更新したとします。そのことに自分が気がついていなくてもビルドした際に最新版がgithubからインポートされるため、問題なく開発を行えます。
-
理由2:パッケージをバージョン管理できる
- githubで管理するということはパッケージもバージョン管理の恩恵を受けることができます。つまりバージョンを指定して過去のパッケージを扱うことができます。
他にもありそうですが、私が納得できたのは以上二つです。
チュートリアル Get started with Go(2)
だいぶ話が逸れてしましたが、チュートリアルに話を戻します。
次は先程触れた外部パッケージの活用に挑戦します。
外部パッケージはここで検索でき、今回はrsc.io/quoteモジュールに含まれるquote パッケージを使用します。
main.goを作成したら、整合性を確認します。ここで使用する% go mod tidy
とは、ソースファイルを解析して必要なライブラリをダウンロードしたり不要になったファイルを削除してくれたりするコマンドです。
% go mod tidy
go: finding module for package rsc.io/quote
go: downloading rsc.io/quote v1.5.2
go: found rsc.io/quote in rsc.io/quote v1.5.2
go: downloading rsc.io/sampler v1.3.0
go: downloading golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
go.mod
を見ると内容が更新されています。
module example/hello
go 1.22.2
require rsc.io/quote v1.5.2
require (
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
rsc.io/sampler v1.3.0 // indirect
)
```bash
しかし、ここで一つ知らないファイルが増えていることに気がつきます。
% ls
go.mod go.sum main.go
確かに、チュートリアルには"Goはquoteモジュールを要件として追加し、モジュールを認証するために使用する go.sum ファイルも追加します。"という記述がありました。ですが、よくわかりません。
疑問4 : go.sumとは?
go.sumとは外部モジュールの不正な改竄を検出するためのハッシュ値が入ったファイルです。このファイルのおかげで、もし外部モジュールが不正に改竄されてもgo mod tidy
時効時に異常を検出することができます。
※詳しくはこちら
チュートリアル Create a Go module
Start a module that others can use
Goのコードはパッケージにグループ化され、パッケージはモジュールにグループ化されます。この章ではモジュールを作成して活用する練習を行います。
% mkdir greetings
% cd greetings
% go mod init example.com/greetings
go: creating new go.mod: module example.com/greetings
% touch greetings.go
チュートリアルではexample.comとなっていますが、example.com
の箇所は自分のgituhubリポジトリ(モジュールがダウンロード可能なリポジトリ)に変えました。
greetings.goではgreetingsパッケージで"Hi," +name+"Welcome!"を出力するHell関数を定義しています。変数messageは:=を使用することで型を宣言していませんが、型を宣言する場合は以下のように記述します。
var message string
message = fmt.Sprintf("Hi, %v. Welcome!", name)
関数はfunc 関数名(引数 型) 戻りの型{return 戻り値}
で定義されます。
Call your code from another module
"Create a Go module"では呼び出すgreetings.goを作成しました。
次はそれを呼び出すためのhellパッケージを作っていきます。
% mkdir hello
% cd hello
% go mod init example.com/hello
go: creating new go.mod: module example.com/hello
しかし、これらのモジュールはまだgithubにpushしていないので、アクセスすることができません。そこで以下のコマンドを使用して、モジュールが見つからない際にローカルへリダイレクトするように設定します。
% go mod edit -replace example.com/greetings=../greetings
さらに、まだ存在しませんが、main.goにgreetingsが必要である依存関係を記述します。この時、go.mod内のrequire
にv0.0.0-00010101000000-000000000000
という記述が追加されますが、これはバージョンがない際に使われる疑似番号です。
% go mod tidy
問題なく実行することもできました。
% go run main.go
Hi, Gladys. Welcome!
Return and handle an error
エラーの扱いについてです。コードをgreetings.go
にコピーします。コードにはname
が空である際にエラーを返す機能が追加されています。
実際、実行してみたところ、エラーが出力されました。
% go run .
greetings: empty name
exit status 1
ちなみに、ここでerrors.New("empty name")
の代わりに使われているnil
はnull
のことです。
Return a random greeting
ここではGoのランダム関数とフォーマットに触れます。
まず、greetings.go
を以下のように書き換えます。
Goはpythonでいうformat関数のようなものを使わなくても変数を代入できるのですね。驚きです。
コードを実行してみると、毎回異なる出力が得られます。
% go run main.go
Great to see you, world!
% go run main.go
Hi, world. Welcome!
Return greetings for multiple people
ここではマップとスライスを活用して複数の要素に対してHello関数の処理を実行します。
実行すると、挨拶文のマップを出力として得られました。
% go run main.go
map[Darrin:Hail, Darrin! Well met! Gladys:Hail, Gladys! Well met! Samantha:Hail, Samantha! Well met!]
mapとslice
ここで登場したmap、sliceについて少し触れます。
mapとは
mapとはPythonでいう辞書にあたる要素です。
m := make(map[string]string, 100)
のようにmakeを使って型とサイズを指定して初期化できます。
sliceとは
sliceとはPythonでいうリストにあたる要素です。
s := make([]int, 5, 10)
のようにmakeを使って型とサイズを指定して初期化できます。
Add a test
次はテストの実装を行います。Goでは、ファイル名を_test.go
で終わらせるとことで、go test
コマンドがそのファイルをテスト関数が含まれるファイルとして認識してくれます。
まず、greetings関数のtestを作成します。テスト関数はTestから始まる名前を持ち、testing.T
型のポインタをパラメータとして持ちます。
一つ目のテスト、TestHelloName
はHello関数が有効な応答を返すことができるかのテストを行います。二つ目のテスト、TestHelloEmpty
はHello関数にからの文字列が渡された際にエラーハンドリングが正しく機能するかを検証します。
実際にgo test
コマンドを実行してみます。
% go test
PASS
ok github.com/{}/greetings 0.379s
% go test -v
=== RUN TestHelloName
--- PASS: TestHelloName (0.00s)
=== RUN TestHelloEmpty
--- PASS: TestHelloEmpty (0.00s)
PASS
ok github.com/{}/greetings 0.352s
test関数は異常を検出しませんでした。