【Ruby】tap()メソッドを使って効率良くデバッグしてみよう

突然ですが、Rubyでプログラミングするうえで、デバッグは欠かせない作業であることは、言うまでもありませんね。

デバッグとは、プログラム上で定義された変数の状態が、実行中どのように変化するのかを観察する作業と言っても良いでしょう。そのため、デバッグするうえで変数の中身を確認するための処理を記述する必要も出てきます。

そんなときは、tap()メソッドを利用するのがおすすめです。

今回は、デバッグ用に用意されたRubyのメソッドである、tap()メソッドの使い方について解説します。実践で応用できるように、サンプルコードを掲載しましたので、デバッグ作業にお役立てください。

なお、Rubyの魅力や特徴については以下の記事で詳しく解説していますので、こちらもぜひご覧くださいね。

おすすめ記事

みなさんこんにちは! 今回の記事は、 悩みを抱えた人 ・Rubyってどんなプログラミング言語なの?・Rubyを勉強すると将来役に立つかな?・Rubyのおすすめの学習方法が知りたい! というお悩みを解決する記事にな[…]

Rubyとは?その特徴や将来性、おすすめの学習方法まで網羅的にご紹介します

フリーランス案件を探すならこのエージェント!

運営会社 レバテック株式会社 株式会社Branding Engineer 株式会社Hajimari
対応エリア 東京・神奈川・埼玉・千葉
大阪・兵庫・京都・愛知・福岡
東京・神奈川・埼玉・千葉
大阪・兵庫・京都・奈良・和歌山・滋賀
東京・神奈川・埼玉・千葉
茨城・大阪・兵庫・福岡
案件数 約28,000件 約3,300件 約4,200件
平均単価 68.2万円 72.2万円
最高月収 145万円 200万円 176万円
特徴高単価な案件が多い
福利厚生サービスが受けられる
・首都圏の案件が中心
給与保証制度で安定した収入を得られる
・税務処理のサポートあり
・マージン率は実質20%
週2/週3の案件が多い
在宅/リモート案件が多い
・スタートアップ企業やベンチャー企業の案件が多い
紹介企業例 ・株式会社Gunosy
・株式会社バンダイナムコホールディングス
・株式会社ユーザベース
・Sansan株式会社
・株式会社一休
・株式会社FiNC Technologies など
・クックパッド株式会社
・株式会社カオナビ
・株式会社Kaizen Platform など
説明記事
公式サイト


転職を考えているならこのエージェント!

運営会社 レバテック株式会社 株式会社Branding Engineer 株式会社リブセンス
対応エリア 東京・神奈川・埼玉・千葉
大阪・兵庫・京都・愛知・福岡
東京・神奈川・埼玉・千葉
大阪・兵庫・京都・奈良・和歌山・滋賀
東京・神奈川・埼玉・千葉
茨城・大阪・兵庫・福岡
案件数 約13,000件 約800件
※非公開求人が多い

※ドラフト制のため、対象外
年齢層 20代:◎
30代:〇
40代:〇
20代:〇
30代:〇
40代:△
20代:〇
30代:〇
40代:〇
特徴エンジニア・デザイナーを専門としている
・カウンセリングに時間をかける
年収アップ率60%の実績
IT/Web/ゲーム業界専門の転職エージェント
・業界経験者がキャリアコンサルタントを務める
・丁寧なカウンセリングで入社後の離職率1%以下を実現
ドラフト制による採用を叶える特殊なサービス
スキル・経験重視で評価してもらえる
・他エンジニアの入札結果を見ることで
自分の市場価値が分かる
紹介企業例 ・サイボウズ株式会社
・株式会社サイバーエージェント
・Chatwork株式会社 など
・DMM.com
・Money Forward
・SmartHR など
・メルカリ
・ZOZOテクノロジーズ
・freee など
説明記事
公式サイト



tap()メソッドの基本

はじめに、tap()メソッドの基本的な使い方と動作について解説していきます。

tap()メソッドは、Objectクラスのメソッドであるため、Rubyで定義された全てのオブジェクトをレシーバとして呼び出すことが可能です。

tap()メソッドは、レシーバ自身を引数としてブロック内に記述された処理を評価します。そして、ブロック内の計算結果に関わらず、レシーバ自身を戻り値として返します。実際に、以下のサンプルコードで動作を確認してみましょう。

sentence = "I'm runy programmer."

value = sentence.tap { |s| # 引数はレシーバ自身
  # ブロック内は、メソッド呼び出しや代入などの
  # 処理を記述可能
  replaced = s.gsub(/runy/, "Ruby")
  p replaced + "(in tap method)" # "I'm Ruby programmer.(in tap method)"
}

# ブロック内の計算結果は反映されない
p value # "I'm runy programmer."

サンプルコードの3行目において、文字列オブジェクトsentenceをレシーバとして、tap()メソッドを呼び出しています。

tap()メソッドはブロックによる記述が基本形であるため、波括弧で処理が囲まれていることが分かります。「|s|」は、tap()メソッドの引数です。レシーバ自身が格納されているため、引数sにはsentenceが格納されています。

tap()メソッドのブロック内には、gsub()メソッドによる文字列置換処理や、ブロック内で宣言した変数への代入処理、puts()メソッドによる文字列の出力処理が記載されています。

しかし、これらの処理結果はtap()メソッドの戻り値として反映されることはありません。つまり、変数valueにはレシーバ自身、すなわちsentenceが代入されることになります。11行目の文字列出力処理を実行しても分かるように、ブロック内の文字列置換処理が反映されてないことが分かります。

tap()メソッドはデバッグ処理にうってつけ

呼び出し後の処理に影響を及ぼさないtap()メソッドを呼び出す目的は、デバッグ処理の実装にあります。特に、メソッドチェーンで実装した処理のデバッグを実施するときに利用することが主な目的です。

例として、税抜金額が格納された配列に対し、以下の処理を施したいとしましょう。

処理1. 税込価格を計算する。ただし、税率は10%とする。
処理2. 小数点以下を切り捨てる。
処理3. 金額の高い順に並べ替える。

そして、上記の処理を以下のサンプルコードのように実装しました。

# 税抜金額が列挙された配列
price_data = [200, 54000, 400, 60, 540, 5500, 300, 140, 2000]

# 以下の処理を実行する
# 1. 税込金額を計算する(税率10%)
# 2. 小数点以下を切り捨てる
# 3. 降順にソートする
formatted_price_data = price_data.map { |p| p * 1.1 }
                                 .map { |p| p.floor }
                                 .sort
                                 .reverse

p formatted_price_data # [59400, 6050, 2200, 594, 440, 330, 220, 154, 66]

8行目のmap()メソッドでは、配列の各々の要素に対して税込金額の計算処理を実行しています。各要素に対する計算結果を格納した配列が戻り値として返された後、さらにその配列をレシーバとしてmap()メソッドを呼び出しています(9行目)。floor()メソッドは、小数点以下を切り捨てる機能を持つメソッドです。

最後の並べ替え処理は、sort()メソッドで一旦昇順に整列させた後にreverse()メソッドで逆順に並べ替えることで、金額の降順ソート処理を実装しています。このように、立て続けにメソッドを呼び出しているような構造が、メソッドチェーンです。

上記処理の実装に際し、各メソッドの呼び出し時に配列の中身がどのように変化するかを確認したいときに利用するのが、tap()メソッドです。試しに、以下のサンプルコードのように、メソッドチェーンの合間合間にtap()メソッドを呼び出して、配列の中身を確認する処理を追加してみましょう。

ormatted_price_data = price_data.map {|p| p * 1.1 }.tap {|array| p array }
                                .map {|p| p.floor }.tap {|array| p array }
                                .sort.tap { |array| p array }
                                .reverse

実行すると、以下の順番で配列の中身が出力されます。

[220.00000000000003, 59400.00000000001, 440.00000000000006, 66.0, 594.0, 6050.000000000001, 330.0, 154.0, 2200.0]
[220, 59400, 440, 66, 594, 6050, 330, 154, 2200]
[66, 154, 220, 330, 440, 594, 2200, 6050, 59400]
[59400, 6050, 2200, 594, 440, 330, 220, 154, 66]

1行目の出力では、元の配列に対して税込金額を計算した結果が反映されていることが分かります。続けて2行目ではfloor()メソッドによって小数点以下が切り捨てられた結果が、3行目はsort()メソッドによる昇順ソートの結果が反映されています。

tap()メソッドは、破壊的メソッドを使わない限り、ブロック内の処理結果が呼び出し後の処理に影響を及ぼすことはありません。つまり、メソッドチェーンの途中で呼び出しても処理結果が変化しません。したがって、メソッドチェーンの途中結果を出力するのに、tap()メソッドはうってつけといえます。

tap()メソッドの応用テクニック

本来、デバッグ目的で用意されていると言っても良いtap()メソッドですが、デバッグ目的以外で利用することもできます。ここでは、tap()メソッドの応用テクニックをいくつか紹介しましょう。

breakキーワードでブロックの評価結果を返す

tap()メソッドのブロック内でbreakキーワードを用いると、戻り値をブロックの処理結果に変更できます。以下のサンプルコードで、breakキーワードが含まれたtap()メソッドの動作を追ってみましょう。

question = "are you good at soccer?"

# 文頭を大文字に置き換え
question_cap = question.tap { |s|
  break s.capitalize
}

p question_cap # "Are you good at soccer?"

tap()メソッドのレシーバである変数questionには、文頭が小文字の疑問文文字列が格納されており、capitalize()メソッドを用いて文頭を大文字に直す処理を実装しています。

ポイントは、5行目に記されたbreakキーワードです。breakキーワードがない場合、capitalize()メソッドを実行したとしても、変数question_capには修正後の文字列が格納されません。しかし、breakキーワードを付与することで、修正後の文字列がtap()メソッドの戻り値として返されるように動作します。

破壊的メソッドでレシーバを更新する

tap()メソッドのブロック内で破壊的メソッドを呼び出すと、レシーバの状態を変化させることができます。

破壊的メソッドとは、Rubyにおいてレシーバ自身の状態を変化させる類のメソッドを指します。tap()メソッドの中で破壊的メソッドを呼び出すとどのような動作になるのか、以下のサンプルコードで詳しく見てみましょう。

sentence_1 = "I like PHP."

sentence_2 = sentence_1.tap { |s|
  # 破壊的メソッドのsub!()メソッドで文字列を置換
  str = s.sub!("PHP", "Ruby")
  p str # "I like Ruby."
  p s # "I like Ruby."
}

p sentence_1 # "I like Ruby."
p sentence_2 # "I like Ruby."

上記サンプルコードを実行すると、「I like Ruby」が4行出力されることが分かります。それぞれ、どのような経緯で出力されているのかを、順を追って説明していきましょう。

1行目の出力は、サンプルコード6行目のputs()メソッドに対応しています。変数strには、直前に実行されたsub!()メソッドの戻り値が格納されています。sub!()メソッドの戻り値は、置換後の文字列です。つまり「PHP」から「Ruby」に置換された後の文字列「I like Ruby.」が格納されています。

つづいて2行目の出力についてです。変数sは、sub!()メソッドのレシーバです。sub!()メソッド含む破壊的メソッドは、レシーバ自身も更新するのが基本的な特徴であり、sub!()メソッドの実行後、レシーバは置換後の文字列に更新されています。そのため、変数sも置換後の文字列である「I like Ruby.」に更新されるのです。

次に3行目の出力ですが、これは変数sentence_1の中身を表しています。変数sentence_1は、tap()メソッドのレシーバであると同時に、tap()メソッドの引数です。sub!()メソッドによって引数の中身が更新されると、sentence_1の中身も更新され「PHP」から「Ruby」に置き換わります。

最後に、4行目の出力処理についてです。変数sentence_2にはtap()メソッドの戻り値が格納されます。肝心のtap()メソッドの戻り値はレシーバ自身、つまり変数sentence_1です。sentence_1がsub!()メソッドによって更新された後に代入されるため、sentence_2には置換後の文字列が格納されるという動作結果となります。

このように、破壊的メソッドを利用すればtap()メソッドのレシーバや戻り値を変化させることができます。

tap()メソッドで処理をスマートに実装する

tap()メソッドを使うことで、処理をスマートに実装できる場合があります。

例として、チーム名と獲得スコアを保持する「チーム」クラス、およびチーム名と初期スコアを設定するinitialize()メソッド、スコアを加算するupdate_score()メソッドがあるとします。サンプルコードは、以下の通りです。

class Sample_Team_Data
  # チームデータ作成
  def initialize(name)
    @team_name = name
    @score = 0            
  end

  # スコア更新
  def update_score(add_points)
    @score += add_points
  end

  # スコアアクセサ
  def get_score
    @score
  end
end

sample_team = sample_team_Data.new("サンプルチーム")

sample_team.update_score(30)
sample_team.update_score(40)
sample_team.update_score(20)
sample_team.update_score(10)

そのうえで、現在のスコアを取得しつつスコアを0点にリセットするメソッドを実装したいとしましょう。通常は、以下のサンプルコードのように実装すれば問題ありません。

# スコアリセット/最終スコア取得
def reset_score
  last_score = @score
  @score = 0
  last_score
end

# メソッド呼び出し
last_score = sample_team.reset_score

しかし、戻り値用にわざわざローカル変数を用意せずとも、tap()メソッドを使って以下のように実装することでも、同様の機能を実現できます。

def reset_score
  @score.tap { @score = 0 }
end

tap()メソッドの中で、インスタンス変数scoreに0を代入することでリセットしています。しかし、tap()メソッドのブロック内で実施された処理は戻り値に影響を及ぼしません。

したがって、tap()メソッドの戻り値は更新前のスコアとなり、reset_score()メソッド全体の戻り値も、更新前のスコアです。

以上のように、tap()メソッドの仕様を活用することで、冗長な実装を避けることができます。

お仕事の途中ですが、少し一休みして、転職独立について考えてみませんか🙌?

現役エンジニアが選ぶおすすめの転職エージェント11選【成功談・失敗談もあります】

レバテックフリーランスの評判ってどう?【現役エンジニアが徹底解説します】

MidWorks(ミッドワークス)の評判ってどう?【現役エンジニアが徹底解説します】

日々の業務に追われて自分を見失わないよう、
定期的にキャリアを振り返るようにしておきましょう🤲

最後に

さて、ここまでRubyのtap()メソッドの使い方について解説してきましたがいかがでしたか?

tap()メソッドは、実際の業務でもあまり使うことの多くないメソッドではありますが、今回ご紹介した通り、メソッド内でのデバッグ処理を実装する際には非常に役に立つメソッドです。

今回の記事でしっかりとマスターして、今後の開発でも積極的に使っていってくださいね。

このブログを通じて少しでも「傍(はた)を楽(らく)にする」ことができていれば嬉しく思います。

最後まで読んで頂きありがとうございました。