de_DEen_USes_ESfa_IRfr_FRhi_INid_IDjapl_PLpt_PTru_RUvizh_CNzh_TW

導入

統合モデル言語(UML)は、ソフトウェアアーキテクチャを可視化する上で、業界標準とされており、その点で疑いのない存在であるが、内在する制限がある。図は、システムが何で構成されているかを示すのに優れているが、システムの振る舞いを規定する複雑で繊細なビジネスルールを表現するには苦労する。何でシステムが何で構成されているかを示すのに優れているが、システムの振る舞いを規定する複雑で繊細なビジネスルールを表現するには苦労する。どのようにシステムがどのように振る舞うかを表現するのに苦労する。多重度や基本型といった視覚的要素にのみ依存すると、重要なシステム論理が曖昧なまま残り、開発フェーズ中に誤解を招くことになる。
登場するのはオブジェクト制約言語(OCL)。UMLがシステムモデルの「骨格」であるならば、OCLはその「神経系」である。骨格がどのように機能するかを規定する正確で曖昧さのない、形式的なルールを提供する。OCLをUMLと統合することで、モデル作成者は高レベルの視覚的設計と厳密な数学的仕様との間のギャップを埋めることができ、実際のコードを書かずに済む。
この包括的なガイドでは、UMLとOCLの強力な連携について探求する。OCLの核心的な概念を分解し、クラス、操作、状態機械に制約を付与する方法を示し、実用的で再現可能なPlantUMLの例を提供する。単純なクラス不変条件を定義する場合でも、複雑なクロス関連ビジネスルールを記述する場合でも、このガイドは、モデルを単なるスケッチから堅牢でエンジニアリングレベルの仕様へと高める知識を提供する。

UML + OCL = Unambiguous Design by Visual Paradigm

UML(統合モデル言語)は、システムの構造を可視化する点で優れているが、複雑なビジネスルールと意味論を正確に表現するには欠ける。ここにオブジェクト制約言語(OCL)が登場する。OCLは、UML図を厳密で曖昧さのないビジネスロジックに結びつける形式的な「接着剤」として機能する。

このガイドでは、OCLがUMLとどのように統合されるか、知っておくべき重要な概念を説明し、実用的なPlantUMLの例を提供して、これらの制約を可視化する。


1. 相乗効果:OCLがUMLを補完する方法

UMLをシステムの「骨格」(クラス、関連、状態)とし、OCLを「神経系」(骨格がどのように振る舞うかを規定するルール、条件、論理)と捉えるとよい。

UMLにおける視覚的表現

標準的なUMLでは、OCLの制約は通常、ノートを関連するモデル要素(クラス、操作、または状態)に付随させることで表現される。

  • ステレオタイプ: OCLを含むノートは、しばしば {制約} ステレオタイプ。

  • コンテキスト: すべてのOCL式は context キーワードで始まり、そのルールが適用されるUML要素を宣言する。


2. UMLにおけるOCLの主要な概念

A. クラス不変条件(inv)

不変条件とは、必須となる条件であり、常に真であるクラスのすべてのインスタンスについて、呼び出される操作にかかわらず常に成り立つ。

重要な概念: 不変条件が違反された場合、システムは無効な状態にある。

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFF9C4

class BankAccount {
  - accountNumber: String
  - balance: Real
  - isOverdraftEnabled: Boolean
  + withdraw(amount: Real)
  + deposit(amount: Real)
}

note right of BankAccount
  **クラス不変条件:**
  context BankAccount
  
  -- 残高は、 overdraftが有効でない限り負になることはない
  inv balanceRule: 
    self.balance >= 0 or self.isOverdraftEnabled
  
  -- overdraft限度額は500を超えることはできない
  inv overdraftLimit: 
    self.isOverdraftEnabled implies self.balance >= -500
end note
@enduml

B. 操作契約(prepostbody)

操作(メソッド)には、実行前後における振る舞いを定義する制約を設定できる。

  • 事前条件(pre):真でなければならないこと前に操作が開始される前に。(呼び出し側の責任)

  • 後条件(post):真になること後に操作が終了した後。(操作の保証)

  • 本体(body):クエリ操作の正確な戻り値を定義する。

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E8F5E9

class ShoppingCart {
  - items: Bag<Product>
  - totalAmount: Real
  + checkout(): Receipt
  + getTotal(): Real
}

note right of ShoppingCart
  **操作契約:**
  
  -- 前提条件:カートは空であってはならない
  context ShoppingCart::checkout(): Receipt
  pre: self.items->notEmpty()
  
  -- 後条件:カートは空になり、領収書が生成される
  post: self.items->isEmpty() and result <> null
  
  -- 本体:クエリ操作の定義
  context ShoppingCart::getTotal(): Real
  body: self.items->collect(price)->sum()
end note
@enduml

C. 導出値および初期値(導出初期)

冗長なデータを保存する代わりに、UMLでは属性に {導出} または {初期}、OCLがその正確な値を定義する。

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #E3F2FD

class Employee {
  - firstName: String
  - birthDate: Date
  - {derived} age: Integer
  - {initial} isActive: Boolean = true
}

note right of Employee
  **導出値と初期値:**
  
  -- 年齢を動的に計算
  context Employee::age: Integer
  derive: self.birthDate.yearsSinceToday()
  
  -- 作成時の初期状態
  context Employee::isActive: Boolean
  init: true
end note
@enduml

D. 状態機械のガード

状態機械図では、遷移にガード—遷移が発生するためには真で評価されなければならない論理式です。これらのガードを記述するための標準言語がOCLです。

@startuml
skinparam stateBackgroundColor #F3E5F5

[*] --> Created
Created --> PendingApproval : submit()

state PendingApproval {
}

PendingApproval --> Approved : [total < 10000]
PendingApproval --> Rejected : [total >= 10000]

Approved --> Shipped : ship()
Shipped --> [*]

note right of PendingApproval
  **状態不変条件:**
  context Order
  inv: self.status = 'Pending' implies 
       self.submittedBy->notEmpty()
end note

note right of Approved
  **approve()の後件条件:**
  context Order::approve()
  post: self.status = 'Approved' and 
        self.approvedDate <> null
end note
@enduml

3. 総合的な事例研究:企業モデル

OCLを、複雑なUMLクラス図に適用してみましょう。対象はEmployeeDepartment、およびProjectです。これにより、OCLが関連をどのように扱い、コレクションイテレータ(selectforAllsize).

@startuml
skinparam classAttributeIconSize 0
skinparam noteBackgroundColor #FFFDE7
skinparam noteBorderColor #FBC02D

class Employee {
  - SSN: String
  - firstName: String
  - birthDate: Date
  - hireDate: Date
  - salary: Float
  + age(): Integer
}

class Department {
  - number: Integer
  - name: String
  - locations: Set<String>
  - {derived} nbrEmployees: Integer
}

class Project {
  - number: Integer
  - name: String
  - location: String
}

Employee "0..*" -- "1" Department : worksFor > department
Employee "0..*" -- "0..*" Project : worksOn > project
Employee "1" -- "0..1" Department : manages > managedDept
Department "1" -- "0..*" Project : controls > projects
Employee "1" -- "0..*" Employee : supervisor > subordinates

note right of Employee
  **複雑な不変条件とナビゲーション:**
  context Employee
  
  -- 1. 基本属性ルール
  inv ageCheck: self.age() >= 18
  
  -- 2. 関連のナビゲーション(多重度)
  inv maxProjects: self.worksOn->size() <= 4
  
  -- 3. イテレータの使用(forAll)
  -- 上司は部下より先に雇用されなければならない
  inv hireOrder: self.subordinates->forAll(
    sub | sub.hireDate > self.hireDate
  )
  
  -- 4. 跨関連ルール
  -- 従業員は所属部署が管理するプロジェクトのみに従事できる
  inv projectControl: 
    self.worksFor.controls->includesAll(self.worksOn)
    
  -- 5. ユニーク識別子(キー)
  inv uniqueSSN: Employee.allInstances()->forAll(e1, e2 | 
    e1 <> e2 implies e1.SSN <> e2.SSN
  )
end note

note bottom of Department
  **導出属性:**
  context Department::nbrEmployees: Integer
  derive: self.worksFor->size()
end note

note right of Project
  **プロジェクトの不変条件:**
  context Project
  
  -- プロジェクトの場所は、所属部署の場所の一つでなければならない
  inv validLocation: 
    self.controls.locations->includes(self.location)
end note

@enduml

4. UMLにおける高度なOCL技術

A. 使用するlet可読性のため

OCL式が複雑になりすぎた場合は、制約内に局所変数を定義するためにletキーワードを使用してください。これにより、UMLノートの可読性が大幅に向上します。

@startuml
skinparam classAttributeIconSize 0

class Employee {
  - worksOn: Set<Project>
}

note right of Employee
  **複雑な論理に 'let' を使用する:**
  context Employee
  
  -- 合計時間の計算と境界値の確認
  inv workingHours: 
    let totalHours: Integer = 
      self.worksOn->collect(hours)->sum() 
    in totalHours >= 30 and totalHours <= 50
end note
@enduml

B. 後条件における以前の値の取り扱い(@pre)

後条件を定義する際、しばしば新しい状態と以前の状態を比較する必要があります。OCLでは、@pre後置記号を使用して、操作が開始される直前のプロパティの値にアクセスします。

@startuml
skinparam classAttributeIconSize 0

class Company {
  - employees: Set<Employee>
  + hireEmployee(p: Employee)
}

note right of Company
  **後条件で @pre を使用する:**
  context Company::hireEmployee(p: Employee)
  
  -- 新しい従業員集合は、以前の集合に新しい人物を追加したもの
  post: self.employees = self.employees@pre->including(p)
end note
@enduml

5. UMLにおけるOCLのベストプラクティス

  1. 制約に名前を付ける:常に不変条件や条件に名前を付けてください(例:inv maxProjects:)。これにより、ドキュメントやトレーサビリティマトリクスで参照しやすくなります。

  2. ノートを戦略的に使用する:PlantUMLや視覚的なUMLツールでは、クラスボックスにOCLを詰め込まないでください。OCLは隣接するノートに配置してください。これにより、図をきれいに保ち、読みやすくできます。

  3. 制約をしすぎない: UMLの多重性(例:)で簡単に表現できないルールのみにOCLを記述する。0..11..*)または基本型。ルールを視覚的に表現できる場合は、視覚的に示す。

  4. コレクションイテレータを活用する: マスターする ->select()->forAll()->exists()、および ->collect()。これらはOCLにおけるUML関連のナビゲーションに最も強力なツールである。

  5. nullや空集合の確認: オプションの関連をナビゲートする際(多重性 0..1 または 0..*)は、常に ->notEmpty() または ->isEmpty() プロパティにアクセスする前に使用し、未定義の評価エラーを回避する。

 

結論

ソフトウェア工学の分野において、曖昧さは品質の敵である。UMLはシステムアーキテクチャについて議論するための貴重な視覚的語彙を提供するが、オブジェクト制約言語(OCL)の統合が、これらの視覚的図を厳密で曖昧さのない仕様に変換する。
このガイドを通じて探求してきたように、OCLは高レベルの設計と実装の間の重要な橋渡しの役割を果たす。クラス不変条件、操作契約、コレクションイテレータを習得することで、設計チームと開発者との間でビジネスルールが誤解や喪失されることを防げる。さらに、PlantUMLのようなツールを活用することで、OCLの制約をバージョン管理可能にし、読みやすく、視覚的図とシームレスに統合できる。
最終的に、OCLを採用することで、モデリングの実践が向上する。単に「箱と線を描く」役割から、システムの正確な数学的意味を定義する役割へと移行する。UMLの視覚的明確性とOCLの形式的厳密性の相乗効果を活かすことで、単に良好に設計されただけでなく、現実のビジネス要件と厳密に一致するソフトウェアを構築するための堅固な基盤を築くことができる。