株式会社Misoca
黒曜(@kokuyouwind)
森 俊介 / 黒曜
@kokuyouwind
株式会社Misoca
名古屋の会社
懇親会スポンサー
@mugi_uno のご縁で富山に
フルリモートで働けるRubyの会社です
こう書いたほうが
読みやすくなる
んじゃないかな
こう直したほうが
Rubyっぽい書き方
になってよさそう
どんなコードが読みやすいんだろう?
Rubyらしいとなぜ良いんだろう?
この2つは関係しているのかな?
「読みやすいコード」を考える
「Rubyらしいコード」を考える
読みやすさとRubyらしさの関係を考える
まとめ
コードは理解しやすくなければいけない。
コードは他の人が最短時間で理解できるように書かなければいけない。
「リーダブルコード」 pp.2-3
理解するとは?
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
コードの意図(なにをしたいか)が読み取れる
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
コードの意図(やりたいこと)が読み取れる
「コードを理解する」というのは、
変更を加えたり バグを見つけたりできる
という意味だ。
「リーダブルコード」 p.3
コードの意図(やりたいこと)が読み取れる
コードの挙動(どう動くか)が読み取れる
良い名前で意図を伝える
getPageではなくfetchPage
意図が誤解されない名前を使う
filterではなくselect, exclude
適切なコメントで意図を伝える
制御フローを簡潔にする
挙動を追いやすくする
巨大な式を説明変数や要約変数で分割する
挙動を追いやすくする
部分式の意図を伝える
無関係の下位問題を抽出する
重要な挙動に注目できる
レベルが揃い意図を伝えやすくなる
一度に1つのことを
挙動と意図を(ry
意図と挙動が伝わるのが読みやすいコード
なにがやりたいのか
どう動くのか
詳しくはリーダブルコードを読もう!
細かいテクニックがいろいろ載っている
「読みやすいコード」を考える
「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
前処理・後処理を分離できる
ブロックの中に意図が集まる
「無関係の下位問題を抽出する」
変数のスコープが短くなる
挙動を追いやすくなる
# これよりも
for i in 1..10 do
print i
end
# こっちのほうがRubyらしい(?)
(1..10).each do |i|
print i
end
# これよりも
for i in 1..10 do
print 'hello'
end
# こっちのほうがRubyらしい!
10.times do
print 'hello'
end
# これよりも
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
# これよりも
sum = 0
for i in 1..10 do
sum += i
end
# こっちのほうがRubyらしい
(1..10).reduce(0) do |sum, i|
sum + i
end
for文はいろんな用途に使えてしまう
意図が伝えづらい
複雑なことをすると挙動も追いづらい
なるべくコレクションメソッドを使う
名前から意図が伝わる
定形処理を任せるので挙動も追いやすい
# これよりも
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らしいコード
ブロックで下位問題を追い出す
意図に合わせた名前のメソッドを使う
メタプログラミングは意図を伝えやすいが
挙動が追いづらくなる諸刃の剣
YAMLファイルから読み込んだ値で定数を定義
知らないと定義にたどり着けない
リファクタ時に利用箇所がgrep漏れ
Hogeと相互変換する際に必要なメソッドを
include Convertible[Hoge]にする提案
意図はわかるが挙動が難しいとrejectされた
「読みやすいコード」を考える
「Rubyらしいコード」を考える
読みやすさとRubyらしさの関係を考える
まとめ
意図と挙動が伝わるのが読みやすいコード
リーダブルコードは実現のテクニック集
Rubyの機能を活かした読みやすいコードが
Rubyらしいコード
よく使われるイディオムには
読みやすくする要素が詰まっている
「読みやすいコード」を考える
「Rubyらしいコード」を考える
実例から読みやすさを考える
まとめ
見積書からは変換できる
見積書への変換はできない
請求書と納品書は
相互に変換できる
変換元文書種別 | 変換先文書種別 |
変換元文書ID | 変換先文書ID |
文書変換テーブル
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問題が発生する
挙動が追いづらい
「見積書へは変換できない」制約がコードで表現されていない
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
変換できない文書間では関連を持たない
制約がコードに反映されている
Railsモデルの標準的な書き方
挙動がわかりやすい
各文書のクラス内に「文書変換」の知識が
分散している
各文書を見比べないと、
見積書に変換できない意図が読み取れない
他の処理とまぎれて扱いづらい
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
文書変換の知識がモジュールに集まった
モジュールを見れば意図が読み取れる
Rubyの標準的なクラス分割方法
挙動がわかりやすい
文書の定義側に「変換できる文書」の知識が
現れていない
文書だけを見ると意図が少しわかりにくい
関連定義で似たコードが繰り返す
同じ処理である、という意図を表したい
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 も同様に定義
各文書のモデルに制約が明確に書かれている
意図が非常にわかりやすい
クラス名から関連・メソッドが定義される
各文書で似た定義にしている意図が明確
定義される関連・メソッドが分かりづらい
挙動が追いづらい
IDEの補完やコードジャンプが効かない
案2の「モジュールに集約」がわかりやすい
という意見が多数だった
文書変換の知識が1モジュールに集約
多少のコード重複はあるが追いやすい
案3の「メタプログラミング」は、
制約がわかりやすいが挙動が追いにくい