mybook

Stage 1: クリーンコード — 読みやすいコードの基礎

「動く」と「良い」は違う

翌朝、ヒロシはマイに声をかけた。

「昨日のロードマップ、家で考えてたんですけど……クリーンコードって結局、きれいに書くってことですよね?インデントとか?」

マイは首を横に振った。

「きれいに見える、じゃなくて。読んだ人が一瞬で意図を理解できること。コードは書いた瞬間より、読まれる時間の方がずっと長い。」

命名はコードの語彙

「まず命名から始めよう。」

マイはヒロシのコードを開いた。

# 悪い例
def calc(u, p, d)
  r = u.pts * 0.1
  if p > 10000
    r = r + (p - 10000) * 0.05
  end
  if d == "express"
    r = r + 500
  end
  r
end

u, p, d, r。何これ?」

「ユーザー、価格、配送方法、割引額……だと思います。」

「思う、でしょ?確認しないとわからない。コードを読むたびに解読が必要になる。これが積み重なると、開発速度が何倍も遅くなる。」

# 良い例
def calculate_discount(user, purchase_price, delivery_type)
  base_discount = user.points * 0.1
 
  volume_discount = if purchase_price > 10_000
    (purchase_price - 10_000) * 0.05
  else
    0
  end
 
  express_surcharge = delivery_type == "express" ? 500 : 0
 
  base_discount + volume_discount - express_surcharge
end

「変数名が増えたけど、意図が明確になった。base_discount, volume_discount, express_surcharge。それぞれが何を表すか、一目でわかる。」

INFO

良い命名の3原則

  1. 省略しない(u より usercalc より calculate_discount
  2. 「何をするか」ではなく「何であるか」を表す(data より active_users
  3. 文脈に合わせた具体性(get_info より fetch_user_profile

Railsでの命名規約

Railsには慣習として確立した命名ルールがある。これに従うことが、クリーンコードへの最短経路だ。

# モデル名: 単数形・PascalCase
class UserProfile < ApplicationRecord
  # スコープ: 動詞ではなく形容詞/状態を表す
  scope :active, -> { where(status: "active") }
  scope :premium, -> { where(tier: "premium") }
 
  # メソッド: 動詞で始まる
  def full_name
    "#{first_name} #{last_name}"
  end
 
  # 真偽を返すメソッド: ?で終わる
  def eligible_for_discount?
    premium? && account_age_days > 30
  end
 
  # 副作用のあるメソッド: !で終わる
  def upgrade_to_premium!
    update!(tier: "premium", upgraded_at: Time.current)
  end
end

関数設計: 一つのことだけやる

次はヒロシがよく書くパターンを見た。

# 悪い例: 一つのメソッドが何でもやる
def process_order(order_id)
  order = Order.find(order_id)
 
  # 在庫チェック
  order.items.each do |item|
    if item.product.stock < item.quantity
      raise "在庫不足: #{item.product.name}"
    end
    item.product.decrement!(:stock, item.quantity)
  end
 
  # 支払い処理
  charge = Stripe::Charge.create(
    amount: order.total_price,
    currency: "jpy",
    customer: order.user.stripe_customer_id
  )
  order.update!(payment_id: charge.id, status: "paid")
 
  # 通知
  OrderMailer.confirmation(order).deliver_later
  SlackNotifier.notify("#orders", "注文確定: #{order.id}")
 
  # ポイント付与
  order.user.points.create!(amount: order.total_price / 100, reason: "purchase")
 
  order
end

「このメソッドが抱える問題は?」

ヒロシは考えた。「……在庫が減った後で支払いが失敗したら、在庫だけ減ってしまいます。」

「その通り。そして支払い処理をテストしたいだけなのに、Slackにも通知が飛ぶ。一つのメソッドに複数の責務が混在してる。」

# 良い例: 各メソッドが一つの責務を持つ
class OrderProcessor
  def initialize(order)
    @order = order
  end
 
  def call
    ActiveRecord::Base.transaction do
      reserve_inventory!
      process_payment!
    end
    send_notifications
    grant_loyalty_points
    @order
  end
 
  private
 
  def reserve_inventory!
    @order.items.each do |item|
      raise InsufficientStockError, item.product.name if item.product.stock < item.quantity
      item.product.decrement!(:stock, item.quantity)
    end
  end
 
  def process_payment!
    charge = Stripe::Charge.create(
      amount: @order.total_price,
      currency: "jpy",
      customer: @order.user.stripe_customer_id
    )
    @order.update!(payment_id: charge.id, status: "paid")
  end
 
  def send_notifications
    OrderMailer.confirmation(@order).deliver_later
    SlackNotifier.notify("#orders", "注文確定: #{@order.id}")
  end
 
  def grant_loyalty_points
    @order.user.points.create!(
      amount: @order.total_price / 100,
      reason: "purchase"
    )
  end
end

OrderProcessor#callは全体の流れを把握するためだけにある。各ステップは独立したメソッドになって、テストも修正もしやすい。」

Loading diagram...

コメントよりもコード

「コメントをたくさん書けばわかりやすいと思ってたんですけど、違うんですか?」とヒロシ。

「コメントは嘘をつく。コードは嘘をつかない。」

# 悪い例: コメントが必要なコード
# ユーザーが30日以上前に登録していて、かつプレミアム会員で、
# かつ今月の購入金額が5000円以上の場合に割引を適用する
def apply_discount?(user)
  user.created_at < 30.days.ago && user.premium? && user.monthly_purchases > 5000
end
 
# 良い例: コードが自己説明する
DISCOUNT_ELIGIBILITY_DAYS = 30
DISCOUNT_ELIGIBILITY_PURCHASE_AMOUNT = 5_000
 
def discount_eligible?(user)
  established_account?(user) &&
    user.premium? &&
    sufficient_monthly_purchases?(user)
end
 
def established_account?(user)
  user.created_at < DISCOUNT_ELIGIBILITY_DAYS.days.ago
end
 
def sufficient_monthly_purchases?(user)
  user.monthly_purchases > DISCOUNT_ELIGIBILITY_PURCHASE_AMOUNT
end

WARNING

コメントが有効な場面

コメントが本当に必要なのは「なぜこうしたか(Why)」を説明するときだけ。「何をしているか(What)」はコード自体が語るべきです。

# Stripe は金額をセント単位で受け取るため100倍する
amount_in_cents = price * 100

Railsらしいクリーンコード

Railsには「Rail way」と呼ばれる慣用的なパターンがある。これを活用することがクリーンコードへの近道だ。

# scope を活用する
class Article < ApplicationRecord
  scope :published, -> { where(status: "published") }
  scope :recent, -> { order(published_at: :desc) }
  scope :popular, -> { where("view_count > ?", 1000) }
 
  # scopeを組み合わせて表現力豊かなクエリに
  # Article.published.recent.popular
end
 
# Concern でロジックを整理する
module Auditable
  extend ActiveSupport::Concern
 
  included do
    before_create :set_created_by
    before_update :set_updated_by
  end
 
  private
 
  def set_created_by
    self.created_by = Current.user&.id
  end
 
  def set_updated_by
    self.updated_by = Current.user&.id
  end
end
 
class Order < ApplicationRecord
  include Auditable
  # 監査ロジックを再利用できる
end

AWSでのコード品質管理

「コードの品質は、チームとツールで守る。」マイが続けた。

# .github/workflows/code_quality.yml
name: Code Quality
 
on: [pull_request]
 
jobs:
  rubocop:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
      - name: Run RuboCop
        run: bundle exec rubocop --format github
 
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Brakeman Security Scan
        run: bundle exec brakeman --no-pager

AWS CodeBuild でも同様のチェックを本番デプロイ前に実行できる。

# buildspec.yml (AWS CodeBuild)
version: 0.2
phases:
  pre_build:
    commands:
      - bundle install
  build:
    commands:
      - bundle exec rubocop
      - bundle exec rspec
      - bundle exec brakeman

Stage 1 のまとめ

ヒロシは一日でたくさんのことを学んだ気がした。

「クリーンコードって、センスじゃなくてルールだったんですね。」

マイは微笑んだ。「そう。そしてルールには理由がある。なぜそうするかを理解すれば、ルールがない場面でも正しい判断ができる。」

原則内容
意図を表す命名省略せず、文脈に合った名前をつける
単一責務一つのメソッドは一つのことだけする
自己説明するコードコメントよりもわかりやすいコードを書く
Railsの規約を活かすscope, concern, naming convention を活用

「次はパラダイム。なぜオブジェクト指向が生まれたのか、その根本から理解しよう。」

クリーンコードの土台ができた。次のStageでは、そのコードを支える「思想」を学ぶ。