$30 off During Our Annual Pro Sale. View Details »

TracePointを活用してモデル名変更の負債解消をした話

alpaca-tc
October 27, 2023

 TracePointを活用してモデル名変更の負債解消をした話

2023-10-27 Kaigi on Rails 発表資料
https://kaigionrails.org/2023/talks/alpaca-tc/

誰もが開発時に直面する「技術的負債」。 あなたもRailsの世界でこの大敵との戦いで手をこまねいていませんか? 特にRubyのような動的言語では、一見シンプルな置換作業でも予期しない障壁が待ち受けています。

私たちのチームは、命名規則の誤りから生じたモデル名の技術的負債と向き合い、60,000行以上の変更を成功させました。 その成功のカギとなったのは「TracePoint」。 さまざまなイベント(メソッド呼び出しなど)をトレースすることができるRubyの強力な標準ライブラリでした。

このセッションでは、TracePointの基本的な使い方や、さらには技術的負債の解消のノウハウを実例とともに紹介します。 この機会に、巨大な技術的負債との戦いに悩むあなたの手札にTracePointという武器を加えてみませんか。

alpaca-tc

October 27, 2023
Tweet

More Decks by alpaca-tc

Other Decks in Programming

Transcript

  1. Λ׆༻ͯ͠
    Ϟσϧ໊มߋͷ
    ෛ࠴ղফΛͨ͠࿩
    @alpaca-tc
    ,BJHJPO3BJMT
    @alpaca_tc
    5SBDF1PJOU

    View Slide

  2. %FWFMPQFS1SPEVDUJWJUZ&OHJOFFS!4NBSU)3
    ΞϧύΧୂ௕
    # 最近のRailsコントリビュート


    # DB制約のチェックをtransaction末尾まで遅延可能


    add_exclusion_constraint(…, deferrable: :deferred)


    add_unique_constraint(…, deferrable: :deferred)

    View Slide

  3. ٕज़తෛ࠴

    View Slide

  4. ٕज़తෛ࠴
    Өڹൣғ͕େ͖͍
    ࡞ۀྔ͕๲େ
    มߋͷ෭࡞༻ͷௐ͕ࠪେม
    Ͳ͔͜ΒखΛ͚ͭΕ͹ʜ

    View Slide

  5. 5SBDF1PJOU
    3VCZඪ४ϥΠϒϥϦ
    ͞·͟·ͳΠϕϯτΛτϨʔε

    View Slide

  6. ͳͥࢲ͸ࠓ೔ൃදΛ͢Δͷ͔
    w ѱ໋໊͍نଇͩͬͨͨΊɺ͢΂ͯͷϞσϧ໊Λมߋ͢Δෛ࠴ղফ
    w DPNNJU ߦ
    w Өڹൣғ͸ΞϓϦέʔγϣϯશମ
    w 5SBDF1PJOU ΠϕϯτΛτϨʔε͢Δඪ४ϥΠϒϥϦ
    Λ׆༻ͯ͠׬਱
    w ࣄྫ͕গͳ͍ͷͰɺօ༷ʹ໾ཱͭώϯτΛൃ৴Ͱ͖Δ͔΋

    View Slide

  7. ෼ͰΈͳ͞·͕ಘΒΕΔ͜ͱ
    w 5SBDF1PJOUͷجૅ஌ࣝ
    w 5SBDF1PJOUΛ׆༻͢ΔͨΊͷ5JQT
    w ॳڃऀ
    w 5SBDF1PJOU͕໘ന͍
    w தڃऀҎ্
    w ෛ࠴ղফʹ໾ཱͭώϯτ

    View Slide

  8. ΞδΣϯμ
    w Ϟσϧ໊Λมߋ͢Δෛ࠴ղফͰ΍Γ͍ͨ͜ͱ
    w 5SBDF1PJOUͷجຊ
    w 5SBDF1PJOUΛ׆༻͢ΔͨΊͷ5JQT
    w σϞ
    w ·ͱΊ

    View Slide

  9. Ϟσϧ໊Λมߋ͢Δ
    ෛ࠴ղফͰ΍Γ͍ͨ͜ͱ

    View Slide

  10. 1SPKFDU4IFFU4FDUJPO*UFN*OQVU/VNCFS
    w ωʔϜεϖʔεͰωετͨ͠Ϟσϧ໊Λ࠾༻͍ͯͨ͠
    w ʮઃఆΑΓن໿ʯʹଇΒͣɺؔ࿈ఆ͕ٛ৑௕
    w ςʔϒϧ໊ɾΧϥϜ໊ɾؔ࿈໊͕௕͘ͳΓ͕ͪ
    w ΧϥϜ໊͕௕͗ͯ͢3BJMTͷόάΛ౿ΉSBJMTSBJMT

    View Slide

  11. 1SPKFDU4IFFU4FDUJPO*UFN*OQVU/VNCFS
    w ϞσϧΛBQQNPEFMT௚Լʹ഑ஔ

    View Slide

  12. 1SPKFDU4IFFU4FDUJPO*UFN*OQVU/VNCFS
    w ϞσϧΛBQQNPEFMT௚Լʹ഑ஔ
    w มߋ͕ඞཁͳ΋ͷ
    w ఆ਺ Ϟσϧ໊
    ɺϑΝΠϧύε
    w ؔ࿈໊ͷϝλϓϩDSFBUF@YYY CVJME@YYY
    w ΧϥϜ໊YYY@JE
    w 'BDUPSZ#PUɺ9YY%FDPSBUPSɺ"3ϝιου܈XIFSFKPJOTʜ

    View Slide

  13. ͱͯ΋ख࡞ۀͰ͸ஔ׵Ͱ͖ͳ͍ʂ

    View Slide

  14. 5SBDF1PJOU
    ର৅ ར༻ՕॴΛಛఆ ஔ׵ॲཧ
    w ఆ਺໊
    w ؔ࿈໊
    w ΧϥϜ໊
    T0ME/FXH
    GJMFQBUIUPBSC-
    GJMFQBUIUPCSC-
    GJMFQBUIUPD@TQFDSC-

    View Slide

  15. 5SBDF1PJOUͷجຊ

    View Slide

  16. 5SBDF1PJOUͷجຊ
    w ඪ४ϥΠϒϥϦ
    w 3VCZ্ͷ༷ʑͳΠϕϯτΛϑοΫͰ͖Δ
    w DBMM3VCZͰهड़͞Εͨϝιουͷݺͼग़͠
    w MJOFࣜͷධՁ
    w SFUVSO3VCZͰهड़͞Εͨϝιουݺͼग़͔͠ΒͷϦλʔϯ
    w SBJTFྫ֎ͷൃੜ
    w ʜଞଟ਺

    View Slide

  17. trace_point = TracePoint.new(:call) do |tp|


    pp([tp.event, tp.method_id, tp.path, tp.defined_class])


    end


    def hello


    "hello"


    end


    def say_hello


    hello


    end


    trace_point.enable


    say_hello


    View Slide

  18. trace_point = TracePoint.new(:call) do |tp|


    pp([tp.event, tp.method_id, tp.path, tp.defined_class])


    end


    def hello


    "hello"


    end


    def say_hello


    hello


    end


    trace_point.enable


    say_hello


    ΠϕϯτͷτϨʔεΛ։࢝

    View Slide

  19. trace_point = TracePoint.new(:call) do |tp|


    pp([tp.event, tp.method_id, tp.path, tp.defined_class])


    end


    def hello


    "hello"


    end


    def say_hello


    hello


    end


    trace_point.enable


    say_hello


    ϝιουݺͼग़͠

    View Slide

  20. trace_point = TracePoint.new(:call) do |tp|


    pp([tp.event, tp.method_id, tp.path, tp.defined_class])


    end


    def hello


    "hello"


    end


    def say_hello


    hello


    end


    trace_point.enable


    say_hello


    ϒϩοΫ͕࣮ߦ
    #=> [:call, :say_hello, "/private/tmp/xxx.rb", Object]

    View Slide

  21. trace_point = TracePoint.new(:call) do |tp|


    pp([tp.event, tp.method_id, tp.path, tp.defined_class])


    end


    def hello


    "hello"


    end


    def say_hello


    hello


    end


    trace_point.enable


    say_hello


    #=> [:call, :say_hello, "/private/tmp/xxx.rb", Object]
    ϝιουݺͼग़͠

    View Slide

  22. trace_point = TracePoint.new(:call) do |tp|


    pp([tp.event, tp.method_id, tp.path, tp.defined_class])


    end


    def hello


    "hello"


    end


    def say_hello


    hello


    end


    trace_point.enable


    say_hello


    ϒϩοΫ͕࣮ߦ
    #=> [:call, :say_hello, "/private/tmp/xxx.rb", Object]


    #=> [:call, :hello, "/private/tmp/xxx.rb", Object]

    View Slide

  23. trace_point = TracePoint.new(:call) do |tp|


    pp([tp.event, tp.method_id, tp.path, tp.defined_class])


    end


    def hello


    "hello"


    end


    def say_hello


    hello


    end


    trace_point.enable


    say_hello


    w EF
    fi
    OFE@DMBTTϝιουΛఆٛͨ͠Ϋϥε͔Ϟδϡʔϧ
    w TFMGΠϕϯτΛൃੜͤͨ͞ΦϒδΣΫτ
    w NFUIPE@JEϝιουͷఆٛ࣌ͷ໊લ
    w QBSNFUFSTϝιουͷύϥϝʔλఆٛ
    w CJOEJOHϝιουͷCJOEJOHɻFWBMΛ࢖͑͹ͳΜͰ΋Ͱ͖Δ

    View Slide

  24. 5SBDF1PJOUΛ
    ׆༻͢ΔͨΊͷ5JQT

    View Slide

  25. 5SBDF1PJOUΛ׆༻͢ΔͨΊͷ5JQT
    w ϝιουͷݺͼग़͠ϑΝΠϧͷಛఆ
    w ϝιουΛݺͼग़ͨ͠ιʔείʔυͷಛఆ
    w 5SBDF1PJOUͷ࠷దԽ
    w ύϥϝʔλ஋ͷऔಘ

    View Slide

  26. ϝιουͷݺͼग़͠ϑΝΠϧͷಛఆ
    5ISFBEFBDI@DBMMFS@MPDBUJPO
    w BQQTQFD഑Լͷஔ׵ର৅ͷϑΝΠϧΛಛఆ͍ͨ͠
    w DBMMFSܥͷελοΫΛར༻͢Δ
    w 3VCZ͔Β͸5ISFBEFBDI@DBMMFS@MPDBUJPO͕଎͍
    ͜Μͳ஋ͷ഑ྻ͕औΕΔ

    View Slide

  27. # 今回の置換では、appかspec配下にあるファイルが対象


    TARGETS = [


    Rails.root.join('app').to_s,


    Rails.root.join('spec').to_s


    ].freeze


    # @return [Thread::Backtrace::Location] ファイルパスや行数情報


    def find_caller_location


    Thread.each_caller_location do |location|


    path = location.path


    next if path == __FILE__


    return location if TARGETS.any? { path.start_with?(_1) }


    end


    end


    #=> "app/models/input_number.rb:11:in `tenant'"


    location = find_caller_location


    View Slide

  28. # 今回の置換では、appかspec配下にあるファイルが対象


    TARGETS = [


    Rails.root.join('app').to_s,


    Rails.root.join('spec').to_s


    ].freeze


    # @return [Thread::Backtrace::Location] ファイルパスや行数情報


    def find_caller_location


    Thread.each_caller_location do |location|


    path = location.path


    next if path == __FILE__


    return location if TARGETS.any? { path.start_with?(_1) }


    end


    end


    #=> "app/models/input_number.rb:11:in `tenant'"


    location = find_caller_location


    ஔ׵ର৅Λࢦఆ

    View Slide

  29. # 今回の置換では、appかspec配下にあるファイルが対象


    TARGETS = [


    Rails.root.join('app').to_s,


    Rails.root.join('spec').to_s


    ].freeze


    # @return [Thread::Backtrace::Location] ファイルパスや行数情報


    def find_caller_location


    Thread.each_caller_location do |location|


    path = location.path


    next if path == __FILE__


    return location if TARGETS.any? { path.start_with?(_1) }


    end


    end


    #=> "app/models/input_number.rb:11:in `tenant'"


    location = find_caller_location


    ελοΫΛॱʹḪΔ

    View Slide

  30. # 今回の置換では、appかspec配下にあるファイルが対象


    TARGETS = [


    Rails.root.join('app').to_s,


    Rails.root.join('spec').to_s


    ].freeze


    # @return [Thread::Backtrace::Location] ファイルパスや行数情報


    def find_caller_location


    Thread.each_caller_location do |location|


    path = location.path


    next if path == __FILE__


    return location if TARGETS.any? { path.start_with?(_1) }


    end


    end


    #=> "app/models/input_number.rb:11:in `tenant'"


    location = find_caller_location


    ஔ׵ର৅ͳΒ஋Λฦ͢

    View Slide

  31. # 今回の置換では、appかspec配下にあるファイルが対象


    TARGETS = [


    Rails.root.join('app').to_s,


    Rails.root.join('spec').to_s


    ].freeze


    # @return [Thread::Backtrace::Location] ファイルパスや行数情報


    def find_caller_location


    Thread.each_caller_location do |location|


    path = location.path


    next if path == __FILE__


    return location if TARGETS.any? { path.start_with?(_1) }


    end


    end


    #=> "app/models/input_number.rb:11:in `tenant'"


    location = find_caller_location


    ύεͱߦ͕Θ͔Δ

    View Slide

  32. ϝιουΛݺͼग़ͨ͠ιʔείʔυͷಛఆ
    5ISFBE#BDLUSBDF-PDBUJPOΑΓਖ਼֬ͳҐஔ৘ใ
    w 5ISFBE#BDLUSBDF-PDBUJPO͸ϑΝΠϧύε΍ߦ਺·Ͱ
    w ϑΝΠϧɾߦ਺ΑΓ΋ਫ਼៛ͳҐஔ৘ใΛಛఆͯ͠ɺஔ׵Λָʹ͍ͨ͠
    w 3VCZ7."CTUSBDU4ZOUBY5SFFͰɺΑΓਖ਼֬ͳίʔυҐஔΛಛఆ͢Δ

    View Slide

  33. class Hello


    def hello


    puts "hello"


    end


    end


    3VCZ7."CTUSBDU4ZOUBY5SFFQBSTF

    SCOPE@#13


    CLASS@#12 COLON2(:Hello)@#0


    SCOPE@#11


    BLOCK@#9


    BEGIN@#1


    DEFN(:hello)@#3 SCOPE@#8


    ARGS@#4


    FCALL(puts)@#5


    LIST@#7


    STR(hello)@#6


    ιʔείʔυ ந৅ߏจ໦ "45

    View Slide

  34. class Hello


    def hello


    puts "hello"


    end


    end


    SCOPE@#13


    CLASS@#12 COLON2(:Hello)@#0


    SCOPE@#11


    BLOCK@#9


    BEGIN@#1


    DEFN(:hello)@#3 SCOPE@#8


    ARGS@#4


    FCALL(puts)@#5


    LIST@#7


    STR(hello)@#6


    3VCZ7."CTUSBDU4ZOUBY5SFFQBSTF

    ֤ϊʔυ͸ϢχʔΫͳOPEF@JEΛ࣋ͭ
    ιʔείʔυ ந৅ߏจ໦ "45

    View Slide

  35. ϝιουͷݺͼग़͠ιʔείʔυͷಛఆ
    3VCZ7."CTUSBDU4ZOUBY5SFF
    w 3VCZ7."CTUSBDU4ZOUBY5SFFOPEF@JE@GPS@CBDLUSBDF@MPDBUJPO
    w 5ISFBE#BDLUSBDF-PDBUJPO͔ΒOPEF@JEΛऔಘͰ͖Δ
    w OPEF@JEͰ୳ࡧ͢Ε͹ɺݺͼग़͠Λͨ͠ιʔείʔυΛಛఆՄೳ
    w ࣮ݧతػೳ
    w ޙํޓ׵ੑ͸อূ͞Εͳ͍
    w ͨ·ʹؒҧͬͨ݁ՌΛฦ͢ ମײͰ͙Β͍

    View Slide

  36. # 呼び出し元のThread::Backtrace::Locationを取得


    caller_location = find_caller_location


    # node_idを取得


    node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(


    caller_location


    )


    # ファイルパス + node_idで正確なソースコードを取得できる


    ast = RubyVM::AbstractSyntaxTree.parse_file(


    caller_location.path,


    keep_script_lines: true


    )


    node = find_node_id(ast, node_id)


    #=> 正確な位置情報やソースコードを取得できる


    [node.first_lineno, node.first_column, node.last_lineno, node.last_column]


    node.source

    View Slide

  37. # 呼び出し元のThread::Backtrace::Locationを取得


    caller_location = find_caller_location


    # node_idを取得


    node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(


    caller_location


    )


    # ファイルパス + node_idで正確なソースコードを取得できる


    ast = RubyVM::AbstractSyntaxTree.parse_file(


    caller_location.path,


    keep_script_lines: true


    )


    node = find_node_id(ast, node_id)


    #=> 正確な位置情報やソースコードを取得できる


    [node.first_lineno, node.first_column, node.last_lineno, node.last_column]


    node.source
    ݺͼग़͠ݩͷ৘ใΛऔಘ

    View Slide

  38. # 呼び出し元のThread::Backtrace::Locationを取得


    caller_location = find_caller_location


    # node_idを取得


    node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(


    caller_location


    )


    # ファイルパス + node_idで正確なソースコードを取得できる


    ast = RubyVM::AbstractSyntaxTree.parse_file(


    caller_location.path,


    keep_script_lines: true


    )


    node = find_node_id(ast, node_id)


    #=> 正確な位置情報やソースコードを取得できる


    [node.first_lineno, node.first_column, node.last_lineno, node.last_column]


    node.source
    OPEF@JEʹม׵

    View Slide

  39. # 呼び出し元のThread::Backtrace::Locationを取得


    caller_location = find_caller_location


    # node_idを取得


    node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(


    caller_location


    )


    # ファイルパス + node_idで正確なソースコードを取得できる


    ast = RubyVM::AbstractSyntaxTree.parse_file(


    caller_location.path,


    keep_script_lines: true


    )


    node = find_node_id(ast, node_id)


    #=> 正確な位置情報やソースコードを取得できる


    [node.first_lineno, node.first_column, node.last_lineno, node.last_column]


    node.source
    "45͔ΒҰக͢ΔOPEFΛऔಘ

    View Slide

  40. # 呼び出し元のThread::Backtrace::Locationを取得


    caller_location = find_caller_location


    # node_idを取得


    node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(


    caller_location


    )


    # ファイルパス + node_idで正確なソースコードを取得できる


    ast = RubyVM::AbstractSyntaxTree.parse_file(


    caller_location.path,


    keep_script_lines: true


    )


    node = find_node_id(ast, node_id)


    #=> 正確な位置情報やソースコードを取得できる


    [node.first_lineno, node.first_column, node.last_lineno, node.last_column]


    node.source ιʔείʔυͷ಺༰

    View Slide

  41. 5SBDF1PJOUͷ࠷దԽ
    w ͜͜ͰΫΠζͰ͢ɻ

    View Slide

  42. count = 0


    TracePoint.new(:call) do


    count += 1


    end.enable


    # この1行で、内部のメソッド呼び出しは何回起こる?


    User.where(id: 1).load


    puts count #=> ???


    DPVOU͸͍ͭ͘ʹͳΔͰ͠ΐ͏͔

    View Slide

  43. DPVOU͸͍ͭ͘ʹͳΔͰ͠ΐ͏͔
    count = 0


    TracePoint.new(:call) do


    count += 1


    end.enable


    # この1行で、内部のメソッド呼び出しは何回起こる?


    User.where(id: 1).load


    puts count #=>


    約5,000回

    View Slide

  44. ૣظϦλʔϯ͢Δ
    ࠷దԽΛ͠·͠ΐ͏

    View Slide

  45. TARGET_METHODS = [


    ActiveRecord::Base.method(:has_one),


    ActiveRecord::Base.method(:has_many),


    ActiveRecord::Base.method(:belongs_to),


    ActiveRecord::Base.method(:has_and_belongs_to_many),


    ].map { [_1.owner, _1.name] }.to_set


    trace_point = TracePoint.new(:call) do |tp|


    next unless TARGET_METHODS.include?([tp.defined_class, tp_method_id]])


    ...


    end


    ඞཁͳϝιουҎ֎͸ૣظϦλʔϯ

    View Slide

  46. TARGET_METHODS = [


    ActiveRecord::Base.method(:has_one),


    ActiveRecord::Base.method(:has_many),


    ActiveRecord::Base.method(:belongs_to),


    ActiveRecord::Base.method(:has_and_belongs_to_many),


    ].map { [_1.owner, _1.name] }.to_set


    trace_point = TracePoint.new(:call) do |tp|


    next unless TARGET_METHODS.include?([tp.defined_class, tp_method_id]])


    ...


    end


    ඞཁͳϝιουҎ֎͸ૣظϦλʔϯ
    ؔ࿈ఆٛͷϝιουΛࢦఆ

    View Slide

  47. TARGET_METHODS = [


    ActiveRecord::Base.method(:has_one),


    ActiveRecord::Base.method(:has_many),


    ActiveRecord::Base.method(:belongs_to),


    ActiveRecord::Base.method(:has_and_belongs_to_many),


    ].map { [_1.owner, _1.name] }.to_set


    trace_point = TracePoint.new(:call) do |tp|


    next unless TARGET_METHODS.include?([tp.defined_class, tp_method_id]])


    ...


    end


    ඞཁͳϝιουҎ֎͸ૣظϦλʔϯ
    τϨʔεର৅֎͸ૣظϦλʔϯ

    View Slide

  48. ύϥϝʔλ஋ͷऔಘ
    w QSFMPBEKPJOTͳͲͷϝιου͸Ҿ਺ͷؔ࿈໊͕ஔ׵ର৅ͩͬͨ
    w 5SBDF1PJOUQBSBNFUFSTͰҾ਺ͷ໊લΛऔಘ
    w 5SBDF1PJOUCJOEJOHMPDBM@WBSJBCMF@HFUʹ౉͢

    View Slide

  49. trace_point = TracePoint.new(:call) do |tp|


    names = tp.parameters.map { _2 }


    parameters = names.to_h do


    [_1, tp.binding.local_variable_get(_1)]


    end


    pp(parameters)


    end


    View Slide

  50. trace_point = TracePoint.new(:call) do |tp|


    names = tp.parameters.map { _2 }


    parameters = names.to_h do


    [_1, tp.binding.local_variable_get(_1)]


    end


    pp(parameters)


    end


    ύϥϝʔλ໊Λऔಘ

    View Slide

  51. trace_point = TracePoint.new(:call) do |tp|


    names = tp.parameters.map { _2 }


    parameters = names.to_h do


    [_1, tp.binding.local_variable_get(_1)]


    end


    pp(parameters)


    end


    ஋Λऔಘ

    View Slide

  52. def a(*, **, &); end


    def b(...); end
    ແ໊Ҿ਺͸MPDBM@WBSJBCMF@HFUͰ͖ͳ͍ʂ

    View Slide

  53. def extract_rest_parameters(*args, **options, &block)


    { :* => args,


    :** => options,


    :"..." => [args, options, block],


    :& => block }


    end


    trace_point = TracePoint.new(:call) do |tp|


    rest_names = tp.parameters.filter_map { _2 if [:*, :**, :&].include?(_2) }


    rest_variables = begin


    tp.binding.eval("extract_rest_parameters(#{rest_names.join(',
    ')})").slice(*rest_names)


    rescue SyntaxError


    tp.binding.eval("extract_rest_parameters(...)").slice(:"...")


    end


    pp rest_variables


    end


    trace_point.enable

    View Slide

  54. def extract_rest_parameters(*args, **options, &block)


    { :* => args,


    :** => options,


    :"..." => [args, options, block],


    :& => block }


    end


    trace_point = TracePoint.new(:call) do |tp|


    rest_names = tp.parameters.filter_map { _2 if [:*, :**, :&].include?(_2) }


    rest_variables = begin


    tp.binding.eval("extract_rest_parameters(#{rest_names.join(',
    ')})").slice(*rest_names)


    rescue SyntaxError


    tp.binding.eval("extract_rest_parameters(...)").slice(:"...")


    end


    pp rest_variables


    end


    trace_point.enable
    ແ໊ͳSFTUҾ਺໊ΛऔΓग़͢

    View Slide

  55. def extract_rest_parameters(*args, **options, &block)


    { :* => args,


    :** => options,


    :"..." => [args, options, block],


    :& => block }


    end


    trace_point = TracePoint.new(:call) do |tp|


    rest_names = tp.parameters.filter_map { _2 if [:*, :**, :&].include?(_2) }


    rest_variables = begin


    tp.binding.eval("extract_rest_parameters(#{rest_names.join(',
    ')})").slice(*rest_names)


    rescue SyntaxError


    tp.binding.eval("extract_rest_parameters(...)").slice(:"...")


    end


    pp rest_variables


    end


    trace_point.enable
    FYUSBDU@SFTU@QBSBNFUFST

    View Slide

  56. def extract_rest_parameters(*args, **options, &block)


    { :* => args,


    :** => options,


    :"..." => [args, options, block],


    :& => block }


    end


    trace_point = TracePoint.new(:call) do |tp|


    rest_names = tp.parameters.filter_map { _2 if [:*, :**, :&].include?(_2) }


    rest_variables = begin


    tp.binding.eval("extract_rest_parameters(#{rest_names.join(',
    ')})").slice(*rest_names)


    rescue SyntaxError


    tp.binding.eval("extract_rest_parameters(...)").slice(:"...")


    end


    pp rest_variables


    end


    trace_point.enable
    ม਺ʹଋറ͞ΕΔ

    View Slide

  57. def extract_rest_parameters(*args, **options, &block)


    { :* => args,


    :** => options,


    :"..." => [args, options, block],


    :& => block }


    end


    trace_point = TracePoint.new(:call) do |tp|


    rest_names = tp.parameters.filter_map { _2 if [:*, :**, :&].include?(_2) }


    rest_variables = begin


    tp.binding.eval("extract_rest_parameters(#{rest_names.join(',
    ')})").slice(*rest_names)


    rescue SyntaxError


    tp.binding.eval("extract_rest_parameters(...)").slice(:"...")


    end


    pp rest_variables


    end


    trace_point.enable
    ύϥϝʔλ໊Λऔಘ

    View Slide

  58. def extract_rest_parameters(*args, **options, &block)


    { :* => args,


    :** => options,


    :"..." => [args, options, block],


    :& => block }


    end


    trace_point = TracePoint.new(:call) do |tp|


    rest_names = tp.parameters.filter_map { _2 if [:*, :**, :&].include?(_2) }


    rest_variables = begin


    tp.binding.eval("extract_rest_parameters(#{rest_names.join(',
    ')})").slice(*rest_names)


    rescue SyntaxError


    tp.binding.eval("extract_rest_parameters(...)").slice(:"...")


    end


    pp rest_variables


    end


    trace_point.enable
    ͷ৔߹͸ͪ͜Β͕ॲཧ͞ΕΔ

    View Slide

  59. σϞ
    w 044NBTUPEPONBTUPEPO
    w ؔ࿈໊Λ࢖ͬͨϝιουݺͼग़͠ՕॴΛର৅
    w CFMPOHT@UPVTFSͳΒ͹
    w CVJME@VTFSDSFBUF@VTFSSFMPBE@VTFSVTFSVTFS

    View Slide

  60. ·ͱΊ
    w 5SBDF1PJOUΛ׆༻͢Δͱϝιουݺͼग़͠ͷ৘ใΛऔಘՄೳ
    w Өڹൣғௐࠪ΍ஔ׵ॲཧʹ׆͔ͤΔ
    w ஔ׵ॲཧ͸τϥϒϧͳ͘׬਱Ͱ͖ͨͷ͔ʁ
    w ʮػցతͳஔ׵ม਺ͳͲҰ෦ͷखಈஔ׵ʯͰ׬਱
    w ςετΛॻ͍͍ͯͳ͔ͬͨՕॴͰΤϥʔ
    w ʮ4NBSU)3Ϟσϧ໊ʯͰݕࡧ͢ΔͱςοΫϒϩά͕ݟΕ·͢
    w 5SBDF1PJOUͷ໘ന͞ɺෛ࠴ղফʹ׆͔ͤͦ͏ͳώϯτ͸఻ΘΓ·͔ͨ͠ʁ

    View Slide

  61. ͋Γ͕ͱ͏͍͟͝·ͨ͠

    View Slide