「if elif else」ステートメントが事実上テーブル形式ではないのはなぜですか?


73
if   i>0 : return sqrt(i)  
elif i==0: return 0  
else     : return 1j * sqrt(-i)

VS

if i>0:  
   return sqrt(i)  
elif i==0:  
   return 0  
else:  
   return 1j * sqrt(-i)  

上記の例を考えると、コードベースに最初のスタイルがほとんど見られない理由がわかりません。私には、コードを表形式に変換して、必要なものを明確に示します。最初の列は事実上無視できます。2列目は状態を示し、3列目は必要な出力を示します。少なくとも私には、簡単で読みやすいようです。それでも、この単純なケース/スイッチの状況は、拡張されたタブインデント形式で常に表示されます。何故ですか?人々は2番目の形式をより読みやすいと感じていますか?

これが問題になる唯一のケースは、コードが変更されて長くなる場合です。その場合、コードを長いインデントされた形式にリファクタリングすることは完全に合理的だと思います。いつもそれがいつも行われていた方法だからという理由だけで、誰もが2番目の方法でそれをしますか?悪魔の擁護者である私は、if / elseステートメントの複雑さに応じて2つの異なる形式を見つけて混乱する別の理由があると思いますか?どんな洞察もいただければ幸いです。


91
人々は2番目のオプションがより読みやすいと思うのですか?
GrandmasterB

65
すべてが同じ型の値を返す相互に排他的なブランチのユースケースは、値を返さない可能性のあるブランチと比較して命令型言語ではあまり登場しません。関数型プログラミング言語を見ると、最初の例によく似たコードが頻繁に表示されます。
ドーバル

47
@horta「これが問題となる唯一のケースは、コードが変更されて長くなることです。」- コードの一部が変更されないことを決して想定しないでください。コードのリファクタリングは、ソフトウェアのライフサイクルの大部分を占めます。
チャールズアディス

7
@horta:私とは何の関係もありません。それはコードです。私が読んでいるコードベースのコンテキストでは、文(および他の言語構成体)が一貫してフォーマットされているかどうか、エッジケースなしで確認したいです。特定の方法でコードを読むことを学んだということではなく、問題なく読むことができますが、すべてが同じであれば読みやすくなります。繰り返しますが、私と同じではなく、残りのコードも同じです。
-GManNickG

44
また、ほとんどのデバッガーは行ベースです。if同じ行にあるif の中にあるステートメントにブレークポイントを置くことはできません。
-isanae

回答:


93

理由の1つは、人気のある言語を使用していないことです。

いくつかの反例:

ガードとパターン付きのHaskell:

sign x |  x >  0        =   1
       |  x == 0        =   0
       |  x <  0        =  -1

take  0     _           =  []
take  _     []          =  []
take  n     (x:xs)      =  x : take (n-1) xs

パターン付きのアーラン:

insert(X,Set) ->
    case lists:member(X,Set) of
        true  -> Set;
        false -> [X|Set]
    end.

Emacs lisp:

(pcase (get-return-code x)
  (`success       (message "Done!"))
  (`would-block   (message "Sorry, can't do it now"))
  (`read-only     (message "The shmliblick is read-only"))
  (`access-denied (message "You do not have the needed rights"))
  (code           (message "Unknown return code %S" code)))

一般に、テーブル形式は関数型言語(および一般的な式ベースの言語)で非常に人気がありますが、行を分割することは他の言語(主にステートメントベース)で最も人気があります。


10
私はこれを支持し、一般的に同意しますが、1。これらはすべて些細なリターンです2. Haskellersは、言語自体の簡潔さに加えて、コミカルに短い識別子が好きです。 、さらに命令型言語でさえ、ステートメントとは異なる式の標準を持っています。OPは、機能アプリケーションとして元の例を書き直した場合、彼は...異なるアドバイスを得るかもしれない
ジャレッド・スミス

3
@JaredSmith表現/文ベースの分割に感謝します-機能的/命令的というよりもさらに適切かもしれません。繰り返しになりますが、ルビーはほとんど表現ベースであり、その規則を頻繁には使用しません。(すべての例外)ポイント1と2については、実際のHaskellコードの50%以上が「些細な戻り値」であり、これは単に大きなコードの一部にすぎません。たとえば、ここの機能の半分に近いものは、1つまたは2つのライナーです。(一部の行はテーブルレイアウトを使用)
viraptor

はい。私はHaskellerではありませんが、いくつかのocamlを実行し、パターンマッチングは論理的に同等のスイッチよりもはるかに簡潔になる傾向があり、ポリモーフィック関数はとにかくその多くをカバーしています。Haskellの型クラスは、そのカバレッジをさらに拡大すると思います。
ジャレッド・スミス

それを促進するのはパターンケース構文だと思います。簡潔で、通常は短いスイッチケースに近いため、ワンライナーとして表現する方が簡単です。同様の理由で、短いswitch-caseステートメントでもこれを頻繁に行います。if-elseただし、事実上、単純な3項ではない場合でも、リテラルステートメントは通常、複数の行に分散されます。
イサイアメドウズ

@viraptorすべてのhaskell関数は機能的に純粋で副作用がないため、技術的にはhaskellコードの他の50%-は「非自明な戻り値」です。コマンドラインから読み取り、コマンドラインに印刷する関数でさえ、長いreturnステートメントです。
ファラプ

134

より読みやすくなりました。いくつかの理由:

  • ほとんどすべての言語がこの構文を使用しています(すべてではなく、ほとんど -あなたの例はPythonのようです)
  • isanaeは、ほとんどのデバッガーは行ベース(ステートメントベースではない)であるというコメント指摘しました。
  • セミコロンまたはブレースをインライン化する必要がある場合、さらに見苦しくなります
  • 上から下までよりスムーズに読み取ります
  • 些細なreturnステートメント以外のものがある場合、恐ろしく読めないように見えます
    • 条件付きコードが視覚的に分離されなくなったため、コードをスキムすると、意味のあるインデント構文が失われます(Dan Neelyから)
    • これは、1行のifステートメントにアイテムをパッチ/追加し続けると特に悪いでしょう
  • すべてのifチェックがほぼ同じ長さの場合にのみ読み取り可能です
  • つまり、複雑なifステートメントを複数行のステートメントにフォーマットすることはできません。それらはonelinersでなければなりません
  • 複数行を一緒に解析しようとせずに、行ごとに垂直に読み取るときにバグ/ logicflowに気付く可能性がはるかに高くなります
  • 私たちの脳は、長くて水平なテキストよりもはるかに速く、狭くて背の高いテキストを読む

このいずれかを実行しようとすると、最終的に複数行のステートメントに書き換えられます。つまり、時間を無駄にしているだけです!

また、人々は必然的に次のようなものを追加します:

if i>0:  
   print('foobar')
   return sqrt(i)  
elif i==0:  
   return 0  
else:  
   return 1j * sqrt(-i)  

この形式が他の形式よりも優れていると判断するまで、これを頻繁に行う必要はありません。ああ、しかし、あなたはそれをすべて1行でインライン化できます! エンダーランドは内側で死ぬ

またはこれ:

if   i>0 : return sqrt(i)  
elif i==0 and bar==0: return 0  
else     : return 1j * sqrt(-i)

これは本当に、本当に迷惑です。このようなフォーマットを好む人はいません。

そして最後に、「タブ用のスペースの数」問題の聖戦を開始します。テーブル形式として画面上で完全にレンダリングされるものは、設定によっては私の画面上でレンダリングされない場合があります。

とにかく読みやすさはIDE設定に依存すべきではありません。


14
@horta は、最初に最初の方法でフォーマットした場合、変換する必要があるためですか?私はすべて、将来のエンダーランドの仕事を最小限にすることです。チェックがまったく楽しくない場合(読みやすさへの影響を無視して)にロジックを追加するときに、かなり空白のテーブルを作成し、スペースとタブをカウントして視覚的な書式を更新します。
エンダーランド

14
@horta彼は必ずしも修正しないと言っているわけではなく、修正する必要があると言っているので、プログラミングではなく面倒なフォーマットに多くの時間を費やしています。
セルビー

11
@horta:あなたのやり方は読みにくいことが多く、フォーマットするのは間違いなく面倒です。また、「if」が小さいライナーの場合にのみ使用できますが、これはめったにありません。最後に、このために2種類の「if」フォーマットを持ち歩くことは、どういうわけか良くありません。
-dagnelies

9
@hortaは、要件、システム、API、およびユーザーのニーズが変わらないシステムでの作業に恵まれているようです。あなたは幸運な魂。
エンダーランド

11
私は追加します:単一の条件の小さな変更は、:位置を一致させるために他を再フォーマットする必要があるかもしれません-> CVSでdiffを行うと、実際に何が変わっているのかを理解するのが突然難しくなります。これは、状態対身体にも当てはまります。それらを別々の行に置くことは、そのうちの1つだけを変更すると、差分ではなく、本体ではなく状態のみが変更されたことを明確に示すことを意味します。
バクリウ

55

私は「コードは何度も読まれ、書かれた回数は少ないので、読みやすさが非常に重要です」と固く信じています。

他の人のコードを読むときに役立つ重要なことは、私の目が認識するように訓練されている「通常の」パターンに従うことです。インデントされたフォームを最も簡単に読むことができるのは、何度も見たことがあるため、ほとんど自動的に登録されるからです(私の側ではほとんど認知的努力は必要ありません)。それは「きれい」だからではなく、私が慣れている慣習に従っているからです。コンベンションは「より良い」ビート...



11
これはなぜ人々が保守的であるかを説明しています。そもそも人々が特定の方法でコードを書くことを選んだ理由は説明されていません。
ヨルゲンフォグ

8
問題は「なぜこれをよく見ますか」であり、このスタイルはどこから来たのかではありません。どちらの質問も興味深いものです。私は尋ねられていると思ったものに答えようとしました。
アートSwri 16

16

すでに述べた他の欠点に加えて、表形式のレイアウトは、バージョン管理マージの競合の確率を高め、手動の介入が必要です。

表形式で整列されたコードのブロックを再配置する必要がある場合、バージョン管理システムはこれらの各行が変更されたものとして扱います。

diff --git a/foo.rb b/foo.rb
index 40f7833..694d8fe 100644
--- a/foo.rb
+++ b/foo.rb
@@ -1,8 +1,8 @@
 class Foo

   def initialize(options)
-    @cached_metadata = options[:metadata]
-    @logger          = options[:logger]
+    @metadata = options[:metadata]
+    @logger   = options[:logger]
   end

 end

ここで、別のブランチで、プログラマーが整列されたコードのブロックに新しい行を追加したとします。

diff --git a/foo.rb b/foo.rb
index 40f7833..86648cb 100644
--- a/foo.rb
+++ b/foo.rb
@@ -3,6 +3,7 @@ class Foo
   def initialize(options)
     @cached_metadata = options[:metadata]
     @logger          = options[:logger]
+    @kittens         = options[:kittens]
   end

 end

そのブランチのマージは失敗します:

wayne@mercury:/tmp/foo$ git merge add_kittens
Auto-merging foo.rb
CONFLICT (content): Merge conflict in foo.rb
Automatic merge failed; fix conflicts and then commit the result.

変更中のコードが表の配置を使用していなかった場合、マージは自動的に成功していました。

(この答えは、コード内の表形式の整列を思いとどまらせる私自身の記事から「盗作された」)。


1
興味深いですが、これはマージツールの失敗ではありませんか?この場合、具体的にはgitですか?これは、簡単な方法である慣習に対するデータポイントです。私にとっては、(ツール側から)改善できるものです。
オルタ

7
@hortaマージツールがコードをほとんど常に壊さない方法で空白を変更するには、コードの意味を変更せずに空白を変更できる方法を理解する必要があります。また、使用されている特定の表配列を理解する必要があります。それは言語に依存するだけでなく(Python!)、コードをある程度理解するにはツールが必要になるでしょう。一方、行ベースのマージはAIなしで実行でき、コードを壊すことさえありません。
ウェインコンラッド

わかった。したがって、コメントの別の場所で述べたように、テーブルをフォーマットに直接組み込むIDEまたはプログラミング入力フォーマットができるまで、ツールの問題は常に存在し、テーブルを好む人にとっては人生を困難にします。
オルタ

1
@horta正解。コード内の表形式の配置に対する私の反対のほとんどは、十分に高度なツールでなくなる可能性があります。
ウェインコンラッド

8

常に割り当てられた幅に収まる場合、表形式は非常に便利です。ただし、割り当てられた幅を超えるものがある場合、テーブルの一部が残りの部分と並んでいないか、テーブルの他のすべてのレイアウトを調整して長いアイテムに合うようにすることがしばしば必要になります。

表形式のデータを操作するように設計されたプログラムを使用してソースファイルを編集し、より小さいフォントサイズを使用して同じセル内で2行に分割するなど、過度に長いアイテムを処理できる場合、表形式を使用するのが理にかなっているより頻繁にフォーマットしますが、ほとんどのコンパイラーは、そのようなエディターがフォーマットを維持するために保存する必要がある種類のマークアップのないソースファイルを望んでいます。さまざまな量のインデントを使用し、他のレイアウトを持たない行を使用するのは、最良の場合は表形式よりも劣りますが、最悪の場合はほとんど問題が発生しません。


本当です。私が使用しているテキストエディタ(vim)が、表形式またはワイドテキストでさえひどいサポートを持っていることに気付きました。他のテキストエディターの方がはるかに優れていることはありません。
オルタ

6

この種のことを特別な場合に提供する「switch」ステートメントがありますが、それはあなたが尋ねているものではないと思います。

テーブル形式のifステートメントを見てきましたが、それを価値のあるものにするためには多くの条件が必要です。3ステートメントが従来の形式で最適に表示される場合、20がある場合は、より明確にするためにフォーマットされた大きなブロックに表示する方がはるかに簡単です。

そして、ポイントがあります:明快さ。見やすいようになっている場合(および最初の例では:区切り文字がどこにあるかを簡単に確認できない場合)、状況に合わせてフォーマットします。それ以外の場合は、常に認識しやすいため、人々が期待するものに固執します。


1
OPはPythonを使用しているように見えるため、no switchです。
ジャレッド・スミス

2
「文が従来の形式で最もよく表示される場合、ただし20の場合は...」と考えると、より大きな問題が考えられます。:))
グリムオピナー

@GrimmTheOpiner言語パーサーまたはAST文字列化ツールを作成している場合、それは非常に可能性のある対処方法です。たとえば、JavaScriptパーサーに貢献したことがあります。そこでは、式のタイプごとに1つずつ、15〜20のケースで関数を分割しました。私はほとんどのケースを独自の機能に分割しました(顕著なパフォーマンスの向上のため)が、長いことswitchは必要でした。
イサイアメドウズ

@JaredSmith:明らかswitchに悪いことですが、ささいな分岐を行うために辞書をインスタンス化してからルックアップを実行することは悪いことではありません...
Mark K Cowan

1
@MarkKCowanああ、私は皮肉をキャッチしたが、あなたはそれを私をm笑するために使用していると思った。インターネットの文脈の欠如など。
ジャレッドスミス

1

あなたの表現が本当に簡単なら、ほとんどのプログラミング言語は?:分岐演算子を提供します:

return  ( i > 0  ) ? sqrt( i)
      : ( i == 0 ) ? 0
        /* else */ : 1j * sqrt( -i )

これは短い読みやすい表形式です。しかし、重要な部分は次のとおりです。「主要な」アクションが何であるか一目でわかります。これはreturnステートメントです!また、値は特定の条件によって決定されます。

一方、異なるコードを実行するブランチがある場合、これらのブロックをインデントする方がはるかに読みやすいと思います。これは、ifステートメントに応じて異なる「メジャー」アクションがあるためです。ある場合にはスローし、ある場合にはログに記録して戻るか、単に戻るだけです。ロジックに応じて異なるプログラムフローが存在するため、コードブロックはさまざまなブランチをカプセル化し、開発者にとってより目立つようにします(たとえば、プログラムフローを把握するための関数の高速読み取り)

if ( i > 0 )
{
    throw new InvalidInputException(...);
}
else if ( i == 0 )
{
    return 0;
}
else
{
    log( "Calculating sqrt" );
    return sqrt( -i );
}

7
実際、私はあなたの「短い読みやすい表形式」を読むのは非常に悪夢だと思いますが、OPによって提案された形式は完全に素晴らしいです。
マッテオイタリア

@MatteoItaliaこの編集版はどうですか?
ファルコ

5
申し訳ありませんが、さらに悪いことです。?および:は、if/ elseキーワードよりも見つけにくい、および/または追加されたシンボルの「ノイズ」のために、それが原因だと思います。
マッテオイタリア

@MatteoItalia:100を超える異なる値を持つケースがありました。テーブル値により、エラーをチェックできます。マルチラインでは、不可能です。
gnasher729

1
100 (もちろん、言語の制限がここに適用される場合があります)。アイテムに「計算」アスペクトが必要な場合、アイテム構造には必要なアクションを実行するための関数へのポインターまたは参照を含めることができます。多くのアプリケーションでは、これによりコードが大幅に簡素化され、メンテナンスがはるかに簡単になります。
マイケルカラス

1

すでにエンダーランドが言っているように、アクションとして「リターン」は1つしかなく、その「リターン」を条件の最後にタグ付けできると仮定しています。これがうまくいかない理由について、もう少し詳しく説明したいと思います。

私はあなたの好みの言語が何なのか分かりませんが、私は長い間Cでコーディングしています。初期コーディング中または後のメンテナンス中にエラーが発生しやすいコード構築を禁止することにより、いくつかの標準コーディングエラーを回避することを目的とした多くのコーディング標準があります。私はMISRA-Cに最も精通していますが、他にもあります。一般に、同じ言語で同じ問題に対処しているため、すべて同じようなルールを持っています。

コーディング標準がしばしば対処する一般的なエラーの1つは、この小さな落とし穴です。

if (x == 10)
    do_something();
    do_something_else();

これはあなたが思っていることをしません。xが10である場合に限りCに関しては、あなたは呼び出しdo_something()たが、その後do_something_else()呼び出さかかわらず、xの値の。「if」ステートメントの直後のアクションのみが条件付きです。これはコーダーが意図したものかもしれません。その場合、メンテナーにとって潜在的なpotentialがあります。または、コーダーが意図したものではない可能性があります。その場合、バグがあります。これは人気のあるインタビューの質問です。

コーディング標準の解決策は、単一行であっても、すべての条件付きアクションを中括弧で囲むことです。今すぐ

if (x == 10)
{
    do_something();
    do_something_else();
}

または

if (x == 10)
{
    do_something();
}
do_something_else();

そして今では正しく動作し、メンテナーにとって明らかです。

これは、テーブルスタイルの形式とは完全に互換性がないことに気付くでしょう。

他の言語(Pythonなど)はこの問題を見て、コーダーがレイアウトを明確にするために空白を使用しているため、ブレースの代わりに空白を使用することをお勧めします。だからPythonでは、

if x == 10:
    do_something()
    do_something_else()

両方の呼び出しを行い、x == 10 do_something()do_something_else()条件としますが、

if x == 10:
    do_something()
do_something_else()

は、do_something()x のみを条件とし、do_something_else()常に呼び出されることを意味します。

これは有効な概念であり、いくつかの言語で使用されています。(最初にOccam2で見たのは昔のことです。)でも、テーブルスタイルの形式が言語と互換性がないことは簡単にわかります。


1
ポイントを逃したと思います。あなたが話す問題は、あなたが話す問題を引き起こす奇妙なC特有の非標準の悪夢です。Cでコーディングする場合、私が提案した表形式で代替の単純なifメソッドを使用することはお勧めしません。代わりに、Cを使用しているため、中括弧をすべて1行で使用します。中括弧は、区切り文字として機能しているため、実際にはテーブル形式をさらに明確にします。
オルタ

また、この場合のreturnステートメントは単なる例です。一般的に、それ自体がコードの匂いかもしれません。私は単純なステートメントの形式に言及しているだけであり、必ずしもreturnステートメントではありません。
オルタ

2
私のポイントは、これによりテーブル形式がさらに不格好になるということでした。ちなみに、C固有ではありません-Cから派生したすべての言語で共有されているため、C ++、C#、Java、およびJavaScriptはすべて同じ落とし穴を許します。
グラハム

1
私はそれがreturnステートメントであることを気にしません-あなたの意図は単純なステートメントを示すことだと思います。しかし、それはより面倒になります。そしてもちろん、ステートメントが単純ではなくなるとすぐに、テーブル形式を維持することが不可能なため、フォーマットを変更する必要があります。コードの難読化を行っていない限り、長いコード行はそれ自体が臭いです。(元々の制限は80文字でしたが、最近は130文字程度が一般的ですが、行末を見るためにスクロールする必要はないという一般的な原則がまだあります。)
Graham

1

表形式のレイアウトは、いくつかの限られた場合に役立ちますが、ifの場合に役立つことはほとんどありません。

単純なケースでは?:がより良い選択かもしれません。中程度の場合、多くの場合、スイッチの方が適しています(言語があれば)。複雑なケースでは、呼び出しテーブルの方が適している場合があります。

コードをリファクタリングするときに、パターンを明確にするために表形式に再配置したことが何度もありました。ほとんどの場合、問題を理解したら問題を解決するためのより良い方法があるという点で、私がそのようにしておくことはめったにありません。コーディング慣行またはレイアウト標準では禁止されている場合があり、その場合はコメントが役立ちます。

についていくつか質問がありました?:。はい、それは三項演算子です(または、私はそれを値と考えたいなら)。最初の赤面では、この例は?:で少し複雑です(そして?:を使いすぎると読みやすくなりませんが、痛いです)。解決。

if i==0: return 0
return i>0?sqrt(i):(1j*sqrt(-i))

1
あなたは、「?:」が未経験者のために何であるかを明確にする必要があるかもしれません(例えば、おそらく質問に関連する例で)。
ピーターモーテンセン

それが三項演算子だと思います。私は、三日月演算子が人々が日々見ている他のスタッフのフォーマットをやり直し、読みやすい場合、標準を再配置する傾向があるという正当な理由のためにそれが避けられたように感じます。
オルタ

@PeterMortensen:未経験者がこれが何を意味するのかわからない場合、彼らは明白な質問をして学習するまでコードから離れるべきです。
gnasher729

@horta Ternaryはif ? do stuff : do other stuffです。if / elseと同じ順序。
ナビン

1
@Navin Ah、多分それは私が最もよく使う言語の失敗だろう(python)。stackoverflow.com/questions/394809/…–
horta

-3

テーブル形式に問題はありません。個人的な好みですが、私はこのような三項を使用します:

return i>0  ? sqrt(i)       :
       i==0 ? 0             :
              1j * sqrt(-i)

return毎回繰り返す必要はありません:)


1
いくつかのコメントで述べたように、returnステートメントは理想的でも投稿のポイントでもありません。単にオンラインで見つけていくつかの方法でフォーマットしたコードの塊です。
オルタ

Pythonの三項はそうではdo_something() if condition() else do_something_else()ありませんcondition() ? do_something() : do_something_else()
イサイアメドウズ

@IsiahMeadows OPは決してPythonについて言及しませんでした。
ナビン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.