配列とスライス
Arrays and slices
配列を使うと、同じ型の複数の要素を特定の順番で変数に格納することができます。
配列を持っていると、それらの要素を繰り返し処理しなければならないことがよくあります。そこで、新しく知ったfor
の知識 を使ってSum
関数を作ってみましょう。Sum
は数値の配列を受け取り、その合計を返します。
テスト駆動開発(TDD)の技術を使ってみましょう
最初にテストを書く
In sum_test.go
配列には固定容量
があり、これは変数を宣言する際に定義します。配列を初期化するには、以下の2つの方法があります。
numbers := [5]int{1, 2, 3, 4, 5}
numbers := [...]int{1, 2, 3, 4, 5}
エラーメッセージに関数への入力を表示するのも便利な場合がありますが、ここでは %v
プレースホルダを使用しています。
テストを実行してみてください
go test
を実行すると、コンパイラは失敗します。 ./sum_test.go:10:15: undefined: Sum
テストを実行するための最小限のコードを書き、失敗したテストの出力をチェックする
In sum.go
これで、テストは明確なエラーメッセージが表示されて失敗するはずです。
sum_test.go:13: got 0 want 15 given, [1 2 3 4 5]
テストをパスするのに十分なコードを書く
特定のインデックスの配列から値を取り出すには、array[index]
構文を使えば大丈夫です。この例ではfor
を使って配列を5回繰り返し、各項目をsum
に加算しています。
リファクタリング♪
コードをきれいにするためにrange
を導入してみましょう。
range
は配列の反復処理を行うことができる。呼び出されるたびにインデックスと値の2つの値を返します。ここではインデックスの値を無視して _
空(スペース)の識別子を使用しています。
配列とその型
配列の興味深い特性として、サイズが型でエンコードされていることが挙げられます。 もし [5]int
を期待する関数に [4]int
を渡そうとしてもコンパイルできません。 これらは異なる型なので、int
を求める関数に string
を渡そうとするのと同じです。
配列の長さが固定されているのは非常に面倒だと思うかもしれませんし、ほとんどの場合、配列を使うことはないでしょう。
Goにはslices
がありますが、これはコレクションのサイズをエンコードするものではなく、任意のサイズを持つことができます。
次の要件は、さまざまなサイズのコレクションを合計することです。
最初にテストを書く
ここでは、任意のサイズのコレクションを持つことができる slice type を使用します。構文は配列と非常に似ていますが、宣言時にサイズを省略するだけです。
myArray := [3]int{1,2,3}
ではなく、mySlice := []int{1,2,3}
こちらです。
テストを実行してみてください
これはコンパイルされません
./sum_test.go:22:13: cannot use numbers (type []int) as type [5]int in argument to Sum
テストを実行するための最小限のコードを書き、失敗したテストの出力をチェックする
ここで問題なのは
引数
Sum
をスライスに変更することで、既存のAPIを壊します。配列に対して、このようなことをすると、破壊された可能性を潜在的発見するので他のテストがコンパイルされません!
新しい関数を作成しましょう
私たちの場合は誰もこの関数を使っていないので、二つの関数を持つよりも一つの関数を持つことにしましょう。
テストを実行しようとしてもコンパイルされないので、最初のテストを配列ではなくスライスで渡すように変更しなければなりません。
テストをパスさせるのに十分なコードを書いてください
コンパイラの問題を修正するだけで、次のことができることがわかりました。
リファクタリング♪
すでにSum
をリファクタリングしており、配列からスライスに変更しただけなので、ここでやることはそれほど多くありません。リファクタリングの段階でテストコードをおろそかにしてはいけないことを覚えておいてください。
テストの価値を問うことは重要です。可能な限り多くのテストを行うことが目標ではなく、むしろコードベースに対して可能な限り多くの信頼
を持つことが目標です。あまりにも多くのテストを持っていると、実際に問題になることがありますし、メンテナンスのオーバーヘッドを増やすだけです。 すべてのテストにはコストがある.
私たちのケースでは、この関数のために2つのテストを持つことが冗長であることがわかります。あるサイズのスライスで動作するなら、どのサイズのスライスでも動作する可能性が高いです。
Goの組み込みテストツールキットには、 カバレッジツールがあり、あなたがカバーしていないコードの領域を特定するのに役立ちます。私が強調したいのは、100%のカバレッジを持つことがゴールではないということです。TDD を厳格に行っていれば、100% に近いカバレッジが得られる可能性が高いでしょう。
実行してみてください。
go test -cover
見てください。
テストの一つを削除して、カバレッジをもう一度確認してください。
これで、十分にテストされた関数を手に入れたことに満足しているので、次の課題に挑戦する前に、あなたの素晴らしい作品をコミットしてください。
次に様々なスライスの数を受け取り、渡された各スライスの SumAll
を含む新しいスライスを返します。
例えば、以下のようになります。
SumAll([]int{1,2}, []int{0,9})
は []int{3, 9}
を返します。
もしくは
SumAll([]int{1,1,1})
は []int{3}
を返します。
最初にテストを書く
テストを実行してみてください
./sum_test.go:23:9: undefined: SumAll
テストを実行するための最小限のコードを書き、失敗したテストの出力をチェックする
テストの目的に応じてSumAll
を定義する必要があります。
Goを使えば、可変数の引数を取ることができる 可変関数 を書くことができます。
コンパイルしようとしても、テストはまだコンパイルされません。
./sum_test.go:26:9: invalid operation: got != want (slice can only be compared to nil)
Goでは、スライスで等号演算子を使うことはできません。got
と want
の各スライスを繰り返し処理して値を確認する関数を書くこともできますが、便利のために reflect.DeepEqual
を使うと、2つの変数が同じであるかどうかを確認するのに便利です。
(DeepEqual
にアクセスできるようにするには、ファイルの先頭でimport reflect
を作成します)
重要なのは、reflect.DeepEqual
は「型安全」ではないことに注意することです。これを確認するには、テストを一時的に変更してください。
ここで行ったことは、slice
と string
を比較しようとしていることです。これでは意味がありませんが、テストはコンパイルされます。だから、reflect.DeepEqual
を使うのはスライスを比較するのに便利な方法ですが、使うときは注意が必要です。
テストを再度変更して実行すると、以下のようなテスト出力が得られるはずです。
sum_test.go:30: got [] want [3 9]
テストをパスするのに十分なコードを書く
必要なのは可変長引数を繰り返し処理して、前に使ったSum
関数を使って合計を計算し、それを返すスライスに追加することです。
新しい学びがたくさんあります!
スライスを作成する新しい方法があります。make
を使うと、numbersToSum
の開始容量が len
であるスライスを作成できるようになります。
配列のように mySlice[N]
でスライスをインデックス化して値を取得したり、=
で新しい値を代入したりすることができます。
これでテストは合格するはずです。
リファクタリング♪
前述したように、スライスには容量があります。容量2のスライスを持っていて mySlice[10] = 1
を実行しようとすると、ランタイムエラーが発生します。
しかし、append
関数を使えば、スライスと新しい値を受け取り、その中にあるすべての項目を含む新しいスライスを返すことができます。
この実装では、容量についてはあまり気にしていません。空のスライス sums
から始め、 可変長引数を処理しながら Sum
の結果をそれに追加します。
次の要件は SumAll
を SumAllTails
に変更することです。コレクションの末尾とは、最初のものを除いたすべてのアイテムのことです。
最初にテストを書く
テストを実行してみてください
./sum_test.go:26:9: undefined: SumAllTails
テストを実行するための最小限のコードを書き、失敗したテストの出力をチェックする
関数名を SumAllTails
に変更し、テストを再実行します。
sum_test.go:30: got [3 9] want [2 9]
テストがパスするのに十分なコードを書く
スライスはスライスすることができます。構文は slice[low:high]
で、:
の片方の辺の値を省略すると、その辺までのすべての値をキャプチャします。この例では、numbers[1:]
を使って「1から最後まで取る」と言っています。スライスを使った他のテストを書いたり、スライス演算子に慣れるために実験をしたりすることに時間を投資したほうがいいかもしれません。
リファクタリング♪
今回はリファクタリングすることはあまりありません。
空のスライスを関数に渡すとどうなると思いますか?
空のスライスの「末尾」とは何ですか?
Goに myEmptySlice[1:]
からすべての要素をキャプチャするように指示するとどうなるか?
最初にテストを書く
テストを実行してみてください
これはランタイムエラーです。 コンパイル時のエラーは、動作するソフトウェアを書くのに役立ちますが、ランタイムエラーはユーザーに影響を与えます。
テストをパスするのに十分なコードを書く
リファクタリング♪
このテストでは、アサーションの周りに繰り返しコードがあるので、それを関数に抽出してみましょう。
これは便利な副作用として、コードに少しだけ型の安全性が追加されます。 アホな開発者が checkSums(t, got, "dave")
で新しいテストを追加しても、コンパイラはその場で止めてくれます。
まとめ
ここで学んだこと
配列
スライス
作り方いろいろ
固定容量を持っていますが、古いものから新しいスライスを作成することができます。
append
を使えば追加できますスライスからスライス
配列やスライスの長さを取得するために
len
を使用します。テストカバレッジツール
reflect.DeepEqual
と、なぜそれが便利なのか、しかしコードの型安全性を低下させる可能性があるのか。
ここまでは整数のスライスや配列を使ってきましたが、配列やスライス自体を含め、他の型でも動作します。ですから、必要に応じて [][]string
の変数を宣言することができます。
スライスの詳細については、スライスに関するGoブログ記事を参照してください。これを読んで学んだことを実証するために、より多くのテストを書いてみてください。
テストを書く以外にGoを使って実験するもう一つの便利な方法は、Goの遊び場です。ほとんどのことを試すことができますし、質問が必要な場合は簡単にコードを共有することができます。 Go playgroundにスライスを入れて実験できるようにしてみました。
配列をスライスした例 では、配列をスライスして、スライスを変更すると元の配列にどのように影響するかを説明していますが、スライスの「コピー」は元の配列には影響しません。
別の例 非常に大きなスライスをスライスした後にコピーを作るのが良い理由があります。
最終更新