テクニックの前に身につけるといいソフトウェア基本設計の思考順

2020/05/09

技術 考察

t f B! P L

さて、本日は設計についてです。
設計と言ってもデザインパターンがどうこうとか、そういうテクニカルなお話ではありません

なんとかかんとか図とかホゲーフガーダイアグラムとか、そういう話でもありません。

システムやソフトウェアの設計を行うに当たって、基本となる思考順の話です。

管理人は最近職場で色々聞かれながら要件定義やら基本設計やらやっているわけですが、
聞かれる内容や出てくるドキュメントから、システムやソフトウェアの設計をする前に、
まずこれを出来ないとそれは設計できないよな、と感じた思考順があります。

そう

段階的詳細化です。

段階的詳細化に基づいた考え方で仕様を整理していかなければ、どんなに優れたテンプレートやどんなに優れた図、どんなに優れた手法を使っても、仕様は出来上がってきません。

しかしネットで試しに基本設計と検索してみたところ、IPAが出しているドキュメントまでほぼ全て、ここではこう書け、ここでは何を使え、など、書き方や手法に偏重しており、全体の根底にある基本的な考え方が抜け落ちがちです。

ですから本日は敢えてこの考え方について、簡単な例を順を追って考えてみます。

ソフトウェアを設計する思考順

何をするかをまず考える

今回は極簡単にするために、サーバソフトウェアAのソケット通信を考えます。

サーバソフトウェアAはTCP/IPソケット通信をポート50100で待受け、
接続先クライアントから受け取った情報を接続先クライアントにそのまま返す。

ありがちな要求ですね。
実際のソフトウェア開発ではこうした対象の機能に対する要求は何項目にも及びますが、一項目一項目を丁寧にこれから各手順で処理していけば、比較的歴の浅い技術者でも設計が出来ると思います。

また、歴の長いエンジニアの方で、若手は求めたものを出してこないと嘆いてみせるエンジニアもおられますが、嘆く前に若手君がこのような思考順が出来ているかチェックしましょう。

「これ書くとしたら次何を考える?」

そう一声かけるだけで簡単にチェックできると思います。
一声すらかけられないならあなたはコミュニケーション能力(笑)不足です。

大まかな流れを考える

要件が決まったら、次はその要件を実現する理想的な処理の流れを考えて書き出します。
今回の例で行けばサーバ側のソケット通信です。

このとき、正常に通信した場合の流れだけを考えます

どのような情報が必要かは、情報と処理が渾然一体となり設計が複雑化する原因となりますので、
考えから意図的に外してください

どのような異常な動作がありえるかも、この段で考えようとする方がいらっしゃいますが、考慮漏れの原因になりますので辞めましょう。
これも考えから意図的に外します

例:
        ソフトウェアAはソケットを作成する
        ソフトウェアAはソケットを待ち受け状態にする
        ソフトウェアAはクライアントから情報を受け取る
        ソフトウェアAは受け取った情報をそのまま返信する
        ソフトウェアAは通信を切断する

これが本来実現したい動作ですよね。

設計者の方と仕事をしていて、設計変更を多発させる設計者の方は、この「大枠の考慮を無駄と思ってもやる」ということを怠っています。

というのもこの時点で入ってくるデータの種類や発生しうるエラーまで拾ってしまおうとするあまり、
大枠の流れを無視したエラー処理のプロセスを書いてしまい当初の目的を見失います
また、情報の内容を考慮しすぎて設計に無理が出てしまう場合もあります

大まかな流れに必要な情報を考える

次に大まかな流れを実現するための情報を考えます。
異常系はここでも考慮から外します

テキストでもエクセルでも何でも良いです、それぞれの処理を実現するときに必ず必要になる情報をここで列挙します。

例:
        ソフトウェアAはソケットを作成する : インターフェースディスクリプタ 待受ポート番号 
        ソフトウェアAはソケットを待ち受け状態にする : インターフェースディスクリプタ ソケットディスクリプタ 待受ポート番号 
        ソフトウェアAはクライアントから情報を受け取る  : ソケットディスクリプタ 受信データ配置先
        ソフトウェアAは受け取った情報をそのまま返信する : ソケットディスクリプタ 送信データ配置元
        ソフトウェアAは通信を切断する : インターフェースディスクリプタ  ソケットディスクリプタ

必要な情報と処理を切り離す

次に、列挙した情報にどんな値がはいるかを考えます。
これについては大していうことはないと思います。

例:
        ソフトウェアAはソケットを作成する : インターフェースディスクリプタ 待受ポート番号 
        ソフトウェアAはソケットを待ち受け状態にする : インターフェースディスクリプタ ソケットディスクリプタ
        ソフトウェアAはクライアントから情報を受け取る  : ソケットディスクリプタ 受信データ配置先
        ソフトウェアAは受け取った情報をそのまま返信する : ソケットディスクリプタ 送信データ配置元
        ソフトウェアAは通信を切断する : インターフェースディスクリプタ  ソケットディスクリプタ

        インターフェースディスクリプタ : ディスクリプタ番号 ディスクリプタは32bit整数値
        ソケットディスクリプタ : ディスクリプタ番号 ディスクリプタは32bit整数値
        待受ポート番号 : TCP/IPのポート番号、ポート番号は16bit整数値
        受信データ配置先 : バイト配列、1024文字まであれば十分
        送信データ配置元 : バイト配列、1024文字まであれば十分

どうやって実現するかを考える

流れと情報が決まれば、次にその情報がどこからもたらされるのかを洗い出し、どう使うかを洗い出します。

更にそれらはまさに詳細な処理となりますので、項目ごとに処理を章立てて書き出します。

例:
        ソフトウェアAはソケットを作成する
         Socket関数からインターフェースディスクリプタを取得する
         インターフェースディスクリプタを待受ポート番号でbindする。

        ソフトウェアAはソケットを待ち受け状態にする
         インターフェースディスクリプタにListenで最大待ち受け数を設定する。
         インターフェースディスクリプタをacceptで待ち受ける。

        ソフトウェアAはクライアントから情報を受け取る
         select関数でソケットディスクリプタへのデータの着信を確認する。
         着信があった場合recv関数でソケットディスクリプタから受信データ配置先にデータを受け取る。

        ソフトウェアAは受け取った情報をそのまま返信する
         着信データを送信データ配置元にコピーする。
         ソケットディスクリプタへsend関数で送信データ配置元からデータを送信する。

        ソフトウェアAは通信を切断する
         ソケットディスクリプタを閉じる。
         インターフェースディスクリプタを閉じる。
        ディスクリプタ
        interface_discripter
                サーバーの待受ディスクリプタ
                型:int32_t
                有効値:0以上の整数値

        connection_discripter
                クライアントの接続ディスクリプタ
                型:int32_t
                有効値:0以上の整数値

        待受ポート番号
        port_num
                TCP/IPのポート番号
                型:uint16_t
                有効値:0~65535

        受信データ配置先メモリ領域
        recv_buffer
                型 int8_t[1024]
                無効値なし
       
        送信データ配置元メモリ領域
        send_buffer
                型 int8_t2048[1024]
                無効値なし

異常系を考える

ここまで来て初めて異常系を考え始めます。
何故ここまで異常系の考慮を引っ張ったかという理由は、前項を見れば明白と思います。

使うシステムコールやAPI、情報の構造などを十分に洗い出した後であれば、各情報の何が異常値で、何が正常値か、システムコールやAPIがエラーとなる場合はどんな場合があるか、それを調べるには何を見れば分かるか。
エラー処理を設計するために必要な情報が明白になっているので誤りが少なく、調べること自体も高い作業効率が実現できます

この順番が前後すると、いざ詳細化していったときに、今この章を書いているところまで進んだところで、
「あ、あのシステムコールも必要だった、あれ入れるのどこに入れれば良いんだ?」
「あ、あの情報も必要だった、あれの有効値とかってどうだっけ?」

といったことが始まります。

異常動作というのはシステムコールやAPIのやってはいけない呼び出し方、情報の誤った使用の仕方が起こすものですので、
それらが十分洗い出せていないうちから異常系を考えてしまうと絵に描いた餅になってしまうのです。

しかしここまで洗い出せていれば、下記のように。

例:
        ソフトウェアAはソケットを作成する
                Socket関数からインターフェースディスクリプタを取得する
                error : 0未満の値が返された場合、システムのディスクリプタ枯渇の可能性があるため再試行は不適切、ソフトウェアを停止
        インターフェースディスクリプタを待受ポート番号でbindする。
                error  :  errnoの値を確認し以下の処理を実施。
                        EACCES : ソフトウェアの実行権限が誤っているため再起動が必要 ソフトウェアを停止
                        EBADF : ディスクリプタが不正、取得は出来ているため一度クローズして再取得する
                        EINVAL : bind 実行済みであるためaccept可能、そのまま次の処理を実行する
        等々

局所的に起こりうるエラーをかなり詳細に設計することが出来ます。

さらに言えば、ここまで詳細に設計することが出来れば、TDDやBDDに使用するテスト仕様書を作成することも、この情報を元に出来てしまいます

ここまで考えたら仕様書を書く

優れたテンプレートや優れた図は、設計を行うためのものではなく、本来設計を誤解なく他者に伝えるための記述法であり、この思考順を円滑に行うためのツールであるはずです。

ですが思考順が全く合致しない考え方をしてしまうと、どんなに優れたツールを使用したとしても適切な設計は出来ません。


ちなみにここで書いたのは基本設計の段階までのやり方です。
しかしながら詳細設計においても、段階的に順を追って詳細化していくという考え方、思考順序は変わりません

さいごに

今回は会社で聞かれた内容を元に、社内で整備した資料を思い出しながらだだーっと書かせていただきました。

もしも設計書を書くことが設計になってしまっているとか、書いた設計書で設計変更が多発するのであれば、
今回ここに書いたような思考順を訓練してから設計書を起こしたほうが、恐らくはストレスなくシステムやソフトウェアの設計が出来ると思います。

また、この考え方は書くだけでなく、誰かが書いた仕様書が要求仕様から順を追ってブレイクダウンされているかレビューする際にも、レビュアーに求められる思考順です。
レビューする際の抜け漏れをこの思考順で追っていけば、いくつもの有効な指摘を出すことが出来ます。

同時に、もしこの思考順に合致したものでない、なんか渾然一体としたものが出てくる、と感じるエンジニアの方がいれば、それはおそらく思考が前後して頭の中が混乱したような状態になっています。

叱る前に、その方の思考の整理をしてあげましょう。

Translate

ページビューの合計

注意書き

基本的にごった煮ブログですので、カテゴリから記事を参照していただけると読みやすいかと存じます。

ADBlocker等を使用していると、Twitterやアクセスカウンタが表示されません。

記事を読むには差し支えませんが、情報を参照したい場合には一時例外にしていただけると全てご参照いただけます。

Featured Post

ボイドラDICEの攻略法

QooQ