web-dev-qa-db-ja.com

個別の関数(またはクラス)を作成する必要がある場合

私は数年間プログラムを行っています。
そして今、私は同僚から私の長所と短所を知っています:
長所:非常に複雑な問題を解決できます
短所:私は単純なタスクのために複雑すぎるソリューションを作ります。

今私は私の短所を修正しようとしていて、この質問のための一般的な原則とガイドラインを探しています:

いつ追加のコード(関数、クラス、モジュールなど)を作成する必要がありますか?そして、私がすべきでないとき。

私はいくつかのPython=男からの回答を見ました:できるときはいつでも、作成しないで、できるだけ簡単にしてください。

これは素晴らしいアドバイスですが、大きな機能を1つ持つこともお勧めできません。

私にとっての問題は、私は確かに別の関数を作成するときの原則を持っていますが、反対の関数は(作成しないとき)ないということです。原則は次のとおりです。

一部のコードをコピーして貼り付ける必要があり、その結果、コードが重複する場合に関数を作成します。

(理由:重複したコードは悪いです。バグを修正するときに一部のコピーを修正するのを忘れるので、コピーする回数だけテストを作成する必要があります。あなたのチームや自分自身のために後で読む手紙が倍になるなど...)これは、私が探している回答の種類の例として聞こえます。

では、別の関数を作成するか、既存の関数を拡張するだけの場合、どのように決定するのでしょうか?

すでにこの質問に回答してくれたすべての人に感謝しますが、しないでください関数を分割する必要がある場合は、少なくともいくつかの原則を追加してください(全体として維持する必要がある場合) 。

なぜなら今のところほとんど(すべて?)の答えは、あなたがすべきである分割したときだけのものだからです。そして、私の問題は、コーダーが私に言うように、私があまりにも多くの関数を作成し、あまりに頻繁に分割することです-これが私の質問がこれと異なる理由です 特定の機能を関数に抽出する必要があるか、そしてなぜですか?

6
Yuri Yaryshev

かなり広い質問のようですが、私は喜んで私の「知恵」のFWIWを共有します

  1. 純粋な関数(出力のみで決定される出力)に抽出できるコードの部分は、純粋な関数に抽出する必要があります。たとえば、関数の途中で特定の方法で文字列をフォーマットする必要があることがわかった場合は、文字列フォーマットロジックを文字列ライブラリに抽出できます。関数を1回だけ使用しても、コードが読みやすくなり、メイン関数の複雑さが軽減されます。

  2. 関数の名前がばかげている場合は、おそらく分割する必要があります。 OpenFileParseContentsAndCreateObjectsは、3つの関数に分けたほうがよいようです。

  3. 平均的な開発者が1分以内に関数を読み取って理解できない場合は、単純化するか、小さなビットに分割することでメリットが得られる可能性があります。

  4. ロジックパスの100%を実行するために関数が約20以上の単体テストを必要とする場合は、分割する必要があります。

  5. 関数がIDEで表示可能な単一のページよりも高い場合、やむを得ない理由がない限り、再構築を検討する必要があります。

13
John Wu

関数を分割する必要があるかどうかを判断する1つの方法は、次のようなパターンが表示された場合です。

error_t getInfoFromFile(const char* filename)
{
    // Open the file
    ... 5 lines to open a file...

    // Check the magic number
    ... 3 lines read some bytes and make sure they match the magic number...

    // Read the header
    ... 5 lines to read the header

    // Find the offset of the info
    ... 10 lines to find the offset of the information you need

    // Read the info
    ... 5 lines to read the information you need

    // Reformat the info
    ... 10 lines to change the format of the information into whatever you need ...

    // etc.
}

これらのコメントのそれぞれは、おそらく、目前のタスク用に記述できる個別の関数を示しています。それらのすべてが必要なわけではありません。たとえば、ファイルを開くコードをそのままにしておくことができます。しかし、おそらくヘッダーを読み取ってファイル内のオフセットを見つける必要がある他のコードもあるので、それらは別個の関数になる可能性があります。情報を読み取ることは、関数が行うことになっていることなので、それはそのままにしておきます。それを再フォーマットすることはおそらく別の関数であるべきですが、そのスコープによっては、この関数からそれを呼び出すのが妥当かもしれません。基本的に、手元のタスクに対して実行する複数のステップがある場合、それらのステップが1行または2行を超える場合は、それらを関数に入れます。

11
user1118321

重複するコードがあるときはいつでも関数を作成することは悪い考えです。その複製のほとんどは偶発的なものである可能性が高く、見た目によっては変更される可能性があります。

より適切なガイドラインは、何かが適切な名前で明確に定義されたビルディングブロックであると識別した場合、抽象化(関数、クラス、名前付き定数、名前空間など)を作成することです。

詳細を抽象化することに加えて、より多くのものを構築するための要素である優れたビルディングブロックは、コードの重複を削減するだけで、喜ばしい副作用です。

5
Deduplicator

モジュール、クラス関数は、アプリケーションの構築に使用するプログラマーのツールです。

プログラマーは、ツールの量に制限なく自由にツールを使用する必要があります。開発中に100個の関数とクラスが作成された場合、それらはすべて必要であると判断したことになります。問題はありません。
プログラマーだけでは質問に答えることができないため、この特定のタスクに対する私のソリューションはやりすぎです。
だからこそ、他のプログラマーがあなたのアプローチを見て意見を述べることができるコードレビューがあります。次に、あなたの意見とレビュアーの意見を入れて、あなたのチームを満足させるアプローチになります。

プログラミングはすべてコンテキストに関するものであり、具体的な問題についての完全な知識があれば、どのアプローチを選択する必要があるか、そしてこのタスクにどれだけの抽象化で十分かを言えるでしょう。
しかし、私たちの問題は、タスクの初めに、問題のコンテキスト(問題のドメインについて)についての情報が、タスクの終わりよりも少ないということです。そのため、通常、タスクが完了した後、プログラマーは別の方法で実行する必要があると感じます。もちろん、次のタスクを開始する必要があるため、ソリューションを書き換えることはできません。これは、1〜2年後に、既存のコードを大幅に書き換えることなく物事を完了するためのひどい回避策を構築することにつながりました。

そのため、少ない労力/コストで変更できるアプリケーションを作成することが非常に重要です。

結合、直線的な依存関係は、効率的な変更を妨げる多くの理由のいくつかです。ある場所での変更が他の場所での大きな変更に追随するという状況になります。

そのため、最小の労力で組み合わせて、置き換えることができる異なる「ブロック」でロジックを分離することが非常に重要です。

SOLIDの原則は、プログラマーにとって非常に優れたガイドラインとなります。より厳密にプログラミング経験が少ない場合は、このガイドラインに従う必要があります。

あなたの特定のケースでは、「単一責任の原則」(SOLIDでは「S」)がガイドラインです-「クラス/関数は変更する理由が1つだけあるべきです」

独自の関数/クラスに抽出する候補を見つけるための有用なアプローチは、「テストファースト」アプローチ(TDDまたはテスト駆動開発)でコードを記述することです。

いくつかの関数のテストの作成を開始し、テストケースの構成が複雑になる複数の異なるテストケースが発生すると、テストのモックアップできる抽象化の背後にロジックの一部を配置できます。

2
Fabio

他の回答に加えて、関数/メソッドを分割することをお勧めするいくつかのインジケーター:

  • 静的コード分析ツールが警告 Cyclomatic Complexity
  • CQS Principle に違反しています。
  • これには、その名前のセマンティクスに適合しないロジックが含まれています。たとえば、SQLデータベースに対してクエリも実行するReadFileというメソッドがあります。
  • 大きなブロックまたは重要なswitchブロックを含むcaseが含まれています。
  • booleanフラグ引数を受け入れ、その動作を大幅に変更します。
  • その引数の1つ以上は、条件付きのネストされたブランチにのみ関連するか、他のものとは関連がないように見えます(例:Save(File[] files, string sqlConnectionString, TextBoxControl directoryTextBox, IpAddress webServer))。
2
Ben Cottrell

単一の責任の原則 を適用して、強力な 懸念の分離 を実現します。これにより、柔軟で保守可能なコードが得られます。

ロバートC.マーティンは、単一の責任原則を「クラスは変更する理由が1つだけあるべきです」と表現しています。これはOOコードにのみ適用されるとは考えないでください。クラスは メタクラスタイプ と見なされます。原則は、コード分解、モジュール、関数、メソッド、プロシージャは、Wordの「クラス」に取って代わることができ、その原則は当てはまります。

コードのどの部分にも、機能の単一の部分に対する責任があります。当然ながら、責任は結合を最小限に抑えるために「クラス」によって完全にカプセル化する必要があります。すべてのソフトウェアサービスは、責任範囲を狭くする必要があります。

実際には、機能を単一の短い語句で説明できない場合は、コードを分解する必要があります。 Wordのコードを「and」で分割します。

「人は名前と年齢で構成されている」という要件を前提とすると、人、名前、年齢の3つの別々のコードが生成されます。

このアプローチは、信頼性と保守性を最大化すると同時に、複雑さをローカライズおよび最小化します 懸念の分離

1
Martin Spamer

私が正しく理解している場合、中心的な問題は、既存の関数をいつ変更するか、新しい関数をいつ作成するか、新しい関数を作成するとコードが重複する可能性がある場合です。

まず、新しい関数を作成する理由は、既存の関数とは大幅に異なる何かを実行する関数が必要だからです。既存の関数がその目的を果たす場合、コードの重複を回避するために別の目的を果たすように変更することはしません。

ただし、新しい関数を作成しても、コードを複製する必要があるわけではありません。新しい関数に既存の関数と同じコードのチャンクが必要な場合は、それを独自の関数に抽出して、既存の関数と新しい関数の両方から呼び出すことができます。

IOW、既存の関数を変更するかどうか、およびコードを複製するかどうかは、通常、無関係な質問です。

特定のクラスで重複したコードが複数回必要な場合は、メソッドにリファクタリングできます。クラス全体で必要な場合は、新しいクラスの良い例です。コードの一部がa)複製したくないほど長く、b)何度も必要になるほど有用である場合、単体テストに値する可能性があります。

1
Scott Hannen

コードの重複を避けることについてすでに書いたものに加えて、

関数またはクラスの適切な名前を見つけるたびに、これを行う必要があります。良い名前は、実装を見ずに関数またはクラスが何をするかを読者に伝えます。

0
Frank Puffer