#Cohesion を python で使う場合
python で計測したい時はここにインストール方法や利用方法が書いてあるので参考にしてほしい。
#Cohesion の概要
色々な数式で表現されるが、Cohesion はクラス設計で活躍するメトリクスなので、オブジェクト指向プログラミングがわからない!という人は是非使ってみてほしいメトリクスである。
ざっくりとした説明をすると、このメトリクスによってクラス変数がクラス関数でどの程度使われているかを数値で表現できるため、設計の指針として活用できるというものである。
#Cohesion で Why & What & How を考える
(Why) なぜこのメトリクスを使うのか?
この数値を利用することで以下の判断を客観的に行う材料として使えるため。
(What) 使うことで何がわかるのか?何に使えるのか?
数値を確認することで、
-
クラス全体の設計が適切かを判断できるようになる。
-> クラス関数内でのクラス変数のアクセス頻度を総合的に確認できるため、クラス変数をまったく使用していないクラス関数の存在の是非を問える。 -
クラス変数がクラスにとって必要/不要のジャッジの判断材料に使える。
-> クラス関数内で全く参照されていない(使われていない)クラス変数を発見できる。
-> クラス関数を別クラスに分割すべきか、もしくはモジュール関数のように外に取り出すべきか、といったジャッジに使える。
(How & Why & What) 数値を確認した後、どのように作業を進める必要があるか?
(How) どのように作業を進めるのか?
-> 数値を確認し、不要/必要、分離する/しない、といった作業を理由をつけながら繰り返す。
(Why) なぜ繰り返す必要があるのか?
-> 精錬されたクラス設計へと向かっていけるため。
-> その結果、何も考えずに作ったプログラムではなく、あなたの哲学がプログラムに宿る。
(What) 何か良いことがあるのか?
-> 可読性/再利用性が向上する。
(What) 何に使えるのか?
-> 他人に説明するときの材料となる。
->(あなたがいる/いないに関わらず)他人が見た時の理解速度を向上させる。
(What) 何をする力がつくのか?
-> オブジェクト思考におけるクラスの概念やクラス設計の最適化する力がつく。
ちなみに、Why -> What -> How -> Why -> What とループしている現状にお気づきだろうか。
ここまで述べていなかったが、5W1H は何度もループさせることが重要で(させすぎ注意でもあるが...)、真のターゲットに向かっていける場合が多いので余裕のある限り繰り返してみてほしい。
では、実際にこの python ツールを使って計測してみよう。
#Cohesion の例
以下のように、非常にやる気のない3つのクラスを作ったとしよう。
class Caliculate:
a = 0
b = 0
c = 0
d = 0
def f(x,y,z):
return x+y*z
class ExampleClass1(object):
def func1(self):
self.instance_variable1 = 7
class ExampleClass2(object):
class_variable1 = 5
class_variable2 = 6
def func1(self):
self.instance_variable = 6
local_variable = self.class_variable1
return local_variable
def func2(self):
print(self.class_variable2)
def func3(self):
print(self.class_variable1)
print(self.class_variable2)
def func4(self):
self.func3()
print(self.instance_variable)
def funcX(variable):
return variable + 7
実際にこのの python ツールを使ってみると、
cohesion --files e.py
以下のような結果が返ってくる。
File: e.py
Class: Caliculate (14:0)
Function: f 0/4 0.0%
Total: 0.0%
Class: ExampleClass1 (22:0)
Function: func1 1/1 100.00%
Total: 100.0%
Class: ExampleClass2 (26:0)
Function: func1 2/3 66.67%
Function: func2 1/3 33.33%
Function: func3 2/3 66.67%
Function: func4 1/3 33.33%
Function: funcX 0/3 0.0%
Total: 40.0%
##1つ目のクラス
わざわざクラス変数を4つも宣言しておきながら、唯一のクラス関数 f(x,y,x) ではまったく参照されていない。
class Caliculate:
a = 0 # クラス変数1
b = 0 # クラス変数2
c = 0 # クラス変数3
d = 0 # クラス変数4
# クラス関数1
def f(x,y,z):
return x+y*z
そのため、計測結果は 0/4(0%) になっている。これはクラス設計が不適であることが明白である。思い浮かぶことは、これはクラスである必要があるのか?クラス変数を削除すれば良いのでは?といったことだろう。(他にも計算をするクラス関数を追加すれば、もしかしたらクラス変数が威力を発揮するようになるかもしれない...?)
#ここで、直感的に$\frac{(クラス関数がクラス変数を参照している数)}{(クラス変数の数)}$ということがわかるだろう。
Class: Caliculate (14:0)
Function: f 0/4 0.0%
Total: 0.0%
##2つ目のクラス
1つ宣言したクラス変数を唯一のクラス関数 func1 で参照しているため、
class ExampleClass1(object):
# クラス関数1
def func1(self):
self.instance_variable1 = 7 # クラス変数1
計測結果は 1/1(100%) になっている。これはクラス設計が適切であることが明白である。
#ここで、100%に近いほど、クラス設計が適切であることがわかるだろう。このクラスはとても最適だとは思わないけれど...
Class: ExampleClass1 (22:0)
Function: func1 1/1 100.00%
Total: 100.0%
##3つ目のクラス
ここまでの経過をまとめると、
計算式は $\frac{(クラス関数がクラス変数を参照している数)}{(クラス変数の数)}$ ぽい
計算結果が 1 に近いほどクラス設計は適切、0 に近いほど不適切
ということがわかったので、発展形に取り組んでみよう。
クラス変数が3つ、クラス関数が5つの場合である。
class ExampleClass2(object):
class_variable1 = 5 # クラス変数1
class_variable2 = 6 # クラス変数2
# クラス関数1
def func1(self):
self.instance_variable = 6 # クラス変数3
local_variable = self.class_variable1 # クラス変数1
return local_variable
# クラス関数2
def func2(self):
print(self.class_variable2) # クラス変数2
# クラス関数3
def func3(self):
print(self.class_variable1) # クラス変数1
print(self.class_variable2) # クラス変数2
# クラス関数4
def func4(self):
self.func3()
print(self.instance_variable) # クラス変数3
# クラス関数5
def funcX(variable):
return variable + 7
計測結果は何やらクラス関数毎に出力されており、Total が 40% になっている。数値としては非常に微妙であることがわかる。
Class: ExampleClass2 (26:0)
Function: func1 2/3 66.67%
Function: func2 1/3 33.33%
Function: func3 2/3 66.67%
Function: func4 1/3 33.33%
Function: funcX 0/3 0.0%
Total: 40.0%
そして、funcX がどうやらクラス変数をまったく使用していないので、クラス関数としては不適切だとわかるだろう。
加えて、計算式は $\frac{(クラス関数がクラス変数を参照している数)}{(クラス変数の数)}$ ではなさそうなこともわかったので、末尾の問題で、いったいこの数値は LCOM のどれを使っているのか を考えてみよう。
#亜流の数値になっていることに気がつけば...
なお、Total は以下の式で計算されている。
※クラス変数が3つ、クラス関数が5つ
\frac{\frac{2}{3}+\frac{1}{3}+\frac{2}{3}+\frac{1}{3}+\frac{0}{3}}{5}=0.4\\
=>\frac{2+1+2+1+0}{3 \times 5}=0.4
おまけとして、funcX を取り除けば Total が 50% となり、クラスとしてはまあまあの状態にアップグレードする。
ここからどうするかは美意識の問題も絡んでくる可能性が高いため、ここで止めておく。
Class: ExampleClass2 (26:0)
Function: func1 2/3 66.67%
Function: func2 1/3 33.33%
Function: func3 2/3 66.67%
Function: func4 1/3 33.33%
Total: 50.0%
なお、Total は以下の式で計算されている。
※クラス変数が3つ、クラス関数が4つ
\frac{\frac{2}{3}+\frac{1}{3}+\frac{2}{3}+\frac{1}{3}}{4}=0.5\\
=>\frac{2+1+2+1+0}{3 \times 4}=0.5
#問題
##問題1
3つ目のクラスにおいて、Cohesion の計算式は $\frac{(クラス関数がクラス変数を参照している数)}{(クラス変数の数)}$ ではなさそうなことがわかった。
この数値は LCOM2 の亜種となっているが、元の数式と比較して何がどれに該当するか考えてみよう。
なお、Total は以下の式で計算されている。
※クラス変数が3つ、クラス関数が5つ
\frac{\frac{2}{3}+\frac{1}{3}+\frac{2}{3}+\frac{1}{3}+\frac{0}{3}}{5}=0.4\\
=>\frac{2+1+2+1+0}{3 \times 5}=0.4
##問題2
以下のプログラムを実行し、この pkg や同じディレクトリ格納されている pkg の Cohesion を計測してみよう。
#エラーになるなら別の pkg を計測しよう。
#余裕があれば MCC も計測してみよう。
import datetime
print(datetime.__file__)