AI Session Notes - 2026-03-29
Go のエラー処理パターン
学んだこと
- Go には例外(try/catch)がなく、エラーを戻り値として返すスタイル
- 関数は
(結果, error) の複数戻り値を返し、呼び出し側で if err != nil をチェックする
- 成功時は error に
nil を返し、失敗時はエラー情報を返す
if err != nil の繰り返しが冗長だという批判もあり、Go 2 での改善が長年議論されている
// 関数は (結果, error) の2つを返す
func readFile(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", err // 失敗を返す
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
return "", err
}
return string(data), nil // 成功時は nil
}
// 呼び出し側
content, err := readFile("/path/to/file.txt")
if err != nil {
return err
}
Go の基本構文
:=(短縮変数宣言)
:= は変数の宣言と代入を同時に行う演算子。型推論により型を書かなくてよい
= は既存の変数への代入のみ。:= は新しい変数を作るときだけ使える
- Go コミュニティでは
:= を積極的に使うのがイディオム。var x int = 42 は冗長とみなされる
var を明示的に書くのはゼロ値で初期化したいとき(var count int)など
x := 42 // 型推論で int(推奨)
var x int = 42 // 冗長(非推奨)
var count int // ゼロ値初期化(var を使うべき場面)
複数戻り値
- Go の関数は複数の値を返せる。これがエラー処理パターンの基盤になっている
func 関数名(引数名 型) (戻り値型1, 戻り値型2) の構文
関数は第一級オブジェクト
- 関数を変数に入れたり、引数として渡すことができる
- HTTP ハンドラの登録(
http.HandleFunc("/path", handler))はコールバックパターン
Go のパッケージシステム
学んだこと
- すべてのファイルは
package 宣言が必須
package main + func main() がプログラムの起動地点。特別扱いされ、import されることがない
- それ以外のパッケージはディレクトリ名と一致させるのが慣例
- import パスは「モジュール名 + ディレクトリパス」のフルパスで指定する。相対パス(
"./fizzbuzz")は使えない
- モジュール名は
go.mod で定義する
myapp/
├── go.mod ← module myapp
├── main.go ← package main
└── fizzbuzz/
└── fizzbuzz.go ← package fizzbuzz
import "myapp/fizzbuzz"
fizzbuzz.Run(100) // パッケージ名.関数名 で呼ぶ
public / private は命名規則で決まる
- 大文字始まり → public(他パッケージから呼べる)
- 小文字始まり → private(パッケージ内のみ)
- 関数だけでなく、変数・構造体にも同じルールが適用される
Go のリントツール・フォーマッター
学んだこと
go vet: Go 標準搭載の静的解析ツール。バグになりそうなコードを検出
gofmt: Go 標準のフォーマッター。コードスタイルを強制的に統一
golangci-lint: コミュニティのデファクトスタンダード。go vet を含む数十種類のリンターをまとめて実行できるメタリンター(別途インストールが必要)
- かつて公式チーム製の
golint があったが 2021 年に非推奨。後継的な立ち位置は golangci-lint に含まれる revive 等
go vet ./... # 標準(インストール不要)
gofmt -d . # 標準(インストール不要)
golangci-lint run # 別途インストールが必要
Go のバージョン管理
学んだこと
- Ruby の
rbenv に相当するツールとして goenv や asdf がある
- ただし Go は後方互換性が非常に強く、バージョン間で壊れることが少ないため、バージョン管理ツールなしで運用している人も多い
go.mod の go 1.22 はランタイムのバージョンを切り替える仕組みではなく、互換性の範囲を宣言するもの。Go 1.25 がインストールされていても、1.22 以降に追加された言語機能を使うとエラーにしてくれる
- 公式サイトのインストーラで入れた場合は
/usr/local/go/ に配置される。Homebrew で入れた場合とは管理方法が異なる
Go と Docker
学んだこと
- Go はマルチステージビルドでバイナリだけの軽量イメージを作れる
- ステージ 1: Go 入りのイメージでビルド
- ステージ 2:
scratch(空のイメージ)にバイナリだけコピー
- 最終イメージは 10〜20MB 程度(Node.js 等は数百 MB〜1GB)
FROM golang:1.25 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM scratch
COPY --from=builder /app/myapp /myapp
CMD ["/myapp"]
- バイナリ 1 つで動くなら Docker の恩恵は薄い。Docker を使う理由は Go 自体の都合ではなく、周辺インフラ・運用の都合(docker compose での複数サービス管理、Kubernetes 前提の運用、CI/CD パイプラインの統一フロー等)
- CLI ツールやシンプルな API サーバーならバイナリを直接置いて動かすのもあり
WebSocket チャットの実装アーキテクチャ(Go)
学んだこと
- Go の HTTP サーバーは
http.ListenAndServe が内部で無限ループし、接続を待ち続ける。main 関数の最終行に置くのが一般的
- ルーティングはコールバックパターン。
http.HandleFunc("/ws", handleWebSocket) で「このパスに来たらこの関数を呼んでね」と登録するだけで、実際の呼び出しはフレームワーク側が行う
- WebSocket 接続ごとに
go readMessages(conn) / go writeMessages(conn) で goroutine を起動し、読み書きを並列で動かす
go キーワードをつけないと無限ループでブロックし、次の行に進めない
func main() {
http.HandleFunc("/ws", handleWebSocket)
http.ListenAndServe(":8080", nil) // ここでブロック(接続待ち)
}
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn := // WebSocket 接続を確立
go readMessages(conn) // goroutine で読み込み
go writeMessages(conn) // goroutine で書き込み
}
接続が増えたときの挙動
- ユーザーが接続するたびに
handleWebSocket が呼ばれ、goroutine が 2 個ずつ増える
- 3 人接続すれば goroutine 6 個が並列で動く
- 3/28 で学んだ通り、I/O 待ちの goroutine は M から外れて RAM 上で寝ているだけなので、大量接続でもメモリ消費が少ない
チャット実装に必要な追加知識
- HTTP サーバー(
net/http)
- goroutine / channel の実際の書き方
- struct(構造体)— ユーザーやメッセージの定義
- WebSocket ライブラリ(
gorilla/websocket 等)
- フロント側は HTML + JavaScript の WebSocket API で最低限動く
メタ情報
- ツール: Claude Code
- 関連技術: Go, エラーハンドリング, パッケージシステム, 型推論, golangci-lint, go vet, Docker, マルチステージビルド, WebSocket, net/http, goroutine