巨大関数を理解する(その2)

 Nassi Shneiderman ダイアグラム(NSチャート)のツールは、Googleで検索したらいくつかみつかりました。ただ、実際にダウンロードして起動してみると、ちょっと期待している方向とは微妙にずれているような気がしてきました。

 図表にすることで、確かにわかりやすくはなると思う。でも、元のソースが一関数で何千行もあるような巨大なものの場合には、詳細よりもまず概観や要約がほしい。

 そこで、まずは、印象の要約を出してみよう、ということで、こんなものを出力するプログラムを書いてみました。

FormHostMessage:
 【◆.【▲】◆.【.▲】...◆【.▲】...........○.【◆..】◆.【】.◆.【.▲】.◆.【."可変長フラグが異常です(bKahenFlg=%d)".▲】.◆.【."テーブル数が異常です(m_nSubTblFlg=%d)".▲】◆.【.◆.【."IMSフィールドデータ内の項番が異常です(nMaxKouban=%d)".▲】】...."hostMsg = new BYTE[nHostMessageLen]:%d"...◆.【◆.【."delete [] hostMsg:%d".】▲】.."%04d"◆.【◆.【."delete [] hostMsg:%d".】▲】.◎.【..】....○.【◆.【◆.【."delete [] hostMsg:%d".】.▲】.◆.【◆.【."delete [] hostMsg:%d".】....▲】◆.【◆.【."delete [] hostMsg:%d".】....▲】◆.."S_NAIYOU"【◆.
   |◆.."1"【】】
  |◆.."S_YOYAKU"【◆.
   |◆.."1"【】
   |◆.."2"【】
   |】
  |◆.."A_TERMINAL_ID"【.◆..【.】◆..】
  |◆.."M_DEPT_DATE"【.◆..
   |【."00".】】
  |◆.."D_DEPT_DATE"【.◆..
   |【."00".】】
  |◆.."A_YOYAKU_NO"【.◆.【】◆..】
  |◆.."N_YOYAKU_NO1"【.◆.【】◆..】
  |◆.."N_YOYAKU_NO2"【.◆.【】◆..】
  |◆.."N_YOYAKU_NO3"【.◆.【】◆..】
  |◆.."INPUT_ID"【.◆.【◆.【】.▲】.】
  |◆.."REQUEST_TIME"【.◆.【◆.【】.▲】.】
  |◆.."SEQ_NO"【.'\0'..◎.【】】◆.
  |.....◆.【◆...【..◎.【◆..【】◆..'\0'【.】】】◆.【...◆.'\0'【◆.【】.▲】◆..【.'\0''\0'】】◆..【◆.'\0'..【.'\0'.◎.【◆..【】
      |◆.'\0'【】】..】◆..【....'\0'...】】.◆..【◆.【】
    |◆.【】
    |◆.【】】】◆.【◆..○.【◆..】◆.】◆【."IMSから%sを複数受信。".◆..○.【◆..】◆.】◆【◆.【."delete [] hostMsg:%d".】."フィールド%sでエラー。"...▲】◆.◎【◆.◆.【◆.】◆.【◆.◆.】
   |【.◆..【】】◆.◆.】◆.【◆.【......◆.'\0'【.'\0'.'\0'...","....",'".."'"...." ,".."='".."'"】◆.'\0'【.'\0'.'\0'.◆.【◆.'\0'【." AND "】.." = '".."'"】
     |◆.'\0'【.◆.'\0'【.'\0''\0'】." = '".."'"】.◆.【.","】..."='"..."'"】】
   |【.",".......",'".."'"...." ,".."='".."'".】】◆.【◆.【.'\0'..◆.【'\0'◆.'\0'【." AND "】.." = '".."'"】
    |◆.'\0'【.◆.'\0'【.'\0''\0'】." = '".."'"】】◆....◆.【◆.【】◆.【◎.【◆.【】
      |◆.【】
      |【..◆.【..】
       |【】】】◆.◎.【】◆.【."不正な漢字コード:%s".】】◆.【◎.【◆.【◆.【】◆.【】】◆.【◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.
       |】
      |◆.【◆.
       |◆.
       |◆.
       |◆..
       |◆.【.◆.【】】
       |◆.【.◆.【】】
       |◆.
       |◆.【.◆.【】
        |◆.【】】
       |◆.
       |◆..
       |◆.
       |◆.
       |◆.
       |◆.
       |◆.【】
       |◆.
       |◆.
       |】
      |【】】◆.◎.【】】◆.【】.】
   |【◆.【.◆.【.】
     |【..】◆.【】.】】◆.】◆..○.【◆..】◆.】◆.【..】◆.【】◆.【】◆.【】◆.【】◆.【.◆.【."%4d"】
  |◆.【."%4d"】
  |【.◆.【."%4d"】
   |."%4d"】.】◆.【◆.【."delete [] hostMsg:%d".】.▲】."SOCK:%d INPUT_ID:%s S_YOYAKU:%d EDT:%s TABLE:%s(%s) DEPTDATE:%s RESERV_NO:%s".◆.." NAIYOU".◆.【◆.【."delete [] hostMsg:%d".】.▲】◆....【."待ち行列の制限を越えたため、MW0でエラー返却します。"◆.【."delete [] hostMsg:%d".】.▲】◆..【◆.【.",S_STATE,ERR_CODE".",'10',''".◆.【◆.【."delete [] hostMsg:%d".】.▲】◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【." ,S_STATE='10' ,ERR_CODE=''".◆.【◆.【."delete [] hostMsg:%d".】.▲】◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【."delete [] hostMsg:%d".】.▲】◆..【◆.【.",S_STATE,ERR_CODE".",'20','MW9'".◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【." ,S_STATE='20' ,ERR_CODE='MW9'".◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【."delete [] hostMsg:%d".】.▲】◎..【.◆.【】
  |【】】◆.【."ALL SESSION CLOSED"◆.【.",S_STATE,ERR_CODE".",'20','MW8'".◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【." ,S_STATE='20' ,ERR_CODE='MW8'".◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【."delete [] hostMsg:%d".】.▲】◆..【◆.【.",S_STATE,ERR_CODE".",'20','MW4'".◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【." ,S_STATE='20' ,ERR_CODE='MW4'".◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【."delete [] hostMsg:%d".】.▲】◆.【.◆.【◆.【."delete [] hostMsg:%d".】.▲】◆.【◆.【."delete [] hostMsg:%d".】.▲】】.◆.【."delete [] m_lpbReceiveBuffer:%d".】◆.【.",S_STATE,ERR_CODE".",'20','MW7'".◆.【◆.【."delete [] hostMsg:%d".】.▲】◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【." ,S_STATE='20' ,ERR_CODE='MW7'".◆.【◆.【."delete [] hostMsg:%d".】.▲】◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆..【.◆.【◆.【."delete [] hostMsg:%d".】.▲】◆.【◆.【."delete [] hostMsg:%d".】.▲】】◆.【◎.【◎.【◎.【◆.【】】◆.【】
    |【】◎.【◆.【】】】】◎.【◎.【◆..【◎.【】】】】】.◎.【◎.【◆.【◆.【】】
   |【】◆.【.】
   |◆.【◆.'\0'【..】◆..【.."SEND_NO = '0' "】◆.【.】】
   |◆.【◆..【.."SEND_NO = '0' "】◆.【.】】◆.【◆.【."delete [] hostMsg:%d".】.▲】◆.【◆.【."delete [] hostMsg:%d".】.▲】】】.◆.【◆.【."delete [] hostMsg:%d".】▲】.."hCompletionEvent = CreateEvent:%d".◆.【.◆.【."delete [] hostMsg:%d".】▲】◆...【◆.【."delete [] hostMsg:%d".】."CloseHandle(hCompletionEvent):%d".."QUEUE INSERT FAILED".▲】."QUEUE INSERT SUCCESS(InputID=%d)".....▲】

 なんだこりゃ、と思うでしょう。
 ごもっとも。

 これは、巨大関数FormHostMessageから抽出した印象要約です。元のソースコードでは、2000行ほどの長さの関数です。
 基本的には、プログラムソースコード中のキーワード(予約語)と文字列定数を抽出したものです。

 この例の場合、元のプログラムはC++ですが、Javaでもかまいません。ただし、予約語は、ぱっと見て目に付くように記号に対応付けてあります。
 たとえば、{}は【】に、▲はreturnに対応しています。
 ◆はifです。elseは|、○はwhile、◎がforです。関数呼び出しにつき物の開き括弧「(」は「.」になります。

 なんとなく、あっちこっちでreturn(▲)してるなあとか、if(◆)が多いなーとか、else if(|◆)の連続があるんだねとか、傾向というか雰囲気はわかるような気がしませんか?
 これで、そこはかとなく気分をつかんだら、量に圧倒されてしまうところからもう一歩踏み出せそうな気になれました。

 きのう自宅でこれを生成するプログラムを書いてみて、怖気づく気持ちをなだめながら、今日はプログラム仕様を理解するために内容の書き直しをやってみました。
 冗長な描き方や無駄な処理を省いて整理していった結果、コメント空白行込みで834行ほどにまで縮めることができました。