条件の割り当ては条件の一部ですか?


35

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のようにコードを書くことは奨励されていますか?私のバージョンは他の人にとって読みやすいと思います。


38
K&Rは1978年に最初に公開されたことを忘れないでください。それ以来、コーディング方法にいくつかの小さな変更がありました。
corsiKa

28
読みやすさは、テレプリンターや行指向のエディターの時代には非常に異なっていました。以前は読みやすくするために、すべてのものを1行につぶしていました。
user2357112は、Monicaを

15
私は、彼らが「\ 0」の代わりのようなものにインデックスとの比較を持っていることをショックを受けていますwhile (*s++ = *t++); K&Rは、その著書の新バージョンをリリースしました(私のCは非常にさびている、私は演算子の優先順位のためにそこに括弧が必要なのですか?)?彼らのオリジナルの本には、非常に簡潔で慣用的なコードがありました。
user949300

4
可読性は非常に個人的なものです-テレタイプの時代でさえ。異なる人は異なるスタイルを好みます。命令の詰め込みの多くは、コード生成に関係していました。当時、いくつかの命令セット(Data Generalなど)は、複数の操作を1つの命令に詰め込むことができました。また、80年代初期には、括弧を使用するとより多くの命令が生成されるという神話がありました。それが神話であることをコードレビューアに証明するために、アセンブラを生成する必要がありました。
カップ

10
2つのコードブロックは同等ではないことに注意してください。最初のコードブロックは、終了元'\0'をコピーしませんtwhile最初に出口があります)。これにより、結果のs文字列は終了せずに残ります'\0'(メモリ位置が既にゼロに設定されていない場合)。2番目のコードブロックは'\0'whileループを終了する前に終了のコピーを作成します。
Makyen

回答:


80

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

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

だから、間違いなく、オプションAに行くと思います。そして、それが私の決定的な答えです。


8
非常に簡潔なイデオロギーですが、条件の割り当てには何の問題もないという事実が残っています。ループを早期に終了するか、ループの前後でコードを複製することをお勧めします。
マイルルーティング

26
@MilesRoutがあります。予期しない副作用、つまり関数の引数の受け渡しや条件の評価など、コードに何らかの問題があります。それif (a=b)を簡単に間違えられることさえ言及していませんif (a==b)
アーサーHavlicek

12
@Luke:「私のIDEはXをクリーンアップできるので、それは問題ではありません」は納得できません。それが問題でない場合、IDEが「修正」を非常に簡単にするのはなぜですか?
ケビン

6
@ArthurHavlicek私はあなたの一般的な点に同意しますが、条件付きで副作用を伴うコードは本当に珍しいことではありません:while ((c = fgetc(file)) != EOF)私の頭に浮かぶ最初のように。
ダニエルジュール

3
+1「最初にプログラムを作成するよりもデバッグが2倍難しいと考えると、それを作成するときにできる限り賢くすれば、どのようにデバッグできますか?」BWKernighan-
クリストフ

32

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

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

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

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


14

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

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

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

  1. 2番目のループの終了後にヌル終端を追加します(コードを追加しますが、readabiliyは価値があると主張できます)または
  2. ループ本体を「最初に割り当て、次に割り当てられた文字を確認または保存してから、インデックスをインクリメントする」に変更します。ループの途中で状態を確認するということは、ループから抜け出すことを意味します(ほとんどの純粋主義者が眉をひそめ、明瞭さを低下させます)。割り当てられた文字を保存することは、一時変数を導入することを意味します(明確さと効率を低下させます)。どちらも私の意見では利点を全滅させるだろう。

正解は、読みやすく簡潔であるよりも優れています。
user949300

5

TulainsCórdovaとhvdの回答は、明快さ/読みやすさの側面を非常によくカバーしています。条件での割り当てを支持する別の理由として、スコーピングをスローします。条件で宣言された変数は、そのステートメントのスコープでのみ使用可能です。後で誤ってその変数を使用することはできません。以下のためのループは、年齢のためにこれをやってきました。そして、次のC ++ 17 ifおよび switchに

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

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

3

いいえ。非常に標準的で通常のCスタイルです。あなたの例は悪い例です、なぜならそれはただforループでなければならないからです、しかし一般に何も悪いことはありません

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

たとえば(またはwhileを使用)。


7
それに何か問題があります。`!= NULL`とC条件付きの同類は、値がtrueまたはfalse(またはその逆)であるという概念に慣れていない開発者をなだめるためだけにあります。
ジョナサンキャスト

1
@jcastいいえ、含める方がより明示的!= NULLです。
マイルルーティング

1
いいえ、と言う方がより明確(x != NULL) != 0です。結局のところ、それはCが本当にチェックしていることですよね?
ジョナサンキャスト

@jcastいいえ、そうではありません。何かが偽と等しくないかどうかをチェックすることは、どの言語で条件文を書くかではありません。
マイルルーティング

「何かが偽ではないかどうかをチェックすることは、どの言語で条件文を書くかではありません。」まさに。
ジョナサンキャスト

2

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

これを行うことができても、非常に悪い考えです。俗称では「The World's Last Bug」として知られています。

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

あなたは可能性があります間違い作ることじゃないものの、かなりその厳しいが誤って台無しにし、あなたのコードベースでは見つけにくいエラーを引き起こすために、それは非常に簡単です。ほとんどの最新のコンパイラは、条件内の割り当てに警告を挿入します。彼らはそこにいるので、あなたは彼らに気をつけて、この構造を避けてください。


これらの警告の前にCODE_RED = alert、コンパイラエラーが発生するように記述しました。
イアン

4
呼び出される@Ian Yoda Conditionals。読みにくいです。それらの必要性は残念です。
メイソンウィーラー

非常に短い導入「慣れ」期間の後、ヨーダの状態は通常の状態よりも読みにくいです。時にはそれらはより読みやすいです。たとえば、if / elseifのシーケンスがある場合、左側でテスト対象の条件を強調することで、IMOがわずかに改善されます。
user949300

2
@ user949300 2つの単語:ストックホルム症候群:P
メイソンウィーラー

2

どちらのスタイルも整形式で、正しく、適切です。どっちが適切かかは、主に会社のスタイルガイドラインによって決まります。最新のIDEでは、混乱の原因となる可能性のある領域を明示的に強調表示するライブシンタックスリンティングを使用して、両方のスタイルの使用を容易にします。

たとえば、次の式はNetbeansによって強調表示されます

if($a = someFunction())

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

ここに画像の説明を入力してください

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

if(($a = someFunction()))

ここに画像の説明を入力してください

結局のところ、それはすべて、企業スタイルのガイドラインと、開発プロセスを促進するための最新のツールの可用性に要約されています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.