エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

「意図どおりに動く」では半分

現在のプロジェクトのソースコードを静的解析ツールにかけた結果がレポートされたので、昨日はその内容を見ていました。
ツールが警告を出した場合、警告が出た箇所にどのように対処するかを報告しないといけないのですが、その報告の中に「現状とする/意図どおり」というのがあり、気になったのでそのコードを見てみると。

int sample_code_A(int n)
{
    if(unsigned(n) > 255)
    {
        n = n < 0 ? 0 : 255;
    }
    return n;
}

(関数名とか元のコードから一部変えてあります)


ちょっと考えて。引数の値が0未満なら0を255より大きければ255をそうでなければ引数の値をそのままの返す関数だと理解。
意図どおりかもしれないけれど、うまいコードとも言えず。うまいか否かはおいておくとしても、とてもよいコードとは言えず。

プログラムは意図どおりに動くようにコードを書くのはもちろんですが、加えて読み手に意図が伝わるように書かなければいけないわけで。


正直、これで充分でしょう。

int sample_code_B(const int n)
{
    if     (n < 0)   return 0;
    else if(n < 255) return n;
    else             return 255;
}


あるいは三項演算子を使って。

int sample_code_C(int n)
{
    return n < 0 ? 0 : n < 255 ? n : 255;
}


こころみに。これをコンパイルするとどういうアセンブリコードになるかも確かめてみました。
Mac OS X 10.5 (PowerPC)上でg++でg++-4.2 -ansi -Wall -arch x86_64 -O2 -Sというオプションでそれぞれコンパイルしてみました*1

結果。

__Z13sample_code_Ai:
    pushq   %rbp
    movq    %rsp, %rbp
    cmpl    $255, %edi
    jbe L8
    sarl    $31, %edi
    notl    %edi
    andl    $255, %edi
L8:
    movl    %edi, %eax
    leave
    ret
__Z13sample_code_Bi:
    pushq   %rbp
    movq    %rsp, %rbp
    testl   %edi, %edi
    js  L20
    cmpl    $255, %edi
    movl    $255, %eax
    cmovge  %eax, %edi
L17:
    movl    %edi, %eax
    leave
    ret
L20:
    xorl    %edi, %edi
    jmp L17
__Z13sample_code_Ci:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $255, %edx
    cmpl    $255, %edi
    cmovle  %edi, %edx
    xorl    %eax, %eax
    testl   %edx, %edx
    cmovns  %edx, %eax
    leave
    ret


わたしのアセンブラの知識はせいぜいi386どまりなので、きちんとした判断は下せませんが、このサイズですしどれも同じようなものでしょう。ジャンプがない分sample_code_Cが多少効率的かもしれません。それでも些細な差だと思いますが。


効率的だ、とか、うまいこと書いた、とか思っても、実はそうでもないわけで。それよりも可読性が落ちる分、最初のコードはトータルでは「非効率なコード」でしょう。


意図どおりに動くのは必達。でもそれだけでは半分。読んでわかるようにすることも重要。とても重要。
…という思いをあらためて感じた一件。



余談。
どうせやるならこれぐらいやってみようぜ、と書いたコード。

int sample_code_D(int n)
{
    static const int BitsPerChar = 8;

    return (n & ~255) ? ((~n >> (sizeof(int) * BitsPerChar - 1)) & 0xff) : n;
}


上と同じ条件でコンパイルしたらsample_code_Cより長くなった…orz。

__Z13sample_code_Di:
    pushq   %rbp
    movq    %rsp, %rbp
    testl   $-256, %edi
    je  L24
    movl    %edi, %eax
    notl    %eax
    sarl    $31, %eax
    movzbl  %al,%edi
L24:
    movl    %edi, %eax
    leave
    ret

*1:最初、PowerPCアーキテクチャコンパイルしてみたんですが、わたしはPowerPCアセンブリコードが読めなかった…orz。