2019はじめに対象読者は初心者です。C.Martinの著書「アジャイルソフトウェア開発の奥義」では、ソフトウェアの構成要素(クラス、モジュール、関数など)は拡張に対して開いていて、修正に対して閉じていなければならないと説明されています。「拡張に対して開いている」とは、雑に言い換えると「機能追加がしやすい」です。「修正に対して閉じている」とは、雑に言い換えると「変更が他の場所に影響を及ぼさない」です。具体例として、自分が考えたものは、くじ引きです。一般くじとプレミアムくじというものがあり、それぞれが違う抽選ロジックを持っているとします。開放閉鎖原則に従って実装したくじ引きのコードは、下記のようになります(インターフェース(publicなメソッド)部分が重要なので細かい部分は除いています)。プレミアムくじを引くなぜ、上記のコードが開放閉鎖原則に従っていると言えるのでしょうか?要件の追加やバグの修正の例を用いて説明します。新たに要件が追加され、スーパープレミアムくじを追加することになりました。異なっているのは抽選ロジックの部分のみのため、新たにSuperPremiumLotteryLogicクラスを実装しました。スーパープレミアムくじを引くこのとき、既存のコードには一切変更を加えずに、新機能を追加できました。これが、「拡張に対して開いている」ということです。機能実装後、スーパープレミアムくじのテストを行っている際に、バグが発見されたため、SuperPremiumLotteryLogicを修正しました。このとき、既存の他のコードに一切影響を及ぼさずに修正を行うことができました。GeneralLotteryLogicでもPremiumLotteryLogicでも同様に問題が発生して修正することになっても(execの引数と戻り値が変わらない限り)他に影響を及ぼしません。これが、「修正に対して閉じている」ということです。開放閉鎖原則を守るべきなのか当たり前のことなのですが、そもそも要求が変わるからです。すべてのプログラムが人の要求を満たすために作られています。人の要求は変わります。要求が変わったときには、機能の追加や変更が必要になります。そのとき、いかに容易に安全にできるかがこの原則を守っているかどうかで変わってきます。すれば開放閉鎖原則を満たすように作れるのか自分は、下記3つを押さえることが大事だと考えてます。変更されやすい部分を見抜く変更発生時に追加する原則を知る変更されやすい部分を見抜く作るものにもよると思いますが、似たようなビジネスを他が行っていたり、抽象化して捉えたときに同じようなものが存在しているときには、変更されやすい部分がわかります。上記で例として上げたくじ引きはわかりやすいと思います。ソシャゲなどで見られるガチャやくじといったものにはパターンが存在しているので、そういったものから考えて変更されやすそうだという当たりをつけることができます。ただの勘ではなく、分析した結果として適用する必要があります。Martinは「アジャイルソフトウェア開発の奥義」の中で、「OCPの適用は起こる可能性の高い変更に限定すべきなのは明らかだ。」と述べています(詳しくは著書をお読みください)。変更発生時に追加する全ての「変更されやすい箇所を見抜く」ことは困難なので、変更が発生されてから抽象を追加して、開放閉鎖原則に従うようにするというのもありだと思います。テストコードが書かれていたり、強い静的型付けに頼れる状態であったりしないと厳しいと思います。従っている例として上げたコードは、デザインパターンで言うところのストラテジーパターンに該当します。いったデザインパターンについて知っておいたり、抽象(インターフェース)に依存するように作るということを知っておくと開放閉鎖原則に従ったコードが書けるようになると思います。Principle)である開放閉鎖原則についてまとめてみました。Architecture」もおすすめなので、ぜひ読んでみてください。(自分の書いたコード含めて)人を不幸にしないコードが普及することを願う。2019はじめに対象読者は初心者です。個人的にいくつかの書籍を読んで、内容や自分の考えをまとめたものです。C.Martinの著書「アジャイルソフトウェア開発の奥義」では、クラスを変更する理由が複数存在してはならないと説明されていました。一方で、比較的最近の彼の著書である「クリーンアーキテクチャ」では、「モジュール1はたったひとつのアクター2に対して責務を負うべきである。」とも説明されていました。まとめると、「クラスを変更する理由は複数のアクター起因であってはならない」と言えると思います。単一責任の話の具体例として自分が考えたものは、よくある管理画面、ユーザー画面に別れているものです。作りとしては、見た目上分かれているが、アプリケーションとしては別れていない、モノリシックな構成です(たぶんRails採用しているところにありがち)。単純に捉えて、管理画面側のアクターとして管理者がおり、ユーザー画面側のアクターとしてユーザーがいるとします。商品の登録は管理者が行い、商品の予約をユーザーが行う場合に、下記のようなProductクラスがあった場合、単一責任の原則に違反しています。このProductクラスは、ユーザーが商品を予約できる機能と、管理者が商品を登録できる機能を別々のクラスに分離する対応が必要です。予約の処理endendなぜ単一責任の原則を守るべきなのかなぜ単一責任の原則を守るべきなのでしょうか?先程の続きで考えてみます。ある日、管理者側のメンバーが登録処理の実装の変更を依頼してきました。開発者は登録処理の変更を正しく行いリリースしました。ところがユーザー画面側で予約処理に不具合が出ているという報告を受けました。開発者は再度修正を入れ、管理画面側でもユーザー画面側でも影響がないことを確認した後リリースして事なきを得ました。この話を聞いてどう思ったでしょうか?開発者が最初のリリースで予約処理への影響を確認しなかったのが悪かったのでしょうか?この話の問題点は、単一責任の原則を破ってしまったことです(テストコードをもっとしっかり書けばとか、例が適当すぎるのは一旦おいておきます)。2019結論Rubyでは、動的にクラスに生えているメソッドを書き換えられるため、テスタビリティを上げるためにDIを使わない。個人的にはストラテジーパターン使いたいときぐらいしか、DI使わないんじゃないかなーって思っている。Rubyでテスタビリティ向上のためにDIを使わない理由DIを使う理由は、結合度の低下、テスタビリティの向上が主な理由だと思います。一般的な言語の場合には、引数にインターフェースを指定して、インターフェースに依存するようにすることでテスト時にモックオブジェクトを注入できるようにすると思います。Rubyの場合には、動的にクラスに生えているメソッドを書き換えれるためテスタビリティを上げるためにDIをするのは意味がないです。Rubyでは、rspecを用いて下記のように書くだけでクラスのメソッドを書き換えることができます。これによりテスト実行時にはクラスのメソッドの内容が変わった状態でテストが実行されます。Rubyのこの柔軟性により、テストを容易にするためにDIを行うという行為は意味がなくなります。receive(:from_service_account_key).and_return(session)endRubyでDIを使う局面Rubyを使ってDIを使うとしたら、それはストラテジーパターンを使用したいときぐらいだろうと思ってます。最近言語周りで思っていること特定の言語には、特定の言語の流儀があるので、それに従おう。一般論とか、別の言語の流儀の知識だけで、考えずに突き進むのはやめよう。ProgrammingRailsRubySOLID原則TypeScriptデザインパターン不正行為を。