おさとの雑記帳

日々の学習のアウトプットに使っていきます!

Rubyでよく見る(&:)を読み解く

目次

はじめに

この記事はCODE BASE OKINAWA Advent Calendar 2022 - Adventarの10日目のエントリーです。
今まで何気なく使っていた(&:method)がどういう挙動をしているのか見ていきます。
↓こんなやつ

%w(urasoe naha ginowan).map(&:upcase)
#=> ["URASOE", "NAHA", "GINOWAN"]

前知識

(&:)を理解するために必要な知識をそれぞれ見ていきます。

Proc

Procとはブロックをオブジェクトにしたものです。
例えば↓のような使い方ができます。

square = Proc.new { |x| x ** 2 }

square.call(3)

#=> 9

&修飾

Procオブジェクトをブロックとして渡す場合には引数の前に&が必要です。

square = Proc.new { |x| x ** 2 }
[1, 2, 3, 4, 5].map(&square) # Procオブジェクトを渡しているので`:`はつかない
#=> [1, 4, 9, 16, 25]

また、メソッドをブロックとして渡す場合は一度to_procでProcオブジェクトに変換する必要があります。

class Integer
  define_method :square do
    self ** 2
  end
end

square = :square.to_proc # ①

[1, 2, 3, 4, 5].map(&square)
#=> [1, 4, 9, 16, 25]

(&:method)を解読する

前知識を踏まえていよいよ(&:)を解読してみます。

例のコードを読んでみる

例に挙げたコードをもう一度貼っておきます。

%w(urasoe naha ginowan).map(&:upcase)
=> ["URASOE", "NAHA", "GINOWAN"]
  1. &に:upcaseが渡されます
  2. &修飾に:methodを渡すと①で見たProcオブジェクトへの変換が行われます。つまり、シンボルのメソッドに対しto_procが呼び出されます
  3. :upcaseが変換されたProcオブジェクトとなります
  4. 3で変換されたProcオブジェクトがブロックとしてmapに渡されます
  5. その後Procの節で見たように、Procオブジェクトとなったupcase.call()引数に配列の各要素を受け取ります
  6. mapメソッドはProcオブジェクトを実行した戻り値を新たな配列に追加します

上記のような流れを経て(&:method)は動いているのですね。

まとめ

メタプログラミングRubyやチェリー本を読んで面白かったので自分なりに(&:method)について整理してみました。
よきRubyライフを💎

参考

【リーダブルコード】第五章 コメントすべきことを知る の要約

コメントすべきでないこと、すべきなこと

コメントの目的とはなんでしょうか?
それは書き手の意図を読み手に知らせることです。
ただし、全ての意図をコメントすればいいわけではありません。
コメントを読むということはその分だけコードを読む時間が減るので、書くならばコードを読むより価値がある必要があります。
以下ではコメントすべきでないこと、すべきことを見ていきます。

コメントすべきでないこと

  • コードからすぐにわかることを書かない
    例えば以下のようなコメントが該当しそうです。
# 引数を二つ与えて、その合計を返す関数
def total(a, b)
   a + b
end

total(2, 5)
#=> 7
  • ひどい名前を補足するようなコメントを書かない
    ひどい名前ならその名前を変えることをまず検討すべきです。
    ひどいコードに優れたコメントをつけるより、優れたコードを目指しましょう💪

コメントすべきこと

  • 自分の考え
    優れたコメントというのは考えを記録するためのものと定義されています。
# a, b, cの3つの手段を試したが、bが最も計算量を抑えることができたので、この実装にしている

みたいなことでしょうか。

  • コードの欠陥
    コードの欠陥にはコメントを残しておきましょう。
    後でプロジェクト内で探しやすいようにprefixをつけておくと便利です。
記法 意味
TODO: 後で手を付ける
FIXME: 不具合がある
HACK: 無理矢理解決している
WARN: 問題あり
MEMO: 実装メモ
# TODO: クエリ減らす

# HACK: N+1発生してる

# WARN: セキュリティホールあり
  • 定数
    定数を定義する時には、おぼろげながらに浮かんでくることはありません。必ず何かしらの意味や背景があるはずです。
    1クラス最大40人であれば以下のようになるでしょうか。
# 1クラスは最大40人
MAX_STUDENT = 40
  • プロジェクトに途中から入る人に説明するようなこと
    他の人にコードがどのように見えるか想像し、その人がどこで疑問を持ちそうか、どこで間違えそうかを考えて全体の要約コメントを残してあげるのが良さそうです。
    ここでいう他の人とは、プロジェクトを熟知していない人のことです。

ライターズブロック

プログラマがコメントを書きたがらないのは、コメントをうまく書くのは大変だと思っているからだそう。
こうした「ライターズブロック(行き詰まってしまって文章が書けないこと)」を解消するための手順が紹介されていた。
(エントリーを書く時にも使えそう🤔)

  1. 頭の中にあるコメントをとにかく書き出す(例:とんでもない数のクエリが飛んどる)
  2. コメントを読んで改善が必要なものを見つける(とんでもない→すごい数の、飛んどる→発行されている)
  3. 改善する(すごい数のクエリが発行されている)

終わりに

上記のことを実践していくことで、適切なコメントによる優れたコード、他の人が読んだ時に理解まで最短であるコードを実現できそうですね💪

【リーダブルコード】第四章 美しさ の要約

優れたソースコードは「目に優しい」

見た目が美しいコードの方が読みやすいはずですよね。
改行やインデントがメチャクチャなコードは大変読みにくいです。
この章では見た目的にコードを読みやすくするための三つの要素と原則が紹介されています。

要素

  • 余白
  • 配置
  • 順序

原則

  • 一貫性のあるレイアウト
  • 似ているコードは似ているように
  • 関連するコードをまとめてブロックに

一貫性のあるレイアウト

以下はscaffoldで生成したuser_controller#create一貫性のないレイアウトにしたものです。

  def create
    @user = User.new(user_params)
    respond_to do |format|
      if @user.save
                format.html { 
                  redirect_to user_url(@user),
                              notice: 
                    "User was successfully created." 
                }
        format.json { render :show,     status: :created, location: @user }
      else
        format.html { render :new, status: :unprocessable_entity }
                  format.json { render        json: @user.errors,       status:                :unprocessable_entity }
      end
    end
  end

んー読みにくい。
このコードを直していきましょう。

まず、一貫性のあるレイアウトにするためにインデントを修正します。

  def create
    @user = User.new(user_params)
    respond_to do |format|
      if @user.save
        format.html {
          redirect_to user_url(@user),
                          notice:
                "User was successfully created."
          }
        format.json { render :show,     status: :created, location: @user }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render        json: @user.errors,       status:                :unprocessable_entity }
      end
    end
  end

formatの位置が揃ったことで見やすくなりました。
次にスペースの位置を揃えましょう。

  def create
    @user = User.new(user_params)
    respond_to do |format|
      if @user.save
        format.html {
          redirect_to user_url(@user),
                          notice:
                "User was successfully created."
          }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

render以下の位置が揃ったことで見やすくなりました。
ただ、if文の中にある一つ目のformat.htmlの改行が不適切で、似ているコードは似ているようにの原則が守られていないので修正しましょう。

  def create
    @user = User.new(user_params)
    respond_to do |format|
      if @user.save
        format.html { redirect_to user_url(@user), notice: "User was successfully created." }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

これで似ているコードが似ている形になりましたね。
最後に意味のある段落分けをしましょう。

  def create
    @user = User.new(user_params)

    respond_to do |format|
      if @user.save
        format.html { redirect_to user_url(@user), notice: "User was successfully created." }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

変数に値を入れているところと、respondを段落分けすることで処理がみやすくなりました💪

他にもコードを見やすくする方法として

  • 宣言をブロックにまとめる
  • 繰り返し使ってる処理をメソッドにする

などがありました。

最後に重要な考えとして、プロジェクトにアサインしているのであれば、個人の好みよりプロジェクトの規約を大事にすべき(一貫性)というものがありました 👀
仮に規約が一般的でないものだとしても、一貫性が大事ということですね 💪

【リーダブルコード】第三章  誤解されない名前 の要約

最善の名前とは誤解されない名前をつける!

ある変数や関数名をつけた際には「他の意味に取られることはないか?」を自問するべきです。
以下では自問・検討の仕方を見ていきます。

曖昧な名前を避ける

変数に対して代入されるものが、何かに処理をくわえて残ったものか、それとも除外されたものなのか。
ここではfilterと言うメソッドが例に挙げられてました。
filterは絞り込んだ結果を選択するのか、除外するのかがわかりにくいですね。
なので、選択するならselect、除外するならexcludeのような名前にした方が意味が伝わりやすいですね。

ちなみにRubyではfilterselectエイリアスなので実行結果は同じになります。意味を考えて使い分けられるといいですね。
除外の場合はrejectというメソッドが用意されています。

arr = [1, 2, 3, 4, 5]

arr.select { |num| num.even? }
#=> [2, 4]

arr
#=> [1, 2, 3, 4, 5]

arr.filter { |num| num.even? }
#=> [2, 4]

arr.reject { |num| num.even? }
#=> [1, 3, 5]

限界値を含めるときはmin, maxを使う

あるグループへの参加は20人までだとした時に以下のようにしてみます。

TOO_MANY_MEMBERS_LIMIT = 20

if @group.members > TOO_MANY_MEMBERS_LIMIT
  flash[:danger] = "グループに追加できるメンバー数を超過しています"
end

このままだと20を含むか含まないかが曖昧です。
限界値を明確にする時には、min_max_を頭につけてあげましょう。
MAX_MEMBERS_TO_GROUPなどとすると限界値であることがわかりやすいですね👀
(余談: TOにするかINにするかで悩みました、、INだと語気が強いかもと思い、、)

bool値の名前

変数であれば頭にis_つけたり、メソッドであればお尻に?つけたりするとわかりやすいよねって話ですね👀
Rubyの組み込みメソッドであるinclude?なども真偽値を返すメソッドですね。

arr = [1, 2, 3, 4, 5]

arr.include?(1)
#=> true
arr.include?(6)
#=> false

複数の名前を検討する

命名するときは複数の名前をあげて、それぞれの長所と短所を比べてよりいい名前を選定しましょう、ということでした。
状況に応じてlengthが適切だったり、sizeが適切だったりするのが命名の面白いところですね💪

【リーダブルコード】第二章 名前に情報を詰め込む の要約

初めに

変数名や関数名には情報を詰め込むべきです。
例えば、 datatmp などは情報があまり含まれていないといえます(経験としてdataを使った際にレビューをいただいたことが)。
情報を詰め込んだ名前とはどのようなものでしょうか。
命名するポイントの中で特に重要だと感じたのは

  • 明確な単語かどうか
  • 汎用的な名前ではない
  • 抽象的より具体的な名前

の三つです。

明確な単語かどうか

get と言う単語には 「受け取る」「手に入れる」「身につける」などの意味があります。
get関数があるとき、いかにして上記いずれかの意味と知ることができるでしょうか。
仮に receive と言う関数名であれば 何かしらを「受け取る」関数であることを推測できそうです。
fetchであれば何かしらを取ってくる関数であるように感じます。

このように明確な単語を変数や関数名につけてあげることで、より読みやすいコードになります。
コツとして類語辞典をひく、と言うのも挙げられていました。

汎用的な名前ではない

# A
data = {
  first_name: "Rails",
  last_name: "太郎",
  age: 20
}

Aの変数名では中身を見るまでどんなデータが入っているかわかりません。

# B
user_info = {
  first_name: "Rails",
  last_name: "太郎",
  age: 20
}

Bはどうでしょうか?
中身を見るまでもなく、「userに関する情報なんだな」とわかりますね💪

抽象的な名前より具体的な名前

「昨日ラケットでボールを打つスポーツをして筋肉痛なんだよね〜」
...ラケットでボールを打つスポーツ?卓球かな?テニスかな?スカッシュかな?
抽象的すぎる表現を使うとわかりにくいお話になってしまいます。

例えば send_message_to(contact) のようなメソッドがあるとします。このメソッドが「問い合わせに対して何らかのメッセージを送る」のは想像に難くないですね。
ただし、send はいささか抽象的ではないでしょうか?送られてきたものに対して送信はしてるのですが、新規送信のない、返信のみである今回の例の場合 reply_to(conntact) のようにしてあげると、
問い合わせに対して返信していることが明確になりそうです 💪

【リーダブルコード】第一章 理解しやすいコード の要約

「簡潔さ」と「安心」のどちらが大切か、またどのようにその判断を行うか

以下のコードを見比べて見ましょう。

# A
n = 3
puts n % 2 == 0 ? "#{n}は偶数です" : "#{n}は奇数です"

#=> 3は奇数です


# B
n = 3
if n % 2 ==0
  puts "#{n}は偶数です"
else
  puts "#{n}は奇数です"
end

#=> 3は奇数です

Aの方が簡潔で、Bの方が実行したいことがわかりやすい気がします。
おそらくBであればプログラミングをやっていない人にもなんとなく意味が伝わるのではないでしょうか?

例のような簡単な条件分岐であれば大きな差はないかもしれませんが、条件や処理が複雑になったときに重要となる考え方が、コードは他人が見たときに最短で理解できるように書く必要があるというものです。

2通りで書いた処理を誰かに見せたときに、より早く理解してもらった方の処理がより読みやすいコードと言えそうです。ここでいう「誰か」はレビュアーだったりあるいは未来の自分かもしれませんね 🤓

極端な例で言うとBは以下のように書けそうです。

# B2
n = 3
if n % 2  == 0; puts "#{n}は偶数です"; else; puts "#{n}は奇数です"; end

B2よりBの方が理解にかかる時間は短そうですね 🧐
より処理が長く複雑になれば、理解にかかる時間は増えていくのは想像に難くなさそうです 👀

このようにわかりやすいコードを目指す際には理解するまでに最短になっているか確認するのが良さそうです 💪

リーダブルなテストコードについて考えように参加しました!

veriserve-event.connpass.com

こちらのイベントに参加させていただいたので感想や特に学びになったことについて書いていこうと思います 💪

概要

以下イベントページからのコピペです.

読みやすいテストコードは自動テストを長期で運用するために重要な技術の1つです。 しかし、テストコードを読みやすくするにはどうすれば良いかといった議論はユニットテスト以外ではあまり行われていないと感じています。 そこで今回、さまざまなテストレベルとロールで活躍されている方々からテストコードをリーダブルにする方法について語っていただき、それぞれの違いや共通点について議論していただきます。

特に印象に残ったこと

テストコードは過度にDRYにし過ぎない

基本的にコードはDRYなほどいいと思っていましたが、ことテストコードに関してはDRYになりすぎないように気をつける必要があるとのことでした。
例えば変数を過度に使用することはあまり好ましいとは言えないようです。

「わかるようでわからんコード、だがしかしパスするテスト」はApproveしない

このようなコードはきっと読みにくかったり、DRYすぎるコードである可能性があるので、変更をお願いする方がいいようです。
その時のコツは自分を棚に上げることが大事であるとのことでした 笑

cssセレクター使いすぎない

button.primary といった要素の指定の仕方はcssを知らないと何を書いているかわからないでしょう。
テストコードの理想は非エンジニアでも理解できるくらいわかりやすく記述されていることなので、できる限りセマンティックな書き方、ユーザーにとって意味のある書き方ができるといいとのことでした。

ハイレベルテストケース、ローレベルテストケース

具体度を上げた(ローレベル)テストを書く前に、何をテストしているか少し抽象度の高い概念(ハイレベル)で宣言してあげると見通しが良くなる、とのことだと解釈しました。

まとめ

上記の他にも Three Amigos や 種々のツール、意図したテストをどのように表現するかなどが紹介されており学びが多く、聞いてて楽しい時間となりました。
「脳内メモリを使わない」ようなテストコードをかけていけたらなと思います ! 次のイベントに向けてテストについての理解、学びを深めておきたい所存です 💪