ろぐれこーど

限界組み込みエンジニアの学習記録とちょっぴりポエム

全ての基本は構造化から。『組み込みソフトウェア開発のための構造化モデリング』

構造化モデリング及び設計に関する本を読みました。オブジェクト指向設計が提唱されて久しいですが、組み込みの製品開発では今でも構造化設計に基づく部分が多くあります。(C言語だと言語仕様的に実装しにくいということも一因だけど、新規開発が少なく実績優先される製品だとその傾向が強いので)

オブジェクト指向を使う前に、今一度基本に立ち返って古典的な手法を学ぶことも大切だと思わせてくれる一冊です。

著者

SESSAME(組み込みソフトウェア管理者・技術者育成協会)というNPO法人の方々が執筆されています。文字通り、日本の組み込みソフトウェア開発の技術者を育成するための教材開発や研究を行っている団体です。「単に技法を紹介する」という内容ではなく、沸騰ポットの事例に対してSESSAMEのメンバーが実際に構造化設計を試し、得られた教訓やTipsなどをまとめた内容になっています。初版が2006年と少し古いですが、今でも基本となる設計原則が書かれているため、新人への教育資料としては最適かと思います。

本の概要

モデリングの利用

組み込みソフトウェア開発では枯れた(実績がある)技術が使われることが多く、現代的なプロセスや開発手法が使われることが少ないと問題提起をしています。本書では曖昧な要求から「要求モデリング」「分析モデリング」を逐次繰り返し、検討した内容を「設計モデリング」へのインプットとして最終的な成果物(ソースコード)を作成することを推奨しています。今では「アジャイル」という名前で体系化されていたりしますが、要はウォーターフォールによる一方向の開発では無理があるという主張です。

要求モデリング

システムが「何をして欲しいのか」と、その結果「何が起こるのか」を定義します。そのための手段として、本書ではイベントリストの作成が挙げられています。イベントリストは

  • 動作のトリガーとなる事象(イベント)
  • イベントが生み出す情報(スティミュラス)
  • スティミュラスによって開始される動作(アクション)
  • アクションの結果、出力される情報(レスポンス)
  • レスポンスによって生じる外界への影響(エフェクト)

を記述することにより、システムの本質と利用者の要求を洗い出すものです。これにより機能要件が抽出されます。

機能要件を満たすためには品質やリソースが制約条件として出てきますが、ここではそれらを非機能要件として別管理します。システムの本質や要求を見失わないようにするためです。また検討段階でタイミングに関する要求が出た場合、タイミング仕様としてこれも別管理します(組み込みシステムにおいて、タイミングは優先的に考慮されるべき要件のため)。

他にも用語の定義などがありますが、大まかには

  1. イベントリストの作成(機能要件の抽出)
  2. 考慮されるべき非機能要件の抽出
  3. タイミング仕様の抽出

の3つで要求モデリングの成果物とします。

分析モデリング

開発対象となるシステムのスコープを決め、入出力を定義するフェーズです。以下のような流れで進めます。

  1. スコープを定義する 要求モデルを入力としてコンテキストダイアグラムを作成する
  2. 機能を分割する コンテキストダイアグラムから、より具体的なプロセス(処理単位といった意味合い)に分割し、データフローダイアグラム(DFD)を作成する。
  3. プロセス毎の動作仕様を決める 機能分割した各プロセスの動作仕様を決定する。

構造化モデリングにおいては、コンテキストダイアグラムはUMLでいうところのユースケース図に近い役割を担っています。外部エンティティとのデータの受け渡しを中心に考え、各プロセスを詳細化していくことでDFDを作成します。

最も重要なのは、HOWではなくWHATを中心に考えることです。つまり実装手段を考慮していくのではなく、何を作りたいかを意識します。その過程でWHYという疑問にぶつかったら、分析モデリングで要件を再定義します。(状態遷移図やディシジョンテーブルといったツールを適宜使用することが推奨されていますが、制御フローに関わる部分は極力DFDには含めないように留意します)

設計モデリング

分析モデルで得られたDFDのプロセスを、どのように実現するかを考えます。主に機能分割に沿ったモジュールの作成という作業となります。ここでいうモジュールは再利用可能な処理単位であり、再利用可能とするためには以下に留意する必要があります。

  • モジュールが提供する機能を単一にする(単一責務)
  • 実装とインターフェースを分離する(情報隠蔽

設計モデリングは以下のような流れで進めます。

  1. DFDから構造図を作成する STS分割やTR分割といった手法でDFD内のプロセスをモジュールに分割する。
  2. 局所的に作成した構造図をマージし、システム全体の構造図を作成する システム形状を見直しながらモジュールの統合・分割を進める
  3. タイミング要求に対応する 要求モデルで作成したタイミング仕様を満たすように詳細設計する

モジュール分割・統合の際は、最大抽象点がどこになるかを意識します。入力データが種々の変換を経て抽象度が最大となる点を最大抽象点と呼び、そこを機能単位での制御モジュールとして扱うことで、データ毎の独立性が高い処理となります。

設計品質

品質特性として、以下に留意しながら設計を行うことが大切です。オブジェクト指向設計でも当たり前に使われる

  • モジュール分割
    • 単一機能・責務となるまで分割する
    • 重複のないモジュールを作成する(共通化できる部分を探す)
    • 実装は隠蔽する(モジュールが何を提供するかのみ公開し、その実現手段は外部に公開しない)
  • 凝集度(コヒージョン)
    • モジュールが単一機能で構成されているかどうかを図る尺度であり、高いほど良い(7段階ある)
    • 中心となるデータがなく、順序的な関連性から一つのモジュールを構成している状態(手順的コヒージョン)は凝集度が低く、避けるべきである
  • 結合度
    • モジュール間の依存関係を図る尺度であり、低い方が良い
    • 制御を意図した情報(フラグなど)が伝達される状態(制御結合)は結合度が高く、避けるべきである
  • システム形状
    • ファンインが多いモジュールは凝集度が高くあるべきである
    • ファンアウトは7程度に抑え、それ以上多い場合は分割を検討する
    • システム階層の上下は「論理 - 物理」で分離されるべきである
    • システムへの入力は上位(論理)層へと抽象化しながら伝達し、上位で判断された上で下位(物理)層へ流してシステムの出力とする構造を目指す

要点

なんとなく感じ取って重要だと思った要点をまとめます。

要件の優先度とステークホルダを明確にする

要求モデリングでは一般的には要件定義と呼ばれるフェーズに相当するかと思います。機能・非機能要件を明確にするのは当然として、各要件の優先度とステークホルダ、(あとできれば対応マストな納期)を把握しておくことが大切です。限られたリソースで要求を実現するためにはある程度妥協しなければならない局面(組み込みではよくある)も出てきますが、妥協する箇所を決める上で重要となるからです。特に「やらなくても良い理由」を説明するためには要件同士の関係や順位を明確にする必要があります。

分析時点で実装手段に拘らない

構造の検討時点で機能実現の詳細な方法を検討してはいけないとされています。特にDFD作成時には分岐やループなどの制御構造に言及しないことが重要です。(CFDという制御を含めた図も本書で紹介されていますが、あまり一般的な記法ではない上に制御フローの定義が曖昧で管理できずにレガシー化の恐れがあります)

DFDはあくまでデータ中心な静的図なので、制御を表すなら他の手段を使ったほうがいいです。もっとも本書ではそこには言及されていませんが…

論理-物理な階層で分割(抽象化)し、かつ隠蔽する

組み込みだとマイコンや周辺IC操作のために種々のハードウェアアクセスを多用しますが、システム上位のレイヤはハードを意識しない設計とするべきです。開発段階ではデータシートとにらめっこしながら試行錯誤するのが常ですが、モジュール作成時点ではシステム上位でハード操作の手順を意識するような実装としてはいけません。下位(物理層)の操作は隠蔽し、上位はあくまで「こういう状態にしたい」「こういう出力を出したい」という要求だけ投げるようなモジュール分割とすると、階層分離と抽象化が実現できます。

実践

少なくとも今後は以下に気をつけようと思います。

静的構造で分析する

自分はよくレビュー時などに「こういう動作(処理)をするから、やりたいことが実現できます」といった動的な説明をしてしまいがちです。ですが動的な振る舞い(HOW)から検討してしまうと「こういう場合はどうなるのか?」といった具体的な事例からボトムアップ的に積み上げていくことになり、最終的な構造が歪になってしまう恐れがあります。(というかよくやります笑)

分析時にはDFDなどの静的構造図を使用する方が、最終的な構造がスッキリすることが多い気がします。適切に階層化すればDFDでもそこそこの規模のシステムが表現できるので、小〜中規模な設計変更やシステム開発では有用でしょう。ただそれ以上の規模になってくるとDFDでは限界があるので、場合によりUMLを使用することも視野に入れておきます。問題はUMLを理解できる人がチームにいないことだが

他機能に依存しない設計とする

情報隠蔽や低結合度とも関連しますが、「この機能はこういう振る舞いをするから、この設計とする」といった他機能の内部処理を知っている前提での設計は極力排除します。「当たり前だろ」と言われるかもしれませんが、種々の理由で依存しまくった設計となってしまうこともままあります。特にC言語だと、ファイル分割とかスコープ管理が曖昧なせいで依存を解消できなくなるケースがあるように思います。開発初期段階で強く方針を打ち出し、開発メンバと共有しておくことが大切です。結局はコミュニケーションですかね。

まとめ

構造化モデリングについての基本を学びました。現代のソフト開発のなんとか原則とかに通ずる部分はあるのかなと思っています。ただ組み込みでは信頼性を最重視される場面も多く、本書のような設計技法によってもたらされる可読性や保守性が犠牲になることもあります。今後ハードウェアの性能が上がっても、モノとしての単価を抑えたい経営層としてはリソース十分なMCUを採用する動機は薄いので、これはそう簡単にはなくならない問題だと思います。

複雑化や急な変更に対応するためにソフトウェア開発は発展してきましたが、組み込みソフトの開発には設計や開発プロセスとは別のところに改善点があるのかもしれません。設計手法の本を読んでたはずなのにどうしてこうなった。。