SoftwareDesign
Object-Oriented

Thinking in quality of code 🤔

More than 1 year has passed since last update.

クラスの設計に関して

Basili BriandとMeloらによる研究

1996年にBasili BriandとMeloらオブジェクト思考に関する研究を行い、次の結果が明らかとなっています。

  • クラスのエラー発生率はクラス内の関数の数が増えるほど上昇する。
  • クラスが使用するクラスの数が増えれば増えるほどエラー発生率が上昇する(ファインアウト)

クラス内で呼び出される関数の数、深い継承ツリーやクラス間の密結合といった要因が上記二つよりエラー率との相関が高いことがわかっています。

参考:A Validation of Object-Oriented Design Metrics as Quality

不安定因子

クラスのファンアウトとファンインの数を用いることで、そのパッケージの不安定因子(Instability factor)を求めることができます。

I = f_o  / (f_i + f_o)
  • ファンイン: 他のクラスに依存しているクラス数。「ファンインが高い」とそのクラスを使用しているクラスが多いということが意味する
  • ファンアウト: そのクラスが使用している他のクラス数。高いファンアウト(一般的には7つ以上)とはクラスが使用するクラスの数が多いことを意味する

Iはパッケージの不安定さを示しており、0に近づくほど安定し、1に近づくほど不安定なパッケージなことを意味しています。すなわち、修正を加えることによるリスクが高いかを示しています。
低いファンアウトかつ高いファンインが理想ですが、ファインインが高すぎても依存関係が高まるだけなのでバランスが重要となります。

参考:Kevlin Henney氏によるThings Every Programmer Should Know: Collective Wisdom from the Experts

デメテルの法則

デメテルの法則とは、ノースイースタン大学で作成されたソフトウェアの設計(特にオブジェクト指向プログラムの設計)におけるガイドラインで、オブジェクトは、自分以外のクラス・構造・プロパティに関しては、最小限のアクセス権限をもつということです。

//bad case
class.subClass().object

// good  case
class.getObject()

オブジェクトAは別オブジェクトBに対して要求することはよいが、オブジェクトBを経由してオブジェクトCに対して要求することはデメテルの法則に反することになります。理由はオブジェクトAがオブジェクトBの内部構造以上の知識を要求されてしまうためです。

ここからは主観的な意見になりますが、共通して重要だと感じたことは、扱う情報量は最小限に抑えることだと思います。クラス関数がふえればもちろん扱う情報も多くなりますし、クラスが依存してるととなるとそのクラスの内部構成や変更による影響範囲を理解する必要もあります。ただ、上記の結果やガイドラインなどはあくまで指標であって、サービスが大規模になってくにつれ全てに従うのは難しくなってきます。必要な情報は必要な箇所に必要なだけ扱えるように心がけることが重要ではないでしょうか。上記のことをある程度頭にいれつつバランスよく設計をすることが重要だと感じます。

まとめると、気をつけることは以下の点です。

  • クラス内の関数・クラスの数は最小限にする
  • プロパティや関数は必要なもの以外はprivateにする
  • 直接的・間接的な呼び出しを避ける

関数に関して

適切な反義語の命名をする

一般的に関数名は、言語の特性に合わせると共に、適切な反義語をつけることが重要である。一貫性が保たれ可読性が向上する。

  • add / remove
  • increment / decrement
  • open / close
  • begin / end
  • insert / delete
  • show / hide
  • create / destory
  • lock / unlock
  • source / target
  • first / last
  • min / max
  • start / stop
  • get / put
  • next / previous
  • up / down
  • get / set
  • old / new

関数のサイズとエラー・修正コスト

関数のサイズ(コードの行数)とエラー・修正コストについても、過去様々な研究が行われています。
以下、いくつかの研究結果を紹介します。

  • 関数のサイズとエラーが反比例する。関数のサイズが増えるにつれコード1行あたりのエラー数は減少する。(Basili and Perricone)
  • 関数のサイズとエラーに相関はないが、複雑さやデータ量がエラーに関係しているという結果もでている(Shen et al 1985)
  • コードが32行以下の関数は、コスト・エラー減少との相関はない。コードが65行以上のコードでは1行あたりの開発コストが減少する(Card, Church and Agresti 1986; Card and Glass 1990)
  • 小さなサイズの(143行未満)の関数は、それ以上の大きいサイズの関数より1行あたりのエラー率が23%高いが、修正コストが2.4倍低い(Selby and Basili)
  • コードの修正が最も発生しにくいのは、100~150行の関数である(Lind and Vairavan 1989)
  • IBMの調査では、最もエラーが発生しやすい関数は、500行以上のもである。500行を越えるとエラー発生率は関数の行数と比例する(Jones 1986)

上記の結果から、関数のサイズとエラー率は密接に関わっているといえます。サイズだけではなく可読性やデータ量などの複雑さとなる要因も大いに関係してると思います。
コードが少ないほうがエラー率が低いと思われがちですが、このような結果が示しているのは、「よりまとまりをもったコードがエラーが発生しにくい」ということではないでしょうか。
例えば、処理のまとまりを小分けに関数化してそれぞれを一つの関数が呼び出すとすると、コードが散らばってしまうのでコードを追うコストがかかります。呼び出し順序なども考慮しなくてはなりませんし、適切な関数名、アクセスレベルを設定する必要もでてきます。このような場合は、一つのまとまりを持った関数として定義するのがよいと考えられます。
とはいえ、関数が大きくなりすぎる場合や、違う箇所で同じ処理を何度もしてる場合など、新たに関数を定義した方がよい場合もあるので、これはケースによって使い分ける必要があるとおもいます。コードサイズが大きくなるとスコープもでかくなってしまうので、注意が必要ですね。

関数の引数は全て使用する

リファクタリングや、仕様変更などで未使用の引数を消し忘れてしまうことはないでしょうか?未使用の引数は必ず消すように心がけましょう。当たり前なのでは?と思う方も多いと思いますが、未使用の引数とエラー率は大いに関係があります。
Card, Church and Agrestiらによって行われた研究では、未使用の引数をもつ関数の46%はエラーがなかったのに対し、未使用の変数をもつ関数のうち、エラーがなかったのは17%~29%という結果がでています。
軽いミスだと思って軽視されがちだと思いますが、未使用な引数には注意しましょう

参考文献

CODE COMPLETE 第2版 上 完全なプログラミングを目指して