Upgrade to Pro — share decks privately, control downloads, hide ads and more …

読みやすいコードと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)

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

    @mugi_uno のご縁で富⼭に
  3. 📢 宣伝 フルリモートで働けるRuby の会社です

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

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

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

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

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

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

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

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

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

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

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

  15. 🤔 理解するとは?

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

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

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

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

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

    どう動くか) が読み取れる
  21. コードの意図と挙動が 読み⼿に伝わるのが 読みやすいコード

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

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

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

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

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

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

  28. Ruby-ish

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

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

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

  32. Ruby でよく使われる 書き⽅と似ているのが Ruby らしいコード ※ 「Ruby らしさ」の定義としては微妙だが、 「Ruby らしいコード」であるかの基準として有⽤

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

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

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

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

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

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

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

  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
  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
  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
  50. 🤔

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

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

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

    Convertible[Hoge] にする提案 意図はわかるが挙動が難しいとreject された
  54. アジェンダ 「読みやすいコード」を考える 「Ruby らしいコード」を考える 読みやすさとRuby らしさの関係を考える まとめ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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