Help us understand the problem. What is going on with this article?

お前らまだ引数に順番指定してんの?

はじめに

vueとnuxtの記事も書こうかなあ、、、

お前らのプロジェクト壊れる可能性あるぞ

先日、仕事中に学んだこと。
インスタンス変数を作成するためにinistializeメソッドを活用していた際に今までの自分は引数の順番に依存していました。

Human.rb
class Human
  attr_render :name, :age, :sex
  def initialize(name, age, sex)
     @name = name
     @age = age
     @sex = sex
  end

〜省略〜

end

Human.new('Taro',20,'male')

この例では、Humanのinitializeメソッドは次の3つの引数を取ります。name, age, sexの3つですね。
つまりHuman.newでインスタンスを作成するところでは引数を3つ渡しています。
まあ教科書通りのinistializeメソッド及び引数の使い方ですね。
しかしこの方法には大きな欠点があります。

  1. 引数を順番に渡さなければならない
  2. 3つの引数をもれなく、だぶりなく渡さなければならない

この引数の使い方は小規模かつ個人での開発なら何ら問題有りません。
自分が作成したインスタンスなので勝手はわかっているし、他のクラスとの依存関係も頭に入っているはずです。
小規模の開発なら変更も少ないですし、この仕様が変わることはないかもしれません。

しかし、大規模な開発かつチームでの開発だったらどうでしょう?
チームでの開発/実務レベルでの大規模な開発では常に変更を考えて実装しなければなりません。
プログラムに機能が追加されたり、改修要望が入った際には、インスタンスの引数の数が変わるかもしれないし、
そもそも既存のインスタンス変数が削除されるかもしれません。
また、この一つのインスタンスだけなら良いですが、別のクラスでHumanクラスのインスタンスを作成していた場合その箇所をすべて直さなければなりません。(10箇所の変更とか普通にあります、、、)
この書き方ではまずそうですね、、、。

ではどのように書きましょう?

引数にハッシュを使う

先程述べた「引数の順番固定」を簡単に回避する方法があります。
ハッシュです!!!

Human.rb
class Human
  attr_render :name, :age, :sex
  def initialize(args)
     @name = args[:name]
     @age = args[:age]
     @sex = args[:sex]
  end

〜省略〜

end

Human.new(name: 'Taro', age: 20, sex: 'male')

このテクニックにはいくつかの利点があります。

  1. 引数の順番依存を取り除ける
  2. 変更に強い
  3. key名が明示的なドキュメントになってくれている

この方法のおかげでどんなに引数を変更しても他のコードに対する副作用がなくなりました。
よって、仕様変更の際になんの気兼ねもなく引数の追加や除去ができます。
安心感がすごいです。
またkey名がなんの情報を渡しているのかを示してくれているため、
インスタンスを生成する際にとてもわかりやすいですね。

その他のテクニック①

Human.rb
class Human
  attr_render :name, :age, :sex
  def initialize(args)
     @name = args[:name] || 'Jiro'
     @age = args[:age]   || '10'
     @sex = args[:sex]   || 'male'
  end

〜省略〜

end

Human.new(name: 'Taro', age: 20, sex: 'male')

||メソッドを使用することでデフォルト値を設定することができます。
||メソッドはor演算子と同様に作用します。
左辺を評価し、その結果がfalseまたはnilであれば右辺の評価に移ります。
こうすることで引数に値を入れ忘れたとき(意図的に入れないとき)でもインスタンス変数を生成することができるでしょう。
例えば「sexの値はほとんどの人がmaleだな」と思えば

@sex = args[:sex]   || 'male'

と設定しておくだけでインスタンス生成時に引数を渡さなくても良くなります。

その他のテクニック②

Human.rb
class Human
  attr_render :name, :age, :sex
  def initialize(args)
     args = defaults.merge(args)
     @name = args[:name]
     @age = args[:age]
     @sex = args[:sex]
  end

〜省略〜

  def defaults
    {name: 'Jiro', age: 10, sex: 'male'}
  end

end

この方法もデフォルト値を設定するのに大変役立ちます。
隔離テクニックとでも名付けましょうか。
defaultsメソッドを設定し、initializeメソッド内で呼び出しています。
この方法は特にデフォルト値が長いときや複雑な時に活用すべきです。
コードの可視性が高まりますね。

終わりに

プログラムは普遍的なものではありません。
常に変更と改善の繰り返しです。
変化に強いコードを書けるスキルは今の時代(アジャイルでの開発が主流になってきた時代)、必須のスキルと言っても過言ではありません。
あなたのコードがチームメンバーに多くの手間と工数をかけさせるかもしれません。
ひどいときにはプログラム自体を破壊してしまう可能性もあるのです。
ただ動くだけではなく、本当に"良いコード"を書けるように一緒に邁進しましょう!!!

(今回はちょっと真面目に書きすぎたな、、、:robot::robot:

参考

オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
Sandi Metz (著), 髙山泰基 (翻訳)

nakanishi03
プログラミング初心者や駆け出しエンジニア向けの記事を書いています。railsの記事多めです。
https://qiita.com/settings/profile
anconsulting
フリーランスITエンジニア向け案件・求人サイト「フリエン」を運営するスタートアップ
https://furien.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした