mybook

Stage 2: プログラミングパラダイム — OOP・関数型・手続き型

なぜパラダイムを学ぶのか

「マイさん、パラダイムって何ですか?なんか難しそうな言葉ですけど……」

マイはホワイトボードにシンプルな図を描いた。

「パラダイムはプログラムをどう考えるかという枠組みのこと。建物の設計でいうと、同じ建物を作るのに『部屋の配置から考える』か、『動線から考える』か、『素材から考える』かという違い。」

「コードにも、考え方のスタイルがある。そのスタイルを理解しないと、どんな言語を使っても同じ罠にはまる。」

手続き型: 手順の記述

最も原始的なパラダイムは手続き型だ。上から下へ、順番に処理を記述する。

# 手続き型スタイル
users = User.all.to_a
active_users = []
 
i = 0
while i < users.length
  if users[i].active? && users[i].email.present?
    active_users << users[i]
  end
  i += 1
end
 
emails = []
j = 0
while j < active_users.length
  emails << active_users[j].email
  j += 1
end
 
puts emails

「動くけど、何をしているか一目でわからない。変数 i, j の管理も煩雑。そして再利用もできない。」

手続き型はコンピュータの動作に近い記述ができるが、複雑さが増すにつれ管理が難しくなる。

オブジェクト指向: データと振る舞いをまとめる

オブジェクト指向(OOP)は現実世界の「もの(オブジェクト)」に例えてプログラムを考える。

Loading diagram...
# オブジェクト指向スタイル
class User
  attr_reader :name, :email, :status
 
  def initialize(name, email)
    @name = name
    @email = email
    @status = :inactive
  end
 
  def activate!
    @status = :active
    self
  end
 
  def active?
    @status == :active
  end
 
  def contact_email
    active? ? @email : nil
  end
end
 
# 利用側はオブジェクトのメソッドを呼ぶだけ
users = User.all
contact_emails = users.select(&:active?).map(&:email).compact

「Rubyは純粋なオブジェクト指向言語。数値も文字列も、全てオブジェクト。1.classInteger"hello".upcase も普通に動く。」

OOPの三本柱

# 1. カプセル化: 内部実装を隠す
class BankAccount
  def initialize(initial_balance)
    @balance = initial_balance  # 外から直接触れない
    @transaction_log = []
  end
 
  def deposit(amount)
    validate_amount!(amount)
    @balance += amount
    log_transaction(:deposit, amount)
    self
  end
 
  def balance
    @balance  # 読み取りのみ公開
  end
 
  private
 
  def validate_amount!(amount)
    raise ArgumentError, "金額は正の数のみ" unless amount.positive?
  end
 
  def log_transaction(type, amount)
    @transaction_log << { type: type, amount: amount, at: Time.current }
  end
end
 
# 2. 継承: 共通の振る舞いを親クラスに
class Vehicle
  attr_reader :make, :model, :year
 
  def initialize(make, model, year)
    @make = make
    @model = model
    @year = year
  end
 
  def age
    Date.current.year - @year
  end
 
  def description
    "#{@year} #{@make} #{@model}"
  end
end
 
class ElectricVehicle < Vehicle
  attr_reader :battery_capacity_kwh
 
  def initialize(make, model, year, battery_capacity_kwh)
    super(make, model, year)
    @battery_capacity_kwh = battery_capacity_kwh
  end
 
  def estimated_range_km
    battery_capacity_kwh * 6  # 概算
  end
end
 
# 3. ポリモーフィズム: 同じメッセージ、異なる振る舞い
class PdfExporter
  def export(content)
    "<PDF>#{content}</PDF>"
  end
end
 
class CsvExporter
  def export(content)
    content.map(&:to_csv).join("\n")
  end
end
 
class JsonExporter
  def export(content)
    content.to_json
  end
end
 
# どのエクスポーターでも同じように呼べる
def generate_report(data, exporter)
  exporter.export(data)
end

INFO

RubyのOOPの特徴

Rubyには**ミックスイン(Module)**という仕組みがある。多重継承の問題を避けながら、機能を共有できる。RailsのConcernもこの仕組みを使っている。

関数型: データの変換として考える

関数型パラダイムは副作用を排除し、データの変換として処理を記述する。

# 命令型(変数を変化させる)
total = 0
[1, 2, 3, 4, 5].each do |n|
  total += n * 2 if n.odd?
end
puts total  # 18
 
# 関数型(データを変換する)
result = [1, 2, 3, 4, 5]
  .select(&:odd?)      # [1, 3, 5]
  .map { |n| n * 2 }  # [2, 6, 10]
  .sum                 # 18
 
puts result  # 18

「Rubyは純粋な関数型言語ではないけど、map, select, reduceなどの高階関数をサポートしている。これらを使うと何をするかが明確になる。」

# 関数型スタイルでのデータパイプライン
module ReportPipeline
  def self.generate_monthly_report(year, month)
    Order
      .where(created_at: Date.new(year, month).all_month)
      .completed
      .includes(:user, :items)
      .then { |orders| aggregate_by_category(orders) }
      .then { |aggregated| apply_ranking(aggregated) }
      .then { |ranked| format_for_display(ranked) }
  end
 
  def self.aggregate_by_category(orders)
    orders.flat_map(&:items)
      .group_by { |item| item.product.category }
      .transform_values { |items| items.sum(&:total_price) }
  end
 
  def self.apply_ranking(aggregated)
    aggregated.sort_by { |_, revenue| -revenue }
              .each_with_index
              .map { |(category, revenue), rank| { rank: rank + 1, category: category, revenue: revenue } }
  end
 
  def self.format_for_display(ranked)
    ranked.map do |entry|
      entry.merge(revenue_formatted:#{entry[:revenue].to_s(:delimited)}")
    end
  end
end

不変性の重要さ

# 悪い例: 副作用がある
def add_tax(prices)
  prices.each_with_index do |price, i|
    prices[i] = (price * 1.1).ceil  # 元の配列を変更してしまう!
  end
  prices
end
 
original = [1000, 2000, 3000]
result = add_tax(original)
puts original  # [1100, 2200, 3300] — 変わってしまった!
 
# 良い例: 副作用なし
def add_tax(prices)
  prices.map { |price| (price * 1.1).ceil }  # 新しい配列を返す
end
 
original = [1000, 2000, 3000]
result = add_tax(original)
puts original  # [1000, 2000, 3000] — 変わっていない
puts result    # [1100, 2200, 3300]

WARNING

Railsでのよくある副作用バグ

map!, select!, reject!などの破壊的メソッドは元のコレクションを変更します。特にActiveRecordのallから取得したコレクションを破壊的に変更すると、予期しないバグの原因になります。

3つのパラダイムを使い分ける

「どれが一番いいんですか?」ヒロシが聞いた。

「優劣はない。状況によって使い分ける。」

class UserDashboardService
  def initialize(user)
    @user = user
  end
 
  def call
    # OOP: ユーザーというオブジェクトの振る舞い
    return { error: "アカウントが無効です" } unless @user.active?
 
    # 関数型: データの変換パイプライン
    recent_orders = @user.orders
      .recent
      .includes(:items)
      .map { |order| format_order(order) }
 
    # 手続き型: 順番に処理が必要な箇所
    stats = {}
    stats[:total_orders] = recent_orders.count
    stats[:total_spent] = recent_orders.sum { |o| o[:total] }
    stats[:favorite_category] = most_purchased_category
 
    {
      user: @user.as_json(only: [:id, :name, :email]),
      orders: recent_orders,
      stats: stats
    }
  end
 
  private
 
  def format_order(order)
    {
      id: order.id,
      total: order.total_price,
      items_count: order.items.count,
      status: order.status,
      created_at: order.created_at
    }
  end
 
  def most_purchased_category
    @user.orders
      .flat_map(&:items)
      .group_by { |item| item.product.category_name }
      .max_by { |_, items| items.count }
      &.first
  end
end

パラダイムと設計の関係

「パラダイムを理解すると、次のステップのSOLID原則がより深く理解できる。」マイが言った。

Loading diagram...
パラダイム得意なことRubyでの活用場面
手続き型シンプルなスクリプト、順次処理Rakeタスク、マイグレーション
OOP状態管理、ドメインモデルActiveRecord Model, Service Object
関数型データ変換、副作用の排除map/select/reduce チェーン、Pure method

「Rubyはマルチパラダイム言語。一つの言語で全てのスタイルが書ける。だからこそ、意識的に選択することが大切。」

Stage 2 のまとめ

「今日学んだことを整理しよう。」マイが言った。

ヒロシはノートにまとめた:

  • 手続き型: 上から下へ。シンプルだが複雑さに弱い
  • オブジェクト指向: データと振る舞いをまとめる。カプセル化・継承・ポリモーフィズム
  • 関数型: データを変換として考える。副作用を排除し、予測可能に

「次のStageでは、OOPをより深く活用するためのSOLID原則を学ぶ。クラス設計の本質に踏み込んでいく。」

ヒロシはRubyのオブジェクト指向の豊かさを、初めて実感した気がした。