はじめに
仕事で Go のコードを追いかける事があったので、ちょっと Go に興味が湧き触ってみました。
ページ下部の"参考"に記載されている Microsoft のサイトでハンズオン。ポイントと感想を記載します
開発環境
- OS : Windows 11 Pro
- go : 1.23.3 windows/amd64
- Cursor(IDE) : 0.42.3
Go とは?
- こちらに解りやすくまとまってます
- ざっくり言うと
- シンプルで効率的
- Go は直感的な構文と高速なコンパイル速度を特徴としており、学習しやすく使いやすい設計
- 高い並行性
- Go のゴルーチンとチャネルにより、並列処理を簡潔かつ効率的に実現できる
- 幅広い用途
- サーバーサイド開発、クラウドアプリケーション、システムプログラムなど、多様な領域で活用できる
- シンプルで効率的
インストール
Go ワークスペースを構成する
- ワークスペースを構成するには、ローカルの Go 環境変数で、Go プロジェクト フォルダーの場所を指定します
> [Environment]::SetEnvironmentVariable("GOPATH", "<project-folder>", "User")
- ただし、Go 1.13 以降は、下記になります
- 反映には、プロンプトの再起動が必要! 20 分ぐらい悩みました
go env -w GOPATH=<project-folder>
Go ワークスペース フォルダー
- Go ワークスペースには、次の 3 つの基本フォルダーがあります
- bin
- アプリケーションの実行可能ファイルを含めます
- src
- ワークステーションに存在するすべてのアプリケーションのソース コードを含めます
- pkg
- 使用可能なライブラリのコンパイル済みバージョンを含めます。 再コンパイルせずに、コンパイラから、これらのライブラリにリンクできます
- bin
スゴク違和感。 慣れなんですかね
サンドボックス環境
- Go Playground が用意されていて、サクッと触れるのは素晴らしいです
変数、関数
rune
- int32 ではダメなんですか?
uint
- まぁ、たまに必要になるかな
使用しない変数
- 使用しない変数を宣言すると Go のエラーが出る。IDE で波線出てても、たまに消し忘れるからいいですね。ただ、逆に関数の戻り値とかで、利用しない変数は
_
で表現する手間はありますね
関数
-
戻り値に名前を設定すると、
return
に何も記載しないのは見難い(断捨離しすぎ)func sum(number1 string, number2 string) (result int) { int1, _ := strconv.Atoi(number1) int2, _ := strconv.Atoi(number2) result = int1 + int2 return }
-
複数の値を返すのが気持ち悪い
func calc(number1 string, number2 string) (sum int, mul int) { int1, _ := strconv.Atoi(number1) int2, _ := strconv.Atoi(number2) sum = int1 + int2 mul = int1 * int2 return }
func main() { sum, mul := calc(os.Args[1], os.Args[2]) fmt.Println("Sum:", sum) fmt.Println("Mul:", mul) }
-
関数のパラメーター値 (ポインター)は慣れるまで注意が必要(パット見で見逃す)
func main() { firstName := "John" updateName(&firstName) fmt.Println(firstName) } func updateName(name *string) { *name = "David" }
パッケージ
パッケージ名とファイル名
- Java のファイル名とクラス名と同じようにファイル名とパッケージ名は別でも OK
- ただし、別にすると"モジュール参照"で分かり難くなる
モジュール(パッケージ)
- Go のパッケージは、Java のパッケージとは違う。Java のクラス名みたいなもの
下記の参照からgo_helloworld
とgo_calculator
の関係を良く見てください!
外部パッケージを参照(パッケージの公開)
- モジュール作成にはコマンドが必要
> go mod init github.com/[モジュールパッケージ]
- "go.mod" ファイルが生成される
- GitHub で公開。コード上に
github.com/XXX
とベタで記載されてるのは変な感じ
制御フロー
fallthrough
- switch では
break
を記載しない。1 case で switch ブロックが終了になるのは、解り易くて良いと思う- 他の言語と同様にフォールスルーするには
fallthrough
を case の最後に記載
- 他の言語と同様にフォールスルーするには
panic / recover
- java のtry/catch な感じだけど、書く場所には慣れが必要。特に
recover
は感覚とは違うfunc highlow(high int, low int) { if high < low { fmt.Println("Panic!") panic("highlow() low greater than high") } defer fmt.Println("Deferred: highlow(", high, ",", low, ")") fmt.Println("Call: highlow(", high, ",", low, ")") highlow(high, low+1) } func main() { defer func() { handler := recover() if handler != nil { fmt.Println("main(): recover", handler) } }() highlow(2, 0) fmt.Println("Program finished successfully!") }
データ型
配列、スライス、マップ
-
make
で初期化。new
と似ているけど別物
ログ
ログのファイルパス設定
```
log.SetOutput(file)
```
log 以外のログ記録フレームワーク
- Logrus、zerolog、zap、Apex
メソッド/インターフェース
メソッド
- Java より柔軟だけど、"レシーバー"に違和感
- メソッドで変数を更新する場合は、ポインターを利用する
type triangle struct { size int } func (t triangle) perimeter() int { return t.size * 3 } func (t *triangle) doubleSize() { t.size *= 2 }
インターフェイス
- インターフェースと実装に関連がない
type Shape interface { Perimeter() float64 Area() float64 } type Square struct { size float64 } func (s Square) Area() float64 { return s.size * s.size } func (s Square) Perimeter() float64 { return s.size * 4 } func main() { var s Shape = Square{3} ←ここインターフェースと実装がマッチ fmt.Printf("%T\n", s) fmt.Println("Area: ", s.Area()) fmt.Println("Perimeter:", s.Perimeter()) }
コンカレンシー
Go には、同時実行プログラムを作成するためのスタイルが 2 つある。
1 つは、他の言語と同様に、スレッドを使用する従来のスタイル。もう 1 つは、goroutines という独立したアクティビティ間で値が渡される Go のスタイル
ざっくり言うと、go で平行処理する時には "goroutine"(スレッドみたいな) を利用する。
goroutine (ゴールーチン)
```
func main() {
start := time.Now()
apis := []string{
"https://management.azure.com",
"https://dev.azure.com",
"https://api.github.com",
"https://outlook.office.com/",
"https://api.somewhereintheinternet.com/",
"https://graph.microsoft.com",
}
for _, api := range apis {
go checkAPI(api) // ここが同時実行される!
}
elapsed := time.Since(start)
fmt.Printf("Done! It took %v seconds!\n", elapsed.Seconds())
}
func checkAPI(api string) {
_, err := http.Get(api)
if err != nil {
fmt.Printf("ERROR: %s is down!\n", api)
return
}
fmt.Printf("SUCCESS: %s is up and running!\n", api)
}
```
goroutine の特徴
- 処理順序が保証されない
- メインゴルーチン(main)が終わると他のゴルーチンの終了を待たずにプログラム全体が終了する
チャネル
goroutine 間で値を送信する必要があるとき、チャネルを使用する。
ざっくり言うと、異なるスレッド(goroutine)同士で、値をやりとり(送受信)すること。
なんとなく、別空間で流しっぱでない(戻りを待つ)場合にチャンネルを使うイメージしてます
チャンネルのいろいろ
- バッファ有り/無し
- バッファーありのチャネルの場合、プログラムがブロックされずにデータが送受信され、バッファーありのチャネルはキューのように動作する
- チャネルの方向
- パラメータで指定したチャンネルへの方向(送信/受信)と異なる方向にするとコンパイルエラーになる
- 多重化
- 複数チャンネルの制御。イベント発生での待機など
チャンネルの理解
言葉だけだと理解できないので、ページ下部の GitHub から go_goroutine
リポジトリをご覧ください。
テスト(ユニットテスト)
Java の Junit とほぼ同じ
名称
- ファイル名 : XXX_test.go
- 関数名 : TestXXX(t *testing.T)
インポート
- import "testing"
サンプルアプリ
銀行業務をイメージしたサンプルアプリ。ページ下部の GitHub から go_bankcore
, go_bankapi
リポジトリをご覧ください。
おわりに
まだ、1 歩も進めてないけど、なんとなく切り分けは出来るようになりました。
Go は違和感多めですが、手軽に始められる言語で好感を持てました。特に goroutine とチャンネルを使った平行処理は他のサービスなどと連携する際には使いどころが多いと思います。
ダラダラと感想を書いてしまったので、次回の投稿はもっとポイントを絞って見やすい記事にしたいです。
参考(感謝)