はじめに
この投稿では説明変数と要約変数は同じものとして扱った上で、
メリット・デメリットの解説 と、説明変数を更に発展させた例を解説していきます。
説明変数・要約変数とは何か?
長くて複雑な処理や、意図が分からない処理を理解しやすくするために
あえて定義する変数のことです。
説明変数の導入例
- サンプルコードは、「最近公開された記事」を取得する処理を題材にしています。
「最近」の定義は、「3日以内に公開されたかどうか」で判定しています。 - サンプルコードにはRuby及びRuby on Rails, RSpecを使った書き方を採用しています。
Before
def hoge
Article.where(publication_start_at: 3.days.ago..Time.current).each do |article|
# 何かの処理を行う
end
end
After
def hoge
# (訳: 最近公開された記事)
recently_published_articles = Article.where(publication_start_at: 3.days.ago..Time.current)
recently_published_articles.each do |article|
# 何かの処理を行う
end
end
メリット
「なぜこの処理を行なっているのか?」の理解が容易になった
BeforeとAfterを見比べると、「3日以内に公開された記事」がなんなのかが分かるようになりました。
-
Beforeの処理から分かること
- 「3日以内に公開された記事」に対して何かの処理を行っている
-
Afterの処理から分かること
- 「3日以内に公開された記事」を「最近投稿された記事(recently_published_atricles)」として定義している
- 「最近投稿された記事」に対して何かの処理を行っている
デメリット
行数が増えた
-
BeforeとAfterを比べると、変数を定義したことで1行増えていることがわかります。
たった一行なら大した変化ではありませんが、
行数が増えてくると目を上下に動かす行為やスクロールの手間が増えるため、処理を理解するのが大変になります。
[発展] 説明変数をメソッド(関数)に切り分ける(抽出する)
説明変数はメソッドとして切り分ける際の手掛かりになります。以降、メソッドを切り分けたことによるメリットとデメリットを説明します。
Before
def hoge
# 追加された何かの処理
recently_published_articles = Article.where(publication_start_at: 3.days.ago..Time.current)
recently_published_articles.each do |article|
# 何かの処理
end
# 追加された何かの処理
end
After
def hoge
# 追加された何かの処理
recently_published_articles.each do |article|
# 何かの処理
end
# 追加された何かの処理
end
def recently_published_articles
Article.where(publication_start_at: 3.days.ago..Time.current)
end
メリット
「最近公開された記事」の実装を知らなくてもコードを読めるようになった
- メソッドを分けることで、「最近公開された記事」に対して何かを行うということだけに集中することができました。
- 前後の処理が増えてきた場合、「最近公開された記事」の定義が「3日以内に公開された記事」なんて、一々気にしたくありません。仕様を知りたいときだけメソッドの中身を見に行けば良くなります。
メンテナンスしやすくなった
- 「最近公開された記事」と言う定義を残しつつ、「最近」の仕様を変えたい時などに、
hodge
内の前後の処理を気にせずに変更できるようになった - そな他にも単体テストをしやすくなると言う利点があります。詳細は後述の [発展] メソッドを切り出すとなぜメンテナンスしやすくなるのか? に記載しています
デメリット
メソッド定義を見にいく手間が増えた
- メソッドの定義はファイルの上部または下部にまとめて定義することが多いため、処理の内容を知りたいときに画面をスクロールする手間が増えてしまいます。
乱用すると理解し辛くなる
- 細かくメソッド分しすぎたり、メソッド名を適当につけたりすると、返って処理を理解するのが大変になります。
[発展] メソッドとして切り出すとなぜメンテナンスしやすくなるのか?
説明変数導入時と比べると、メソッド分けのメリットが理解し難くなりました。
これを実感できるようになるのは、処理の単体テストを描き始めてからです。
単体テストというのは、「処理を呼び出した時に、期待した値(今であれば3日以内に公開された記事)を返すことを確認する処理」のことです。単体テストがあることで、処理を変更した後も正常に動作していること(壊れていないこと)を証明できます。
開発を進めていくと、仕様変更の依頼などが多々発生します。
例えば、最近公開された記事を「3日以内に公開」から「5日以内に公開」に変更して欲しいという依頼があったときに、下記の様な変更が発生すると思います。
Before
def hoge
# 何かの処理
recently_published_articles = Article.where(publication_start_at: 3.days.ago..Time.current)
recently_published_articles.each do |article|
# 何かの処理
end
# 何かの処理
end
# --- RSpecを使ったテスト ---
describe 'hoge' do
it '最近公開された記事に対して何かの処理を行う' do
expect(hoge).to eq '何かの結果'
end
end
上記のhoge
に変更を加えて処理が壊れてしまったとき、
「最近公開された記事」を取得する処理に問題があるのか、それ以外の処理に問題があるのかを特定することが大変です。
下記のようにメソッドを分け、メソッドの単体テストを追加することで、「最近公開された記事」の取得は問題ないことを証明でき、hoge
内の「最近公開された記事」を取得する処理以外の処理に問題があることが分かるようになり、問題の特定が容易になります。
After
def hoge
# 何かの処理
recently_published_articles.each do |article|
# 何かの処理
end
# 何かの処理
end
def recently_published_articles
Article.where(publication_start_at: 3.days.ago..Time.current)
end
# --- RSpecを使ったテスト ---
describe 'hoge' do
it '最近公開された記事に対して何かの処理を行う' do
expect(hoge).to eq '何かの結果'
end
end
describe 'recently_published_articles' do
it '5日以内に公開された記事を返す' do
# 記事データの準備
article1 = Article.create(title: '4日前に公開された記事', publication_start_at: 4.days.ago)
article2 = Article.create(title: '5日前に公開された記事', publication_start_at: 5.days.ago)
expect(recently_published_articles).to eq [article2]
end
end
最後に
ここまで説明変数の導入から、メソッド分けまでの発展的な内容を解説しましたが、場合によっては使わない方がいいケースもあります。
導入において大切な考え方は、「デメリット < メリット」になる時にのみ導入するという考え方です。コードを理解するのに時間がかかる様では元も子もありません。
「じゃあどういう時に使えばいいんだ?」と分かり難くなってしまいますが、時間をかけて慣れて覚えていくしかないと考えています。また、プログラマーによって考え方が違うこともあります。少しづつ覚えていけばよいと思います
この記事に書いた知識については、下記の書籍で知り、業務で実践することで理解していきました。
- 「リーダブルコード」
- 8章 巨大な式を分割する
- 説明変数、要約変数
- 10章 無関係の下位問題を抽出する
- 入門的な例
- 思いも寄らない恩恵
- 8章 巨大な式を分割する
- 「リファクタリング:Rubyエディション 」
- 6章 メソッドの構成方法 6.1 メソッドの抽出
ご拝読ありがとうございました。