第 12 章: エラーハンドリングと型安全性¶
12.1 error インターフェースと安全なファクトリ¶
Go の標準的なエラーハンドリングは error インターフェースと多値返却です。第 7-9 章では panic を使用していましたが、Go の慣習に従い error を返すファクトリメソッドに改善します。
テスト: 安全なファクトリメソッド¶
func TestTryNewFizzBuzzType_正常なタイプを生成できる(t *testing.T) {
fbt, err := TryNewFizzBuzzType(1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
got := fbt.Generate(3)
if got.Value() != "Fizz" {
t.Fatalf("got.Value() = %q, want %q", got.Value(), "Fizz")
}
}
func TestTryNewFizzBuzzType_不正なタイプでエラーを返す(t *testing.T) {
_, err := TryNewFizzBuzzType(99)
if err == nil {
t.Fatal("expected error for invalid type")
}
}
実装コード: TryNewFizzBuzzType
// TryNewFizzBuzzType は安全なファクトリです。不正なタイプでは error を返します。
func TryNewFizzBuzzType(fizzBuzzType int) (FizzBuzzType, error) {
switch fizzBuzzType {
case 1:
return FizzBuzzType01{}, nil
case 2:
return FizzBuzzType02{}, nil
case 3:
return FizzBuzzType03{}, nil
default:
return nil, fmt.Errorf("該当するタイプは存在しません: %d", fizzBuzzType)
}
}
テスト: 安全な FizzBuzzValue 生成¶
func TestTryNewFizzBuzzValue_正の値で生成できる(t *testing.T) {
v, err := TryNewFizzBuzzValue(1, "1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if v.Number() != 1 {
t.Fatalf("Number() = %d, want 1", v.Number())
}
}
func TestTryNewFizzBuzzValue_負の値でエラーを返す(t *testing.T) {
_, err := TryNewFizzBuzzValue(-1, "-1")
if err == nil {
t.Fatal("expected error for negative number")
}
}
実装コード: TryNewFizzBuzzValue
// TryNewFizzBuzzValue は安全なファクトリです。負の値では error を返します。
func TryNewFizzBuzzValue(number int, value string) (FizzBuzzValue, error) {
if number < 0 {
return FizzBuzzValue{}, fmt.Errorf("値は正の値のみ許可します: %d", number)
}
return FizzBuzzValue{number: number, value: value}, nil
}
12.2 型スイッチによるパターンマッチング¶
Go の型スイッチ(type switch)はインターフェース値の具体的な型で分岐するパターンマッチングです。
テスト: 型スイッチで FizzBuzzType の種類を判定する¶
func TestDescribeFizzBuzzType_タイプ名を返す(t *testing.T) {
tests := []struct {
input int
want string
}{
{1, "Standard"},
{2, "NumberOnly"},
{3, "FizzBuzzOnly"},
}
for _, tt := range tests {
fbt := NewFizzBuzzType(tt.input)
got := DescribeFizzBuzzType(fbt)
if got != tt.want {
t.Errorf("DescribeFizzBuzzType(%d) = %q, want %q", tt.input, got, tt.want)
}
}
}
実装コード: DescribeFizzBuzzType
// DescribeFizzBuzzType は型スイッチで FizzBuzzType の種類を文字列で返します。
func DescribeFizzBuzzType(fbt FizzBuzzType) string {
switch fbt.(type) {
case FizzBuzzType01:
return "Standard"
case FizzBuzzType02:
return "NumberOnly"
case FizzBuzzType03:
return "FizzBuzzOnly"
default:
return "Unknown"
}
}
12.3 型安全な列挙型ファクトリ¶
マジックナンバーを排除し、型安全なファクトリパターンを導入します。
テスト: FizzBuzzTypeName で型安全にタイプを指定する¶
func TestFizzBuzzTypeName_型安全にタイプを生成する(t *testing.T) {
fbt := CreateFizzBuzzType(FizzBuzzTypeStandard)
got := fbt.Generate(3)
if got.Value() != "Fizz" {
t.Fatalf("got.Value() = %q, want %q", got.Value(), "Fizz")
}
}
func TestFizzBuzzTypeName_全てのタイプを生成できる(t *testing.T) {
types := []FizzBuzzTypeName{
FizzBuzzTypeStandard,
FizzBuzzTypeNumberOnly,
FizzBuzzTypeFizzBuzzOnly,
}
for _, tn := range types {
fbt := CreateFizzBuzzType(tn)
if fbt == nil {
t.Fatalf("CreateFizzBuzzType(%v) should not return nil", tn)
}
}
}
実装コード: FizzBuzzTypeName
// FizzBuzzTypeName は FizzBuzz タイプの型安全な識別子です。
type FizzBuzzTypeName int
const (
FizzBuzzTypeStandard FizzBuzzTypeName = iota + 1 // 1
FizzBuzzTypeNumberOnly // 2
FizzBuzzTypeFizzBuzzOnly // 3
)
// CreateFizzBuzzType は FizzBuzzTypeName から FizzBuzzType を生成します。
func CreateFizzBuzzType(name FizzBuzzTypeName) FizzBuzzType {
return NewFizzBuzzType(int(name))
}
12.4 FizzBuzzList の検索メソッド¶
テスト: FindFirst — 条件に合致する最初の要素を返す¶
func TestFizzBuzzList_FindFirst_最初のFizzBuzzを見つける(t *testing.T) {
fbt := FizzBuzzType01{}
cmd := NewFizzBuzzListCommand(fbt, 100)
list := cmd.Execute().(*FizzBuzzList)
isFizzBuzz := MakeValuePredicate("FizzBuzz")
v, found := list.FindFirst(isFizzBuzz)
if !found {
t.Fatal("should find FizzBuzz")
}
if v.Number() != 15 {
t.Fatalf("Number() = %d, want 15", v.Number())
}
}
func TestFizzBuzzList_FindFirst_見つからない場合(t *testing.T) {
values := []FizzBuzzValue{NewFizzBuzzValue(1, "1")}
list := NewFizzBuzzList(values)
isFizzBuzz := MakeValuePredicate("FizzBuzz")
_, found := list.FindFirst(isFizzBuzz)
if found {
t.Fatal("should not find FizzBuzz")
}
}
実装コード: FindFirst
// FindFirst は条件に合致する最初の要素を返します。
// 見つからない場合は found が false になります。
func (l *FizzBuzzList) FindFirst(pred Predicate) (FizzBuzzValue, bool) {
for _, v := range l.value {
if pred(v) {
return v, true
}
}
return FizzBuzzValue{}, false
}
テスト: AnyMatch / AllMatch — 存在チェック¶
func TestFizzBuzzList_AnyMatch_Fizzが存在する(t *testing.T) {
fbt := FizzBuzzType01{}
cmd := NewFizzBuzzListCommand(fbt, 15)
list := cmd.Execute().(*FizzBuzzList)
if !list.AnyMatch(MakeValuePredicate("Fizz")) {
t.Fatal("should contain Fizz")
}
}
func TestFizzBuzzList_AllMatch_全て数値ではない(t *testing.T) {
fbt := FizzBuzzType01{}
cmd := NewFizzBuzzListCommand(fbt, 15)
list := cmd.Execute().(*FizzBuzzList)
isNumber := func(v FizzBuzzValue) bool {
return v.Value() != "Fizz" && v.Value() != "Buzz" && v.Value() != "FizzBuzz"
}
if list.AllMatch(isNumber) {
t.Fatal("not all values should be numbers")
}
}
実装コード: AnyMatch / AllMatch
// AnyMatch は条件に合致する要素が 1 つでもあれば true を返します。
func (l *FizzBuzzList) AnyMatch(pred Predicate) bool {
for _, v := range l.value {
if pred(v) {
return true
}
}
return false
}
// AllMatch は全要素が条件に合致すれば true を返します。
func (l *FizzBuzzList) AllMatch(pred Predicate) bool {
for _, v := range l.value {
if !pred(v) {
return false
}
}
return true
}
12.5 ジェネリクス(Go 1.18+)¶
Go 1.18 で導入された型パラメータにより、型安全な汎用関数を書けるようになりました。
テスト: ジェネリック Map 関数¶
func TestGenericMap_FizzBuzzValueを文字列に変換する(t *testing.T) {
values := []FizzBuzzValue{
NewFizzBuzzValue(1, "1"),
NewFizzBuzzValue(3, "Fizz"),
}
result := MapSlice(values, func(v FizzBuzzValue) string {
return v.Value()
})
if result[0] != "1" || result[1] != "Fizz" {
t.Fatalf("MapSlice result = %v", result)
}
}
実装コード: MapSlice(ジェネリクス)
// MapSlice は任意の型のスライスに関数を適用し、変換結果のスライスを返します。
func MapSlice[T any, R any](slice []T, fn func(T) R) []R {
result := make([]R, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
テスト: ジェネリック Filter 関数¶
func TestGenericFilter_正の値だけを抽出する(t *testing.T) {
numbers := []int{-2, -1, 0, 1, 2, 3}
positives := FilterSlice(numbers, func(n int) bool { return n > 0 })
if len(positives) != 3 {
t.Fatalf("len(positives) = %d, want 3", len(positives))
}
}
実装コード: FilterSlice(ジェネリクス)
// FilterSlice は条件に合致する要素だけを含む新しいスライスを返します。
func FilterSlice[T any](slice []T, pred func(T) bool) []T {
var result []T
for _, v := range slice {
if pred(v) {
result = append(result, v)
}
}
return result
}
テスト: ジェネリック Reduce 関数¶
func TestGenericReduce_合計を計算する(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
sum := ReduceSlice(numbers, 0, func(acc, n int) int { return acc + n })
if sum != 15 {
t.Fatalf("ReduceSlice sum = %d, want 15", sum)
}
}
実装コード: ReduceSlice(ジェネリクス)
// ReduceSlice は初期値から始めて各要素に関数を適用し、単一の値に畳み込みます。
func ReduceSlice[T any, R any](slice []T, initial R, fn func(R, T) R) R {
acc := initial
for _, v := range slice {
acc = fn(acc, v)
}
return acc
}
12.6 各言語のエラーハンドリング比較¶
| 機能 | Go | Java | Ruby | TypeScript |
|---|---|---|---|---|
| エラー型 | error インターフェース |
例外(Exception) |
例外(StandardError) |
例外(Error) |
| null 安全 | 多値返却 (T, error) |
Optional<T> |
nil / try_create |
T \| undefined |
| パターンマッチング | 型スイッチ | instanceof / switch |
case/in |
typeof / switch |
| ジェネリクス | 型パラメータ [T any] |
<T> |
なし(動的型付け) | <T> |
| 型安全な列挙 | iota + 型定義 |
enum |
定数モジュール | enum / Union 型 |
Go のエラーハンドリングは例外を使わず、明示的な error 返却により呼び出し側にエラー処理を強制します。これは冗長に見えますが、エラーの流れを追跡しやすい利点があります。
12.7 まとめ¶
本章では以下を学びました:
- error インターフェース:
panicからerror返却への改善(TryNewFizzBuzzType、TryNewFizzBuzzValue) - 型スイッチ: インターフェース値の具体的な型で分岐するパターンマッチング
- 型安全な列挙型:
iotaを使ったマジックナンバーの排除(FizzBuzzTypeName) - 検索メソッド:
FindFirst、AnyMatch、AllMatch - ジェネリクス:
MapSlice[T, R]、FilterSlice[T]、ReduceSlice[T, R]による汎用関数
Go のエラーハンドリングと型システムは Java の Optional や Ruby のパターンマッチングとは異なるアプローチですが、同じ目的(安全性と明確さ)を実現しています。ジェネリクスの導入により、型安全な汎用プログラミングが可能になりました。