他の関数のみを呼び出す関数。これは良い習慣ですか?


13

現在、多くの異なるセクション(すべて異なる書式設定が必要)を含む一連のレポートに取り組んでおり、コードを構造化する最適な方法を見つけようとしています。過去に行った同様のレポートには、レポートのすべてのデータ操作と書式設定を行う非常に大きな(200行以上の)関数があり、ワークフローは次のようになります。

DataTable reportTable = new DataTable();
void RunReport()
{
    reportTable = DataClass.getReportData();
    largeReportProcessingFunction();
    outputReportToUser();
}

これらの大きな機能を小さなチャンクに分割できるようにしたいのですが、再利用できない機能が何十個もあり、同様の機能を「ここですべて実行」するだけで済むのではないかと心配しています。次のように、これらすべての小さな関数を呼び出します。

void largeReportProcessingFunction()
{
    processSection1HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    processSection1SummaryTableData();
    calculateSection1SummaryTableTotalRow();
    formatSection1SummaryTableDisplay();
    processSection1FooterData();
    getSection1FooterSummaryTotals();
    formatSection1FooterDisplay();

    processSection2HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    calculateSection1HeaderAverages();
    ...
}

または、さらに一歩進んだ場合:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

これは本当に良い解決策ですか?組織的な観点から私はそれがそうであると思います(すなわち、すべてはそうでなければそうであるかもしれないよりもはるかに組織化されます)が、コードの読みやすさに関して私は確信していません(潜在的に他の関数を呼び出すだけの関数の大きなチェーン)。

考え?


一日の終わりに...もっと読みやすいですか?はい、それはより良い解決策です。
シルバナール

回答:


18

これは一般に機能分解と呼ばれ、正しく行われた場合は一般的に行うのが良いことです。

また、関数の実装は、抽象化の単一レベル内にある必要があります。を使用する場合largeReportProcessingFunction、その役割は、どの処理手順をどの順序で実行するかを定義することです。これらの各ステップの実装は、下の抽象化レイヤー上にありlargeReportProcessingFunction、それに直接依存するべきではありません。

ただし、これは命名の悪い選択であることに注意してください。

void largeReportProcessingFunction() {
    callAllSection1Functions();
    callAllSection2Functions();
    callAllSection3Functions();
    ...        
}

あなたが見るcallAllSection1Functions名前は実際には抽象化を提供しない名前です。なぜならそれは実際にそれが何をするではなく、むしろそれをどのように行うを言うからです。processSection1代わりに呼び出すか、コンテキストで実際に意味のあるものを呼び出す必要があります。


6
「processSection1」が意味のある名前であることがわからないことを除いて、私は原則としてこの答えに同意します。「callAllSection1Functions」と実質的に同等です。
エリックキング

2
@EricKing:callAllSection1Functions実装に関する詳細を漏らします。外部から見ると、processSection1作業を「すべてのセクション1機能」に任せるか、直接実行するか、pixie-dustを使用して実際の作業を行うユニコーンを呼び出すかどうかは関係ありません。本当に重要なのは、を呼び出した後processSection1section1処理済みと見なされることです。処理は一般的な名前であることに同意しますが、凝集度が高い場合(常に努力する必要があります)、通常、コンテキストはそれを明確にするのに十分なほど狭くなります。
back2dos

2
私が意味したのは、「processXXX」と呼ばれるメソッドはほとんど常にひどい、役に立たない名前です。すべてのメソッドは「処理」するため、「processXXX」という名前は「doSomethingWithXXX」または「manipulateXXX」などの名前を付けるのと同じくらい便利です。ほとんどの場合、「processXXX」という名前のメソッドにはより良い名前があります。実用的な観点から見ると、「callAllSection1Functions」と同じくらい便利です(役に立たない)。
エリックキング

4

C#を使用しているとおっしゃいましたが、オブジェクト指向言語を使用しているという事実を利用して、構造化されたクラス階層を構築できるのでしょうか。たとえば、レポートをセクションに分割することが理にかなっている場合は、Sectionクラスを作成し、クライアント固有の要件に合わせて拡張します。

非対話型のGUIを作成しているかのように考えるのは理にかなっているかもしれません。作成するオブジェクトを、異なるGUIコンポーネントであるかのように考えてください。基本的なレポートがどのようになるかを自問してください。複雑なものはどうですか?拡張ポイントはどこにあるべきですか?


3

場合によります。作成しているすべての関数が本当に単一の作業単位である場合、それらが呼び出し関数の1〜15行、16〜30行、31〜45行である場合は問題ありません。20個すべてをチェックする検証機能を備えたレポートに20個のフィルターがある場合、1つのことをすれば、長い関数は悪ではありません。それは問題ありません。フィルターまたはフィルターのグループごとに分割すると、実際のメリットが得られないだけの複雑さが追加されます。

多くのプライベート機能があると、自動化された単体テストがより難しくなります。そのため、一部の人はそれを回避しようとしますが、テストに対応するためにより大きく複雑なデザインを作成する傾向があるため、これはかなり悪い推論です。


3
私の経験では、長い機能悪です。
バーナード

あなたの例がオブジェクト指向言語である場合、基本フィルタークラスを作成し、20個のフィルターはそれぞれ異なる派生クラスにあります。Switchステートメントは、正しいフィルターを取得するための作成呼び出しに置き換えられます。各機能は1つのことを行い、すべてが小さいものです。
DXM

3

C#を使用しているので、オブジェクトをもっと試してみてください。後続の関数が依存する「グローバル」クラス変数を持つことで、手続き的にコーディングしているように見えます。

ロジックを個別の関数に分割することは、すべてのロジックを単一の関数に含めるよりも間違いなく優れています。関数が動作するデータをいくつかのオブジェクトに分割することで、さらに処理を進めることもできます。

レポートデータを渡すことができるReportBuilderクラスを持つことを検討してください。ReportBuilderを個別のクラスに分割できる場合は、これも行います。


2
手順は完全に優れた機能を果たします。
-DeadMG

1

はい。

複数の場所で機能を使用する必要があるコードセグメントがある場合。それ以外の場合、機能を変更する必要がある場合は、複数の場所で変更を行う必要があります。これは作業を増やすだけでなく、ある場所でコードが変更されて別の場所では変更されたり、ある場所で導入されても別の場所では導入されない場合にバグが発生するリスクを高めます。

また、説明的なメソッド名を使用すると、メソッドが読みやすくなります。


1

関数を区別するために番号付けを使用するという事実は、複製またはクラスの処理が多すぎるという根本的な問題があることを示唆しています。大きな関数を多くの小さな関数に分割することは、重複をリファクタリングする際の有用なステップになる可能性がありますが、それが最後ではありません。たとえば、次の2つの関数を作成します。

processSection1HeaderData();
processSection2HeaderData();

セクションヘッダーの処理に使用されるコードに類似点はありませんか?違いをパラメーター化することで、両方の共通の関数を抽出できますか?


これは実際には製品コードではないため、関数名は単なる例です(製品名では関数名の方がわかりやすい)。通常、異なるセクション間に類似性はありません(類似性がある場合、可能な限りコードを再利用しようとします)。
エリックC.

1

関数を論理的なサブ関数に分割することは、サブ関数が再利用されない場合でも役立ちます。それは、それを短く、理解しやすいブロックに分割します。ただし、n#行だけでなく、論理ユニットで行う必要があります。分割方法はコードによって異なります。共通のテーマは同じオブジェクトに対するループ、条件、操作です。基本的に、個別のタスクとして説明できるものすべて。

それで、はい、それは良いスタイルです-これは奇妙に聞こえるかもしれませんが、記述の再利用が制限されていることを考えると、再利用の試みについて慎重に考えることをお勧めします。使用法は、偶然に同じではなく、本当に同一であることを確認してください。同じブロック内のすべてのケースを処理しようとすることが、最初の場所で大規模な不格好な機能になってしまうことがよくあります。


0

これは主に個人的な好みだと思います。もしあなたの関数が再利用されるとしたら(そしてもっと一般的で再利用可能なものにできないと確信していますか?)私は間違いなくそれらを分割すると言うでしょう。また、関数やメソッドには2〜3画面以上のコードを含めないことをお勧めします。そのため、大きな関数が何度も繰り返されると、コードを読みやすくして分割することができます。

これらのレポートの開発に使用しているテクノロジーについては言及しません。C#を使用しているようです。あれは正しいですか?その場合、関数をより小さなものに分割したくない場合は、#regionディレクティブを代替と見なすこともできます。


はい、C#で作業しています。一部の機能を再利用できる可能性はありますが、これらのレポートの多くでは、セクションごとにデータとレイアウトが大きく異なります(顧客の要件)。
エリックC.

1
地域の提案を除き、+ 1。リージョンは使用しません。
バーナード

@Bernard-特別な理由はありますか?質問者は、関数の分割を既に除外している場合にのみ#regionsの使用を検討すると想定しています。
ジョシュアカーモディ

2
リージョンはコードを隠す傾向があり、悪用される可能性があります(ネストされたリージョン)。コードファイルですべてのコードを一度に見るのが好きです。ファイル内のコードを視覚的に分離する必要がある場合は、スラッシュの行を使用します。
バーナード

2
リージョンは通常、コードがリファクタリングを必要とするサインです
コーダー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.