web-dev-qa-db-ja.com

なぜ人々はメソッドの#regionタグに強く反対するのですか?

メソッドを短くすることについて多くのことを聞き、多くのプログラマーが、メソッド内で#regionタグを使用することは、それが長すぎて複数のメソッドにリファクタリングする必要があるという確かな兆候だと聞いたと聞きました。ただし、メソッド内で#regionタグを使用してコードを分離する方が、複数のメソッドにリファクタリングする優れたソリューションである場合が多いようです。

計算を3つの異なるフェーズに分離できるメソッドがあるとします。さらに、これらの各段階はthisメソッドの計算にのみ関連するため、それらを新しいメソッドに抽出しても、コードを再利用することはできません。では、各フェーズを独自のメソッドに抽出することの利点は何でしょうか?私の知る限り、得られるのは読みやすさと各フェーズの個別の変数スコープです(特定のフェーズの変更が別のフェーズを誤って壊すのを防ぐのに役立ちます)。

ただし、これらの両方は、各フェーズを独自のメソッドに抽出することなく実現できます。リージョンタグを使用すると、コードを同じように読みやすい形式に折りたたむことができ(コードを展開して調べることにした場合に、このファイルに場所を残す必要がないという追加の利点があります)、各フェーズを{}は、使用する独自のスコープを作成します。

このようにすることの利点は、実際には4番目のメソッドの内部動作にのみ関連する3つのメソッドでクラスレベルのスコープを汚染しないことです。長いメソッドを一連の短いメソッドに即座にリファクタリングすることは、コードの再利用が時期尚早な最適化に相当するように思えます。多くの場合決して発生しない問題に対処するために、余分な複雑さを導入しています。コードを再利用する機会が生じた場合は、いつでもフェーズの1つを独自のメソッドに抽出できます。

考え?

27
jjoelson

気にする必要があるのは、コードを再利用できないように使用できることだけです。サルは、変換を行う必要がある場合、使用可能なコードを再利用可能なコードに変換できます。

「ここでしか必要ない」という議論は、丁寧に言えば不十分です。あなたが説明しているテクニックは、 ヘッドラインテクニック と呼ばれることが多く、一般的には嫌われています。

  1. リージョンをテストすることはできませんが、実際のメソッドを分離してテストすることはできます。
  2. リージョンはコメントであり、構文要素ではありません。最悪の場合、リージョンとブロックのネストは互いに矛盾します。常に、使用している言語の構文要素で構造のセマンティクスを表すように努める必要があります。
  3. メソッドにリファクタリングした後、テキストを読み取るために折りたたむ必要はなくなりました。ターミナル、メールクライアント、VCS用のWebビュー、diff-viewerなど、折りたたみ機能を持たないツールのソースコードを見ている可能性があります。
  4. メソッドにリファクタリングした後の結果のメソッドは、少なくともリージョンの場合と同じくらい優れています。さらに、個々のパーツを移動する方が簡単です。
  5. 単一責任原則では、どのユニットにも1つのタスクと1つのタスクのみを設定することを推奨しています。 「main」メソッドのタスクは、「ヘルパー」メソッドを構成して目的の結果を取得することです。 「ヘルパー」メソッドは、個別の単純な問題を分離して解決します。これらすべてのメソッドを含むクラスは、1つのタスクのみを実行し、そのタスクに関連するメソッドのみを含む必要があります。そのため、ヘルパーメソッドはクラススコープに属しているか、最初にコードをクラスに含めるべきではありませんが、少なくともいくつかのグローバルメソッドに含めるか、さらには を挿入する必要があります

また、関連あり: Jeff Atwoodsによるコードの折りたたみに関する考え

66
back2dos

問題となるのはメソッド内の領域ではありませんが、メソッド内に領域を配置する必要がある(または使用する)必要がある場合です。

200-300行のメソッドをたくさんデバッグしなければならなかったので、誰にもそれを望まないだろうと私は言うことができます。あなた自身のコードを書いて世話をしているなら、それは問題ありません-好きなことをしてください。しかし、誰かがそれを見ると、メソッドが何をしているのかすぐにわかるはずです。

「最初に物事を取得し、次にそれらをジグザグに動かします。そして、彼らがバズを鳴らす必要がある場合はそれを行います。そうでない場合は、それらが青かどうかをチェックし、コペルニクスの値を反転します。次に、2番目のものが最初のものよりも大きい場合、ジュースボックスを取り出して挿入する必要があります...」

いいえ、それでは不十分です。それをあなたが望むすべての地域に分けてください、しかし私はそれについて考えているだけで私の頭をまだ振っています。

メソッドに割り込むと、多くの利点が得られます。

  • メソッドの名前だけでも、どこで何が起こっているのかを明確に理解できます。そして、あなたは間違ったメソッドを完全に文書化することはできません-文書ブロック(///をヒット)を追加し、説明、パラメータ、戻り値の型、およびexceptionexampleremarksを入力しますこれらのシナリオを詳しく説明するノード。それはそれが行くところです、それはそれが何のためにあるのですか!
  • 副作用やスコープの問題はありません。すべての変数を{}を使用してサブスコープで宣言すると言うかもしれませんが、すべてのメソッドをチェックして、「チート」していないことを確認する必要がありますか?このdodgyObjectは、この地域で使用されているという理由だけでここで使用しているのですか、それとも他の場所から来たのですか?私が自分の方法でいれば、それが自分に渡されたかどうか、または自分で作成したかどうか(または、自分で作成したかどうか)を簡単に確認できます。
  • イベントログには、例外が発生したメソッドが示されます。はい、問題のあるコードを行番号で見つけることができる場合がありますが、メソッド名(特に、適切に名前を付けた場合)を知っていると、何が起こったのか、何が間違っていたのか。 「ああ、TurnThingsUpsideDownメソッドが失敗しました-悪いことをしているときに失敗する可能性があります」は、「ああ、DoEverythingメソッドが失敗しました-50の異なることがあり、1時間探して見つける必要があります。それ"。

これはすべて、再利用可能性または改善可能性の懸念の前にあります。適切に分離されたメソッドは明らかに再利用を促進し、実行していないメソッドを簡単に置き換えることもできます(遅すぎる、バグが多すぎる、依存関係の変更など)。これらの巨大なメソッドのいずれかをリファクタリングしてみたことがありますか?変更した内容が他の機能に影響しないことを確認する方法はありません。

適切なサイズのメソッドにロジックを適切にカプセル化してください。私は彼らが手に負えなくなるのは簡単であることを知っています、そしてその時点で一度再設計する価値がないと主張することは別の問題です。しかし、害がなく、少なくとも潜在的な利点が適切にカプセル化され、きれいに記述され、単純に設計されたメソッドを持つことを受け入れる必要があります。

15
Kirk Broadhurst

メソッドが非常に複雑で、3つの異なるフェーズに分離できる場合、それらは3つの個別のメソッドであるだけでなく、実際には、その計算専用の個別のクラスである必要があります。

「しかし、私のクラスにはパブリックメソッドが1つしかありません!」

あなたは正しい、そしてそれで何も悪いことはありません。単一責任の原則の考え方は、クラスが1つのことを行うということです。

クラスは3つのメソッドのそれぞれを順番に呼び出し、結果を返すことができます。ひとこと。

おまけとして、このメソッドはプライベートメソッドではなくなったため、テスト可能になりました。

しかし、気にしないでくださいoneクラス。実際には、fourが必要です。メソッドの各部分に1つと、3つそれぞれを依存関係として取得して呼び出すもの適切な順序で結果を返します。

これにより、クラスがさらにテスト可能になります。また、これら3つの部分のいずれかを簡単に変更できます。古いクラスを継承する新しいクラスを記述し、変更された動作をオーバーライドするだけです。または、3つの要素それぞれの動作のインターフェイスを作成します。次に、1つを置き換えるには、そのインターフェイスを実装する新しいクラスを記述し、依存関係注入コンテナー構成の古いクラスに置き換えます。

何?依存性注入を使用していませんか?まあ、あなたはおそらくあなたはまだ必要性を見ていません。なぜなら、あなたは単一責任の原則に従っていないからです。始めたら、各クラスに依存関係を与える最も簡単な方法は、DIコンテナーを使用することです。

[〜#〜]編集[〜#〜]

OK、リファクタリングの質問は別として。 #regionの目的はコードを非表示にすることです 、これは主に通常の状況で編集する必要のないコードを意味します。 生成コードが最良の例です。通常は編集する必要がないため、リージョンで非表示にする必要があります。必要な場合は、領域を拡張することによって、「ここにドラゴンがいる」スタイルの警告が少し表示され、自分が何をしているのかを確実に知ることができます。

したがって、メソッドの途中で#regionnot使用しないでください。メソッドを開くと、コードが表示されます。 #regionを使用すると、必要のない別のレベルの非表示が得られ、それが煩わしくなります。メソッドを展開すると、コードが表示されません。

メソッドの構造を見ている人が心配な場合(そして、メソッドのリファクタリングを拒否した場合)、次のようなバナーコメントを追加します。

//
// ---------- COMPUTE SOMETHING OR OTHER ----------
//

これらは、#regionタグを展開したり折りたたんだりする必要なく、コードを閲覧している誰かがメソッドの個々の部分を見るのに役立ちます。

7
Kyralessa

私はあなたの議論で与える例はYAGNI(あなたはそれを必要としないでしょう)についてではなく、簡単に保守できる適切な品質のコードを書くことについてより多くだと思います。

最初のプログラミングコースでは、メソッドが700 LOCの長さの場合、メソッドはおそらく大きすぎて、間違いなく試してデバッグするのが非常に面倒になることを学びました。

次に、メソッドD内でのみ使用されるメソッドA、B、Cを作成すると、Dから独立してA BとCをより簡単に単体テストでき、4つすべてのメソッドでより堅牢な単体テストが可能になります。

4
maple_shaft

計算を3つの異なるフェーズに分離できるメソッドがあるとします。さらに、これらの各ステージはこのメソッドの計算にのみ関連するため、新しいメソッドに抽出することでコードを再利用することはできません。では、各フェーズを独自のメソッドに抽出することの利点は何でしょうか?私の知る限り、得られるのは読みやすさと各フェーズの個別の変数スコープです(特定のフェーズの変更が別のフェーズを誤って壊すのを防ぐのに役立ちます)。

これらは2つの利点です。しかし、私にとってとにかく重要なのはdebuggabilityです。そのコードをトレースする必要がある場合は、3つのセクションのうち2つをステップオーバーして、気になるセクションのみをトレースできるようにする方がはるかに簡単です。より小さなメソッドにリファクタリングすると、これが可能になります。リージョンタグにはありません。

3
Mason Wheeler

私の個人的なルールは、メソッドが1画面よりも長い場合、たとえば40行だと、長すぎるということです。クラスが500行を超える場合、クラスが大きすぎます。私はこれらの規則をときどき、場合によっては大きな倍数でさえ違反しますが、通常は1年以内にそれを後悔します。

20行から30行の方法でも、ほとんどの場合少し長くなります。したがって、メソッドでリージョンを使用することは、通常はお勧めできません。20〜30行のリージョンを複数のサブリージョンに折りたたむことはほとんど意味がないからです。

この定義で長い関数を分解すると、少なくとも2つの利点があります。

  1. それらのサブ機能が何をしているかに名前を付けると、現在の考え方が明確になり、後のメンテナーのタスクが簡単になります。

  2. 小さい関数に分解すると、小さい関数が必要とするパラメーターのみを渡す必要があるため、関数が必要とし、変更するデータの範囲は非常に明確です。これは、100種類のリーフ関数でオブジェクトの状態にアクセスして変更していないことを前提としています。

私の経験では、これらのルールは、一般的なミスを犯すことなく簡単に記述できるコードを生成し、微妙な動作を壊すことなく後で理解および変更できます。コードを理解または変更する必要のある人が他の誰かであるか、私がすべてを眠って忘れた後の私であるかは問題ではありません。

したがって、メソッド内の領域は、持続不可能な方法が適用されていることを示す「コードのにおい」であると考えます。いつものようにcouldには例外がありますが、発生頻度が非常に低いため、説明する価値はほとんどありません。

2
PeterAllenWebb

ここに行く 再び ...同じ議論に終わったプログラマには ここに他のトピックがたくさん がある。

あなたは短い関数に関連するいくつかの非常に興味深い引数を指摘します。それは人気のあるステートメントではありませんが、 私はこれについてあなたと一緒にいます 。以前何度もそれについて話し合った後、それは通常、主に適切なカプセル化に焦点を当てるか、またはテスト駆動開発を最大限に行うかどうかという議論に要約されます。これらはさらに別の聖なる戦争が起こっているように思われるものの2つの主要な候補であり、私たちは不十分な力の側にいると思います。

しかしながら ...

私の意見では、ソリューションは地域の「フェーズ」を包むものではありません。 コードの折りたたみは、ラグの下のコードをスイープするために使用されます。 コードをそのままにしておくと、後でそれをリファクタリングする必要があることを思い出させるかもしれません!それをあなたの質問の議論に関連付けるには:コードが表示されない場合、どのようにしてコードの再利用の機会を見ることができますか?コードの長いセグメントはまさにそれであり、それをリファクタリングしたくなる目の棘です。

リージョンの適切な置き換えとして、コード段落を使用して、Alex Papadimoulisが コメント内 と命名することをお勧めします。あなたは実際にすでに同じようなアプローチを使っているかもしれません。これは、コメントヘッダーを含むコードのブロックであり、ブロック全体の機能を説明しています。関数の可読性はありますが、通常の間隔の英語の文を使用でき、カプセル化を失うことはありません。

2
Steven Jeuris

通常、#regionを使用するよりもコードをリファクタリングする方が良いことに同意しますが、常に可能であるとは限りません。

その声明を裏付けるために、例を挙げましょう。

class Foo : IComparable
{
  private String name;
  private int foo;
  private Bar bar;

  public int CompareTo(Foo other)
  {
#region Ascending on name
     if (this.name < other.name) return -1;
     if (this.name > other.name) return 1;
#endregion
#region Usual order on bar
     int compBar = bar.compareTo(other.bar);
     if (compBar != 0) return compBar;
#endregion
#region Descending on foo
     if (this.foo > other.foo) return -1;
     if (this.foo < other.foo) return 1;
#endregion
     return 0; // equal
  }

リージョンを再調整して順序を変更したり、クラスに追加フィールドが追加されたときに拡張したりするのは簡単です。いずれにしても、順序がどのように機能するかをすばやく確認するには、コメントが必要です。そして何よりも、この場合、リファクタリングが読みやすさを向上させる方法がわかりません。

ただし、これらの例外はまれです。通常、他の多くの答えが言うように、リファクタリングは可能です。

1
Sjoerd

特定の人やチームが#regionを使用しないことにした理由は1つではないと思いますが、いくつか挙げる必要がある場合、これが私が見た最大の理由です。

  • リージョンの名前と順序により、コードの順序と構成がより明確になり、特定のスタイルが適用され、競合やバイクシェディングの原因になる可能性があります。
  • ほとんどのコンセプトは、少数のリージョンに完全には適合しません。通常は、アクセス修飾子publicprivateからリージョンを開始し、次にメンバータイプ(メソッド、プロパティ、フィールドなど)を多分使用しますが、これらの修飾子にまたがるコンストラクター、静的これらすべての種類、保護されたメンバー、内部メンバー、保護された内部メンバー、暗黙的なインターフェイスメンバー、イベントなど。最終的には、細かく分類されているため、各リージョンに単一のメソッドまたはメソッドを持つ最も適切に設計されたクラスがあります。
  • 実用性を維持し、3つまたは4つの一貫したタイプの領域にのみグループ化できる場合、それは機能する可能性がありますが、実際にはどのような価値があるのでしょうか。クラスを適切に設計している場合は、コードのページをふるいにかける必要はありません。
  • 上でほのめかしたように、領域は醜いコードを隠すことができるので、問題が少ないように見えます。目の痛みとしてそれを保つことはそれを対処するのに役立つかもしれません。
0
jpierson