プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 14

その他インテル® DPC++/C++ コンパイラー特集

この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。


14. 優れたコンパイラーとコーディング手法でも対応できない場合がある

これまで良いコーディング手法について述べてきましたが、ここでは反例を示します。良いコードを記述するだけでは十分ではありません。エラーにはさまざまなものがあり、良いプログラミング・スタイルが必ずしもすべてのエラーに対応できるわけではないからです。

PostgreSQL* プロジェクトから抜粋した以下のコードについて考えてみます。このエラーは、次の PVS-Studio 診断によって検出されます。

V575 The ‘memcmp’ function processes ‘0’ elements. Inspect the third argument. (‘memcmp’ 関数は ‘0’ 要素を処理しています。第 3 引数を確認してください。)

Cppcheck (英語) アナライザーもこのようなエラーを検出して、次の警告を出力します。

Invalid memcmp() argument nr 3. A non-boolean value is required. (無効な memcmp() 引数 nr 3。非ブール値が必要です。)

Datum pg_stat_get_activity(PG_FUNCTION_ARGS)
{
  ....
  if (memcmp(&(beentry->st_clientaddr), &zero_clientaddr,
             sizeof(zero_clientaddr) == 0))
  ....
}

説明

右括弧の位置が正しくありません。単純なタイプミスですが、残念なことにコードの意味が全く違うものになってしまいます。

オブジェクトのサイズは常に 0 よりも大きいため、sizeof(zero_clientaddr) == 0 は常に ‘false’ になります。false は 0 に変換されるため、memcmp() 関数は 0 バイトと比較し、2 つの配列は等しいと仮定して 0 を返します。つまり、このコード例は if (false) と同等です。

正しいコード

if (memcmp(&(beentry->st_clientaddr), &zero_clientaddr,
           sizeof(zero_clientaddr)) == 0)

推奨事項

このようなタイプミスを防ぐ安全なコーディング手法はありません。唯一考えられるのは、比較演算子の左側に定数を記述する、ヨーダ記法です。

if (0 == memcmp(&(beentry->st_clientaddr), &zero_clientaddr,
                sizeof(zero_clientaddr)))

ただし、このスタイルは推奨しません。私は 2 つの理由からこのスタイルが好きではなく、使用していません。

1 つ目は、条件が目立たなくなるからです。正確な由来は知りませんが、このスタイルが「ヨーダ」と呼ばれているのには理由があると思います。

2 つ目は、不正な位置にある括弧には役立たないからです。ミスはさまざまな方法で起こります。以下は、ヨーダ記法では防ぐことができない不正な位置にある括弧のコード例です。

if (0 == LoadStringW(hDllInstance, IDS_UNKNOWN_ERROR,
        UnknownError,
        sizeof(UnknownError) / sizeof(UnknownError[0] -
        20)))

これは、ReactOS* プロジェクトから抜粋したコードです。分かりづらいですが、sizeof(UnknownError[0] – 20) に問題があります。

そのため、ヨーダ記法はここでは役立ちません。

すべての右括弧が左括弧の下にくるように記述することもできます。しかし、コード行が増え、美しくありません。誰もそのような書き方はしたくないでしょう。

そのため、繰り返しになりますが、右括弧を不正な位置に記述するのを防ぐコーディング手法はありません。

このような場合にこそ、コンパイラーは奇妙な構造を検出し、警告を出力すべきです。本来そうあるべきですが、実際にはそうではありません。Visual Studio* 2015 で /Wall オプションを指定しても、警告は出力されません。しかし、コンパイラーを責めることはできません。コンパイラーには、すでに十分な仕事があります。

ここで引き出すことができる最も重要な結論は、優れたコーディング手法とコンパイラー (私は VS2015 のコンパイラーを愛用しています) であっても、必ずしもエラーに対応できるわけではないということです。「コンパイラーの警告レベルを最高に設定し、良いコーディング手法を使用するだけで、すべてがうまくいく」というような発言を聞くことがありますが、そうとも限りません。プログラマーのコーディング手法が悪いのではなく、誰でもミスをします。例外なく、誰でもです。多くのタイプミスは、コンパイラーやコーディング手法の目をくぐり抜けます。

良いコーディング手法とコンパイラーによる警告は重要ですが、十分ではありません。そのため、さまざまな方法で問題を検出する必要があります。特効薬はありません。いくつかの手法を組み合わせることでのみ高品質のコードが得られます。

ここで述べたエラーは、次の方法で見つけることができます。

  • コードレビュー
  • ユニットテスト
  • 手動テスト
  • 静的コード解析
  • その他

皆さんは、私が静的コード解析に最も関心を持っていることをすでに推察されているでしょう。静的コード解析は、エラーを最も早い段階 (つまり、コードを記述した直後) に検出できるため、この特定の問題を解決するのに最適な手法です。

実際、Cppcheck や PVS-Studio などのツールを使用することで、このエラーを簡単に見つけることができます。

結論: スキルがあってもミスを防げないことを認めない方がいますが、誰でもミスをします。それは避けられないことです。カリスマ・プログラマーでさえミスをすることがあります。ミスは避けられないため、プログラマー、コンパイラー、コーディング手法を責めるべきではありません。それで状況が良くなることはありません。代わりに、ソフトウェア品質を向上するさまざまな手法を使用すべきです。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

タイトルとURLをコピーしました