Go 言語を公式チュートリアルで始めてみる 2 (Tutorial: Create a module)
記事概要
Go入門者が公式チュートリアルのTutorial: Create a moduleを実施した時の手順と
調べたことを備忘録として残したものです。
Go 言語を公式チュートリアルで始めてみる 1 (Tutorial: Get started with Go)の続きです。
リポジトリはこちら
挨拶モジュール作成
挨拶モジュール用フォルダ作成と移動。
> mkdir greetings && cd greeetings
モジュールの初期化
> go mod init example.com/greetings
greetings.go作成
package greetings
import "fmt"
func Hello(name string) string {
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message
}
- 大文字で始まる関数はエクスポートされ、パッケージ外部から呼び出せる関数となる。
-
:=
は変数の定義と初期化を同時にするショートカット。-
変数を定義する場合は明示的に型を指定して宣言する必要があるが、
:=
を使うことで代入値によって型を推論してくれる。
通常の変数定義var number int number = 5
:=による変数定義
number := 5
-
メインモジュール作成
メインモジュールフォルダ作成と移動。
> cd .. && mkdir hello && cd hello
メインモジュールの初期化。
> go mod init example.com/hello
hello.goの作成
package main
import (
"fmt"
"example.com/greetings"
)
func main() {
message := greetings.Hello("Gladys")
fmt.Println(message)
}
メインモジュール実行
挨拶モジュールは実際に公開しているわけではないので、そのままではexample.com/greetings
の依存関係を解決できない。
example.com/greetings
はローカルの挨拶モジュールを参照するように変更する。
> go mod edit -replace example.com/greetings=../greetings
依存関係更新
> go mod tidy
メインモジュール実行
> go run .
Hi, Gladys. Welcome!
無事表示されました!
エラーハンドリング
greetrings.go にバリデーションを追加。
package greetings
import (
"errors"
"fmt"
)
func Hello(name string)(string, error) {
if name == "" {
return "", errors.New("empty name")
}
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message, nil
}
- 関数は複数の値を返すことができる。
- try/catch/throwがないので、関数の戻り値でエラーを返すのが一般的。
- 大域脱出がしたい場合は defer/recover/panic などを使って実現できるが、パッケージを跨ぐのは非推奨。
hello.goでエラーが発生するように変更するのとエラーチェックの処理を追加する。
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
log.SetPrefix("greetings: ")
log.SetFlags(0)
message, error := greetings.Hello("")
if error != nil {
log.Fatal(error)
}
fmt.Println(message)
}
- log.SetFlagsでログ出力する内容を変更できる。
- eg. ファイル名・行番号(Log.Lshortfile)とローカルタイムゾーン(Log.Ldate)を指定する。
log.SetFlags(log.Lshortfile | log.Ldate)
- https://pkg.go.dev/log#pkg-constants
- eg. ファイル名・行番号(Log.Lshortfile)とローカルタイムゾーン(Log.Ldate)を指定する。
- log.Fatal はログを出力して exit status 1 (異常終了)でプログラムを終了する。
動的な配列(slice)を使って定義済みのフォーマットからランダムなメッセージを返す
greetings.goのHelloでランダムなメッセージを返すように変更。
package greetings
import (
"errors"
"fmt"
"math/rand"
"time"
)
func Hello(name string) (string, error) {
if name == "" {
return "", errors.New("empty name")
}
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
func init() {
rand.Seed(time.Now().UnixNano())
}
func randomFormat() string {
formats := []string{
"Hi, %v. Welcome",
"Great to see you, %v!",
"Hail, %v! Well met!",
}
return formats[rand.Intn(len(formats))]
}
-
randomFormatは小文字から始まるので外部に公開されない(パッケージ内スコープ)
-
init関数はプログラム起動時に自動で呼び出される。
-
配列は要素数が固定の型でサイズの変更はできない。
var numbers [5]int numbers = append(numbers, 9) // エラー
-
スライスでは配列のサイズを動的変更できる。
var numbers []int numbers = append(numbers, 9) // OK
Mapを使って複数人にメッセージを返す。
複数の名前を取得して名前ごとにメッセージを作成するHellos関数を作成する。
package greetings
import (
"errors"
"fmt"
"math/rand"
"time"
)
func Hello(name string) (string, error) {
if name == "" {
return "", errors.New("empty name")
}
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
func Hellos(names []string) (map[string]string, error) {
messages := make(map[string]string)
for _, name := range names {
message, err := Hello(name)
if err != nil {
return nil, err
}
messages[name] = message
}
return messages, nil
}
func init() {
rand.Seed(time.Now().UnixNano())
}
func randomFormat() string {
formats := []string{
"Hi, %v. Welcome",
"Great to see you, %v!",
"Hail, %v! Well met!",
}
return formats[rand.Intn(len(formats))]
}
- 配列の要素分ループするには for … range 構文を使う
- _(空白識別子) は使わない戻り値を安全に破棄するために使う。
- 未使用の変数やインポートのコンパイルエラー抑止。
- https://go.dev/doc/effective_go#blank
- make は指定した型のオブジェクトを割り当て初期化する。
- newはポインターを返し、makeはオブジェクトの実態を返す違いがある。
- https://pkg.go.dev/builtin#make
みんな大好きテスト
挨拶モジュールをテストする greetings_test.go を作成する
package greetings
import (
"regexp"
"testing"
)
func TestHelloName(t *testing.T) {
name := "Gladys"
want := regexp.MustCompile(`\b` + name + `\b`)
msg, err := Hello("Gladys")
if !want.MatchString(msg) || err != nil {
t.Fatalf(`Hello("Gladys") = %q, %v, want match for %#q, nil`, msg, err, want)
}
}
func TestHelloEmpty(t *testing.T) {
msg, err := Hello("")
if msg != "" || err == nil {
t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
}
}
- testing は go標準のテスティングパッケージ。
- _test.goで終わるファイルをテストが含まれているファイルとして認識する。
- テスト名は Test[Name] の形式でつける。
- テストパッケージへのポインタをパラメータとして受け取る。
テストを実行する
> go test
PASS
ok example.com/greetings 0.002s
- _test.goで終わるファイル内のTestName形式の関数をテストする。
- -v オプションで詳細表示
ビルド & インストール
ビルド時して実行
> go build
> ./hello
map[Darrin:Hi, Darrin. Welcome Gladys:Hail, Gladys! Well met! Samantha:Hail, Samantha! Well met!]
- パッケージと依存関係をコンパイルするが、インストールはしない。
- インストールまでする場合は
go install
- テストファイル(_test.go)で終わるファイルは無視する。
- https://pkg.go.dev/cmd/go#hdr-Compile_packages_and_dependencies
- インストールまでする場合は
インストールして実行
> go install
> cd ~/ && hello # インストールされているので他のフォルダからでも呼び出せる
map[Darrin:Hi, Darrin. Welcome Gladys:Hail, Gladys! Well met! Samantha:Hail, Samantha! Well met!]
- パッケージと依存関係をコンパイルしてインストールまで行う。
- インストール先を確認したい場合は
go list -f '{{.Target}}’
- https://pkg.go.dev/cmd/go#hdr-Compile_packages_and_dependencies
- インストール先を確認したい場合は
以上で Tutorial: Create a module
は終わりとなります。
お読みいただいてありがとうございました。
お気づきの点等ありましたらコメントお願いします。
Discussion