ログイン新規登録

Qiitaにログインして、便利な機能を使ってみませんか?

あなたにマッチした記事をお届けします

便利な情報をあとから読み返せます

7

【Ruby】ラーメンでわかる!引数多すぎ問題の解決方法

最終更新日 投稿日 2024年03月07日

はじめに

この記事は、「クラスをnewする際の引数が多くて困ってる」人向けのものです。
オブジェクト指向設計実践ガイド 3.2 疎結合なコードを書く-引数の順番への依存を取り除く』を参考にしてこの記事を作成しました。

ラーメンの例を用いて、クラスをnewする際の引数が多いときの問題点とその解決方法を紹介しているのでぜひご覧ください。

まずは結論から

引数をハッシュで渡そう!

ラーメンを用いての例

あぁ、ラーメンが食べたい。そうだラーメンクラスを使ってラーメンを作ろう!
ラーメンに必要な具材は、これとあれとそれだから...っと
よし、これをRamenクラスに渡してあげればきっとラーメンが作れるはずだ〜
(私は煮玉子いらない派なのでnilにしてます。)

class Ramen
    def initialize(noodles, soup, roast_pork, bamboo_shoots, marinated_boiled_egg)
        @noodles = noodles # 麺
        @soup = soup # スープ
        @roast_pork = roast_pork # チャーシュー
        @bamboo_shoots = bamboo_shoots # メンマ
        @marinated_boiled_egg = marinated_boiled_egg # 煮玉子
    end
end


Ramen.new('硬めの麺', '醤油', '厚めチャーシュー', '薄めメンマ', nil)

お気づきでしょうか? initializeの引数(ラーメンの具材) が多すぎることに。。。
このように引数が多すぎる場合に起こり得る問題として以下が挙げられます。

問題点

  1. 引数の順番に変更があった際に、Ramenクラスをnewしている箇所全てを修正する必要があるため、変更の手間がかかる。(引数の順番に対する依存)
  2. Ramenクラスをnewするために必要な引数(ラーメンの具材)が増えるたびに、認知負荷が高まる。

解決策

上記2つの問題点を簡単に解決する方法があります。
それは 引数にハッシュを使う というものです。

さっそくこの方法を実践してみましょう。
以下が引数にハッシュを使った例です。

class Ramen
    def initialize(args)
        @noodles = args[:noodles] # 麺
        @soup = args[:soup] # スープ
        @roast_pork = args[:roast_pork] # チャーシュー
        @bamboo_shoots = args[:bamboo_shoots] # メンマ
        @marinated_boiled_egg = args[:marinated_boiled_egg] # 煮玉子
    end
end


Ramen.new(:noodles => '硬めの麺',
          :soup => '醤油',
          :roast_pork => '厚めチャーシュー',
          :bamboo_shoots => '薄めメンマ',
          :marinated_boiled_egg => nil)

引数をハッシュで受け取るように変更したことで以下のメリットがあります。
・引数の順番に対する依存が解消された(問題点 1 の解決)
・引数をハッシュで持っているため、Ramenクラスをnewするために必要なものが増えたとしても引数が増えない(問題点 2 の解決)

余談

問題点の一つとして「引数の順番に対する依存」が挙げられていましたが、これに対する解決策はもう一つあります。
それは キーワード引数 です。

キーワード引数ってなに?

キーワード引数は、その名前の通り、引数をそのキーワード(名前)で指定できる機能です。
以下のように、引数の順番に依存することなく処理を実行できます。
また、特定の引数が何を指しているのかが明確になるため、コードの可読性が向上します。

def order(ramen:, count:)
    puts "#{ramen}#{count}杯注文お願いします"
end

order(ramen: '塩ラーメン', count: 2)
-> "塩ラーメンを2杯注文お願いします"

order(count: 2, ramen: '塩ラーメン')
-> "塩ラーメンを2杯注文お願いします"

ラーメンを用いての例でキーワード引数を使用しなかった理由

まずご覧いただきたいのは以下の表です。
これは、引数をハッシュで渡す方法とキーワード引数で渡す方法のメリット・デメリットをまとめたものです。

方法 メリット デメリット
キーワード引数 ・ 順番に依存しない
・引数に過不足があるときにエラーが発生する
引数が多いときは可読性が低下する
ハッシュで渡す ・順番に依存しない
・引数の受け取りが楽になる
引数に過不足があるときにエラーが発生しない

キーワード引数を選択した際のデメリットを考慮した結果、引数をハッシュで渡す方が良いと判断したので、キーワード引数を使用しなかったのです。
実際に、Ramenクラスをnewする際の引数をキーワード引数にしてみると納得がいくと思います。

class Ramen
    def initialize(noodles:, soup:, roast_pork:, bamboo_shoots:, marinated_boiled_egg:)
        @noodles = noodles # 麺
        @soup = soup # スープ
        @roast_pork = roast_pork # チャーシュー
        @bamboo_shoots = bamboo_shoots # メンマ
        @marinated_boiled_egg = marinated_boiled_egg # 煮玉子
    end
end

Ramen.new(noodles: '硬めの麺',
          soup: '醤油',
          roast_pork: '厚めチャーシュー',
          bamboo_shoots: '薄めメンマ',
          marinated_boiled_egg: nil)

キーワード引数にすることによって、引数の順番に対する依存は解決されました。
しかし、initializeの部分を見ていただければ分かる通り、もう一つの問題点である
「Ramenクラスをnewするために必要な引数(ラーメンの具材)が増えるたびに、認知負荷が高まる」が全く解決されていません。
やはり、キーワード引数は引数が多い時に使用するには向いていなさそうですね。

新規登録して、もっと便利にQiitaを使ってみよう

  1. あなたにマッチした記事をお届けします
  2. 便利な情報をあとで効率的に読み返せます
  3. ダークテーマを利用できます
ログインすると使える機能について
manrikitada
d-publishing
__知識のエージェント__ 私たちは、実生活、実務に役立つ専門家の知識を商品化し、それをマーケティングして顧客に届けています。社内開発チームは、そのコンテンツを快適に観るためのプラットフォームを開発しています。毎日人狼やってます。
この記事は以下の記事からリンクされています

コメント

ashworth
@ashworth(ash worth)
(編集済み)

んー…、

ハッシュやなくてオブジェクトを投げるのがオブジェクト指向的には良いと思う。

…んやけど、俺様、C言語的な関数のオーバーロードが大好きなので、
引数違いで別の挙動する(=実行する関数が変わる)のが、
実はとても素敵なことやと思ってるので、

関数のオーバーロード出来ない言語は、劣等で不便やなぁ…と思ったりもする。

0
fujitanozomu
@fujitanozomu(藤田 望)

@manrikitadaさん、

引数にデフォルト値設定してカスタマイズしたい項目だけ引数で渡してやる方法で良くないですか?

class Ramen
    def initialize(
        noodles:              'ふつうの麺',
        soup:                 '醤油',
        roast_pork:           'ふつうのチャーシュー',
        bamboo_shoots:        'ふつうのメンマ',
        marinated_boiled_egg: '固ゆで味玉'
    )
        @noodles = noodles # 麺
        @soup = soup # スープ
        @roast_pork = roast_pork # チャーシュー
        @bamboo_shoots = bamboo_shoots # メンマ
        @marinated_boiled_egg = marinated_boiled_egg # 煮玉子
    end
end

Ramen.new(soup: 'トンコツ', noodles: 'ハリガネ(本物)')
0

いいね以上の気持ちはコメントで

記事投稿キャンペーン開催中
Qiita×Findy記事投稿キャンペーン 「自分のエンジニアとしてのキャリアを振り返ろう!」
~
7

新規登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる

ソーシャルアカウントでログイン・新規登録

メールアドレスでログイン・新規登録