> For the complete documentation index, see [llms.txt](https://andmorefine.gitbook.io/learn-go-with-tests/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://andmorefine.gitbook.io/learn-go-with-tests/go-fundamentals/maps.md).

# マップ

マップ

[**この章のすべてのコードはここにあります**](https://github.com/quii/learn-go-with-tests/tree/main/maps)

[配列とスライス](/learn-go-with-tests/go-fundamentals/arrays-and-slices.md)では、値を順番に格納する方法を見ました。 では、`key`でアイテムを保存し、すばやく検索する方法を見てみましょう。

マップを使用すると、辞書と同じようにアイテムを保存できます。 `key`は単語、`value`は定義と考えることができます。 マップについて学ぶのに、自分たちで辞書を作ってみる以上に良い方法があるでしょうか？

まず、辞書にいくつかの単語とその定義がすでに登録されているとして、単語を検索したらその定義が返されるようにします。

## 最初にテストを書く

`dictionary_test.go`

```go
package main

import "testing"

func TestSearch(t *testing.T) {
	dictionary := map[string]string{"test": "this is just a test"}

	got := Search(dictionary, "test")
	want := "this is just a test"

	if got != want {
		t.Errorf("got %q want %q given, %q", got, want, "test")
	}
}
```

マップの宣言は、配列と多少似ています。 例外として、`map`キーワードで始まり、2つのタイプが必要です。 1つはキーのタイプで、`[]`内に記述されます。 2番目は値のタイプで、 `[]`の直後に続きます。

キーのタイプは特別です。 2つのキーが等しいかどうかを判別できないと、正しい値が取得されていることを確認する方法がないため、比較可能な型にしかできません。比較可能な型については、[言語仕様](https://golang.org/ref/spec#Comparison_operators)で詳しく説明しています。

一方、値タイプは任意のタイプにすることができます。別のマップにすることもできます。

このテストのそれ以外の部分は、見覚えがあるはずです。

## テストを実行してみます

`go test`を実行すると、コンパイラは「`./dictionary_test.go:8:9: undefined: Search`」で失敗します。

## テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します

`dictionary.go`

```go
package main

func Search(dictionary map[string]string, word string) string {
	return ""
}
```

テストは *明確なエラーメッセージ* で失敗するはずです。

`dictionary_test.go:12: got '' want 'this is just a test' given, 'test'`.

## 成功させるのに十分なコードを書く

```go
func Search(dictionary map[string]string, word string) string {
	return dictionary[word]
}
```

マップから値を取得する方法は、配列から値を取得する方法と同じで、`map[key]`と書きます。

## リファクタリング♪

```go
func TestSearch(t *testing.T) {
	dictionary := map[string]string{"test": "this is just a test"}

	got := Search(dictionary, "test")
	want := "this is just a test"

	assertStrings(t, got, want)
}

func assertStrings(t testing.TB, got, want string) {
	t.Helper()

	if got != want {
		t.Errorf("got %q want %q", got, want)
	}
}
```

実装をより一般的なものにするために、 `assertStrings`ヘルパーを作成することにしました。

### カスタムタイプを使用する

マップをラップする新しい型を作成し、 `Search`をメソッドにすることで、辞書の使用法を改善できます。

`dictionary_test.go`:

```go
func TestSearch(t *testing.T) {
	dictionary := Dictionary{"test": "this is just a test"}

	got := dictionary.Search("test")
	want := "this is just a test"

	assertStrings(t, got, want)
}
```

まだ定義していない `Dictionary`タイプを使い始めました。次に、 `Dictionary`インスタンスで`Search`を呼び出します。

`assertStrings`を変更する必要はありませんでした。

`dictionary.go`:

```go
type Dictionary map[string]string

func (d Dictionary) Search(word string) string {
	return d[word]
}
```

ここでは、 `map`の薄いラッパーとして機能する`Dictionary`タイプを作成しました。 カスタムタイプが定義されたら、`Search`メソッドを作成できます。

## 最初にテストを書く

基本的な検索は非常に簡単に実装できましたが、辞書にない単語を指定するとどうなりますか？

実際には何も返されません。 プログラムは実行し続けることができるのでこれは良いですが、より良いアプローチがあります。 関数は、単語が辞書にないことを報告できます。 このように、ユーザーは単語が存在しないのか、それとも定義がないのか疑問に思うことはありません（これは、辞書にとってはあまり役に立たないように思われるかもしれません。ただし、他のユースケースで重要になる可能性があるシナリオです）。

```go
func TestSearch(t *testing.T) {
	dictionary := Dictionary{"test": "this is just a test"}

	t.Run("known word", func(t *testing.T) {
		got, _ := dictionary.Search("test")
		want := "this is just a test"

		assertStrings(t, got, want)
	})

	t.Run("unknown word", func(t *testing.T) {
		_, err := dictionary.Search("unknown")
		want := "could not find the word you were looking for"

		if err == nil {
			t.Fatal("expected to get an error.")
		}

		assertStrings(t, err.Error(), want)
	})
}
```

Goでこのシナリオを処理する方法は、 `Error`タイプである2番目の引数を返すことです。

[ポインタとエラーのセクション](/learn-go-with-tests/go-fundamentals/pointers-and-errors.md)で見たように、ここではエラーメッセージをアサートするために、 まずエラーが`nil`でないことを確認し、次に`.Error()`メソッドを使って文字列を取得し、それをアサーションに渡していることに注目してください。

## テストを実行してみます

これはコンパイルされません

```
./dictionary_test.go:18:10: assignment mismatch: 2 variables but 1 values
```

## テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します

```go
func (d Dictionary) Search(word string) (string, error) {
	return d[word], nil
}
```

テストは失敗し、より明確なエラーメッセージが表示されます。

`dictionary_test.go:22: expected to get an error.`

## 成功させるのに十分なコードを書く

```go
func (d Dictionary) Search(word string) (string, error) {
	definition, ok := d[word]
	if !ok {
		return "", errors.New("could not find the word you were looking for")
	}

	return definition, nil
}
```

このテストを通すために、マップのルックアップの興味深い性質を利用しています。ルックアップは2つの値を返すことができます。 2番目の値は、キーが正常に検出されたかどうかを示すブール値です。

この性質により、存在しない単語と、単に定義がないだけの単語を区別できます。

## リファクタリング♪

```go
var ErrNotFound = errors.New("could not find the word you were looking for")

func (d Dictionary) Search(word string) (string, error) {
	definition, ok := d[word]
	if !ok {
		return "", ErrNotFound
	}

	return definition, nil
}
```

`Search`関数にハードコードされたエラー（マジックエラー）は、変数に抽出することで取り除けます。 これにより、より良いテストを書くこともできます。

```go
t.Run("unknown word", func(t *testing.T) {
	_, got := dictionary.Search("unknown")
	if got == nil {
		t.Fatal("expected to get an error.")
	}
	assertError(t, got, ErrNotFound)
})
```

```go
func assertError(t testing.TB, got, want error) {
	t.Helper()

	if !errors.Is(got, want) {
		t.Errorf("got error %q want %q", got, want)
	}
}
```

新しいヘルパーを作成することで、テストを簡素化し、 `ErrNotFound`変数の使用を開始できるため、将来エラーテキストを変更してもテストが失敗しません。

## 最初にテストを書く

辞書を検索する優れた方法はできました。しかし、辞書に新しい単語を追加する方法がまだありません。

```go
func TestAdd(t *testing.T) {
	dictionary := Dictionary{}
	dictionary.Add("test", "this is just a test")

	want := "this is just a test"
	got, err := dictionary.Search("test")
	if err != nil {
		t.Fatal("should find added word:", err)
	}

	assertStrings(t, got, want)
}
```

このテストでは、 `Search`関数を使用して、辞書の検証を少し簡単にします。

## テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します

`dictionary.go`

```go
func (d Dictionary) Add(word, definition string) {
}
```

これでテストは失敗するはずです

```
dictionary_test.go:31: should find added word: could not find the word you were looking for
```

## 成功させるのに十分なコードを書く

```go
func (d Dictionary) Add(word, definition string) {
	d[word] = definition
}
```

マップへの追加も配列に似ています。キーを指定して、値に設定するだけです。

### ポインタ、コピーなど

マップの興味深い特性は、アドレスとして（例：`&myMap`）渡さなくても変更できることです。

これにより、マップが「参照型」であるかのように *感じられる* かもしれませんが、[Dave Cheneyが説明しているように](https://dave.cheney.net/2017/04/30/if-a-map-isnt-a-reference-variable-what-is-it)、そうではありません。

> マップの値は、runtime.hmap構造体へのポインタです。（A map value is a pointer to a runtime.hmap structure.）

つまり、マップを関数/メソッドに渡すと、実際にコピーされますが、コピーされるのはポインタの部分だけで、データを保持している基礎となるデータ構造はコピーされません。

マップの落とし穴は、マップが`nil`値になる可能性があることです。 `nil`マップは読み取り時に空のマップのように動作しますが、`nil`マップに書き込もうとすると、ランタイムパニックが発生します。 マップの詳細については、[こちら](https://blog.golang.org/go-maps-in-action)をご覧ください。

したがって、nilのマップ変数を初期化しないでください。

```go
var m map[string]string
```

代わりに、空のマップを初期化するか、`make`キーワードを使用してマップを作成できます。

```go
var dictionary = map[string]string{}

// OR

var dictionary = make(map[string]string)
```

どちらのアプローチでも空の`hash map`が作成され、`dictionary`はそれを指すようになります。これにより、ランタイムパニックが発生しないことが保証されます。

## リファクタリング♪

実装にはリファクタリングするところはあまりありませんが、テストは少し簡潔にできそうです。

```go
func TestAdd(t *testing.T) {
	dictionary := Dictionary{}
	word := "test"
	definition := "this is just a test"

	dictionary.Add(word, definition)

	assertDefinition(t, dictionary, word, definition)
}

func assertDefinition(t testing.TB, dictionary Dictionary, word, definition string) {
	t.Helper()

	got, err := dictionary.Search(word)
	if err != nil {
		t.Fatal("should find added word:", err)
	}
	assertStrings(t, got, definition)
}
```

単語と定義の変数を作成し、定義アサーションを独自のヘルパー関数に移動しました。

`Add`はよさそうです。ただし、追加しようとしている値がすでに存在する場合に何が起こるかを考慮していませんでした！

値がすでに存在する場合、マップはエラーをスローしません。 代わりに、新しく渡された値でそのまま上書きします。これは実際には便利なこともありますが、関数名が実態を正確に表さなくなります。 `Add`は既存の値を変更すべきではありません。辞書に新しい単語を追加するだけであるべきです。

## 最初にテストを書く

```go
func TestAdd(t *testing.T) {
	t.Run("new word", func(t *testing.T) {
		dictionary := Dictionary{}
		word := "test"
		definition := "this is just a test"

		err := dictionary.Add(word, definition)

		assertError(t, err, nil)
		assertDefinition(t, dictionary, word, definition)
	})

	t.Run("existing word", func(t *testing.T) {
		word := "test"
		definition := "this is just a test"
		dictionary := Dictionary{word: definition}
		err := dictionary.Add(word, "new test")

		assertError(t, err, ErrWordExists)
		assertDefinition(t, dictionary, word, definition)
	})
}
```

このテストでは、エラーを返すように`Add`を変更しました。 これは、新しいエラー変数`ErrWordExists`に対して検証しています。また、前のテストを変更して、`nil`エラーをチェックするようにしました。

## テストを実行してみます

`Add`が値を返していないため、コンパイラは失敗します。

```
./dictionary_test.go:30:13: dictionary.Add(word, definition) used as value
./dictionary_test.go:41:13: dictionary.Add(word, "new test") used as value
```

## テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します

`dictionary.go`

```go
var (
	ErrNotFound   = errors.New("could not find the word you were looking for")
	ErrWordExists = errors.New("cannot add word because it already exists")
)

func (d Dictionary) Add(word, definition string) error {
	d[word] = definition
	return nil
}
```

これで、さらに2つのエラーが発生します。まだ値を変更しており、 `nil`エラーを返しています。

```
dictionary_test.go:43: got error '%!q(<nil>)' want 'cannot add word because it already exists'
dictionary_test.go:44: got 'new test' want 'this is just a test'
```

## 成功させるのに十分なコードを書く

```go
func (d Dictionary) Add(word, definition string) error {
	_, err := d.Search(word)

	switch err {
	case ErrNotFound:
		d[word] = definition
	case nil:
		return ErrWordExists
	default:
		return err
	}

	return nil
}
```

ここでは、エラーを照合するために `switch`ステートメントを使用しています。このような`switch`があると、`Search`が`ErrNotFound`以外のエラーを返す場合に備えて、追加の安全策が提供されます。

## リファクタリング♪

リファクタリングするものはあまりありませんが、エラーの使用が増えるにつれて、いくつかの変更を加えることができます。

```go
const (
	ErrNotFound   = DictionaryErr("could not find the word you were looking for")
	ErrWordExists = DictionaryErr("cannot add word because it already exists")
)

type DictionaryErr string

func (e DictionaryErr) Error() string {
	return string(e)
}
```

エラーを定数にしました。そのために、 `error`インターフェースを実装する独自の`DictionaryErr`型を作成する必要がありました。詳細については、[Dave Cheneyによるこの優れた記事](https://dave.cheney.net/2016/04/07/constant-errors)を参照してください。 簡単に言うと、エラーがより再利用しやすく、不変になります。

次に、単語の定義を`Update`する関数を作成しましょう。

## 最初にテストを書く

```go
func TestUpdate(t *testing.T) {
	word := "test"
	definition := "this is just a test"
	dictionary := Dictionary{word: definition}
	newDefinition := "new definition"

	dictionary.Update(word, newDefinition)

	assertDefinition(t, dictionary, word, newDefinition)
}
```

`Update`は`Add`と非常に密接に関連しており、次はこれを実装します。

## テストを実行してみます

```
./dictionary_test.go:53:2: dictionary.Update undefined (type Dictionary has no field or method Update)
```

## テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します

このようなエラーに対処する方法はすでに知っています。関数を定義する必要があります。

```go
func (d Dictionary) Update(word, definition string) {}
```

これを実行すると、単語の定義を変更する必要があることがわかります。

```
dictionary_test.go:55: got 'this is just a test' want 'new definition'
```

## 成功させるのに十分なコードを書く

`Add`で問題を修正したときに、これを行う方法はすでに見ました。 それでは、`Add`に本当に似たものを実装しましょう。

```go
func (d Dictionary) Update(word, definition string) {
	d[word] = definition
}
```

これは単純な変更だったので、これに必要なリファクタリングはありません。ただし、`Add`と同じ問題が発生しました。 新しい単語を渡すと、 `Update`はそれを辞書に追加します。

## 最初にテストを書く

```go
t.Run("existing word", func(t *testing.T) {
	word := "test"
	definition := "this is just a test"
	dictionary := Dictionary{word: definition}
	newDefinition := "new definition"

	err := dictionary.Update(word, newDefinition)

	assertError(t, err, nil)
	assertDefinition(t, dictionary, word, newDefinition)
})

t.Run("new word", func(t *testing.T) {
	word := "test"
	definition := "this is just a test"
	dictionary := Dictionary{}

	err := dictionary.Update(word, definition)

	assertError(t, err, ErrWordDoesNotExist)
})
```

単語が存在しない場合のエラータイプをさらに追加しました。また、`Update`を変更して`error`値を返すようにしました。

## テストを実行してみます

```
./dictionary_test.go:53:16: dictionary.Update(word, newDefinition) used as value
./dictionary_test.go:64:16: dictionary.Update(word, definition) used as value
./dictionary_test.go:66:23: undefined: ErrWordDoesNotExist
```

今回は3つのエラーが発生しますが、対処方法はわかっています。

## テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します

```go
const (
	ErrNotFound         = DictionaryErr("could not find the word you were looking for")
	ErrWordExists       = DictionaryErr("cannot add word because it already exists")
	ErrWordDoesNotExist = DictionaryErr("cannot perform operation on word because it does not exist")
)

func (d Dictionary) Update(word, definition string) error {
	d[word] = definition
	return nil
}
```

独自のエラータイプを追加し、`nil`エラーを返しています。

これらの変更により、非常に明確なエラーが発生します。

```
dictionary_test.go:66: got error '%!q(<nil>)' want 'cannot perform operation on word because it does not exist'
```

## 成功させるのに十分なコードを書く

```go
func (d Dictionary) Update(word, definition string) error {
	_, err := d.Search(word)

	switch err {
	case ErrNotFound:
		return ErrWordDoesNotExist
	case nil:
		d[word] = definition
	default:
		return err
	}

	return nil
}
```

この関数は、`dictionary`を更新する場合とエラーを返す場合が入れ替わっている点を除けば、`Add`とほとんど同じです。

### 更新（Update）の新しいエラーの宣言に関する注意

`ErrNotFound`を再利用して、新しいエラーを追加しないこともできます。しかし、更新が失敗したときには、それを正確に表すエラーがある方がよい場合が多いです。

特定のエラーがあると、何が問題だったかに関する詳細情報が得られます。以下はWebアプリの例です。

> `ErrNotFound`が発生したときにユーザーをリダイレクトできますが、`ErrWordDoesNotExist`が発生したときにエラーメッセージを表示できます。

次に、辞書の単語を削除（`Delete`）する関数を作成しましょう。

## 最初にテストを書く

```go
func TestDelete(t *testing.T) {
	word := "test"
	dictionary := Dictionary{word: "test definition"}

	dictionary.Delete(word)

	_, err := dictionary.Search(word)
	assertError(t, err, ErrNotFound)
}
```

このテストでは、単語を含む`Dictionary`を作成し、単語が削除されているかどうかを確認します。

## テストを実行してみます

`go test`を実行すると、次のようになります。

```
./dictionary_test.go:74:6: dictionary.Delete undefined (type Dictionary has no field or method Delete)
```

## テストを実行するための最小限のコードを記述し、失敗したテスト出力を確認します

```go
func (d Dictionary) Delete(word string) {

}
```

これを追加すると、テストは単語が削除されていないことを教えてくれます。

```
dictionary_test.go:78: got error '%!q(<nil>)' want 'could not find the word you were looking for'
```

## 成功させるのに十分なコードを書く

```go
func (d Dictionary) Delete(word string) {
	delete(d, word)
}
```

Goには、マップで機能する組み込み関数`delete`があります。 2つの引数を取り、何も返しません。1つ目の引数はマップで、2つ目は削除するキーです。

## リファクタリング♪

リファクタリングするものはあまりありませんが、`Update`と同じロジックを実装して、単語が存在しない場合に対処できます。

```go
func TestDelete(t *testing.T) {
	t.Run("existing word", func(t *testing.T) {
		word := "test"
		dictionary := Dictionary{word: "test definition"}

		err := dictionary.Delete(word)

		assertError(t, err, nil)

		_, err = dictionary.Search(word)

		assertError(t, err, ErrNotFound)
	})

	t.Run("non-existing word", func(t *testing.T) {
		word := "test"
		dictionary := Dictionary{}

		err := dictionary.Delete(word)

		assertError(t, err, ErrWordDoesNotExist)
	})
}
```

## テストを実行してみます

`Delete`の値を返していないため、コンパイラは失敗します。

```
./dictionary_test.go:77:10: dictionary.Delete(word) (no value) used as value
./dictionary_test.go:90:10: dictionary.Delete(word) (no value) used as value
```

## 成功させるのに十分なコードを書く

```go
func (d Dictionary) Delete(word string) error {
	_, err := d.Search(word)

	switch err {
	case ErrNotFound:
		return ErrWordDoesNotExist
	case nil:
		delete(d, word)
	default:
		return err
	}

	return nil
}
```

ここでも、存在しない単語を削除しようとしたときのエラーを照合するために`switch`ステートメントを使用しています。

## まとめ

このセクションでは、多くのことを取り上げました。辞書用に完全なCRUD（作成、読み取り、更新、削除）APIを作成しました。プロセス全体を通じて、次の方法を学びました。

* マップを作成する
* マップ内のアイテムを検索
* マップに新しいアイテムを追加する
* マップのアイテムを更新する
* マップからアイテムを削除する
* エラーについてさらに学んだ
  * 定数であるエラーを作成する方法
  * エラーラッパーを書く
