コードでやりたいことを具現化する
簡単に一言でいいますが、スンゲー難しい。使える表現を増やせど、むしろ学んだものの制約に縛られて、これはこんな感じでしか使えねーし、それならどうすればやりたいことをコードに起こせるのかで悩む方が多い・・・私はまだ未熟な身なので、こんな経験が多い。今回はそんな自分のコードにあった問題点から学んだことをケース毎に纏めて思考の整理するそんな記事です。
第一に表現が冗長
date = []
registerd_list.each do |data|
date.push data.createdAt
end
cnt = 0
date.each do |data|
if date.year == data.year
cnt += 1
end
end
cnt
これはとあるEnumerableクラスを継承しているオブジェクトのデータをループで回し、データ内の作成された日付を配列に突っ込み、その中で年が今と対応している物だけをカウントすると言う代物です。
これはこれでいいんですが、
registerd_list.select {|data| data.created_at.year == date.year}.size
たった1行で表現可能なんですね。ここで使っているのはselectメソッドとsizeメソッドです。
rubyには各クラス毎に使用できるメソッドがかなり大量に用意されています。配列操作系のメソッドはかなりあります。
selectメソッド
各要素に対してブロックを評価した値が真であった要素を全て含む配列を返します。真になる要素がひとつもなかった場合は空の配列を返します。
便利ですなぁ。ループさせてかつ、条件もその中に入れ込めると言うeachとifの能力を併せ持ったメソッドです。
sizeメソッド
配列の長さを返します。配列が空のときは 0 を返します。
lengthとかcountと同じ表現です。
{}.sizeでメソッドチェーンさせられるんですなぁ。do end.sizeもできるみたいですな。兎にも角にもこれで数行のコードの処理がたった1行で粉砕できることになります。いやはや、恐れ入る。
第二にリテラル表記が多い
マジックナンバーとも呼ばれる単発で使われている数字はなんぞや?となるようなコードは読み返したり、誰かに読んで貰う時、この数字は何を表しているのわかりにくいと言うことで極力排除するか、もしくは、意味付けを行うなどして工夫をする必要があります。
マジックナンバーを排除した例
def self.get_year
year = Time.new.year + 1
years = []
while year > 2017 do
year = year - 1
years.push year
end
years
end
これは2017年から今年までのセレクトフォームを作るように作った処理ですが、+1とか-1とか何だこれってなると思うのですが、実際こうでもしないと2017から今の年まで出てこんのですな。Timeオブジェクトで今年から出しているので。
def self.get_years
(2017..Time.new.year).to_a.reverse
end
はい、またたった1行で片付きました。使用しているのはrangeオブジェクトを利用してそこにメソッドチェインさせているのが、to_aとreverse。
to_aメソッド
self を返します。ただし、Array のサブクラスのインスタンスに対して呼ばれた時は、自身を Array に変換したものを返します。
reberseメソッド
reverse は自身の要素を逆順に並べた新しい配列を生成して返します。 reverse! は自身を破壊的に並べ替えます。 reverse! は self を返します。
と言うことで、配列にしなおして、その要素を逆転させることで、最新の年からきれいに配列化させることができていますし、マジックナンバーも消し飛んでますね。
曜日が数字で比較
if full_week_of_one_month.wday == 0
calculation_result_of_amounts.push cnt
cnt = 0
else
calculation_result_of_amounts.push " "
end
上記は、計算結果の配列を作ってる時のコードの1部分ですが、特定の曜日の時だけpushしたかったので、条件式に曜日を使っています。
rubyでは曜日が1が月曜日で0が日曜日、と言うように表現されるので、日曜を判定したかったので、0を使っていたんですが、この6なんだよってなりそうですよね。
WEEK_SUNDAY = 0
if full_week_of_one_month.wday == WEEK_SUNDAY
calculation_result_of_amounts.push cnt
cnt = 0
else
calculation_result_of_amounts.push " "
end
こんな時は数字に意味を持たせて解決します。定数化しておけば別の場所で再利用も可能。これなら何を意味して比較しているのか理解しやすいと思われます。
第三に初期化を理解
初期化と代入の重要性。呼吸するくらいには当然のことなのでしょうが、忘れがち。下記のコードは無限ループする。
それは、当然で、yearを変数に入れ直していないから、ずっと2017より大きくあり続けるからですね。
def self.get_year
year = Time.new.year + 1
while year > 2017 do
year - 1
p year
end
end
直すと下記みたいになる。変数に入れ直せば、-1された年がyearとして次のループした時の条件式に使われるので、きちんと引かれた年数を使って条件に到達するまで引かれるだけで済むと言うことです。
def self.get_year
year = Time.new.year + 1
while year > 2017 do
year = year - 1
p year
end
end
初期化の利用
1週間分のデータを集計したら、次の週のカウントへ移行させるための処理をしているコードの1部分です。
大枠を1ヶ月の日付分ループさせて、その中で1週間分日付を集計させるループを作り、1週間に到達したら配列にデータをpushさせてかつ、カウントをこの時点で初期化すると言う処理です。ループの中に条件式として入れ込んで、cntされた数値を0に戻す訳ですな。
cnt = 0
amounts_of_calculation_result = []
(beginning_week..last_week_of_next_month).each do |date|
users_dates.each do |user_date|
if date.year == user_date.year && date.day == user_date.day
cnt += 1
end
end
if date.wday == WEEK_SUNDAY
amounts_of_calculation_result.push cnt
cnt = 0
end
end
amounts_of_calculation_result
いやはや、難しや、ややこしや
と言うわけで、ロジックを考える時には豊富なメソッドがあること、リテラル表現を避けるか意味をつけること、初期化を利用したりして何とかやりたいことを表現するようにすること。手に入れた表現を何とか組み合わせてロジックを組むことですね。使えるようになった表現は忘れないように都度、アウトプットしていきたいと思います。