愛記システム概念設計:システム構築の品質評価のポイント7 試験性② | 続・ティール組織 研究会のブログ

続・ティール組織 研究会のブログ

ティール組織が話題になっているが、具現化するにはどうしたらよいか?
その研究を続けるにあたり、さらに次の形態である、続・ティール組織なるものまで視野に入れ、具体的な施策・行動内容を研究・支援する会。

先までは、"愛記"についての記載で、どのようにブロックチェーンSNSに組み込んで実装していけばよいのか、概念的なところからアプローチ方法を記載していった。大まかな概念としてはひとまず終えた。次は、ブロックチェーンの概念設計といえるところまで、基本設計書に着手できるようなところまで、概念を具体化していきたい。

愛記システムのシステム評価について

システム評価とは、つまりは、このシステムを導入して成功だったか失敗だったかという効果検証という意味だ。概念設計をする上で必ず抑えておくべきポイントということだ。それには各項目があり、それぞれの項目を見ていくことで、その結果が得られる。そのシステム評価項目を1つずつ見ていきたい。

システム構築の品質評価のポイント1:理解可能性(Understandability)

システム構築の品質評価のポイント2:完全性(Completeness)

システム構築の品質評価のポイント3:簡潔性(Conciseness)

システム構築の品質評価のポイント4:移植性(Portability)

システム構築の品質評価のポイント5:一貫性(Consistency)と構造化の度合い

システム構築の品質評価のポイント6:保守性(Maintainability)

システム構築の品質評価のポイント7:試験性(Testability)

システム構築の品質評価のポイント8:ユーザビリティ(Usability)

システム構築の品質評価のポイント9:効率性(Efficiency)

システム構築の品質評価のポイント10:セキュリティ(Security)

システム構築の品質評価のポイント7:試験性②(Testability)

システム構築の品質評価ポイントとなっている試験性とは、不具合があるかどうかを確認するための各種テスト項目が簡単に実施できるかどうかである。不具合があった際にその不具合が階層まで調査する必要があり、見つけるまで非常に時間がかかるのは問題である。この試験性は先ほど記載した保守性と非常に関連性があり、試験性の高さは保守性の高さに繋がる。逆に低いと保守性の品質評価も下がる。

 

さて、試験性の定義として、7つの特性があるのでそれぞれについて見ていこう。

  • 実行円滑性(Operability)
  • 観測容易性(Observability)
  • 制御容易性(Controllability)
  • 分解容易性(Decomposability)
  • 単純性(Simplicity)
  • 安定性(Stability)
  • 理解容易性(Understandability)

これらの特性はJames Bach著『ソフトウェアテスト293の法則』やRoger S. Pressman著『実践ソフトウェアエンジニアリング』 で挙げられている。

3. 制御容易性(Controllability)

テスターがシステムの異なる部分をどれだけ容易に制御できるか、特定の条件や状態を作成できるかどうかを表す特性である。例えば次のような状況は、制御容易性が低い。

・パラメータや依存関係がハードコーディングされていて、制御が出来ない

まさにここがネックとなるのだろう。一度実行してしまえば、後から変更ができないという特徴のため、困難をもたらす。もはや制御出来ない前提で、どこまで対処できるのかを考えていくしかない。

 

確かに、ブロックチェーンやフェデレーションモデルにおいては、一度実行されたトランザクションやスマートコントラクトが変更されない性質がある。このため、制御容易性の確保は重要な課題である。以下は、この特性に対処するためのアプローチと考えられる対策である。

  1. 柔軟性のあるスマートコントラクト設計:

    • スマートコントラクトを柔軟に設計し、変更が必要な部分を外部から制御可能にする。例えば、設定パラメータや挙動を変更できるようにするなどの工夫が考えられる。
    • 以下は、Rustで独自のスマートコントラクトをブロックチェーンに実装し、変更可能性を持たせるための例である。このコードでは、SmartContract構造体がブロックチェーンに実装され、ブロックチェーン上でコントラクトの状態を保持するようになっている。set_thresholdとset_rewardメソッドは、ブロックチェーン上のスマートコントラクトの状態を変更するために使用される。このように実装することで、ブロックチェーン上でスマートコントラクトの状態を変更することができ、変更可能性を持たせることができる。

      // スマートコントラクトの状態を保持する構造体
      pub struct SmartContract {
          threshold: u64,
          reward: u64,
      }

      impl SmartContract {
          // コントラクトを新規作成する関数
          pub fn new(initial_threshold: u64, initial_reward: u64) -> Self {
              SmartContract {
                  threshold: initial_threshold,
                  reward: initial_reward,
              }
          }

          // 閾値を変更する関数
          pub fn set_threshold(&mut self, new_threshold: u64) {
              self.threshold = new_threshold;
          }

          // 報酬を変更する関数
          pub fn set_reward(&mut self, new_reward: u64) {
              self.reward = new_reward;
          }

          // 閾値を取得する関数
          pub fn get_threshold(&self) -> u64 {
              self.threshold
          }

          // 報酬を取得する関数
          pub fn get_reward(&self) -> u64 {
              self.reward
          }
      }

      fn main() {
          // スマートコントラクトの初期状態を設定
          let mut contract = SmartContract::new(50, 100);

          println!("Current threshold: {}", contract.get_threshold());
          println!("Current reward: {}", contract.get_reward());

          // 閾値と報酬を変更
          contract.set_threshold(75);
          contract.set_reward(150);

          println!("Updated threshold: {}", contract.get_threshold());
          println!("Updated reward: {}", contract.get_reward());
      }
       
  2. 外部パラメータや構成ファイルの利用:

    • システムの挙動を制御するパラメータや構成情報を外部の設定ファイルやパラメータストアに配置し、実行時にそれらを読み込んで利用する。これにより、実行前に設定の変更が可能となる。
    • 以下の例では、外部の設定ファイル(config.ini)から閾値と報酬の設定を読み込むクラス FederationConfiguration を作成している。外部の設定ファイルはプレーンテキスト形式で、configparser ライブラリを使用してそれを解析する。これにより、実行時に設定の変更が容易になる。

      import configparser

      class FederationConfiguration:
          def __init__(self, config_file_path):
              # 外部設定ファイルから設定を読み込む
              self.config = configparser.ConfigParser()
              self.config.read(config_file_path)

          def get_threshold(self):
              # 閾値の取得
              return int(self.config.get('Federation', 'Threshold'))

          def get_reward(self):
              # 報酬の取得
              return int(self.config.get('Federation', 'Reward'))

      # 外部の設定ファイルの例(config.ini)
      # [Federation]
      # Threshold = 150
      # Reward = 15

      # 設定ファイルのパス
      config_file_path = 'config.ini'

      # FederationConfigurationのインスタンス化
      federation_config = FederationConfiguration(config_file_path)

      # 閾値と報酬の取得
      threshold_value = federation_config.get_threshold()
      reward_value = federation_config.get_reward()

      # 閾値と報酬を利用した処理(例)
      print(f"Threshold: {threshold_value}")
      print(f"Reward: {reward_value}")
       
  3. スマートコントラクトのアップグレード機構:

    • スマートコントラクトにアップグレード機構を組み込み、新しいバージョンへの切り替えを可能にする。ただし、慎重に検討する必要があり、セキュリティ上のリスクが伴う。
    • スマートコントラクトのアップグレード機構を組み込むことは、慎重に検討されるべきである。以下の例では、UpgradeableContract という基底のコントラクトを用意し、そのコントラクトを継承した MyContract が具体的な機能を提供する。UpgradeableContract はオーナーによるアップグレードを可能にする機能を提供している。MyContract は UpgradeableContract を継承しており、その機能を利用しつつ、独自のデータを管理する。アップグレードの際には、upgrade 関数内に新しいコントラクトのロジックを追加することになる。アップグレード後には upgraded フラグが立ち、これ以降はアップグレード不可能になる。アップグレード前には notUpgraded モディファイアを使用することで、アップグレード前にしか呼び出せない関数を制御している。

      // アップグレード可能なスマートコントラクトの基底となるトレイト
      trait UpgradeableContract {
          fn upgrade(&mut self);
      }

      // 具体的な機能を提供するスマートコントラクト
      struct MyContract {
          owner: Address,
          upgraded: bool,
          data: u32,
      }

      impl MyContract {
          fn new(owner: Address) -> Self {
              MyContract {
                  owner,
                  upgraded: false,
                  data: 0,
              }
          }

          fn set_data(&mut self, new_data: u32) {
              self.data = new_data;
          }
      }

      impl UpgradeableContract for MyContract {
          fn upgrade(&mut self) {
              // バージョンアップのロジックをここに追加

              // アップグレード完了
              self.upgraded = true;
          }
      }

      fn main() {
          let owner = Address::new("owner_address");
          let mut my_contract = MyContract::new(owner);

          // アップグレード前にデータを設定
          my_contract.set_data(42);
          println!("Data before upgrade: {}", my_contract.data);

          // アップグレード
          my_contract.upgrade();
          println!("Upgrade status: {}", my_contract.upgraded);

          // アップグレード後にデータを設定
          my_contract.set_data(100);
          println!("Data after upgrade: {}", my_contract.data);
      }
       
  4. デバッグ機能の導入:

    • システム全体にデバッグ機能を組み込み、異常な状態やトランザクションの挙動を観察し、必要に応じて手動で制御できるようにする。
    • デバッグ機能を組み込むことで、システムの異常な状態やトランザクションの挙動を観察し、手動で制御できるようになる。以下の例では、debug_mode パラメータを用いて各ブロックチェーンがデバッグモードで動作するかどうかを制御している。enable_debug_mode メソッドを使用してデバッグモードを有効化することができる。有効な場合、ブロックが追加されるたびにログが表示される。デバッグ機能は、システム全体や各モジュールに組み込むことで、異常な挙動を検知し、手動で制御することが容易になる。

      # メインブロックチェーン
      class MainBlockchain:
          def __init__(self, debug_mode=False):
              self.debug_mode = debug_mode
              self.municipality_blockchains = []

          def add_municipality_blockchain(self, municipality_blockchain):
              self.municipality_blockchains.append(municipality_blockchain)

          def enable_debug_mode(self):
              self.debug_mode = True

      # 各市町村のブロックチェーンモジュール
      class MunicipalityBlockchain:
          def __init__(self, name, debug_mode=False):
              self.name = name
              self.debug_mode = debug_mode
              self.blocks = []

          def add_block(self, block):
              self.blocks.append(block)
              if self.debug_mode:
                  print(f"Block added to {self.name} blockchain: {block.data}")

      # ブロック
      class Block:
          def __init__(self, data):
              self.data = data
              self.timestamp = time.time()

      # メインブロックチェーンのセットアップ(デバッグモード無効)
      main_blockchain = MainBlockchain()

      # 各市町村のブロックチェーンのセットアップ(デバッグモード無効)
      municipality1 = MunicipalityBlockchain("City1")
      municipality2 = MunicipalityBlockchain("City2")

      main_blockchain.add_municipality_blockchain(municipality1)
      main_blockchain.add_municipality_blockchain(municipality2)

      # テストデータを生成してブロックを追加
      test_data = "Test Data"

      municipality1.add_block(Block(test_data))
      municipality2.add_block(Block(test_data))

      # デバッグモードを有効化
      main_blockchain.enable_debug_mode()
      municipality1.enable_debug_mode()

      # テストデータを生成してブロックを追加(デバッグモード有効時のログが表示される)
      municipality1.add_block(Block("Debug Block Data"))

      # Output:
      # Block added to City1 blockchain: Test Data
      # Block added to City2 blockchain: Test Data
      # Block added to City1 blockchain: Debug Block Data
       
  5. 柔軟なテストデータの導入:

    • 制御容易性を確保するためには、柔軟なテストデータの導入が重要である。テストデータを簡単に変更できるような仕組みを整備し、テストケースの設計や検証を効果的に行う。
    • 制御容易性を確保するために柔軟なテストデータの導入が重要である。以下は、柔軟なテストデータの導入を示す簡単なプログラミング例である。この例では、generate_test_data 関数が柔軟なテストデータを生成するためのユーティリティである。各市町村のブロックチェーンにはこのテストデータを使用してブロックが追加されている。これにより、テストデータを柔軟かつ効果的に変更できるような仕組みが整備されている。

      # メインブロックチェーン
      class MainBlockchain:
          def __init__(self):
              self.municipality_blockchains = []

          def add_municipality_blockchain(self, municipality_blockchain):
              self.municipality_blockchains.append(municipality_blockchain)

      # 各市町村のブロックチェーンモジュール
      class MunicipalityBlockchain:
          def __init__(self, name):
              self.name = name
              self.blocks = []

          def add_block(self, block):
              self.blocks.append(block)

      # ブロック
      class Block:
          def __init__(self, data):
              self.data = data
              self.timestamp = time.time()

      # テストデータ生成ユーティリティ
      def generate_test_data():
          # テストデータを生成するロジック(例: ランダムな文字列や数値)
          return "Test Data"

      # メインブロックチェーンのセットアップ
      main_blockchain = MainBlockchain()

      # 各市町村のブロックチェーンのセットアップ
      municipality1 = MunicipalityBlockchain("City1")
      municipality2 = MunicipalityBlockchain("City2")

      main_blockchain.add_municipality_blockchain(municipality1)
      main_blockchain.add_municipality_blockchain(municipality2)

      # テストデータを生成してブロックを追加
      test_data = generate_test_data()

      municipality1.add_block(Block(test_data))
      municipality2.add_block(Block(test_data))

これらのアプローチは、一度ブロックチェーンやフェデレーションモデルにおいてトランザクションが確定された後の変更への対処を考える上での手段となる。システムの柔軟性や適応性を向上させ、制御容易性を確保するために検討されるべきであろう。

 

4. 分解容易性(Decomposability)

システムを分割して個々の部分を独立してテストすることがどれだけ容易かを表す特性である。例えば次のような状況は、分解容易性が低い。

・テスト対象と直接関係がないパラメータや依存関係が必要になる

・依存をテストスタブに置き換えられない

ここも同様に、分解が容易ではない。テストタブに置き換えられないため、実践で小規模から始めてトラブルに対処していきつつ、規模を拡大していくしかないのだろう。

 

例えば、あるブロックチェーンシステムにおいて、各市町村のブロックチェーンノードが個別のモジュールと考えられる。各市町村のブロックチェーンノードは、それぞれが独立して動作し、他の市町村との連携が必要な場合は、それを適切に実現できるようになる。分解容易性を高めるための具体的なアプローチとして、以下の点が挙げられる。

  1. モジュール化: システムの機能や責務ごとにモジュールを定義し、それぞれが独立して機能できるようにする。この例では、MunicipalityBlockchainが各市町村のブロックチェーンを表すモジュールとなり、それぞれが独立して機能する。各モジュール内でのデータ処理や整合性検証がそれぞれのブロックチェーンに閉じ込められ、他の市町村のブロックチェーンとは疎結合となる。

    # モジュール化: 各市町村のブロックチェーンを独立したモジュールとして定義
    class MunicipalityBlockchain:
        def __init__(self, municipality_name):
            self.name = municipality_name
            self.blocks = []

        def add_block(self, data):
            # 新しいブロックを生成し、チェーンに追加するロジック
            new_block = Block(data)
            self.blocks.append(new_block)

        def validate_blockchain(self):
            # ブロックチェーンの整合性を検証するロジック
            # ...

    class Block:
        def __init__(self, data):
            self.data = data
            self.previous_hash = None
            self.hash = self.calculate_hash()

        def calculate_hash(self):
            # ブロックのハッシュを計算するロジック
            # ...

    # 各市町村のブロックチェーンをインスタンス化
    municipality1_blockchain = MunicipalityBlockchain("Municipality1")
    municipality2_blockchain = MunicipalityBlockchain("Municipality2")

    # ブロックチェーンに新しいブロックを追加
    municipality1_blockchain.add_block("Transaction data 1")
    municipality2_blockchain.add_block("Transaction data 2")

    # ブロックチェーンの整合性を検証
    municipality1_blockchain.validate_blockchain()
    municipality2_blockchain.validate_blockchain()
     

  2. 依存性の最小化: フェデレーションモデルでは、各参加者(市町村)が独自のブロックチェーンを持ち、これらのブロックチェーンがメインブロックチェーンで結びついている。つまり、各市町村が独自のブロックチェーンを維持しながら、それらのブロックチェーンが一定のルールやプロトコルに基づいて連携し、メインブロックチェーン上でデータのやり取りや合意形成を行う。以下の例では、各市町村が独自のブロックチェーンを持ち、それぞれがメインブロックチェーンに組み込まれている。ブロックチェーンはそれぞれが独立して機能し、メインブロックチェーンを介してデータを共有している。

    # 各市町村のブロックチェーンモジュール
    class MunicipalityBlockchain:
        def __init__(self, municipality_name):
            self.name = municipality_name
            self.blocks = []

        def add_block(self, data):
            # 新しいブロックを生成し、チェーンに追加するロジック
            new_block = Block(data)
            self.blocks.append(new_block)

        def validate_blockchain(self):
            # ブロックチェーンの整合性を検証するロジック
            # ...

    # メインブロックチェーンモジュール
    class MainBlockchain:
        def __init__(self):
            self.municipality_blockchains = []

        def add_municipality_blockchain(self, municipality_blockchain):
            # メインブロックチェーンに市町村のブロックチェーンを追加するロジック
            self.municipality_blockchains.append(municipality_blockchain)

        def validate_main_blockchain(self):
            # メインブロックチェーンの整合性を検証するロジック
            # ...

    # 各市町村のブロックチェーンを独立してインスタンス化
    municipality1_blockchain = MunicipalityBlockchain("Municipality1")
    municipality2_blockchain = MunicipalityBlockchain("Municipality2")

    # メインブロックチェーンに市町村のブロックチェーンを追加
    main_blockchain = MainBlockchain()
    main_blockchain.add_municipality_blockchain(municipality1_blockchain)
    main_blockchain.add_municipality_blockchain(municipality2_blockchain)

    # ブロックチェーンに新しいブロックを追加
    municipality1_blockchain.add_block("Transaction data 1")
    municipality2_blockchain.add_block("Transaction data 2")

    # 各ブロックチェーンの整合性を検証
    municipality1_blockchain.validate_blockchain()
    municipality2_blockchain.validate_blockchain()
    main_blockchain.validate_main_blockchain()
     

  3. テストスタブの活用: モジュールが外部のリソースやサービスと連携する場合、テストスタブを使用して本番環境の依存性をなくし、モジュール単位でテストできるようにする。テストスタブの活用は、外部のリソースやサービスと連携する際に、その依存性をなくしてモジュール単位でのテストを実現する手法である。フェデレーションモデルにおいて、各市町村のブロックチェーンが独立していると仮定する。以下の例では、MunicipalityBlockchain クラスが外部サービスとの連携がある場合に、ExternalServiceStub をテストスタブとして活用している。テストスタブは外部サービスの代替として機能し、モジュール単位でのテストを可能にする。これにより、外部サービスが本番環境に接続されていなくても、モジュールの挙動を確認できる。

    # メインブロックチェーン
    class MainBlockchain:
        def __init__(self):
            self.municipality_blockchains = []

        def add_municipality_blockchain(self, municipality_blockchain):
            self.municipality_blockchains.append(municipality_blockchain)

    # 各市町村のブロックチェーンモジュール
    class MunicipalityBlockchain:
        def __init__(self, name, external_service_stub=None):
            self.name = name
            self.blocks = []
            self.external_service_stub = external_service_stub

        def add_block(self, block):
            # 外部サービスとの連携がある場合、テストスタブを使用
            if self.external_service_stub:
                processed_data = self.external_service_stub.process_data(block.data)
                block.data = processed_data

            self.blocks.append(block)

    # ブロック
    class Block:
        def __init__(self, data):
            self.data = data
            self.timestamp = time.time()

    # テストスタブ
    class ExternalServiceStub:
        def process_data(self, data):
            # テスト用のデータ処理ロジック
            return data.upper()
     

分解容易性が確保されると、各モジュールは独立してテストでき、問題が発生した場合も特定しやすくなる。このアプローチにより、ブロックチェーンシステム全体の安定性や信頼性を向上させることが期待される。

 

 

いかがであろうか、制御容易性、分解容易性について記載した。わかりやすいのではないだろうか。これを実際に設計に組み込んでいきたい。