os操作を含む副作用抽象化レビュー完全ガイド:ファイル・環境変数・テスト容易性を支える設計指針
os操作を含む副作用抽象化レビュー完全ガイド
Go言語で os
パッケージは最も設計崩壊を引き起こしやすい危険領域である。
レビューアーは「動くコード」ではなく、副作用抽象化・依存分離・差し替え容易性を基準に構造評価を行う必要がある。
本稿は実務での os
操作コードに対するレビュー観点を、副作用分離・設計責務整理の観点から体系的に解説する実務レビューガイドである。
良い実装例:副作用を責務分離・抽象化した構造
type FileWriter interface {
Write(path string, data []byte) error
}
type OSFileWriter struct{}
func (w *OSFileWriter) Write(path string, data []byte) error {
return os.WriteFile(path, data, 0644)
}
type LogSaver struct {
writer FileWriter
}
func NewLogSaver(w FileWriter) *LogSaver {
return &LogSaver{writer: w}
}
func (s *LogSaver) Save(path, data string) error {
return s.writer.Write(path, []byte(data))
}
- 依存注入による外部依存切り離し
- 実環境とテスト環境での差し替え容易性
- 責務分離により保守性・変更耐性が高い設計
良くない実装例
1. ファイルパス直埋め+副作用直呼び出し
func SaveLog(data string) error {
return os.WriteFile("/var/log/app.log", []byte(data), 0644)
}
@Reviewerファイルパスが直埋めで環境切替不能です。副作用抽象化が行われておらず、テスト容易性も著しく低下します。FileWriterインターフェース分離を検討してください。
2. os.Getenvの直参照構造
func LoadConfig() string {
return os.Getenv("CONFIG_PATH")
}
@Reviewer環境変数への直依存により、テスト時の環境差異を直接持ち込みます。EnvProviderインターフェース設計に切り出し、差し替え可能構造へ移行してください。
3. カレントディレクトリ依存の危険設計
func WriteFile(filename string, data []byte) error {
cwd, _ := os.Getwd()
path := filepath.Join(cwd, filename)
return os.WriteFile(path, data, 0644)
}
@Reviewer実行ディレクトリ依存により環境間で動作差異が発生します。呼び出し側から絶対パスを渡す設計へ変更してください。
4. ファイルハンドル解放漏れ
func ReadFile(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
// defer f.Close()忘れ
return io.ReadAll(f)
}
@Reviewerdefer f.Close()が欠落しています。ファイルディスクリプタリークの原因となります。リソース解放は最優先確認対象です。
副作用抽象化の責務分離モデル
副作用レビューでの典型的依存関係の悪化フロー
よくある抽象設計パターン集
構造 | 抽象インターフェース |
---|---|
ファイル出力 | FileWriter.Write(path, data) |
環境変数取得 | EnvProvider.Get(key) |
現在時刻取得 | Clock.Now() |
UUID生成 | UUIDProvider.New() |
プロセス情報取得 | ProcessInfo.GetPID() |
レビューでは os利用を直接許可しない方針を取る実務現場も多い。副作用抽象を標準構成として整備する設計文化が有効。
テスト容易性の確保:差し替え例
type MockFileWriter struct {
LastPath string
LastData []byte
}
func (m *MockFileWriter) Write(path string, data []byte) error {
m.LastPath = path
m.LastData = data
return nil
}
func TestLogSaver(t *testing.T) {
mockWriter := &MockFileWriter{}
saver := NewLogSaver(mockWriter)
err := saver.Save("test.log", "sample")
if err != nil {
t.Fatal(err)
}
if mockWriter.LastPath != "test.log" {
t.Errorf("unexpected path: %v", mockWriter.LastPath)
}
}
- 実ファイル出力無しで副作用検査可能
- Mock実装の差し替え容易性が構造上保証される
副作用抽象化レビュー観点チェックリスト
観点 | チェック内容 |
---|---|
ファイル操作抽象 | os.WriteFile直参照排除 |
環境変数抽象 | os.Getenv直参照排除 |
カレントディレクトリ依存 | Getwd依存排除 |
リソース解放 | defer Close漏れ確認 |
インターフェース分離 | FileWriter等による抽象化 |
テスト容易性 | Mock差し替え可能構造 |
副作用責務集中 | 副作用アクセス箇所の集中管理設計 |
まとめ:副作用レビューは「外部依存の統制力」を問う
副作用抽象設計は、レビューアーの設計意図読解力と実務統制力が試される領域である。
- 副作用を抽象インターフェース化し構造依存を分離できているか
- テスト・本番・CIすべてで同一構造が成立する設計か
- 責務がファイル・環境変数・プロセスで混在せず集中管理されているか
副作用を「隠蔽」せず「抽象化」する。
この違いをレビューで見抜き、改善指導できるかがレビューアーの設計力である。