web-dev-qa-db-ja.com

条件文の条件の割り当ては悪い習慣ですか?

Cで2つの文字列を連結する関数を記述したいとします。それを記述する方法は次のとおりです。

void concat(char s[], char t[]){
    int i = 0;
    int j = 0;

    while (s[i] != '\0'){
        i++;
    }

    while (t[j] != '\0'){
        s[i] = t[j];
        i++;
        j++;
    }

    s[i] = '\0';

}

ただし、彼らの本の K&R は、特にwhileループの条件部分にできるだけ多くを含めて、異なる方法で実装しました。

void concat(char s[], char t[]){
    int i, j;
    i = j = 0;
    while (s[i] != '\0') i++;

    while ((s[i++]=t[j++]) != '\0');

}

どちらの方法が好ましいですか? K&Rのようにコードを記述することは推奨または推奨されませんか?私のバージョンは他の人にとって読みやすいと思います。

35
Richard Smith

賢さよりも常に明快さを優先してください。過去数年間、最良のプログラマーは誰も理解できないコードを持つプログラマーでした。 "私は彼のコードを理解できません。彼は天才でなければなりません"、と彼らは言った。今日、最高のプログラマーは誰でもコードを理解できる人です。コンピュータの時間は、プログラマの時間よりも安いです。

どんな愚か者も、コンピューターが理解できるコードを書くことができます。優れたプログラマーは、人間が理解できるコードを作成します。 (M.ファウラー)

ですから、間違いなく、オプションAを選びます。それが私の決定的な答えです。

79

黄金のルールは、TulainsCórdovaの答えと同じように、わかりやすいコードを必ず書くことです。しかし、私はその結論に同意しません。その黄金律とは、コードを保守することになる最終的なプログラマーが理解できるコードを書くことを意味します。そしてあなたは、あなたのコードを保守することになる最終的なプログラマーが誰であるかについての最高の裁判官です。

Cで始まらなかったプログラマーにとって、最初のバージョンは、すでに知っている理由から、おそらく理解しやすいでしょう。

そのスタイルのCで育った人にとっては、2番目のバージョンの方が理解しやすいかもしれません。彼らにとって、コードが何をするかについても同様に理解できるので、コードの記述方法についての質問が少なくなり、彼らにとっても、 、垂直方向のスペースが少ないということは、より多くのコンテキストを画面に表示できることを意味します。

あなたは自分の良識に頼らなければならないでしょう。コードを最も理解しやすいようにしたい視聴者は誰ですか?このコードは会社向けに書かれていますか?その場合、ターゲットユーザーはおそらくその会社の他のプログラマーでしょう。これは、あなただけが取り組む個人的な趣味のプロジェクトですか?次に、あなたはあなた自身のターゲットオーディエンスです。このコードを他の人と共有しますか?次に、それらの他の人があなたのターゲットオーディエンスです。そのオーディエンスに一致するバージョンを選択します。残念ながら、推奨する単一の推奨方法はありません。

32
hvd

編集:s[i] = '\0';が最初のバージョンに追加され、以下のバリアント1で説明されているように修正されたため、これは現在のバージョンの質問のコードには適用されなくなりました。

2番目のバージョンには正しいであるという明確な利点がありますが、最初のバージョンはそうではありません-ターゲット文字列をnullで終了しません。

「条件付き代入」により、「すべての文字をコピーbeforenull文字をチェックする」という概念を非常に簡潔かつ最適化して、コンパイラは多少簡単ですが、最近の多くのソフトウェアエンジニアは、このスタイルのコードを読みにくくしています。最初のバージョンの使用を主張する場合は、次のいずれかを行う必要があります。

  1. 2番目のループの終了後にnull終了を追加します(コードを追加しますが、可読性により価値があると主張できます)または
  2. ループ本体を「最初に割り当て、次に割り当てられた文字を確認または保存してから、インデックスを増分する」に変更します。ループの真ん中の状態をチェックすることは、ループから抜け出すことを意味します(ほとんどの純粋主義者が不快に思う、明瞭さを減らします)。割り当てられた文字を保存することは、一時的な変数を導入することを意味します(明快さと効率を低下させます)。それらの両方は私の意見で利点を全滅させるでしょう。
14
hjhill

TulainsCórdovaとhvdの回答は、明快さ/読みやすさの側面を非常によくカバーしています。条件での割り当てを支持するもう1つの理由として、scopingを投入します。条件で宣言された変数は、そのステートメントのスコープでのみ使用できます。後でその変数を誤って使用することはできません。 forループは古くからこれを行ってきました。そして、次のC++ 17 ifswitch に同様の構文を導入することは非常に重要です:

if (int foo = bar(); foo > 42) {
    do_stuff();
}

foo = 23;   // compiler error: foo is not in scope
5
besc

いいえ。これは非常に標準的で通常のCスタイルです。あなたの例は悪いものです、それはforループであるべきだからですが、一般的には何も問題はありません

if ((a = f()) != NULL)
    ...

たとえば(またはwhileで)。

3
Miles Rout

どちらのスタイルも適切に形成され、正しく、適切です。どちらがより適切かは、会社のスタイルガイドラインに大きく依存します。最新のIDEでは、他の方法では混乱の原因となる可能性のある領域を明示的に強調するライブ構文リンティングを使用して、両方のスタイルの使用を容易にします。

たとえば、次の式はNetbeansで強調表示されています。

if($a = someFunction())

「偶然の割り当て」を理由に。

enter image description here

「ええ、私は本当にそうするつもりでした...」とNetbeansに明示的に伝えるには、式を括弧のセットで囲むことができます。

if(($a = someFunction()))

enter image description here

結局のところ、それはすべて、企業スタイルのガイドラインと、開発プロセスを容易にする最新のツールの可用性にまで及びます。

2
Luke A. Leber

K&R時代

  • 「C」は移植可能なアセンブリコードでした
  • アセンブリコードで考えるプログラマによって使用されました
  • コンパイラは最適化をあまり行いませんでした
  • ほとんどのコンピューターには「複雑な命令セット」があり、たとえば、while ((s[i++]=t[j++]) != '\0')はほとんどのCPUで1つの命令にマップされます(12月のVACを期待しています)

数日あります

  • Cコードを読むほとんどの人はアセンブリコードプログラマではありません
  • Cコンパイラーは多くの最適化を行うため、コードを読み取るのが簡単な場合は、同じマシンコードに変換される可能性があります。

(常に中かっこを使用する場合の注意–最初のコードセットは、「不要な」{}、私の経験では、これらは多くの場合、コンパイラから不適切にマージされたコードを防ぎ、誤った「;」のエラーを許可しますツールによって検出される配置)

ただし、昔はコードの2番目のバージョンが読み取られていました。 (うまくいけば!)

concat(char* s, char *t){      
    while (*s++);
    --s;
    while (*s++=*t++);
}
2
Ian

これを行うことができるとしても、非常に悪い考えです。これは口語的に「世界の最後のバグ」として知られています。

if (alert = CODE_RED)
{
   launch_nukes();
}

かなりほどの過ちを犯す可能性は低いですが、誤って失敗してコードベースで見つけにくいエラーを引き起こすことは非常に簡単です。最新のコンパイラのほとんどは、条件付きの代入に対して警告を挿入します。それらは理由のためにそこにあります、そしてあなたはそれらに耳を傾け、この構造を避けるためにうまくやるでしょう。

2
Mason Wheeler