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

読みやすいコードとRubyらしいコード

 読みやすいコードとRubyらしいコード

富山Ruby会議01での登壇資料です。

Ruby未経験のエンジニア層が多めと聞いていて、かつ午前2番目の発表だったため、入門っぽい題材にしました。とはいえ経験者もいるので、よく聞く「Rubyらしい」とはどういうことなのかを分析的に考えてみる、という内容にしています。

kokuyouwind

November 03, 2019
Tweet

More Decks by kokuyouwind

Other Decks in Programming

Transcript

  1. 読みやすいコードと
    Ruby
    らしいコード
    株式会社Misoca
    黒曜(@kokuyouwind)

    View Slide

  2. $ whoami
    森 俊介 /
    黒曜
    @kokuyouwind
    株式会社Misoca
    名古屋の会社
    懇親会スポンサー
    @mugi_uno
    のご縁で富⼭に

    View Slide

  3. 📢 宣伝
    フルリモートで働けるRuby
    の会社です

    View Slide

  4. 読みやすいコードと
    Ruby
    らしいコード

    View Slide

  5. 例えばコードレビュー

    View Slide

  6. こう書いたほうが
    読みやすくなる
    んじゃないかな
    こう直したほうが
    Ruby
    っぽい書き⽅
    になってよさそう

    View Slide

  7. 🤔
    どんなコードが読みやすいんだろう?
    Ruby
    らしいとなぜ良いんだろう?
    この2
    つは関係しているのかな?

    View Slide

  8. このふたつの関係を
    考えてみよう

    View Slide

  9. アジェンダ
    「読みやすいコード」を考える
    「Ruby
    らしいコード」を考える
    読みやすさとRuby
    らしさの関係を考える
    まとめ

    View Slide

  10. 「読みやすいコード」を
    考える

    View Slide

  11. https://www.oreilly.co.jp/books/9784873115658/

    View Slide

  12. 🙋読んだことある⼈?

    View Slide

  13. コードは理解しやすくなければいけない。
    コードは他の⼈が最短時間で理解できるように
    書かなければいけない。
    「リーダブルコード」 pp.2-3

    View Slide

  14. 他の⼈が
    素早く理解できるのが
    読みやすいコード

    View Slide

  15. 🤔
    理解するとは?

    View Slide

  16. 「コードを理解する」というのは、
    変更を加えたり バグを⾒つけたりできる
    という意味だ。
    「リーダブルコード」 p.3

    View Slide

  17. 「コードを理解する」というのは、
    変更を加えたり バグを⾒つけたりできる
    という意味だ。
    「リーダブルコード」 p.3

    View Slide

  18. 「コードを理解する」というのは、
    変更を加えたり バグを⾒つけたりできる
    という意味だ。
    「リーダブルコード」 p.3
    コードの意図(
    なにをしたいか)
    が読み取れる

    View Slide

  19. 「コードを理解する」というのは、
    変更を加えたり バグを⾒つけたりできる
    という意味だ。
    「リーダブルコード」 p.3
    コードの意図(
    やりたいこと)
    が読み取れる

    View Slide

  20. 「コードを理解する」というのは、
    変更を加えたり バグを⾒つけたりできる
    という意味だ。
    「リーダブルコード」 p.3
    コードの意図(
    やりたいこと)
    が読み取れる
    コードの挙動(
    どう動くか)
    が読み取れる

    View Slide

  21. コードの意図と挙動が
    読み⼿に伝わるのが
    読みやすいコード

    View Slide

  22. 第I
    部 表⾯上の改善
    良い名前で意図を伝える
    getPage
    ではなくfetchPage
    意図が誤解されない名前を使う
    filter
    ではなくselect, exclude
    適切なコメントで意図を伝える

    View Slide

  23. 第II
    部 ループとロジックの単純化
    制御フローを簡潔にする
    挙動を追いやすくする
    巨⼤な式を説明変数や要約変数で分割する
    挙動を追いやすくする
    部分式の意図を伝える

    View Slide

  24. 第III
    部 コードの再構成
    無関係の下位問題を抽出する
    重要な挙動に注⽬できる
    レベルが揃い意図を伝えやすくなる
    ⼀度に1
    つのことを
    挙動と意図を(ry

    View Slide

  25. 「読みやすいコード」を考える
    意図と挙動が伝わるのが読みやすいコード
    なにがやりたいのか
    どう動くのか
    詳しくは リーダブルコードを読もう!
    細かいテクニックがいろいろ載っている

    View Slide

  26. アジェンダ
    「読みやすいコード」を考える
    「Ruby
    らしいコード」を考える
    読みやすさとRuby
    らしさの関係を考える
    まとめ

    View Slide

  27. 「Ruby
    らしいコード」を
    考える

    View Slide

  28. Ruby-ish

    View Slide

  29. 🤔
    Ruby
    らしいってどういうこと?

    View Slide

  30. https://magazine.rubyist.net/articles/0043/0043-BeALibraryDeveloper.html

    View Slide

  31. 「Ruby
    らしい」とはどういうことでしょうか。
    「○○
    らしい」とは「他と似ている」ということ
    です。
    「Ruby
    らしい」書き⽅だとまわりのコードと似
    たような記述になります。
    「ライブラリー開発者になろう」

    View Slide

  32. Ruby
    でよく使われる
    書き⽅と似ているのが
    Ruby
    らしいコード

    「Ruby
    らしさ」の定義としては微妙だが、
    「Ruby
    らしいコード」であるかの基準として有⽤

    View Slide

  33. アジェンダ
    「読みやすいコード」を考える
    「Ruby
    らしいコード」を考える
    読みやすさとRuby
    らしさの関係を考える
    まとめ

    View Slide

  34. 読みやすさと
    Ruby
    らしさの
    関係を考える

    View Slide

  35. Ruby
    らしいコード例:
    ブロックを使う

    View Slide

  36. ブロックを使う
    #
    これよりも
    file = File.open(path)
    file.read
    file.close
    #
    こっちのほうがRuby
    らしい
    File.open(path) do |file|
    file.read
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9

    View Slide

  37. ブロックを使う
    #
    これよりも
    file = File.open(path) #
    前処理
    file.read #
    やりたいこと
    file.close #
    後処理
    #
    こっちのほうがRuby
    らしい
    File.open(path) do |file|
    file.read #
    やりたいこと
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9

    View Slide

  38. ブロックを使う
    前処理・後処理を分離できる
    ブロックの中に意図が集まる
    「無関係の下位問題を抽出する」
    変数のスコープが短くなる
    挙動を追いやすくなる

    View Slide

  39. Ruby
    らしいコード例:
    for
    ⽂を使わない

    View Slide

  40. View Slide

  41. for
    ⽂を使わない
    #
    これよりも
    for i in 1..10 do
    print i
    end
    #
    こっちのほうがRuby
    らしい(?)
    (1..10).each do |i|
    print i
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9

    View Slide

  42. for
    ⽂を使わない
    #
    これよりも
    for i in 1..10 do
    print 'hello'
    end
    #
    こっちのほうがRuby
    らしい!
    10.times do
    print 'hello'
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9

    View Slide

  43. for
    ⽂を使わない
    #
    これよりも
    arr = [1,2,3]
    for i in 0...arr.length do
    arr[i] = arr[i] ** 2
    end
    #
    こっちのほうがRuby
    らしい!
    arr.map do |i|
    i ** 2
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  44. for
    ⽂を使わない
    #
    これよりも
    sum = 0
    for i in 1..10 do
    sum += i
    end
    #
    こっちのほうがRuby
    らしい
    (1..10).reduce(0) do |sum, i|
    sum + i
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  45. for
    ⽂を使わない
    for
    ⽂はいろんな⽤途に使えてしまう
    意図が伝えづらい
    複雑なことをすると挙動も追いづらい
    なるべくコレクションメソッドを使う
    名前から意図が伝わる
    定形処理を任せるので挙動も追いやすい

    View Slide

  46. Ruby
    らしいコード例:
    メタプログラミング

    View Slide

  47. メタプログラミング
    #
    これよりも
    class Test
    def field1; @field1; end
    def field2; @field2; end
    end
    #
    こっちのほうがRuby
    らしい
    class Test
    attr_reader :field1, :field2
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  48. メタプログラミング
    #
    これよりも……
    class Test
    def set_status_hoge
    @status = :hoge
    end
    def set_status_fuga
    @status = :fuga
    end
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    View Slide

  49. メタプログラミング
    #
    こっちのほうがRuby
    らしい?
    class Test
    status_setter :hoge, :fuga
    def status_setter(*statuses)
    statuses.each do |status|
    define_method "set_status_#{status}" do
    @status = status
    end
    end
    end
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    View Slide

  50. 🤔

    View Slide

  51. メタプログラミング
    似た処理を柔軟に共通化し、名前を付けられる
    意図をより明確にできる
    挙動は追いにくくなる(
    !)
    挙動が信頼できると安⼼して使える
    コアやフレームワーク
    ⾃前で書くときは読みやすさに注意する

    View Slide

  52. 「Ruby
    らしいコード」を考える
    Ruby
    の⾔語機能や標準メソッドを活⽤した
    読みやすいコードがRuby
    らしいコード
    ブロックで下位問題を追い出す
    意図に合わせた名前のメソッドを使う
    メタプログラミングは意図を伝えやすいが
    挙動が追いづらくなる諸刃の剣

    View Slide

  53. 余談:
    メタプロ社内事例
    YAML
    ファイルから読み込んだ値で定数を定義
    知らないと定義にたどり着けない
    リファクタ時に利⽤箇所がgrep
    漏れ
    Hoge
    と相互変換する際に必要なメソッドを
    include Convertible[Hoge]
    にする提案
    意図はわかるが挙動が難しいとreject
    された

    View Slide

  54. アジェンダ
    「読みやすいコード」を考える
    「Ruby
    らしいコード」を考える
    読みやすさとRuby
    らしさの関係を考える
    まとめ

    View Slide

  55. まとめ
    意図と挙動が伝わるのが読みやすいコード
    リーダブルコードは実現のテクニック集
    Ruby
    の機能を活かした読みやすいコードが
    Ruby
    らしいコード
    よく使われるイディオムには
    読みやすくする要素が詰まっている

    View Slide

  56. 読みやすくRuby
    らしい
    コードを書こう!

    View Slide

  57. 予備スライド
    (
    練習したら全然収まらなかった)

    View Slide

  58. アジェンダ
    「読みやすいコード」を考える
    「Ruby
    らしいコード」を考える
    実例から読みやすさを考える
    まとめ

    View Slide

  59. Misoca
    の⽂書変換
    ⾒積書からは変換できる
    ⾒積書への変換はできない
    請求書と納品書は
    相互に変換できる

    View Slide

  60. Misoca
    の⽂書変換
    変換元⽂書種別 変換先⽂書種別
    変換元⽂書ID
    変換先⽂書ID
    ⽂書変換テーブル

    View Slide

  61. Misoca
    の⽂書変換
    class DocumentConversion < ActiveRecord::Base
    def self.source_document_of(document)
    find_by(converted_document: document)
    end
    def self.converted_document_of(document)
    find_by(source_document: document)
    end
    end
    class Invoice < ActiveRecord::Base
    #
    請求書クラスは⽂書変換について知らない
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    View Slide

  62. 問題点
    各⽂書から「変換元⽂書」「変換先⽂書」に
    関連を持っていない
    includes
    が使えずN+1
    問題が発⽣する
    挙動が追いづらい
    「⾒積書へは変換できない」制約がコードで
    表現されていない

    View Slide

  63. 案1.
    普通に関連を持たせる
    class DocumentConversion < ActiveRecord::Base
    belongs_to :source_document, polymorphic: true
    belongs_to :converted_document, polymorphic: true
    end
    class Invoice < ActiveRecord::Base
    #
    中間テーブル(
    ⽂書変換)
    への関連
    has_many :document_conversions, as: :converted_document
    #
    変換元の⾒積書への関連
    as_many :source_estimates, through: :document_conversio
    source: :source_document,
    source_type: 'Estimate'
    #
    変換先の納品書への関連も同じ感じ
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    View Slide

  64. 案1
    の改善点
    変換できない⽂書間では関連を持たない
    制約がコードに反映されている
    Rails
    モデルの標準的な書き⽅
    挙動がわかりやすい

    View Slide

  65. 案1
    の問題点
    各⽂書のクラス内に「⽂書変換」の知識が
    分散している
    各⽂書を⾒⽐べないと、
    ⾒積書に変換できない意図が読み取れない
    他の処理とまぎれて扱いづらい

    View Slide

  66. 案2.
    モジュールにまとめる
    class Invoice < ActiveRecord::Base
    #
    ⽂書変換できることを明示するが、詳細な実装はモジュールに隠す
    include DocumentConvertible::Invoice
    end
    module DocumentConvertible
    module Invoice
    has_many :document_conversions, as: :converted_docume
    as_many :source_estimates, through: :document_convers
    source: :source_document,
    source_type: 'Estimate'
    end
    module Estimate; ...; end
    module DeliverySlip; ...; end
    end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    View Slide

  67. 案2
    の改善点
    ⽂書変換の知識がモジュールに集まった
    モジュールを⾒れば意図が読み取れる
    Ruby
    の標準的なクラス分割⽅法
    挙動がわかりやすい

    View Slide

  68. 案2
    の問題点
    ⽂書の定義側に「変換できる⽂書」の知識が
    現れていない
    ⽂書だけを⾒ると意図が少しわかりにくい
    関連定義で似たコードが繰り返す
    同じ処理である、という意図を表したい

    View Slide

  69. 案3.
    メタプロで宣⾔的にする
    class Invoice < ActiveRecord::Base
    #
    ⾒積書から変換できる
    include DocumentConvertible::From[Estimate]
    #
    納品書に変換できる
    include DocumentConvertible::To[DeliverySlip]
    end
    module DocumentConvertible
    module From
    module_function
    def [](*class_names)
    #
    受け取ったクラスを変換元⽂書として参照するために
    #
    必要な関連・メソッドなどを定義する
    end
    end
    # module To
    も同様に定義
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    View Slide

  70. 案3
    の改善点
    各⽂書のモデルに制約が明確に書かれている
    意図が⾮常にわかりやすい
    クラス名から関連・メソッドが定義される
    各⽂書で似た定義にしている意図が明確

    View Slide

  71. 案3
    の問題点
    定義される関連・メソッドが分かりづらい
    挙動が追いづらい
    IDE
    の補完やコードジャンプが効かない

    View Slide

  72. 意⾒を集めた結果
    案2
    の「モジュールに集約」がわかりやすい
    という意⾒が多数だった
    ⽂書変換の知識が1
    モジュールに集約
    多少のコード重複はあるが追いやすい
    案3
    の「メタプログラミング」は、
    制約がわかりやすいが挙動が追いにくい

    View Slide

  73. 意図を表現するために
    挙動のわかりやすさを
    犠牲にするべきではない

    View Slide