タグ: アジャイル開発の奥義

単一責任の原則(SRP)

Single Responsibility Principle(単一責任の原則)は、Tom DeMarco の “Structured Analysis and Systems Specification” と、Meilir Page-Jones の “The Practical Guide to Structured Systems Design” で説明されている。この原則は次のようなものだ。

クラスを変更する理由は複数存在してはいけない

なぜこうする必要があるのか?

 役割が単一なクラスの仕様要求が変化した場合、その変更部分は浮き彫りになり、どのように変化したのかわかりやすい。これは、ほとんどの人が納得するはなしではないだろうか。

 しかし、役割が複数あると、その1つ1つが変更理由になってしまう。複数の変更理由によってクラスが変更されると、変更部分がぼやけてしまう。また、ある理由により変更した部分が、他の役割に影響してしまい、連鎖的に変更が必要になるなどといったケースが生じうる。これは もろい設計 にあてはまる。もろい設計がソフトウェア開発にどんな影響を及ぼすかは、前に書いた記事を参照されたい。

役割とはなにか?

 「役割=変更理由」と定義している。この判断が非常に難しいケースも多々有る。

変更の理由が変更の理由たるのは、実際に変更の理由が生じた場合だけである

 変化の兆候がないのにSRPを含めた原則を適用するのは賢明ではない。原則を適用することを目的にしちゃいけないというのは、いつも気に止めておかなければならないなぁと思います。

結合している役割を見つけそれらを分離する作業は、ソフトウェア設計の本質である

 非常に身にしみる言葉。

どのように分離するのか

たとえば、次のようなモデムインターフェースを考える。

 最初の2つのメソッドは接続の開始と終了、すなわち接続管理の機能を持つ。後半の2つのメソッドはメッセージのやりとりに用いる。この2つの役割を分離すべきかどうかは今後このアプリケーションがどのように変更されるか次第。たとえば接続管理の機能が影響を受けるような変更がはいる場合は、sendrecv のみを呼び出しているクラスをリコンパイル、リロードしなおさなければならない。もっとも単純にこの状態から打破するには、以下のように分離すればよい。ModelImplはその場しのぎではあるが、すべての依存関係が解消しているはずだ。

oymhIIrAIqnELN19B4bqpaZCoqlDKQZcqbOeJirBqR1ApaWiqWJoYbAJopGqkMgvk3BXEfppWOgJIpBpyy0QKZ8JSpGAylAj589BYZBpqa7QWEQbbnP0D29RO5EZgwlWcv-IckkPMv2Zm8La59eS0000

 テスト主導の開発方法に従えば、設計に怪しい兆しが現れる前に、複雑な役割を持ったクラスは分離されるはずだ。なぜならば、複雑な役割をもったクラスのテストを書くのは非常に難しいからだ。しかしそれでもなお、「硬さ」や「もろさ」を感じる場合には FacadeProxy などを使ってリファクタリングするとよいそうだ[要確認]。

余談

 アジャイル開発の奥義を読むまでは、この原則の名前からして、なぜ「クラスの役割は複数存在してはいけない」という内容ではないのかという疑問を持っていた。読んだ結果自分なりには、「変更」という観点に立ったときにはじめて「役割」ごとに分離する必要がでてくるから、「クラスを変更する理由は複数存在してはいけない」と主張しているのではないかと思った。

 たしかに役割は1つのほうが、見通しがよいかもしれないが、複雑にはなる。変更がとうてい生じないような場所に関して、無駄に分離してコードを複雑にする必要がないという意味を込めたい場合、たしかに「クラスを変更する理由は複数存在してはいけない」のほうがよい言葉だと感じた。この解釈自体は、完全に自分が読んだ感想なので、なにか間違っているなどの指摘がある方はコメントいただけるとありがたい。

アジャイル設計の本を読んでいる

 SOLID原則良いなぁ(←適当)…と思いつつ、それが書かれているアジャイルソフトウェア開発の奥義を読んでいなかったので、最近ちらちらと読み始めた。開発プロセスのあたりにはあんまり興味がわかないので、さらさらと軽い感じに読み飛ばしている。開発プロセス関連のはなしはなるほどなぁとは思うし、全員が内容を理解した上でやっていくと意味はありそうなんだけど、現実的にそんなことは起こり得ないと思っていて、学習コストが必要になる開発プロセスに意味はあるのか…とか偉そうなことを思っている。まあそんなことはどうでもよくて、第2部からのアジャイル設計の部分は本当に参考になるなぁ…と思いながら読んでる。

注意:この文章はポエムです

アジャイル設計とは?

 本によると、アジャイルはソフトウェアを少しずつ開発する手法だそうだ。基本的にラビットハウスでは完成させたソフトウェア(完成とは…という話になるが)を納品するようなお仕事ではなく、ソフトウェアを少しずつ拡張していくスタイルなので、まさに合致している。

腐敗するソフトウェアの兆候

 さて、そんな「少しずつ開発していく」スタイルの場合に、どうやってソフトウェアを設計できるのだろうか?という問いが冒頭にくる。少しずつ開発するとしても、最終的な目的は大きなソフトウェアを保守可能な状態で動かすことにあるので、その段階まで持っていけるような状態を各工程(アジャイル言葉ではイテレーションとかよんでるっぽい)で踏んでいかなければならない。それを阻む要素として次のような項目が挙げられている。

  1. 硬さ: 設計変更が困難
  2. もろさ: 設計が壊れやすい
  3. 移植性のなさ: 設計の再利用が困難
  4. 扱いにくさ: 正しい設計をするのが困難
  5. 不必要な複雑さ: 行き過ぎた設計
  6. 不必要な繰り返し: マウスを使いすぎ
  7. 不透明さ: 整然としない表現

 なるほど、たしかにという気持ちになる。こういった構造を作らないための原則として、有名なSOLID原則が紹介されている

  1. 単一責任の原則(SRP: Single Responsibility Principle)
  2. オープンクローズド原則(OCP: Open-Closed Principle)
  3. リスコフの置換原則(LSP: Liskov Substitution Principle)
  4. 依存関係逆転の原則(DIP: Dependency Inversion Principle)
  5. インターフェース分離の原則(ISP: Interface Segregation Principle)

 設計に関するいろいろな本がでているが、基本的にまずこれを守れてない上でそういうものを取り入れようとしても、かえってカオスになるケースのほうが多いのではないかなぁと個人的に感じている。まあ採用しているアーキテクチャなど状況によりけりでしょうけど。とはいえ、ソフトウェアのうまくいっている部分に無理やり、この5原則を適用しにかかるのはやめなさいという忠告も書かれている。そういう部分にはいろいろ変更がくわわってカオスになってきてから、原則に則ってリファクタしても遅くないと思うので、なるほどなぁ…と思った。こいつらについては、別記事で個別にみていく。

硬さ

 ソフトウェアのちょっとした変更が難しい、1行の変更をしようとすると芋づる式に変更箇所が膨らむということを、硬い設計とよんでいる。これって要はその一行に他のモジュールが依存しているのと変わらないので、結局のところモジュールに分割している意味がないってことな気がする。そうならないようにちゃんと考えて書けよ〜ってことなんでしょうね。

もろさ

 1つの変更により他の多くの部分が壊れてしまう傾向のことを、もろい設計というらしい。そういった箇所は、ソフトウェアの部品としての抽象化に失敗しているということを指すのではないかなと思った。

移植性のなさ

 他のシステムでも役に立つ部分を含んでいるのに、その場所から切り離すことが困難・リスクをともなうような状態。これも機能の抽出に失敗してしまっているときに起こるようなことかなと解釈した。移植するかしないかはともかく、移植できる状態にあることは良いことだとも思う。ただ他のシステムでも役に立つが、移植可能な状態にするのがあまりにも厳しいものはあると思うので、そういった部分はまた別の話かなぁとも思う。難しかったり前例がなかったりする実装はそうなってしまいがちなのはしょうがないと思う。バランスが大事。

(ソフトウェアの)扱いにくさ

 設計構造を保持するやり方が難しく、これまでの設計とは違うが圧倒的に簡単に機能を追加したり変更したりできてしまう場合、そのソフトウェアは扱いにくい。これは非常に重要だと思っていて、難しい設計技法とかをあたまのいい人たちが頑張って取り入れても、そこに追っついていける人が開発メンバでなければ、思想のまったくことなる実装が入り組み、余計複雑なソフトウェアを生み出してしまうと思う。同じ設計手法を用いるとしても、その考え方を理解して、やり方を揃えていけるのであればきっとうまくいくのではないかなと思う。そうでない場合は失敗するだろう。当然のことのはずだけど、新しいことを覚えて感動しているときには、こういうバランス感覚を忘れてしまいがちになるはずで、気をつけなければならない。

(環境の)扱いにくさ

 開発環境がクソゴミで非効率なことを指す。コンパイルに時間が掛かる場合に、設計を維持できないとわかっていてもモジュール分割してしまうみたいなこと。差分管理システムへのコミットに時間が掛かるようなことも例として書かれていた。まあgit使ってる分にはよほどなことがない限りこんな感じにはならないかなと思う。モジュール分割については、もうちょっと考えないと本のいっていることの意味が理解しきれない。そもそもコンパイル時間を減らすことを直接の目的としてモジュール分割するのはどうなんだろうか?あたまが悪いので自分には判断がつかない。

不必要な複雑さ

 設計している時点で不必要な要素が含まれている設計のことを指す。これ気をつけないとやってしまいがちですよね。結局あとから手直しするハメにあったり、つかわれることがないまましれっと削除コミットをしたり。このようなことをすると不必要にソフトウェアは複雑になるそうだ。たしかに分かるような、分からないような。不必要なコードをコミットするのは読み手にとっては非常につらみを増やすだけだが、それがソフトウェアの複雑さに結びつくかどうかの部分が自分の中であんまりはっきりとイメージできないので、これからも継続的に考えていきたい。

不必要な繰り返し

 同じようなコードが繰り返しでてくる。読み手がつらい。こういうコードのレビューをしているとき、推敲されていない文章(この記事のように…)を読んでいるかのようでつらい気持ちになる。このリポジトリはお前のブログじゃねえんだよという気持ちにさせないように、ちゃんと自分の書いたコードは推敲しないといけない。でもこれを応用すれば、嫌いなレビュワーを苦しめることができるということか…(嫌いなレビュワーとは…)。そしてこれを放置していると複雑さにつながると思う。

不透明さ

 わかりにくいモジュールのこと。他人が見て、わかりずれえなってところはこれに該当するんじゃないだろうか。モジュールという言葉はJavaのパッケージ的なものだけでなく、普通にサブルーチン、クラスレベルを含んだ話だと解釈した。書いている本人はわかったつもりだけど、誰かが読むと意味不明なのは、日本人の書く英語のようなものなのかなぁと思う。

アジャイルな設計とはなんなのか?

 アジャイルな設計とは、プロセスやイベントではなく、ソフトウェアの構造や可読性を向上させるために、原則・パターン・プラクティスを継続的に適用する行為だそうだ!(意識たかそう!!!)そのための原則とかパターンが8章以降に書かれているので随時まとめていきたい。