Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
13
Help us understand the problem. What are the problem?

posted at

updated at

Organization

【初心者向け】順番に意味がある配列はハッシュに置き換えよう

はじめに:順番に意味のある配列を定義するのは避けよう

プログラミング初心者向けの簡単なTipsです。

何か複数のデータをまとめてひとつの変数に保持したい場合には配列が使えます。
しかし「複数のデータ」とひとことに言っても、配列にまとめて良いデータといけないデータがあります。

たとえば以下のようなデータは配列にまとめるのは望ましくありません。

user_data = [
  170, # 身長
  65,  # 体重
  "AB" # 血液型
]

上のような配列は順番が意味を持ってしまいます。すなわち、

  • indexが0のデータは身長(0以外のindexを指定してはダメ)
  • indexが1のデータは体重(1以外のindexを指定してはダメ)
  • indexが2のデータは血液型(2以外のindexを指定してはダメ)

という暗黙のルールが生まれてしまうためです。
この配列を適切に扱うためには、index(添え字)を間違いなく指定する必要があります。

それゆえ、うっかり以下のようなコードを書いてしまうとバグになってしまいます。

# あっ、index=1は体重ですよ!!
puts "私の身長は#{user_data[1]}cmです"
#=> 私の身長は65cmです

プログラムが小さいうち(100行未満)はこういった配列を使っていてもバグを出さずにコードを書けるかもしれませんが、業務で扱うような巨大で複雑なプログラムになると、「暗黙のルール」が仇(あだ)になってバグの原因になります。

正しい配列かどうかをチェックする方法

「ループ処理でまったく同じ処理を書けるか?(indexによって条件分岐が発生しないか?)」、数値の配列であれば「単純にデータを合計(sum)できるか?(もしくはその結果に意味があるか?)」といった点を自問自答してみましょう。
その答えがNoなら、配列を使うのは適切でない可能性が高いです。

user_data.each_with_index do |value, index|
  # ループ処理してるのにindexに応じて条件分岐するのは何かおかしい
  case index
  when 0
    puts "身長 = #{value}cm"
  when 1
    puts "体重 = #{value}kg"
  when 2
    puts "血液型 = #{value}"
  end
end
#=> 身長 = 170cm
#   体重 = 65kg
#   血液型 = AB
# 単純にsumできないのは何かおかしい(配列に異なるデータ型が混在している)
user_data.sum
#=> `+': String can't be coerced into Integer (TypeError)

# 仮に最初の2要素だけをsumしても、出てきた数値に意味がない(235っていったい何??)
user_data[0..1].sum
#=> 235

一方、クラス全員のテストの得点などは配列にまとめても構いません。

# クラス全員のテストの得点を配列にまとめるのは問題なし
points = [90, 43, 100]

# ループ処理でまったく同じ処理を書けるか? = YES
points.each do |point|
  puts "得点 = #{point}点"
end
#=> 得点 = 90点
#   得点 = 43点
#   得点 = 100点

# 単純にデータを合計(sum)できるか? = YES
points.sum #=> 233

では、「順番に意味のある、おかしな配列」はどう書き換えればいいのでしょうか?

修正方法:代わりにハッシュを使おう

こういうケースではハッシュを使うのが良いです。
冒頭で示したuser_dataはハッシュを使って次のように書き換えることができます。

user_data = {
  height: 170,
  weight: 65,
  blood_type: "AB"
}

こうすればindexではなく、キーで値を参照できるようになるため、不具合が発生する確率が減りますし、可読性も良くなります。

# ハッシュであればキーで指定できるので、:height = 身長ということが見て明らか
puts "私の身長は#{user_data[:height]}cmです"
#=> 私の身長は170cmです

応用:クラスやStructを使うのもアリ

他にもクラスやStructを使う方法もあります。以下はクラスを定義するコード例です。

class UserData
  attr_reader :height, :weight, :blood_type

  def initialize(height, weight, blood_type)
    @height = height
    @weight = weight
    @blood_type = blood_type
  end
end

user_data = UserData.new(170, 65, "AB")
puts "私の身長は#{user_data.height}cmです"
#=> 私の身長は170cmです

ハッシュの場合、うっかりキーをtypoするとnilが返りますが、クラスであればエラーになるのでミスに気づきやすくなります。

# ハッシュでキーをtypoするとnilが返るので間違いに気づきにくい
puts "私の身長は#{user_data[:heihgt]}cmです"
#=> 私の身長はcmです

# クラスであればtypoするとエラーが発生するのですぐ気づける
puts "私の身長は#{user_data.heihgt}cmです"
#=> undefined method `heihgt' for #<UserData:...> (NoMethodError)
#   Did you mean?  height

「単純なデータの入れ物」としての用途しかないのであれば、クラスの代わりにStructを使う(Structでシンプルにクラス定義する)方法もあります。

UserData = Struct.new(:height, :weight, :blood_type)

user_data = UserData.new(170, 65, "AB")
puts "私の身長は#{user_data.height}cmです"
#=> 私の身長は170cmです

typoしたときにエラーが発生する点もクラスと同じです。

puts "私の身長は#{user_data.heihgt}cmです"
#=> undefined method `heihgt' for #<struct UserData ...> (NoMethodError)
#   Did you mean?  height

まとめ

というわけで、この記事では順番に意味がある配列を避け、ハッシュ(またはクラス)に置き換える方法を紹介しました。
複数のデータをひとつにまとめたい、と思ったときは、毎回配列を使うのではなく、適切なデータ型を選択できるようになりましょう😉

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
13
Help us understand the problem. What are the problem?