フィヨルドブートキャンプのコードレビューでよく指摘してるシリーズです。
コードレビューをしていると、次のように2つ(もしくは2つ以上)の配列を作って、それらをシンクロさせながらループ処理するロジックをよく見かけます。
# 配列を2つ用意する
names = ['Alice', 'Bob', 'Carol']
points = [90, 98, 85]
# 2つの配列から同じindexの要素を取得しながらループさせる
names.size.times do |i|
name = names[i]
point = points[i]
puts "#{name}さんは#{point}点です"
end
#=> Aliceさんは90点です
# Bobさんは98点です
# Carolさんは85点です
しかし、このロジックは2つの配列の要素数と並ぶ順番に大きく依存します。たとえば次のように片方の要素が増えたり、片方の要素がソートされたりすると、おかしな内容を表示することになります。
names = ['Alice', 'Bob', 'Carol']
points = [90, 98, 85]
# 名前だけ要素が増える
names << 'Dave'
# Daveさんの得点が表示されない!
names.size.times do |i|
name = names[i]
point = points[i]
puts "#{name}さんは#{point}点です"
end
#=> Aliceさんは90点です
# Bobさんは98点です
# Carolさんは85点です
# Daveさんは点です
names = ['Alice', 'Bob', 'Carol']
points = [90, 98, 85]
# 点数がソートされる
points.sort!
# 90点だったAliceさんが85点になっちゃった!
names.size.times do |i|
name = names[i]
point = points[i]
puts "#{name}さんは#{point}点です"
end
#=> Aliceさんは85点です
# Bobさんは90点です
# Carolさんは98点です
配列の二人三脚?
僕はこういうロジックを見ると配列同士が二人三脚しているように見えます。
すなわち、プログラムをちゃんと動かすには「はい、0番目の要素!」「次は1番目の要素!」と2つの配列が息を合わせて交互に足を出さなければなりません。勝手に片方の配列の要素が増えたりすると、足がもつれて転んでしまうわけです。
ちなみにコードレビューしていると「三人四脚」や「五人六脚」みたいなケースもまあまあの頻度で見かけます😅
自分が書いたコードは絶対に大丈夫?でも実務では……
もちろん、プログラミング学習で作るような小規模なプログラムでは「勝手に要素が増えたりソートされたりすることはないから絶対大丈夫!」と思うかもしれません。
しかし、仕事で書くコードはめちゃくちゃ巨大で複雑です(下記Qiita記事を参照)。
そのため、こういうロジックを書いていると、自分の予期しないところで配列の中身が変更されるかもしれません。実際に変更されることはなかったとしても、「ここで使っている配列はどれも絶対に変更されていない!」と断言するのが難しくなるので、不安を抱えたままリリースすることになったり、自信が得られるまでコードを調査するのに時間を取られたりします。
よってこうしたロジックは避けるべきです。
解決策:ハッシュを使おう
この問題の解決策はいくつかありますが、一番お手軽なのはハッシュを使うことです。ハッシュを使えば、nameだけが増えたり、pointだけがソートされたりすることは起きにくくなります。
exam_results = []
exam_results << {name: 'Alice', point: 90}
exam_results << {name: 'Bob', point: 98}
exam_results << {name: 'Carol', point: 85}
exam_results.each do |result|
puts "#{result[:name]}さんは#{result[:point]}点です"
end
#=> Aliceさんは85点です
# Bobさんは90点です
# Carolさんは98点です
他にもデータを格納するクラスを定義したり、Structを使ったりする解決策が考えられます。
いずれにしても、「nameとpointはいつでも1つのセットだよ!!」ということが保証できるようなデータ構造を使うのが大事なポイントです。
まとめ:「もろいプログラム」ではなく「堅牢なプログラム」を書こう
プログラムの良し悪しを評価するときに「このプログラムはもろい」とか、「このプログラムは堅牢だ」という言い方をするときがあります。
もろいプログラムは「壊れやすいプログラム」という意味です。「うっかり○○してしまうと、エラーが出たりおかしな結果になったりする」という場合はもろいプログラムだと言えます。たとえば、最初に紹介した「2つの配列を用意して二人三脚するロジック」はもろいプログラムです。
反対に堅牢なプログラムは「壊れにくいプログラム」という意味です。「よっぽど特殊なことをしない限り、エラーが出たりおかしな結果になったりすることはない」というプログラムは堅牢なプログラムだと言えます。
ちなみに、上で示したハッシュを使う解決策は配列の二人三脚に比べれば堅牢ですが、ハッシュ自体は自由に要素を出し入れできてしまうので、実はそこまで堅牢ではありません。より堅牢さを求めるのであればクラスを導入する方がベターです。この話はチェリー本の第7章の冒頭で同じような事例を出しているので、そちらを参考にしてください。