結合度とは
結合度とは、モジュール同士の関係の密接さを表す尺度です。
モジュールというのは、システムを構成する要素となるもの。具体例を出すと、検索機能や登録機能などのクラスや関数を指します。
つまり、結合度とは、モジュールの影響範囲がどれだけ狭いかを測る尺度です。
各機能において、どこで利用されて、どこで利用されていないかを判断できるよう整理・分割がどの程度されているかを表す尺度です。
凝集度との違い
凝集度は、モジュール内の評価に使われるのに対し、結合度はモジュール間の相互依存性の度合いに使われます。
高凝集を意図して強く関係していそうなロジックを 1 箇所にまとめ上げようとしたものの、結果として密結合に陥るケースは多く見られます。
そのため、それぞれの概念は分離し、疎結合にしなければなりません。(疎結合高凝集)
結合度の高い・低い
この結合度は、「結合度が高い」、「結合度が低い」といった言い方で使われます。
基本的に、
- 単一の機能をモジュールにしたか
- 複数の機能をモジュールにした場合、関係性や規則性があるか
- モジュールの入力、出力に共通性、独立性があるか
の 3 つを基準に強度が決定します。
モジュールの影響範囲が狭いと 1 つの事柄に特化していることになるので、
- 可読性
- 保守性
の点から、結合度は低い方が望ましいとされています。
結合度のレベル
結合度の高さには、レベルがあります。主に 6 つ存在しており、
- 内容結合
- 共通結合
- 外部結合
- 制御結合
- スタンプ結合
- データ結合
があります。
内容結合の方は「結合度が高い」とされており、データ結合の方が「結合度が低い」とされています。
内容結合
あるモジュールと別のモジュールが一部を共有するようなモジュールの結合を指します。
高水準言語を使用したモジュールには見られませんが、アセンブラ言語などを使用したモジュールではしばしば見られます。
private func contentCoupling() -> Void {
let user = UserData()
let userId = user.findId(3)
let output = user.getFirstName(id: userId) + user.getLastName(id: userId)
print(output)
}
class UserData{
func findId(_ id: Int) -> Int {
return id
}
func getFirstName(id: Int) -> String {
let firstNameList = ["佐藤", "田中", "高橋", "本田"]
return firstNameList[id]
}
func getLastName(id: Int) -> String {
let lastNameList = ["太郎", "二郎", "サブロー", "四郎"]
return lastNameList[id]
}
}
(ソースコードに自信がない・・・)
共通結合
共通結合とは、共通域に定義したデータを、いくつかのモジュールが共同使用するような結合を指します。
「共通域に定義したデータ」の具体的な物の中にグローバル変数があります。
あるモジュールでグローバル変数を変更すると、他のモジュールでも変更されてしまいます。
var globalMember: Int = 5
private func add() {
var privateMember1: Int = 2
let sum = globalMember + privateMember1
print("add: \(sum)")
}
private func add2() {
var privateMember2: Int = 3
globalMember += 10
let sum = globalMember + privateMember2
print("sum: \(sum)")
}
add() // add: 7
add2() // add2: 18
add() // add: 17
これはイメージがつきやすいかと思います。
可変なグローバル変数はいつ何処で変更されるのかわからないのが問題です。
エラーが出た時に容疑者が多いと特定するだけで時間がかかります・・・。
外部結合
外部宣言されたデータを共有したモジュール間の結合を指します。
要は、public
変数のことです。
class SampleA {
var sampleAPrivateMember: Int = 5
private func add() {
var privateMember1: Int = 2
let sum = sampleAPrivateMember + privateMember1
print("add: \(sum)")
}
}
private func add2() {
var privateMember2: Int = 3
let sampleAMember = SampleA().sampleAPrivateMember + 5
let sum = sampleAMember + privateMember2
print("add2: \(sum)")
}
共通結合との違いは、不要なデータを共有するか否かです。
共通結合のように、不必要なデータも共有しないので、結合度は弱くなります。
制御結合
呼び出し側のモジュールが、呼び出されるモジュールの制御を支持するデータを、パラメータとして渡す結合を指します。
要は if
文を使って、実行する処理を外部のデータによって制御している状態をさします。
func register(userData: Data) {
if userData == "japan" {
//国内ユーザーの登録
return
}
// 海外ユーザーの登
}
呼び出し側が、呼び出されるモジュールの論理を知っている必要があり、相手をブラックボックス化できず、結合度が強くなります。
ただ、データの共有ということではないので、共通結合や外部結合より結合度は弱いと言えます。
スタンプ結合
共通域にないデータ構造を、2 つのモジュールで受け渡しするような結合を指します。
func findUserEmail() {
return UserData().findByUserName(User().name)
}
特別問題がなさそうなコードに見えますが、User
インスタンスを渡しているため、User
インスタンスに依存している状態です。
そのため、User
クラスから name
プロパティが削除されると、エラーが発生します。
データ結合
モジュール間のインターフェースとして、型のデータ要素だけを、パラメータとして受け渡す結合を指します。
private func findUserEmail(name: String, id: Int) {
let user = User()
user.addName(name)
user.addIdFilter(id)
return userData().findbyUser(user)
}
受け渡すのがデータ構造で、その一部だけを利用します。
そのため、相手モジュール(今回は User()
)をブラックボックス化できるので、結合度は一番弱くなります。
まとめ:参照透過性
違います。
ある関数が、同じ入力に対して同じ作用、同じ出力を持つプログラムを指します。
具体例を挙げると、数学には参照透過性があります。
$y = 2 x + 3$という一次関数は、入力$x = 1$に対して、出力$y = 5$を返します。
これは、どの値でも同じ処理、同じ出力を返します。
それに対し、プログラムはグローバル変数などによる副作用により、異なる処理や値を返す場合があります。
今回の記事「結合度」で紹介した結合度の高いメソッドなどがその例です。
参照透過性のメリットは、メソッド、変数について値を参照する必要がありません。
故に、「参照について透過的な性質」→「参照透過性」ということです。
参考文献
参照透過性