update.はじめに:「引数があるよりは、ない方が良い」?先日、同僚の西見さん(@mah_lab)がこんな技術ブログを書いていました。インスタンスメソッドとクラスメソッドはどのようにして使い分けるべきか?(Rubyの場合)同じ内容を僕だったらどういうふうに書くかな~?と思って、ちょっと書き始めてみたんですが、わかりやすく実践的な説明をするのは意外と難しく、内容も西見さんのブログとほぼ同じになりそうだったので、途中で断念しました。いうわけで、インスタンスメソッドとクラスメソッドの使い分けが未だにあやふやだという方は、ぜひ西見さんのブログを読んでみてください!・・・なんですが、1点だけ気になる点がありました。それはインスタンスメソッドの利点に関する以下の記述です。クラスの外側でのデータの受け渡しはなくなり、データの受け渡しはクラス内部で行われるようになりました。クラスの中で十分な情報を蓄えているため、renderメソッドは引数を取る必要がありません。(中略)引数があるよりは、ない方が良いのです。引数がなければ、そもそも何を情報として渡さなければいけないかを考えなくて済みます。引数があることで、コードを使う人に余計な仕事をさせることになります。ブログの中で使われているサンプルコードを見れば、その利点は明らかで疑問の余地はありません。「引数があるよりは、ない方が良いのです。」という判断基準を機械的に適用してしまうと、かえって扱いづらいプログラムが出来上がる可能性があります。いうわけで、この投稿では「引数があるよりは、ない方が良いのです。」を誤解した人のサンプルコードを挙げて、何がダメなのかを説明します。ダメなコード例ここでは架空の販売管理システムを考えてみます。このシステムは1件の注文に対する注文明細をCSVから読み込んで作成できる、という機能があります。初心者プログラマのAさんは「引数があるよりは、ない方が良い」というルールを機械的に適用して次のようなコードを書きました。'作成に失敗しました。endend読み込んだCSVファイルのデータはインスタンス変数に格納したので、save_detailsメソッドには引数がありません。やった!・・・と思わないようにしましょう。このコードを利用するときはどんなコードになるか?実際に上のコードを利用する場合を想定してみましょう。次のようになると思います。メソッドを呼び出す順番を間違えるとエラーになるsave_detailsメソッドは、事前にCSVデータが読み込まれていることを前提としています。次のような呼び出し方をするとエラーになります。@csv_dataがnilなのでエラーが起きる同様に、resultを読み出す場合も先に処理を実行していないと中身が空っぽです。中身が空このクラスを作ったAさんは「先に必要なメソッドを呼び出さない方が悪い!」と考えるかもしれませんが、こういうケースは呼び出す順番(=コンテキスト)に依存するメソッドを作ってしまったAさんが悪いです。内容がどんどん追記されてしまい、使い物にならなくなります。order.result「あー、ごめんなさい。save_detailsメソッドを修正しておいたんでこれで大丈夫です!」と、Aさんはコードを修正してきました。'作成に失敗しました。end・・・はい、確かにここではこの対応でなんとかなりますね。そもそもこのクラスの問題はもっと本質的なところにあります。その根本的な問題を解決する方法を見ていきましょう。@result)に格納してしまっていることです。一時的なデータをインスタンス変数に入れるとクラスの利用者が不便になるだけでなく、そのクラスに手を加えるときもインスタンス変数が「クラス内のグローバル変数」になってしまい、クラスが大きくなるにつれて保守性がどんどん低下します。いった一時的なデータはメソッドの引数と戻り値、またはローカル変数として受け渡す必要があります。1つの要求につき1メソッドにするもうひとつの問題はクラスの利用者から見たときの1つの要求が、複数のメソッドに分かれていることです。前述の例の場合、クラスの利用者が要求は「1件の注文に対する注文明細をCSVから読み込んで作成すること」でした。この要求を実現するためには3つのメソッドが必要になっています。(order.read_csv、order.save_details、order.result)しかも決まった順番で呼び出さないと適切な結果が得られません。クラスの利用者の利便性を考えれば、こうした処理は1つのメソッドで完結させるべきです。改善後のコードここまでに見てきた問題点と改善策を踏まえてコードを書き直すと、以下のようになります。'作成に失敗しました。endendこれにより、呼び出す側のコードは次のように変わります。order.create_details_from_csv(file_path)1つのメソッドで「注文明細をCSVから読み込んで作成すること」が実現できているので、メソッドを呼び出す順番に気を遣う必要はありません。処理の結果は戻り値として返ってくるようになりました。最初のダメなコード(下記)に比べると「クラスの利用者にとって優しい設計」になっているのがわかると思います。これはこれでまだ改善できそうな点があると思います。今回の記事は「インスタンス変数の間違った使い方を理解してもらう」というテーマにしているので、これ以上深入りしません。どこをどう改善できそうかは隣の席の同僚と議論してみてください。上級プログラマのみなさんへここで紹介したサンプルコードはあくまで「インスタンス変数を正しく使わないとどうなるか」を示すために作成した架空のサンプルコードです。初心者の方が理解しやすくなるように、説明上必要のない項目はすべて省略しています。クラスメソッドにすべきだ!」という指摘です)。具体的なコードを見せられるとついつい具体的な指摘をしたくなる気持ちは十分理解できるのですが、「これはあくまで説明用のサンプルコードだから」という温かい目で見てもらえるとありがたいです。!架空のコードではなく、実際に動くコードでこれをやったらどうなるのか確認してもらうために、このサンプルアプリケーションをRailsで実際に作ってみました。https://github.com/JunichiIto/instance-variable-sandbox/インスタンス変数を使う場合と使わない場合のdiffはこちらで確認できます。メソッドがインスタンスメソッドのままでも問題ないことを理解してもらえるんじゃないかと思います。まとめというわけで、今回はクラスの利用者にも保守担当者にも迷惑をかける「インスタンス変数の間違った使い方」とその改善策を紹介してみました。ちなみに「ダメなコード例」で見せたようなコードは、前職でも前々職でもよく見かけたコードです。すると、あなたの周りでも同じようなコードを書いている人がいるかもしれません。(それがあなたではないことを祈りますが)その場合は職場内でコードレビューをしたり、オブジェクト指向設計に関する技術書の読書会を開いたりして、「みんなが幸せになるクラス」をチーム全員が作れるようになりましょう!あわせて読みたいプログラマ歴12年の僕が選んだ「10年経っても役立つ技術書17選」きれいなコードを書いたり、きちんとしたクラスを設計したりするのに役立つ(僕は役に立った)技術書を紹介しています。RubyやRailsでリファクタリングに使えそうなイディオムとか便利メソッドとかRubyらしい効率のいい書き方を学びたい方はこちらの記事が役に立つかもしれません。クラスのフィールド変数をグローバル変数のように利用する。」は今回紹介した内容とほぼ同じです。弊社ソニックガーデンでも技術トレーニングを行っていますRubyらしく、Railsらしく、きれいなコードできちんと動くWebアプリケーションを作りたい、という方は弊社ソニックガーデンの技術トレーニングを受講してみてはいかがでしょうか?興味がある方は弊社Webサイトからお気軽にお問い合わせください。Ito@jnchitoSIer、社内SEを経て、ソニックガーデンに合流したプログラマ。「プロを目指す人のためのRuby入門」の著者。RSpecによるRailsテスト入門」の翻訳者。upLoginユーザーは見つかりませんでした。