トラブルシューティングの極意―達人に訊く問題解決のヒント

第11回[ソフトウェア開発編]むやみやたらにデバッグ+テストしていませんか?「ソースコード」指紋からわかるバグの原因

一般的な開発シーンで、静的解析ツールを利用して文法的な誤りを検出するアプローチがあります。さらに全網羅テスト(考え得る組み合わせを全数テストすること)なども一般的に行われています。また、トラブルが発生した場合に「全員総掛かり」で、しかもすべてのコードを1本目から「しらみつぶし」に読破して「確認する!」――といった場面が少なくありません。果たしてそれは効率的な仕事でしょうか?

今回はトラブルの原因をコードの中に追求します。

ソースコードの傾向に見る誤りの予測(初級編)

全網羅テストのムダ

テストをしらみつぶしですると、安心感や自信につながるので、そこに目的と理由があるかもしれません。しかし、これらのアプローチは一見合理的に見えて、実は投入対効果(=投資効率)の観点で非常に無駄が多いのです。

ソフトウェア開発における障害は、そもそもソースコードに起因します。ならば、全コードを目視検査すれば良いのか?̶̶それでは元の木阿弥です。スマートな方法は、バグが発生する原因を「ソースコードの位置」「バグの種類」から特定することです。これならば、短時間で原因追及ができます。いわば「トラブルを狙い撃つ」技術です。

兆候を読み取る技術とは

テストケースは少ないに越したことはありません。従来どおりのテスト手法や製品領域があることも否定しませんが、テストケースにあらかじめ「偏り」を持たせることで、重点的にテスト実施すべき場所か否かといった「加減」ができるようになります。それが兆候を読み取る技術です。これを「Fingerprint Detection(指紋検出⁠⁠」テクニックといいます。

ソースコードの兆候を観察し誤りを予測する

まず、3つの兆候検知方法を次に挙げます。

[方法1]コードの外形
(兆候が現れている対象を選ぶ)
[方法2]コードのデータ
(各種コードメトリクスからのアプローチ)
[方法3]内部の兆候検知
(NGワード/一発でおかしい記述の検出)

端的に言えば、これらは「特異点を見つけ出す」方法です。実はトラブルシュートや問題判別手順に共通な考え方があります。それは、できるだけ「考えられる可能性を排除する」ことです。つまり探索/調査範囲を小さくする手順です。その位置と種類の検出を最優先課題とする思考方法です。実際には希望的観測や認知バイアスが存在するので簡単にはいきません。ですが「段階的に可能性を排除する思考」で、問題判別を迅速に行い被害を最小化できるようになります。

[方法1]コードの外形で兆候検知

ソースコードのファイル群から、目視で問題のあるファイルを狙い撃つには、決定的なポイントがあります。前述のように特異点を見つけることです。もちろんファイルをひとつひとつ開くような手間をかけません。次の方法を行います。

最終更新日付が怪しい

最後に更新された(更新日付が最新の)ファイルを選びます。とくにトラブルが発生した場合などは、トラブルが発生した前後の日付が対象ファイルです。

ファイル名の長さが怪しい

クラス/ソース名を確認し、そのシステムの命名規約から逸脱しているものを優先的に選び出します。名前や長さが異なるものは、リリース後に追加された可能性があります。つまり、開発当初のルールを踏襲していないので、機能が不整合を起こしている可能性があります。

ハイフン付きは怪しい

JavaやC言語では「ハイフン」がファイル名にあるものを選びます図1⁠。これは(特別な命名規約がない限り)ベテランに多い傾向です。汎用機言語(COBOLやPL/I)の名残りです。変数名やファイル名にハイフンを使うのは開発者の「手癖」です。ハイフンによる命名規約が悪いのではなく、他コード群と比べてわずかに人の癖が見える特徴です。

図1 ハイフン付きは怪しい。手癖が原因?
図1 ハイフン付きは怪しい。手癖が原因?

一番サイズが大きいものが怪しい

サイズが大きいコードは、複雑度が高い可能性があります。一般的にコード行数とIF分岐条件には相関があり、高い相関係数を示します。行数が多いコードは、IF文のような分岐文を多く含んでいます。つまり「全部テストしきれていない」可能性が高いのです。

[方法2]データからの兆候検知(各種コードメトリクスからのアプローチ)

次に各種の定量数値(コードメトリクス)を取得し、そのデータから対象を選ぶ方法です。

NGワードの含有率(トラブル誘発因子の検出)

NGワードとは、コードの中に通常では記述されない表現・単語です。NGワードの含有数や含有率(=NGワード数÷コード行数)などが高い数値を示しているものを選びます。たとえば、⁠要検討」⁠要確認」⁠TODO」⁠TBD」などが、コード中にコメントとして残っている場合があります。これは開発時に仕様書を、そのままソースにコピー&ペーストした形跡を示唆しています。未確定仕様があれば、関連する単語がソースコードに残る場合があります。もちろん保守・改変時にもこれらの記述が増える可能性は高く、頻繁に改変を行っているコードに頻出して現れます。

コメント比率の確認(欠陥混入の間接メトリクス)

ソースコード全行数に対する「空行」および「コメントだけの行」の割合です。このメトリクスから、システムや製品の経年劣化状況を確認できます図2⁠。とくに、誤りの予測という観点では、一般的には次のような傾向があります。

  • コメント率が高いコードは、頻繁に改変された可能性が高い(ブロックコメントアウトによる履歴保存=コメント率が高くなるため)
  • 頻繁に改変されたコードは、テストが不足している可能性が高い(十分な回帰テスト工数を投資しているか疑問)
  • 頻繁に改変されたコードは、誤修正欠陥の混入確率が高い(複数人数でテストするため)
  • 急場で作ったコードはコメント率が低い可能性がある
  • 業務アプリケーションや制御のコードではない、移行ユーティリティやテストコードは、コメント率が低い場合がある
図2 コメント率で怪しいものを発見
図2 コメント率で怪しいものを発見

これらの理由から、コメント率の極端に高いものと低いもの(例:コメント率90%やコメント率5%以下)をサンプリング抽出します。

IFとELSEの比率(設計欠陥の間接メトリクス)

いわゆる分岐の片抜け、例外設計、信頼性設計などの確認です。IFとELSEなど、対にならなくてはならないキーワードを数えます。これで内容を推察します。たとえばELSEの個数をIFの個数で割ります。そうすると、ELSEがゼロのものや、極端にIFブロックだけ記載しているコードが浮かび上がってきます図3⁠。逆にIFとELSEがすべてのコードで1対1、つまり同数の場合は、何らかのコードジェネレータを使っている可能性があります図4⁠。

図3 IFとELSEペアの一般的傾向
図3 IFとELSEペアの一般的傾向
①や②はIFが数十~100以上のコードには、ELSEが0 ~10以下というものもあります。これは仕様書にもともとELSEが書かれていない影響もありますが、プログラマ側がELSE/その他の処理について意識する必要があります。とくにテストケース設計時にELSEやレコード0件、Null値などの異常テストケースの設計を見落とす可能性が高いため、テストケース設計基準についても併せて見直しが必要です。
図4 IFとELSEの対応について(自動生成の例)
図4 IFとELSEの対応について(自動生成の例)

もちろんC言語におけるK&R表記を標準とする場合など、標準化ルールやガイドにも関係しますが、一般的にELSE記述は設計段階での「例外略記/例外考慮不足」に最も影響を受ける数値です。これらの数値によって対象を絞り込むと効率よく設計欠陥を検出できるようになります(1本にIFが1,000個ある「おかしい」コードも、言うまでもなく対象です⁠⁠。

ソースコードリーディング技術によるバグの見つけ方(上級編)

[方法3]内部の兆候検知(特異記述の検出)

「コード開けたら1行目からいきなり読むな」はコードリーディングの鉄則です。何らかの兆候を示しているソースコードを見つけたら、エディタ上でも検査対象を絞り込みましょう。

バグのキスマークを探せ!

例外処理ブロックが存在し、エラーを捕捉するところまではできていても、実は例外処理が空、すべてコメントアウトされている個所も少なくありません。

例1
} catch (Exception e) {
}
例2
} catch (Exception e) {}

これを「例外処理の握りつぶし」と言います。これは例外設計が不十分であることを示唆します。これを発見するには、次の正規表現を使います。

  • {}または{\s+}

Javaならば、catch ( Exception e ) {}のように例外未実装の個所を特定します。該当個所があれば、それは例外処理が未実装です。機能追加を依頼しましょう(たとえばデバッグコードを挟むだけでも、ログを吐かせる一文だけでも!⁠⁠。

この「{}」は、見た目が唇の形に似ていることから、俗にキスマークと呼ばれます。一般的に頻出する欠陥・バグの類(たぐい)です図5⁠。色恋にかまけて「大切なことを忘れる」という意味ではありません。

図5 キスマークの原因(多くの場合「時間制約」が原因)
図5 キスマークの原因(多くの場合「時間制約」が原因)

セミコロンだけの行を探せ!

一般的なオープンソースの静的解析ツール(例:JavaであればFindbugsなど)を用いて品質検査を行った場合、⁠例外処理ブロックが空です(empty catch block⁠⁠」といった指摘が出ることがあります。

前述のキスマーク未実装などは、これに該当します。ところが、開発途中のコード(とくに初期開発や、例外処理実装失敗に起因するコード更改などの場合)には、⁠ブロックの内部にセミコロンを挿入」してツールを黙らせることがありますリスト1⁠。

リスト1 セミコロンだけの行があるぞ!
} else {
  try {
    //log.debug( "REMOVE_SESSION--->" + paramString);
    // その他操作の場合、Sessionに値があれば削除する
    this.session.remove(paramString);
  } catch (Exception e) { 
      ;   ←これ何?!
  }
}

つまり、セミコロンが挿入されることで「命令行が存在する=空ではない状態にする」という方法です。これも、

  • ^(\s+¦\t+);

といった正規表現で簡単に検出できます。

これは「悪質な手抜き」の1つです。意図的にツール指摘を回避することは、本来のツールの意味を無に帰すばかりでなく、後続工程や将来のデバッグを極めて困難にする可能性が高いからです図6⁠。

図6 ⁠例外の握りつぶし」または「怠慢」
図6 「例外の握りつぶし」または「怠慢」

筆者は、このキーワードが出たソースコードについて、全行をチェックします。ほかにも実装誤りやツール指摘回避が行われていないか徹底的に目視検査をします。

「はず/かも」チェック

これは「はず」⁠かも」というNGワードを、全コードに対してgrepコマンド実行をします。これら表現が含まれるコードは、必ず目視対象として重点検査します。とくに次の場合に有効です。

  • 期待値と異なる
  • 機能更改後
  • 保守後

原理は簡単です。ソースコードの特定のブロック文の中に「はず」⁠かも」という日本語キーワードを探すと、

  • 「ここには制御が到達しないはず」
  • 「今後変更されるかもしれない」
  • 「暫定対応」

といった開発者の「未確定」⁠自信がない」ことを示すコメントが出てきます。この前後コードは必ず確認しなくてはいけません(ヒドいものになると「苦肉の策」というコメントにも遭遇します⁠⁠。これらの記述に関しては、もちろん該当個所を詳細に分析し、暫定対応を恒久化する必要はありますが、それ以上に、当該「未確定」個所の混入理由を分析することで得た教訓を再発防止に使いましょう。

おわりに

コードが示す兆候の利用とその対策・効果は、必ずしもすべてのプロジェクト・プログラムに適用できるものではないかもしれません。しかし、本稿のアプローチで、完全網羅にこだわった人海戦術のムダなテスト実施を避けることができ、デバッグ速度を圧倒的に向上させることができます。

筆者は、さまざまな技法や手法を(工数をかけて)大量に実施し、網羅的にテストでバグを出す手法をあまり好みません。大量テストの実施が目的でない限り、テストケースは少ないに越したことはないはずです。国内市場は開発量が飽和点を迎え、産業としての保守・維持が主要課題になっています。トラブルを未然に防ぐ品質エンジニアの育成が急務です。全部テストしきれないほど複雑さが増加し、いわゆる「手が付けられない状態」を打破するためには、このようなメトリクスを駆使する「センス」を持った「品質エンジニア」が求められています。本連載を起点に目指してみませんか!

Software Design

本誌最新号をチェック!
Software Design 2022年9月号

2022年8月18日発売
B5判/192ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    MySQL アプリ開発者の必修5科目
    不意なトラブルに困らないためのRDB基礎知識
  • 第2特集
    「知りたい」⁠使いたい」⁠発信したい」をかなえる
    OSSソースコードリーディングのススメ
  • 特別企画
    企業のシステムを支えるOSとエコシステムの全貌
    [特別企画]Red Hat Enterprise Linux 9最新ガイド
  • 短期連載
    今さら聞けないSSH
    [前編]リモートログインとコマンドの実行
  • 短期連載
    MySQLで学ぶ文字コード
    [最終回]文字コードのハマりどころTips集
  • 短期連載
    新生「Ansible」徹底解説
    [4]Playbookの実行環境(基礎編)

おすすめ記事

記事・ニュース一覧