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

  • 2021年7月13日
  • 2021年10月16日
  • Ruby

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

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

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

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

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

おすすめ記事

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

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

レバテックフリーランス … 業界最大級の案件数業界トップクラスの高単価報酬を誇る最大手のサービスです。実績豊富なコーディネーターが丁寧な対応をしてくれるため、案件の無理な提案はありません。フリーランスで生きていくためにはまず登録しておきましょう。
MidWorks … フリーランス賠償責任保障生命保険の折半など、フリーランスでありながらも正社員並みの保障を受けられるのが特徴です。また、経験豊富なキャリアコンサルタントによる手厚いサポートも受けられるため、安定したフリーランス生活を送りたい方には特におすすめのサービスです。
ポテパンフリーランス … IT業界・技術に詳しいコンサルタントが担当してくれるため、こちらの要望をきちんと理解した上で案件を紹介してくれます。また、案件情報のみならず、フリーランスのイロハについても教えてくれるため、フリーランスとして初めて活動される方には特におすすめのサービスです。

レバテックキャリア … ITエンジニアが利用したい転職エージェントNo.1にも選ばれており、年収600万円以上のハイクラス求人を5,000件以上も保有しています。エンジニアが転職を考えた時にまず初めに登録しておくべきサービスです。
Tech Stars Agent … Tech Stars Agentでは、担当エージェントが全員エンジニア出身のため、スキルやキャリアを見据えたきめ細かな転職支援が受けられます。運営元の株式会社Branding Engineerは、独立支援サービス「MidWorks」も展開しているため、独立を視野に入れたサポートも受けられます
転職ドラフト … 年収UP率93.8%/平均年収UP額126万円と圧倒的な年収UP率を誇るイベント型のエンジニア向け 転職サービスです。毎月1回開催され、厳選された優良IT/Web系企業約150社からダイレクトスカウトを受け取ることができます。年収アップを目指す方は登録必須です。


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

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

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

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