最近、「設計する」というの意味が理解できていないソフトウェアエンジニアが多くなったように思います.既に『「設計」できないエンジニア 』というテーマで、昨年5月に、このホームページに掲載しましたが、その後も、「設計できないエンジニア」が目に付くことがありますので、”第2報”としてここに掲載します.状態遷移が書けない
組み込みシステムの世界では、「状態遷移」の認識は、殆ど不可欠といっていいものです.ところが、現実に、この状態遷移(マトリクスまたはダイアグラムで表現する)が、設計書の中で書かれていない例を幾つか見る機会がありました.確かに、状態遷移も、プログラムの中では、フラグや状態コードに対する「IF」による分岐ですが、「状態」という概念で統合され整理されていることに意義があるのです.この「状態」という概念を理解できなければ、ただの思いつきのフラグの集団(集合という言葉を使いたくない)に過ぎなくなります.その結果として、ソフトウェアの安定性は低く、ある場面の修正によって、他の場面との統一性などは、簡単に崩れてしまい、テストには相当な工数がかかってしまいます.
これは、新人教育の中では、「C」や「C++」などの言語に慣れる教育が中心に行われているため、「設計する」ことの意味を理解し、認識するようにカリキュラムが工夫されていないのが原因かと思われます.テキストに沿ってコーディングの練習ばかりやっていても、「設計する」ということの意味は理解できないでしょうし、体得できません.そこでは「状態」という概念を習得する機会は無いかもしれません.
「概念」は、明確に定義されないままでは使うことはできません.いろんな文献を読んで、「状態」というものを定義する練習をしなければならないでしょう.たとえば、「状態」は、一般に継続性があって、一定時間を占有します.その中で、別の状態に変化させることのできる特定のイベント(刺激)を受け付ける事が出来ます.受付られるイベントは、「状態」によって制限されるのです.もし、イベントが発生しなければ、いつまでもその「状態」を維持し続ける可能性もあり、そのような事態を避けるために、「タイマー」という方法で強制的に「状態」を遷移させることもあります.「状態」の遷移は、状態遷移図や状態遷移マトリクスで表現することが出来ます.
このように「状態」について、自分の言葉で定義できれば、それを使いこなすことができるようになります.少なくとも、定義できた範囲では使うことが出来るはず.PCアプリでは、「状態遷移」はそれほど重要ではないのかもしれませんが、組み込みシステムの開発(設計)では、「状態」がうまく定義され、その遷移を適切にコントロールできなければ、まともなシステムを作り上げることは難しいのです.
複雑さを解消すること
設計するということは、そこに要求されている複雑な要件(仕様)を実現するための筋道を見出すことです.その際のポイントは、如何にして複雑さを解消し、シンプルな処理の集合になるようにするかです.
複雑さを解消する一つの方法は、「分割」することです.一つひとつの処理が単純になるように、モジュールやクラスを分割することです.また「階層化」を組み合わせることで、分割によって複雑(煩雑)になりそうな状況を隠すことができます.分割の基準は、構造化手法でも、オブジェクト指向でも、それが「手法」であるならば、設計のステップや基本要素は定義されているはずです.
例えば、構造化手法に於ける分割は、
入力/変換/出力分割
トランザクション分割
共通機能分割
データ構造分割
の4種類を駆使します.この中で、「共通機能分割」というのは、同じような処理をいくつも作らないということですが、「共通機能分割」だけでは、決して複雑さは解消しません.そしてこれらの分割の基準は、たぶんオブジェクト指向でも使えるはずですが、オブジェクト指向の言語を使っている人でも、必ずしも、このような分割の基準を使っているとは限らないかもしれません.もちろん、経験的に何らかの基準を手に入れているのかもしれませんが、実際に、巨大なクラスやメソッドを見ることがあります.この人場合は、「複雑さを解消する」という設計の基本的姿勢を理解しているとは思えません.適切な「複雑さを解消する」方法を持っていなければ、プログラムは複雑になり、その完成は遅れるでしょう.彼にとって「週40時間」という目標は、遠くに見え隠れするネオンのようなものかも知れません.
データ構造の問題
データ構造の問題は、『設計できないエンジニア』の(1)の方でも少し触れましたが、もう少し補足しておきます.
データ構造は、アプリの範囲によっては「ファイル構造」という形で表現されるかもしれません.例えば、8万件のPOSデータをどのように管理するかというとき、単品の商品レコードをどのような形で保有するかということは、システムにとって非常に重要な問題となってきます.「ISAMファイル」の構造を採用する方法もあるでしょうが、CPUのスピードと、多くの機能をリアルタイムで実現しなければならないという状況によっては、別のファイル構造を選ぶ必要があるでしょう.
POSの場合、たとえばそこに求められる機能は、新規商品の登録や追加、単価の変更と云ったメンテナンスがリアルタイムに入ってきます.もちろん、レジからの参照もリアルタイムに要求されます.これらの機能は、今日ではDBのミドルウェアを挟むことで、アプリの設計者の手から切り離されているのかもしれませんが、それでも、単価変更やセット売りなどの売り方の要求を満たすには、レジ端末の方に、スキャンした商品のデータを保持しなければなりません.途中でキャンセルもあります.一つの商品をスキャンすることで、いままで単品の状態であったものが、セット売りとして成立し、突然単価が変わったり、割引が適用されたりします.途中でタイムセールスが適用されることもあります.このようなすべての要求に応えるには、単に、スキャンした商品をそのまま順番に「テーブル」に並べて行くだけでは実現しません.単純な「テーブル構造」では、パフォーマンスは満たさないでしょうし、そこでのプログラムも複雑なものになってしまうでしょう.データ構造が要求を満たすように設計されていない状態を、処理プログラムでカバーすることは殆ど不可能です.
この場合は、リスト構造をベースに設計しないと、すべての要求を満たすことは困難なのです.そして、処理(モジュール/クラス)の構造やアルゴリズムは、「リスト構造」というデータ構造に適合させることになります.そのためには、「リスト構造」あるいは「キュー」という仕組みを知っていなければ、どうにもならないわけです
あるいは、ネットワーク関連の組み込み製品で、相手先アドレスに対して適切な送信経路を探しだすような要求で、情報テーブルを4096個の3層構造に設計した場合、単純なサーチであれば、最大サーチ回数は、680億回以上にもなってしまいます.1回のサーチが1ns という高速で回せるとしても、68秒もかかってしまいます.各層のテーブルのサーチが、平均の2048回としても8億回を越えます.これでは使い物にならないわけですが、一旦このようなデータ構造が設計されてしまえば、もはや処理でカバーすることはできません.作り替える意外に方法はありません.
最近の、PCなどのアプリケーションでは、DBのミドルウェアを挟むため、データ構造を設計するということが減ってきているのかもしれません.実際に、ソフト会社のエンジニアに、要求仕様の段階で一定のパフォーマンスを要求しても、実現方法を考えることができず、「○○のDBミドルの限界です」という返事が返ってくる始末です.従って、最近のソフトウェア技術者は、最適のデータ構造を自ら設計する機会が無くなっているのかもしれません.でも、それは「必要ない」という意味ではないでしょう.