読みやすいコードとRubyらしいコード
株式会社Misoca
黒曜(@kokuyouwind)
$ whoami
-
森 俊介 / 黒曜
-
@kokuyouwind
-
株式会社Misoca
-
名古屋の会社
-
懇親会スポンサー
-
@mugi_uno のご縁で富山に
-
📢 宣伝
フルリモートで働けるRubyの会社です
読みやすいコードとRubyらしいコード
例えばコードレビュー
こう書いたほうが
読みやすくなる
んじゃないかな
こう直したほうが
Rubyっぽい書き方
になってよさそう
どんなコードが読みやすいんだろう?
Rubyらしいとなぜ良いんだろう?
この2つは関係しているのかな?
このふたつの関係を
考えてみよう
アジェンダ
-
「読みやすいコード」を考える
-
「Rubyらしいコード」を考える
-
読みやすさとRubyらしさの関係を考える
-
まとめ
「読みやすいコード」を
考える
🙋読んだことある人?
コードは理解しやすくなければいけない。
コードは他の人が最短時間で理解できるように書かなければいけない。
「リーダブルコード」 pp.2-3
他の人が
素早く理解できるのが
読みやすいコード
理解するとは?
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
コードの意図(なにをしたいか)が読み取れる
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
コードの意図(やりたいこと)が読み取れる
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
コードの意図(やりたいこと)が読み取れる
コードの挙動(どう動くか)が読み取れる
コードの意図と挙動が
読み手に伝わるのが
読みやすいコード
第I部 表面上の改善
-
良い名前で意図を伝える
-
getPageではなくfetchPage
-
-
意図が誤解されない名前を使う
-
filterではなくselect, exclude
-
-
適切なコメントで意図を伝える
第II部 ループとロジックの単純化
-
制御フローを簡潔にする
-
挙動を追いやすくする
-
-
巨大な式を説明変数や要約変数で分割する
-
挙動を追いやすくする
-
部分式の意図を伝える
-
第III部 コードの再構成
-
無関係の下位問題を抽出する
-
重要な挙動に注目できる
-
レベルが揃い意図を伝えやすくなる
-
-
一度に1つのことを
-
挙動と意図を(ry
-
「読みやすいコード」を考える
-
意図と挙動が伝わるのが読みやすいコード
-
なにがやりたいのか
-
どう動くのか
-
-
詳しくはリーダブルコードを読もう!
-
細かいテクニックがいろいろ載っている
-
アジェンダ
-
「読みやすいコード」を考える -
「Rubyらしいコード」を考える
-
読みやすさとRubyらしさの関係を考える
-
まとめ
「Rubyらしいコード」を
考える
Ruby-ish
Rubyらしいってどういうこと?
「Ruby らしい」とはどういうことでしょうか。
「○○らしい」とは「他と似ている」ということです。
「Ruby らしい」書き方だとまわりのコードと似たような記述になります。
「ライブラリー開発者になろう」
Rubyでよく使われる
書き方と似ているのが
Rubyらしいコード
※「Rubyらしさ」の定義としては微妙だが、
「Rubyらしいコード」であるかの基準として有用
アジェンダ
-
「読みやすいコード」を考える -
「Rubyらしいコード」を考える -
読みやすさとRubyらしさの関係を考える
-
まとめ
読みやすさと
Rubyらしさの
関係を考える
Rubyらしいコード例:
ブロックを使う
ブロックを使う
# これよりも
file = File.open(path)
file.read
file.close
# こっちのほうがRubyらしい
File.open(path) do |file|
file.read
end
ブロックを使う
# これよりも
file = File.open(path) # 前処理
file.read # やりたいこと
file.close # 後処理
# こっちのほうがRubyらしい
File.open(path) do |file|
file.read # やりたいこと
end
ブロックを使う
-
前処理・後処理を分離できる
-
ブロックの中に意図が集まる
-
「無関係の下位問題を抽出する」
-
-
変数のスコープが短くなる
-
挙動を追いやすくなる
-
Rubyらしいコード例:
for文を使わない
for文を使わない
# これよりも
for i in 1..10 do
print i
end
# こっちのほうがRubyらしい(?)
(1..10).each do |i|
print i
end
for文を使わない
# これよりも
for i in 1..10 do
print 'hello'
end
# こっちのほうがRubyらしい!
10.times do
print 'hello'
end
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
for文を使わない
# これよりも
sum = 0
for i in 1..10 do
sum += i
end
# こっちのほうがRubyらしい
(1..10).reduce(0) do |sum, i|
sum + i
end
for文を使わない
-
for文はいろんな用途に使えてしまう
-
意図が伝えづらい
-
複雑なことをすると挙動も追いづらい
-
-
なるべくコレクションメソッドを使う
-
名前から意図が伝わる
-
定形処理を任せるので挙動も追いやすい
-
Rubyらしいコード例:
メタプログラミング
メタプログラミング
# これよりも
class Test
def field1; @field1; end
def field2; @field2; end
end
# こっちのほうがRubyらしい
class Test
attr_reader :field1, :field2
end
メタプログラミング
# これよりも……
class Test
def set_status_hoge
@status = :hoge
end
def set_status_fuga
@status = :fuga
end
end
メタプログラミング
# こっちのほうが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
メタプログラミング
-
似た処理を柔軟に共通化し、名前を付けられる
-
意図をより明確にできる
-
挙動は追いにくくなる(!)
-
-
挙動が信頼できると安心して使える
-
コアやフレームワーク
-
自前で書くときは読みやすさに注意する
-
「Rubyらしいコード」を考える
-
Rubyの言語機能や標準メソッドを活用した
読みやすいコードがRubyらしいコード-
ブロックで下位問題を追い出す
-
意図に合わせた名前のメソッドを使う
-
-
メタプログラミングは意図を伝えやすいが
挙動が追いづらくなる諸刃の剣
余談: メタプロ社内事例
-
YAMLファイルから読み込んだ値で定数を定義
-
知らないと定義にたどり着けない
-
-
リファクタ時に利用箇所がgrep漏れ
-
Hogeと相互変換する際に必要なメソッドを
include Convertible[Hoge]にする提案-
意図はわかるが挙動が難しいとrejectされた
-
アジェンダ
-
「読みやすいコード」を考える -
「Rubyらしいコード」を考える -
読みやすさとRubyらしさの関係を考える -
まとめ
まとめ
-
意図と挙動が伝わるのが読みやすいコード
-
リーダブルコードは実現のテクニック集
-
-
Rubyの機能を活かした読みやすいコードが
Rubyらしいコード-
よく使われるイディオムには
読みやすくする要素が詰まっている
-
読みやすくRubyらしい
コードを書こう!
予備スライド
(練習したら全然収まらなかった)
アジェンダ
-
「読みやすいコード」を考える
-
「Rubyらしいコード」を考える
-
実例から読みやすさを考える
-
まとめ
Misocaの文書変換
見積書からは変換できる
見積書への変換はできない
請求書と納品書は
相互に変換できる
Misocaの文書変換
変換元文書種別 | 変換先文書種別 |
変換元文書ID | 変換先文書ID |
文書変換テーブル
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
問題点
-
各文書から「変換元文書」「変換先文書」に関連を持っていない
-
includesが使えずN+1問題が発生する
-
挙動が追いづらい
-
-
「見積書へは変換できない」制約がコードで表現されていない
案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_conversions,
source: :source_document,
source_type: 'Estimate'
# 変換先の納品書への関連も同じ感じ
end
案1の改善点
-
変換できない文書間では関連を持たない
-
制約がコードに反映されている
-
-
Railsモデルの標準的な書き方
-
挙動がわかりやすい
-
案1の問題点
-
各文書のクラス内に「文書変換」の知識が
分散している-
各文書を見比べないと、
見積書に変換できない意図が読み取れない
-
-
他の処理とまぎれて扱いづらい
案2. モジュールにまとめる
class Invoice < ActiveRecord::Base
# 文書変換できることを明示するが、詳細な実装はモジュールに隠す
include DocumentConvertible::Invoice
end
module DocumentConvertible
module Invoice
has_many :document_conversions, as: :converted_document
as_many :source_estimates, through: :document_conversions,
source: :source_document,
source_type: 'Estimate'
end
module Estimate; ...; end
module DeliverySlip; ...; end
end
案2の改善点
-
文書変換の知識がモジュールに集まった
-
モジュールを見れば意図が読み取れる
-
-
Rubyの標準的なクラス分割方法
-
挙動がわかりやすい
-
案2の問題点
-
文書の定義側に「変換できる文書」の知識が
現れていない-
文書だけを見ると意図が少しわかりにくい
-
-
関連定義で似たコードが繰り返す
-
同じ処理である、という意図を表したい
-
案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 も同様に定義
案3の改善点
-
各文書のモデルに制約が明確に書かれている
-
意図が非常にわかりやすい
-
-
クラス名から関連・メソッドが定義される
-
各文書で似た定義にしている意図が明確
-
案3の問題点
-
定義される関連・メソッドが分かりづらい
-
挙動が追いづらい
-
IDEの補完やコードジャンプが効かない
-
意見を集めた結果
-
案2の「モジュールに集約」がわかりやすい
という意見が多数だった-
文書変換の知識が1モジュールに集約
-
多少のコード重複はあるが追いやすい
-
-
案3の「メタプログラミング」は、
制約がわかりやすいが挙動が追いにくい
意図を表現するために
挙動のわかりやすさを
犠牲にするべきではない
読みやすいコードとRubyらしいコード
By 黒曜
読みやすいコードとRubyらしいコード
富山Ruby会議01
- 5,045