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