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


27

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

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

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

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

考え?


4
現在、かなり大きなクラスで作業しています。これは、単一責任の原則に従います。クラス内のすべてが必要な機能です。主にリファクタリングのため、多くのプライベートメソッドがあります。#regionを使用してこれらのメソッドを機能的なカテゴリに分割しました。これは非常にうまく機能しています。このプロジェクトには、このような処理を必要としない他の多くのクラスがあります。適切な仕事のための適切なツール、私は言います。
ロバートハーベイ

4
@RobertHarvey、クラスでそれらを使用することは、メソッド内で使用することとは異なります。
CaffGeek

18
@チャド:ああ、それに気づかなかった。メソッドでそれらを使用することは少し極端に思えます。メソッドが長すぎて#regionsが必要な場合は、別のメソッドにリファクタリングします。
ロバートハーベイ

8
実際には答えではありませんが、すべての#regionタグを嫌うだけでなく、Visual Studioでコードの折りたたみを完全にオフにします。私は私から隠そうとするコードが好きではありません。
ダニエルプライデン

2
「さらに、これらの各段階はこのメソッドの計算にのみ関連しているため、新しいメソッドに抽出してもコードの再利用はできません」。OTOH、関数を3つに分割すると、後で個々の部分が<i> </ i>役に立つことがよくわかります。たとえば、いくつかの入力データが一つだけの位相を変更した場合、あなたはなど、単独でそれをテストすることができます
ジャック・V.

回答:


65

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

「ここでしか必要ない」という議論は、丁寧に言えば貧弱です。あなたが説明しているテクニックは、しばしばヘッドラインテクニックと呼ばれ、一般的には眉をひそめています。

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

関連するもの:コードの折りたたみに関するジェフアトウッズの考え


9
1.ほとんどのプログラマは、すべてのプライベートメソッドをテストするわけではありません。2.メソッド名は、そのメソッドについて知っておくべきすべてのことを伝えることはできません。どのような例外がスローされる可能性がありますか?nullは有効な入力ですか?コードをより理解しやすくするために、非構文構造を使用しても問題ない場合があります。多くの言語では、ファイルはコードを編成する手段としてかなり広く受け入れられている非構文要素の例です。3.さまざまな理由により、IDEでメソッドの内部構造に移行するのが最適です。VCSまたはメールクライアントでリージョンが噛み付く可能性のある状況はかなりまれです。
ジョエルソン

3
4.しかし、クラスの残りの部分に深刻な複雑さをエクスポートしたばかりです。それだけの価値はありますか?また、メソッド呼び出しと同じくらい簡単に領域を移動できます。5.ここで、1つの場所でのみ使用されるクラスを追加して、ネームスペースを汚染することについてお話します。なぜクラスとメソッドがあなたが受け入れるカプセル化の唯一の単位なのですか?この別のクラスが必要になった場合(または必要になった場合)は、いつでも新しいクラスを作成できます。
ジョエルソン

7
@jjoelson:1. だから何?2.正確に言うと、構文だけでは不十分な場合にのみ、非構文構造を使用しても構いません。3.「まれ?」-あなたはそれを示す数字を持っていると思います。TortoiseSVNにバンドルされているdiff(または非難など)ツールには折りたたみ機能がないことを確認できます。4.私がしたポイントにどのように関連していますか。5.そのため、ほとんどの現代言語にはネスト可能な名前空間があります。利用可能な言語要素の幅広いパレットを使用してコードを構成するのではなく、ASCIIアートに頼っています。
back2dos

4
@jjoelson:1.それはあなたの意見であり、この質問の範囲外です。2.引数の拡張により、ネストされた関数はリージョンよりも優れています、はい。3.問題を追跡するために、いくつかのリビジョンを参照する必要がある場合があるため。いずれにしても、ソースコードはIDEではなく人間のために書かれています。4. 1つは、論点が私の論点とは無関係であり、2つ目はやはりあなたの意見であり、3つ目はこの質問の範囲外である。5. C#には、ネスト関数以外のコードを構造化する手段があります。これらを使用するか、関数をネストできる言語を使用する必要があります。
back2dos

13
まだ議論したいなら、これをチャットに持っていくべきです... OPに言う価値はありません。彼の心を変えるものは何もありませんが、IMOをもっと体験してください。
maple_shaft

15

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

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

「最初に物を手に入れ、次にそれをジグし、バズが必要な場合はそれを行い、そうでない場合は青であるかどうかを確認し、コペルニクスの値を反転させます。ジュースボックスを入手して挿入する必要があります...」

いいえ、それでは十分ではありません。必要なすべての領域に分割しますが、それについて考えるだけで頭を振っています。

メソッドに侵入すると、多くの利点が得られます。

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

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

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


Visual StudioのXMLコメント解析と#regionタグは、Visual Studio機能のカテゴリに分類されます。私の全体的なポイントは、プロジェクトの全員がVisual Studioを使用している場合、これらの機能に依存することは間違いではないということです。副作用と範囲の問題については、グローバルフィールドで物事を台無しにすることができます。どちらの場合でも、副作用を伴う愚かなことをしないようにプログラマに頼らなければなりません。
ジョエルソン

5
@jjoelson Visual StudioがなくてもXMLコメントを読むことができますが、VS以外ではregionタグはほとんど完全に役に立ちません。引数を論理的に拡張することは、アプリケーション全体を実行する巨大な方法の1つです。リージョンに分割していれば問題ありません。
カークブロードハースト

2
@jjoelsonは、グローバルフィールドがいかに悪いかについて始めてはいけません。とにかくそれを台無しにする「回避策」があるので、何かを言うことは役に立たない。メソッドを短くし、変数のスコープを維持するだけです。
オリヴァーズ

@OliverS、カークは、私のリージョン・イン・ア・メソッド方式の問題として共有状態をもたらした人でした。基本的に、私はあなたが言っていることを正確に言っていました:グローバル間でメソッド間で共有された状態を台無しにする能力がそうであるように、プログラマーがリージョン間であまりにも多くの変数を共有することで状態を乱用できるという事実はスキーム全体の告発ではありません」 t複数方法スキームの告発。
ジョエルソン

7

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

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

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

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

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

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

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

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

編集

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

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

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

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

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


2
1か所で呼び出されるメソッドの場合、これらすべてですか?ほとんどの筋金入りのLispersでさえ、関数をより簡単に定義するために使用できる少なくとも2つの場所を特定するまで、高次関数を抽象化しません。
ジョエルソン

1
@jjoelson新しいクラスを作るのはとても難しいですか?それは何ですか、1行、1つの開始ブレースと1つの終了ブレースです。ああ、あなたは押す必要がありますctrl+n
カークブロードハースト

2
新しいクラスを作成するのが難しいというわけではありません、追加のクラスを作成することでオーバーヘッドが発生します。これは、コードメンテナーが探しているものを見つけるのをより困難にする、もう1つの間接層です。そうではありませんその取引の大きなが、それはまた、あなたのコードのこのビットは、どこからでも呼び出すことがしにくくなる場合には何も購入していません。あなたが言及したように、それはその新しいクラスを作成し、それはあなたが判明した場合そこにコードを置くことは非常に簡単です行う複数の場所でコードのこのビットを呼び出す必要があります。
ジョエルソン

2
@jjoelson、これはかなり一般的な反応です。私がSRPやDIなどのことを初めて知ったのは私のものであり、依存関係を解消し始めたときの同僚の反応でした。個々のケースを見ている場合、それは価値がないようです。利点は総計にあります。すべてのクラスでSRPを実行し始めると、アプリケーションの半分に波及する変更をせずにコードの一部を変更する方がはるかに簡単であることがわかります。
キラレッサ

@Kyralessa、これらは単一の場所でのみ使用されるメソッドです。そのような方法を変更しても、アプリケーションの半分に波及することはありません。そのようなコードがそれを使用するメソッドの領域として実装されている場合、完全なカプセル化ができます。含まれているメソッドのみがコードを使用しているため、そのメソッドを除いて、その影響を心配することなく自由に変更できます。
ジョエルソン

4

あなたの議論であなたが与える例は、YAGNI(あなたはそれを必要としません)についてのものではなく、簡単に維持できる適切な品質のコードを書くことについてのものだと思います。

初期のプログラミングコースでは、メソッドの長さが700 LOCである場合、大きすぎる可能性があり、デバッグしようとすると大きな痛みになることを学びます。

次に、メソッドD内でのみ使用されるメソッドA、B、Cを記述すると、Dとは無関係にABとCをより簡単に単体テストでき、4つのメソッドすべてでより堅牢な単体テストが可能になります。


ユニットテストは確かに公正なポイントですが、すべてのケースでクラススコープの汚染を正当化するかどうかはわかりません。現実的に言えば、誰かが書いた小さなプライベートメソッドを実際にユニットテストしますか?私は、よく使われるプライベートメソッド、つまり、とにかくコード再利用の候補となるプライベートメソッドをユニットテストする傾向があると思います。
ジョエルソン

@jjoelsonメソッドに意味のあるロジックがあり、他の機能をラップしていない場合は、常にそれに対する単体テストを作成します。単体テストメソッドは、コードの再利用とは関係ありません。メソッドを単体テストして、与えられた入力に対して、期待される出力が一貫性のある反復可能な方法で得られることを確認する必要があります。呼び出したときにクラススコープが汚染されていることに気付いている場合、クラスが単一責任原則に違反している可能性があります。
maple_shaft

2
私が出会ったほとんどのプログラマは、すべてのプライベートメソッドを単体テストするという考えに同意しません。すべてのプライベートメソッドでテストを実装および維持するのにかかる時間は、価値がありません。
ジョエルソン

3

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

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


1
ctrl-f10-カーソルまで実行
スティーブンジュリス

2

私の個人的なルールは、メソッドが1画面より長い場合(40行など)、長すぎるということです。クラスが500行より長い場合、大きすぎます。私は時々これらの規則を破り、時には大きな倍数でさえ破りますが、一般的には一年以内に後悔しています。

20〜30行のメソッドでさえ、ほとんどの場合、少し長くなっています。したがって、メソッドでリージョンを使用することは通常悪い考えです。なぜなら、20〜30行のリージョンを複数のサブリージョンに折り畳むことはほとんど意味をなさないからです。

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

  1. これらのサブ機能が実行していることに名前を付けることができます。これにより、現在の考え方が明確になり、後のメンテナーのタスクが簡素化されます。

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

私の経験では、これらのルールは一般的な間違いを犯さずに記述しやすく、微妙な動作を壊さずに後で理解および変更できるコードを生成します。コードを理解/修正しなければならない人が他の人なのか、私が寝て忘れてしまった後の自分なのかは関係ありません。

したがって、メソッド内の領域は、持続不可能なプラクティスが適用されていることを示す「コードのにおい」であると考えます。いつものように、例外が存在する可能性がありますが、まれにしか発生しないため、ほとんど議論する価値はありません。


2

ここに私達は行く、再び ...あり他のトピックの多くは、ここで同様の議論で終わっているプログラマーに。

短い関数に関連する非常に興味深い議論をいくつか指摘します。これは人気のある声明ではありませんが、これについてはあなたと一緒にいます。何度も議論した後、議論は通常、適切なカプセル化に主に焦点を合わせるか、テスト駆動開発を最大限に活用するかどうかに要約されます。これらは、さらに別の聖戦を仕掛けているように思われる2つの主な候補であり、私たちは力不足の側にいるのではないかと心配しています。

しかしながら ...

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

地域の適切な代替として、Alex Papadimoulis がコメントでそれら命名するので、コードの段落を使用することをお勧めします。実際にすでに同様のアプローチを使用している可能性があります。これは、コメントヘッダーが付いたコードのブロックであり、ブロック全体の機能を説明しています。関数は読みやすくなっていますが、通常の間隔の英語の文章を使用でき、カプセル化を失うことはありません。


まあ、私は本質的にコードの段落を区切るためにリージョンを使用します(リージョンには、コードが折り畳まれていても見えるコメントを付けることができます)。これは、メンテナーが実装を調べたい場合にコードを表示するか、「全体像」のみを表示したい場合にコードを表示するかを選択できるようにするため、リージョンを使用するのが好きです。
ジョエルソン

1

通常、コードをリファクタリングする方がを使用するよりも優れていることに同意しますが#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
  }

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

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


0

特定の人やチームが使用しないことを決めた理由について単一の答えがあるとは思いませんが#region、いくつかをリストしなければならなかった場合、これが私が見た一番の理由になります。

  • 領域の名前と順序により、コードの順序と編成がより明確になり、特定のスタイルが強制され、競合や自転車乗りの原因になります。
  • ほとんどの概念は、少数の地域にきれいに収まりません。通常、アクセス修飾子publicprivateからリージョンで開始し、その後メンバータイプ(メソッド、プロパティ、フィールドなど)によって開始しますが、これらの修飾子にまたがるコンストラクター、これらすべての静的な種類、保護メンバー、内部メンバー、保護内部メンバ、暗黙的なインターフェイスメンバ、イベントなど。最終的には、きめ細かな分類により、各領域に単一またはメソッドを持つ最も適切に設計されたクラスがあります。
  • あなたが実用的で、3つまたは4つの一貫したタイプのリージョンに物事をグループ化することができるなら、それはうまくいくかもしれませんが、実際にはどのような価値がありますか?クラスをうまく設計している場合、コードのページをふるいにかける必要はありません。
  • 上記で示唆したように、リージョンはいコードを隠すことができるため、実際よりも問題が少ないように見える場合があります。それを目の痛みとして保つことは、それを解決するのを助けるかもしれません。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.