エピローグ — 3Rが身につくとき
3ヶ月後
「これ、見てください」
ユイが田中さんのデスクに近づき、ターミナルを見せた。
# 3ヶ月前: 変更頻度が高いファイルTOP5
git log --format=format: --name-only | sort | uniq -c | sort -rg | head -5
# 89 app/models/order.rb ← Fat Model(1247行)
# 67 app/services/discount_service.rb ← 重複コードの源
# 45 app/controllers/orders_controller.rb ← ビジネスロジックが漏れ出ている
# 38 app/models/user.rb ← 別のFat Model
# 31 app/helpers/application_helper.rb ← 雑多なメソッド置き場
# 3ヶ月後(同じコマンド)
git log --format=format: --name-only | sort | uniq -c | sort -rg | head -5
# 28 spec/services/order_creation_service_spec.rb ← テストが増えた!
# 23 app/services/order_creation_service.rb ← 分割・整理済み
# 18 app/models/concerns/user/loyalty_program.rb ← Concern
# 15 spec/models/concerns/user/loyalty_program_spec.rb
# 12 app/services/discount_service.rb「変更頻度が最も高いのはテストファイルになりましたね」ユイが言った。「コードを変えるたびにテストも書くようになったから。そして変更ファイルが分散した——1000行のFat Modelを1箇所触る代わりに、複数の小さなファイルを適切な場所で触るようになった」
田中さんは微笑んだ。「数字を見てみよう」
計測: 3Rの改善を数値で確認する
3Rは「感覚の改善」ではなく「計測できる改善」だ。
# テストカバレッジの計測(SimpleCov)
COVERAGE=true bundle exec rspec
# Coverage report:
# +----------+--------+----------+--------+
# | Group | Lines | Relevant | % |
# +----------+--------+----------+--------+
# | Services | 1,234 | 1,156 | 87.2% |
# | Models | 892 | 845 | 91.1% |
# | Concerns | 456 | 432 | 94.7% |
# +----------+--------+----------+--------+
# | Total | 3,847 | 3,201 | 83.2% |
# +----------+--------+----------+--------+# RuboCopでコード品質を定量化
bundle exec rubocop --format json | jq '
{
total_offenses: .summary.offense_count,
files_inspected: .summary.inspected_file_count,
avg_per_file: (.summary.offense_count / .summary.inspected_file_count | floor)
}
'
# 3ヶ月前:
# { "total_offenses": 847, "files_inspected": 124, "avg_per_file": 6 }
# 3ヶ月後:
# { "total_offenses": 89, "files_inspected": 156, "avg_per_file": 0 }| 指標 | 3ヶ月前 | 3ヶ月後 | 改善率 |
|---|---|---|---|
| テストカバレッジ | 34% | 83% | +49pt |
| RuboCop違反数 | 847件 | 89件 | -89% |
| 最大メソッド長 | 87行 | 18行 | -79% |
| Fat Model行数(order.rb) | 1,247行 | 94行 | -92% |
| Fat Model行数(user.rb) | 1,089行 | 50行 | -95% |
| バグ修正の平均時間 | 4.2時間 | 1.1時間 | -74% |
| 新機能の平均実装時間 | 3.8日 | 2.1日 | -45% |
「バグ修正の時間が4分の1になった」田中さんが言った。「可読性が上がると、問題の特定が速くなる。再利用性が上がると、変更箇所が1箇所になる。リファクタリング可能性が上がると、テストが自動で検証してくれる。3つのRが揃うと、相乗効果が出る」
3Rが習慣になるとき
「どうすれば3Rが自然にできるようになりますか?」ユイが聞いた。
田中さんは3つのことを答えた。
習慣1: コードを書く前に「読む人」を想像する
「書く前の5秒間を使う習慣だ」
# コードを書く前に問いかける質問
# 変数名を付けるとき:
# 「3ヶ月後の自分がこの変数を見たとき、何のための変数かわかるか?」
# メソッドを作るとき:
# 「このメソッドは1つのことだけしているか?名前がその1つを表しているか?」
# クラスを作るとき:
# 「このクラスが変更される理由は何か?複数あるなら分割すべきか?」
# コードを書いた後に確認する:
# 「初めてこのコードを見る人が5分で理解できるか?」習慣2: PRを出す前にセルフレビューする
「レビュワーに見せる前に、自分でチェックする」
# PRセルフレビューチェックリスト
## このPRは何をしているか?
- [ ] 1文で説明できるか(できなければ分割を検討)
- [ ] タイトルが変更内容を正確に表しているか
## R1チェック(Readability)
- [ ] 変数名・メソッド名を読むだけで意図が伝わるか
- [ ] マジックナンバーが定数化されているか
- [ ] メソッドが15行以内か
- [ ] コメントが「なぜ」だけを説明しているか(「何をする」ではない)
## R2チェック(Reusability)
- [ ] 重複コードを書いていないか(既存のService/Concernを使えているか)
- [ ] 新しいビジネスロジックが適切な場所にあるか(コントローラに漏れていないか)
## R3チェック(Refactorability)
- [ ] 新しいテストを書いたか
- [ ] テストがグリーンになっているか(CIの確認)
- [ ] 変更の影響範囲が限定されているか(変えた場所だけが変わっているか)
- [ ] 外部サービスへの依存が注入可能になっているか
## 将来の変更者へ
- [ ] なぜこの実装にしたかがコードまたはコメントから読み取れるか
- [ ] 将来変わりそうな数値・文字列が定数になっているか習慣3: 継続的なリファクタリングを当たり前にする
「ボーイスカウトの法則——来たときよりキャンプ場をきれいにして帰る」
# 毎回のPRで「少し良くする」を積み重ねる
# 新機能を追加するついでに:
# - 通り道のメソッド名を改善する(変数 x → order)
# - 見かけたWETなコードをDRYにする(割引ロジックの重複をServiceに抽出)
# - カバレッジが0のメソッドにテストを追加する
# 大規模なリファクタリングスプリントより
# 継続的な小さな改善の方が持続可能
# なぜなら:
# 大規模リファクタリング → 長期間コードが不安定 → チームが嫌がる → 実行されない
# 継続的な小改善 → 常に安定 → チームに受け入れられる → 継続できるINFO
「あとでリファクタリングする」は99%実現しません。今日のPRで少しだけ良くすることを積み重ねる方が、技術的負債を確実に返済できます。「あとで」という言葉は、エンジニアリングの世界では「永遠に」を意味することが多い。
継続的改善のサイクル: 計測→改善→計測
3Rを個人の感覚に頼るのではなく、数値で追跡する仕組みを作る。
# app/services/quality_report.rb
class QualityReport
def self.generate_and_notify
report = new.generate
Slack::Notifier.new(ENV['QUALITY_SLACK_WEBHOOK']).ping(
attachments: [report.to_slack_attachment]
)
end
def generate
QualityMetrics.new(
coverage: current_test_coverage,
rubocop_offenses: rubocop_offense_count,
fat_models: fat_model_count,
average_method_length: average_method_length,
measured_at: Time.current
)
end
private
def current_test_coverage
json = JSON.parse(File.read('coverage/.last_run.json'))
json.dig('result', 'line').round(1)
rescue StandardError
nil
end
def rubocop_offense_count
output = `bundle exec rubocop --format json 2>/dev/null`
JSON.parse(output).dig('summary', 'offense_count')
rescue StandardError
nil
end
def fat_model_count
Dir['app/models/**/*.rb'].count do |f|
File.read(f).lines.count > 200
end
end
def average_method_length
total_methods = 0
total_length = 0
Dir['app/**/*.rb'].each do |file|
content = File.read(file)
methods = content.scan(/^\s+def .+?(?=\s+(?:def |end\s*$))/m)
methods.each do |method|
total_methods += 1
total_length += method.lines.count
end
end
return 0 if total_methods.zero?
(total_length.to_f / total_methods).round(1)
end
end
QualityMetrics = Data.define(
:coverage,
:rubocop_offenses,
:fat_models,
:average_method_length,
:measured_at
) do
def to_slack_attachment
{
title: "週次コード品質レポート(#{measured_at.strftime('%Y/%m/%d')})",
color: overall_health_color,
fields: [
{ title: "テストカバレッジ", value: "#{coverage}%", short: true },
{ title: "RuboCop違反", value: "#{rubocop_offenses}件", short: true },
{ title: "Fat Model数", value: "#{fat_models}個", short: true },
{ title: "平均メソッド長", value: "#{average_method_length}行", short: true }
]
}
end
private
def overall_health_color
return 'good' if coverage >= 80 && rubocop_offenses <= 50 && fat_models == 0
return 'warning' if coverage >= 60
'danger'
end
end# config/schedule.rb(whenever gemでcron設定)
# 毎週月曜9時にレポートをSlackに送信
every :week, at: '9:00am', roles: [:app] do
runner "QualityReport.generate_and_notify"
endチームへの広め方: 共通言語を作る
ユイはコードレビューで3Rを伝え、月次の技術共有会で「今月のBefore/After」を発表し、チームのPRテンプレートに3Rチェックリストを追加した。
6ヶ月後には、ミーティングで自然と「これってR1的に大丈夫?」「R2でサービス化した方が良くない?」という言葉が飛び交うようになった。3Rがチームの共通言語になった。
新入社員のヒロキが最初のPRを出したとき、コードを見た瞬間に「あ、これは変数名が意図を伝えていないな」と気づいてコメントできた。3Rがチームの文化として根付いていた。
ユイからの手紙: 新入社員研修での登壇
9ヶ月後、新入社員向けの技術研修でユイは登壇した。
「3Rは魔法ではありません。最初は時間がかかります。
良い変数名を考えるのに5分かかることがある。 メソッドを分割するのに30分追加でかかることがある。 テストを書くのに実装の2倍の時間がかかることがある。
でも3ヶ月後、バグ修正の時間が4分の1になりました。 新機能の実装が半分の時間でできるようになりました。 土日にアラートで起こされる回数が減りました。 コードを変えることへの恐怖がなくなりました。
3Rは投資です。今日の30分が、来月の3時間を節約します。」
「最初にどこから始めれば良いですか?」新入社員のひとりが聞いた。
「今日の作業で書いた変数が1つでもある。その変数の名前を1つ改善することから始めてください。x を order に。d を discount_amount に。たった1行の変更から、3Rの旅は始まります」
3Rの本質: コードは人のために書く
# プロローグで見たコード(3Rの旅の出発点)
def p(o, u, c)
x = Order.find(o)
if x.st == 1
if u.pr == true
x.tt = x.tt * 0.8
end
# ...
end
end
# 9ヶ月後(3Rの旅の到達点)
class OrderCompletionService
PREMIUM_DISCOUNT_RATE = 0.20
def initialize(order, user, coupon_code: nil)
@order = order
@user = user
@coupon_code = coupon_code
end
def call
return ServiceResult.failure('注文が保留中ではありません') unless @order.pending?
ActiveRecord::Base.transaction do
apply_premium_discount
apply_coupon_if_present
complete_order
end
ServiceResult.success(@order)
end
private
def apply_premium_discount
return unless @user.premium?
discount = (@order.total * PREMIUM_DISCOUNT_RATE).round(2)
@order.discount_amount = discount
@order.total -= discount
end
def apply_coupon_if_present
return unless @coupon_code.present?
coupon = Coupon.find_by(code: @coupon_code)
return unless coupon&.redeemable?
coupon.apply_to(@order)
end
def complete_order
@order.update!(status: :completed, completed_at: Time.current)
OrderMailer.confirmation(@order.user, @order).deliver_later
end
end同じ機能を実現するコードだ。でも後者を読んだ人は、1分で「何をするコードか」がわかる。変更したい場所がわかる。変更しても何が壊れるかわかる。そして変更しても壊れていないことを自動で確認できる。
3Rの本質:
Readability = 読む人へのリスペクト
Reusability = 解いた問題を知識にして次の人に渡す
Refactorability = 未来の変更者(しばしば自分自身)への配慮
どれも「コードは人のために書く」という一点に集約される。
コンピュータはどんな書き方でも動く。
でも人間は、読めなければ理解できない。
変えられなければ前進できない。
同じ問題を何度も解くしかない。
3Rは技術的なプラクティスだが、その根本は「思いやり」だ。
チームの誰かが——それは未来の自分かもしれない——
そのコードを読んで、変えて、拡張する。
その人のために書く。
それが3Rだ。
あなたの3R旅
この本を読み終えた今、あなたの旅が始まる。
# 今日からできる3アクション
# アクション1: 一番読みにくいメソッドを1つ選んで名前を改善する
# Before: def p(o, u)
# After: def process_order(order, user)
# アクション2: 重複している処理を1つ見つけてDRYにする
# 3箇所に同じ割引ロジック → DiscountService に集約
# アクション3: テストがないメソッドを1つ選んでテストを書く
# describe OrderCompletionService do
# it '注文を完了させる' do
# ...
# end
# endコードは書かれた瞬間から「過去のもの」になる。未来の誰かが——それはあなた自身かもしれない——そのコードを読んで、変えて、拡張していく。
3Rは、未来の誰かへの贈り物だ。
INFO
3Rは完璧を目指す原則ではありません。「今日少しだけ良くする」を積み重ねることが本質です。完璧なコードより、昨日より良いコードを。焦らず、着実に。旅を楽しんでください。
「書いたコードを誇りに思える日が来る。3Rはその道標だ」— 田中さん
付録: 3R クイックリファレンス
## R1: Readability チェックリスト
- [ ] 変数名が意図を表している(x → order, d → discount_amount)
- [ ] メソッド名が1つの動作を表している(process → process_order)
- [ ] ブール値メソッドが ? で終わる(premium?, active?, expired?)
- [ ] コレクションが複数形(item → items, order → orders)
- [ ] メソッドが15行以内(例外的に長い場合は分割を検討)
- [ ] ガード節で入れ子を減らしている
- [ ] コメントが「なぜ」だけを説明している(「何をする」はコードが説明する)
## R2: Reusability チェックリスト
- [ ] 同じロジックが3箇所以上あれば抽出(3回ルール)
- [ ] ビジネスロジックがコントローラに漏れていない
- [ ] 複数モデルに共通するロジックはConcernに切り出している
- [ ] チーム内/複数プロジェクトで共通のコードはGem化を検討
- [ ] マジックナンバーが定数になっている
## R3: Refactorability チェックリスト
- [ ] テストカバレッジが80%以上
- [ ] 1クラス1責任(SRP)
- [ ] 新機能追加で既存コードを変えずに済む(OCP)
- [ ] 依存性注入でテストが書ける(DIP)
- [ ] インフラはTerraform/CloudFormationで管理(IaC)
- [ ] CIでテスト・Lintが自動実行される