クラスとは

classとはなんぞやと考えている。たどり着いたところまでまとめる。

クラスとは、同じ性質を持ったオブジェクトを分類 (classification) したものである。漢字で書くと類です。同じクラスに属しているオブジェクトは同じような性質を持っている。クラスは、その要素 (element, 元という) が共通して持つ性質によって定義される。名前が付いているというのも大事なところだ。*1正しいソースコードであれば、クラスには属するオブジェクトの共通した性質を代表した名称がついている。

オブジェクトの性質とはなんだろう。それはメソッドだ。性質というのは、外から観察できるもののことだ。オブジェクトはメソッドによって外部とやりとりする。したがって、同じメソッドを持っていて、そこから外部に出力されるものが似ているオブジェクトは共通した性質を持っていると言え、それらは同じクラスに属していることとなる。

しかし、プログラミングの世界では、オブジェクトがまずあって、その性質を人間が分類して整理していくわけではない。

プログラミングの世界では概念が先にあるという世界観でものを見なければならない。先に分類があって、それに属するオブジェクトが後から作られる。ものが先にあるのか、名称(概念)が先にあるのかというのは、世界観によって立場が異なる。前者が主流の考え方だろうけれども、オブジェクト指向は後者の考え方をする。

概念としてのクラスを定義するのは設計者だ。プログラムの設計は難しい仕事だと思うけれども、それは概念からまず考えて行かなければならないからだろう。詳しくないので誤っているかもしれないが、概念が先にあるという世界観では概念は神的なものが天下り的に与えるものだと思うが、プログラムの設計はオブジェクトたちの神の視点に立って世界を描いていけなければならないのだ。

人間は神でないから、いきなり抽象概念をつくるのは無理だ;何かをヒントにクラスを見つけていく。クラスの見つけ方がオブジェクト指向設計の勘所なのだろう。オブジェクト指向プログラミングが発明されてから、もう長いのでクラスの見つけ方は体系化されつつある。クラスの見つけ方は将来的にまとめてみたいと思っている。

全てのクラスを厳密に定義できるかというとおそらく無理だ。同じクラスに属するオブジェクのメソッドは似たような振る舞いをするといっても、似たようなとはどこまでが許されるのかを厳密に定義できないからだと思う。

クラスを定義するには自然言語を一部使わざるをえない。したがって、正しいオブジェクト指向にはどうしてもドキュメンテーションは必要となる。

クラスを定義することとは、そのクラスに属しているオブジェクトの振る舞いに期待してよいことを規定することだ。オブジェクト指向設計に内部仕様なんてない。どこまでも外部仕様しかない。オブジェクト指向の世界は自己相似的な世界だ。

*1:無名クラスというものもあるが、これは名前を付ける必要がない場面でしか使えない。

リスコフの置換原則は呼び出し側にも責任が伴う

以前にオブジェクト指向になっているならば、あるクラスを継承したクラスは継承元のクラスと置き換えても動かなければならないという気付きについて書きました。名前がついていて、それはリスコフの置換原則と呼ばれるらしいです。

fj.hatenablog.jp

このリスコフの置換原則を守るには、使われる側のクラスだけでなく、使う方も気をつけなければならないことがあると気がつきました。

継承というのは、クラスの意味は変えずに、振る舞いを変えることです。継承して、メソッドをOverrideすれば当然メソッドの振る舞いは変わります。

そうであれば、呼び出し元は継承元クラスの細かい振る舞いに依存してはならないことになります。派生クラスでメソッドの振る舞いが変わったときに、置換可能ではなくなるからです。

派生クラスがメソッドのOverrideを行わずにメソッドを増やすだけなら、特にこの問題は起きません。Overrideというメソッドをつけかえるようなことをする場合に起こることですが、呼び出し元が派生クラスがメソッドをOverrideしているかどうかなんて分からないことです。したがって、Overrideされていても大丈夫なようにしておかなければならない。

ある共通の性質をもったものの集まりをクラスといいます。クラスの利用者はその共通の性質だけを期待して、クラスを利用しなければならないということになります。継承する側が共通の性質から外れるようなことをしてはいけないというのは、派生クラスを提供する側の責任です。一方で、共通の性質以外を期待してはいけないのは呼び出し側の責任になります。

共通の性質というのは、つまりクラスの仕様になるかと思う。Javadocに記述される内容です。

呼び出し元は、Javadocに記述されていないようなことを期待しては行けないということになります。つまり、具体的な実装に依存する振る舞いを知った上でそれを期待するということは誤りです。ソースコードを読んでもよいが、読まなければ分からないようなことを利用してはいけないということになる。呼び出され側の仕様外の動きが変わって、呼び出し側が動かなくなっても、それは呼び出し側が悪いという話になります

呼び出し側が呼び出され側に期待してよい振る舞いを規定しておくことは、呼び出され側のクラスを作る人がやらなければならないことなる。こういう視点に立つと、仕様とは何なのか、Javadocに何を書くべきかというのが分かってきます。

Martinのオブジェクト指向設計メトリクス

コードの品質のスカウターをどうにか作れないかと考えていて、ソフトウェアメトリクスについて調べている。

古い記事ですが、読んだのでメモ書きする。

Robert Martin, OO Design Quality Metrics. An Analysis of Dependencies

https://linux.ime.usp.br/~joaomm/mac499/arquivos/referencias/oodmetrics.pdf

Instability

パッケージ*1の不安定性 (Instability) I は

I = Ce / (Ca + Ce)

で定義される。ここで、Ca は被依存数 (Afferent Couplings) でパッケージ内のクラス*2に依存しているパッケージ外のクラスの数である。また、Ce は依存数 (Efferent Couplings) でパッケージ内のクラスのうちパッケージ外のクラスに依存しているクラスの数である。

Iは[0, 1]の範囲を取る。I = 0のパッケージは非常に多くのパッケージ外のクラスから依存されていて、最大に安定である。一方、I = 1のパッケージはパッケージ外のクラスから依存されておらず、最大に不安定である。

Abstractness

パッケージの抽象度 (Abstractness) A は

A = パッケージ内の抽象クラスの数 / パッケージ内の総クラス数

で定義されている。

AはIと同様に[0, 1]の範囲を取る。A=0のパッケージは具体的で、A=1のパッケージは抽象的となる。

Distance from the Main Sequence

f:id:fjkz:20160410021038p:plain

安定が良くて不安定が悪いかというとそういうわけではない。安定なパッケージほど抽象度が高くあるべきで、不安定なパッケージは具体的であった方がよい。I と A のバランスがとれているといい。そこで、(I, A) = (0, 1)の点と(1, 0)の点を結んだ直線 (Main Sequence) を引いて、それに乗っているようなパッケージが理想的だと考えられる。

そこで、Main Sequenceからの距離 Dをパッケージを評価するときの指標としたい。I, Aが与えられた時に、Main Sequenceからの距離 D は

D = | A + I - 1 | / sqrt(2)

となる。Dは[0, 0.707...]の範囲を取るので、[0, 1]の範囲になるように規格化して | A + I - 1 |としてもいいだろう。これをDnとする。Dn = 0となるような設計が理想的で、Dn = 1のときは非常に悪い設計と言える。

Dn = 1となるのは、(I, A) = (0, 0)のときか(I, A) = (1, 1)のときである。前者の場合は、具体的なクラスが多くのクラスから依存されているので良くない設計となる。後者の場合は、無意味に抽象的なので良くない設計となる。

ノート

  • コンセプトは分からんでもないが、測るのは無理だろう。
  • 不安定性は他のパッケージによって決まる値なので、コントロールできる値ではない。また、この値は変化しうる。
  • 抽象度の定義はもっとよい定義がありそう。
  • 具体はどこのパッケージにあることを想定しているのか。
  • Main Sequenceにあることが本当に理想なのだろうか。抽象度の低い安定なパッケージが有益な場合も多い。そうでないと標準ライブラリは使えない。

*1:元はClass Categoryと書かれている。再利用するときは同時に再利用されるようなクラスのグループのこと。Javaではパッケージに相当すると思われるのでパッケージとする。

*2:ここでのクラスは全てpublicクラスになるかな?

BashでStrategyパターン

デザインパターンオブジェクト指向言語だけのものではない。シェルスクリプトにもデザインパターンの概念は適用可能です。

Strategyパターンとは、アルゴリズムを動的に付け替えることができるデザインパターンです。

f:id:fjkz:20160408185448p:plain

この概念は決してオブジェクト指向だけのものではありません。Cでもコールバック関数という形で実現することができます。

シェルスクリプトであっても、処理を動的に付け替えるコマンドを作ることができます。例えば、

  • exec
  • nohup
  • chroot
  • time
  • strace

などのコマンドは引数に指定されたコマンドで動きが変わります。Strategyパターンと同じ発想と言えると思います。

こういうコマンドを引数に取るようなコマンドを作ると便利なことが多い。例えば、前処理と後処理を共通化したいときに有効かと思われる。

以下は例。

function do_between_before_after() {
  # trapが効くように()で囲む
  (
    # 前処理
   echo 'BEFORE'

    # 後処理
    trap "
      exit_code=$?
      echo 'AFTER'
      exit ${exit_code}
    " EXIT HUP INT QUIT TERM

    "$@"
  )
}

実行すると以下のように前処理と後処理が行われる。

$ do_between_before_after echo DO
BEFORE
DO
AFTER

単体テストの数とコードの行数の関係

興味深い記事を見つけた。

Cyclomatic Complexity and Lines of Code: Empirical Evidence of a Stable Linear Relationship

http://dx.doi.org/10.4236/jsea.2009.23020

コードの行数(LOC)と循環的複雑度には強い相関があるとのことだ。

循環的複雑度とは、プログラム内のif, for, whileといった制御構文の数に1を足した数である。つまり、プログラム内の分岐の数を示している。分岐の多いプログラムは複雑度が高く、分岐の少ないプログラムは複雑度が低い。サブルーチンの循環的複雑度は10ぐらいにしておくのが、最も障害が少ないとのことだ。*1

また、循環的複雑度はプログラム内の分岐の数であるので、C1カバレージを100%にするテストの数と一致している。したがって、単体テストの数の目安は、循環的複雑度と同じぐらいにすればよい。

しかしながら、複雑度という指標を持ちだしたところで、計測も面倒なばかりか、毎回定義から説明しないと通じないわけです。*2

そこで、この調査の結果が有用となります。SourceForgeにあるコードを調査した結果、以下のようにLOCと循環的複雑度の間には強い相関があって、比例関係にあることが示されました。

f:id:fjkz:20160403223105p:plain

CCとLOCの関係は以下の表のようになります。

言語 CC / LOC
C/C++ 0.179
Java 0.187

この数字は単体テストの数の目安にもなります。理想的には1000行あたり190個のテストケースが必要ということです。

それなりに科学的根拠がありそうなテストの数の基準値が導かれました。

*1:Cyclomatic Complexity Revisitedによる。このデータの出処は辿れなかったのでどこまで信用できるか分からないが、感覚とは一致している。小さすぎてもバグが増えるのは、切り分けを細かく過ぎたら部品の数が増えすぎて整理しきれなくなるということだろうか?

*2:LOCも定義がないと指標になりえないと思うけどね。この調査も計測ツールは明記されているが、LOCの定義が書いてないのだよね。最低でも計測ツールぐらいは決めないと度量衡が統一されない。

ubuntuでブートプロセスを表示

OSの起動プロセスは皮が被せられているけれども、できるだけ裸の方が何がどうなっているのか意識できて良いだろうと思ったので、ブートプロセスを表示するようにする。一方で、ノイズでもあるけれども。

/etc/default/grubを編集して、

GRUB_CMDLINE_LINUX_DEFAULT="nosplash"

とする。その後、

# update-grub2

と打つと、/boot/grub/grub.cfgが更新される。

再起動すると、サービスが順に起動していくのが見えるようになる。

ClassクラスのClassオブジェクト

具体についてのクラスはインスタンスと同じもの? - 超ウィザード級ハッカーのたのしみ

クラスって何なのだろうと疑問に思い始めた。

さて、Javaにはjava.lang.Classクラスという変なクラスがある。Classクラスのオブジェクトはクラスそのものではないみたい。

面白いのは、ClassクラスのClassオブジェクトもあることだ。

Class<?> class2 = Class.class;

<?>でなしに、<Class>と明記することもできる。

Class<Class> class3 = Class.class;
      ^^^^^

そうすると<Class>がWarningとなる。何のクラスのClassクラスか分からないからだ。

しかし、

Class<Class<?>> class4 = Class.class;
Class<Class<?>> class5 = Class<?>.class;
Class<Class<?>> class6 = Class<Class>.class;

これらはコンパイルエラーになってしまう。

だから何という話だけれども、自己言及はパラドックスを伴うこともあるので、Javaの言語仕様はパラドックスでもなんでもないが、表現しきれないという事実がちょっと面白いなと思った。