引数が多い時のコード設計についてと、
デフォルト値の使い方について。
状況によって最適に使い分けたい。
引数が多い時のコード設計について
- キーワード引数を使う方法
- ハッシュ引数を使う方法
- 値オブジェクトを作る方法
「オブジェクト指向設計実践ガイド」や「オブジェクト設計スタイルガイド」を読む限り、疎結合で変更に強くなる3の方法が良さそうに感じる。値オブジェクトのクラスにバリデーションを儲けたり、共通利用したり、テスト書きやすかったりもする。型がある言語の方が受ける恩恵は大きい。
ただしIDEのコード補完等の恩恵が受けづらくなったり、人によっては可読性が低くなったり。パラメータの中身がわかりづらいので、yard等書いたほうが良さそう。
1は増減等変更に弱いが、行数が少なくて済み、引数が明示的で直感的に理解しやすく、2はさらに行数が少なくて済むが、呼び出し側の引数指定が多い。
キーワード引数を使う方法
class HogeService
def initialize(name:, address:, age:, sex:, dob:)
@name = name
@address = address
@age = age
@sex = sex
@dob = dob
end
def call
# 何らかの処理
# @name、@address などを利用
end
end
HogeService.new(name: 'taro', address: 'tokyo', age: 20, sex: 'M', dob: '2000-01-01')
ハッシュ引数を使う方法その1
class HogeService
def initialize(**params)
@params = params
end
def call
# 何らかの処理
# @params[:name]、@params[:address] などを利用
end
end
HogeService.new(name: 'taro', address: 'tokyo', age: 20, sex: 'M', dob: '2000-01-01')
ハッシュ引数を使う方法その2
class HogeService
def initialize(name:, address:, age:, sex:, dob:)
@name = name
@address = address
@age = age
@sex = sex
@dob = dob
end
def call
# 何らかの処理
# @name、@address などを利用
end
end
hash = {name: 'taro', address: 'tokyo', age: 20, sex: 'M', dob: '2000-01-01'}
HogeService.new(**hash)
値オブジェクトを作る方法
class UserParams
attr_reader :name, :address, :age, :sex, :dob
def initialize(name:, address:, age:, sex:, dob:)
@name = name
@address = address
@age = age
@sex = sex
@dob = dob
end
end
class HogeService
def initialize(user_params)
@user_params = user_params
end
def call
# 何らかの処理
# @user_params[:name], @user_params[:address] などを利用
end
end
params = UserParams.new(name: 'taro', address: 'tokyo', age: 20, sex: 'M', dob: '2000-01-01')
HogeService.new(params)
デフォルト値について
- クラス内でデフォルト値を設定する方法
- 呼び出し側でデフォルト値を設定する方法
オライリーの「オブジェクト設計スタイルガイド」には以下のように書いてあり、2推し
オブジェクトが必要とする設定値は、常にクラスのユーザーが提供するようにしましょう
理由としては、
- デフォルト値を知るには、そのクラスのコードを読む必要があること
- デフォルト値が変更された際にそれを利用している側に影響があるから
クラス内でデフォルト値を設定
class HogeService
def initialize(name:, address:, age: 20, sex: 'M', dob: '2000-01-01')
@name = name
@address = address
@age = age
@sex = sex
@dob = dob
end
end
HogeService.new(name: 'taro', address: 'tokyo')
メリット
- 呼び出し側の負担を減らせる。可読性向上
- デフォルト値の一元管理ができる
class HogeService
def initialize(params)
@params = params
end
def call
# 何らかの処理
end
end
user_params = {
name: 'taro',
address: 'tokyo',
age: 20,
sex: 'M',
dob: '2000-01-01'
}
HogeService.new(user_params).call
メリット
- 呼び出し側でデフォルト値を管理すれば、変更の影響を 特定の呼び出し元 のみに抑えられる
- 呼び出し側の文脈に応じて適切なデフォルト値を設定できる
- IDE や型チェックの恩恵を受けやすい
- 「デフォルトの管理」という責務がなくなり、クラスの責務をシンプルにできる
デフォルト値の使い方のまとめ
クラス内でデフォルト値を設定するのが良い時
- オプションがあるが、頻繁に変更されるわけではない場合
- 一方、デフォルト値を変更すると影響が大きいため、変更頻度が高い場合は呼び出し側で設定する方がよい。
呼び出し側でデフォルト値を設定するのが良い時
- デフォルト値が状況ごとに変わる可能性がある場合
- 一方、デフォルト値が一貫している場合は、クラス内で設定するほうがコードがスッキリする。
感じたこと
- 可読性と変更容易性はトレードオフな側面がある
- 可読性を重視すると影響調査が大変になるかも
- 変更容易性を重視すると読みづらいかも