web-dev-qa-db-ja.com

長いメソッドのリファクタリング:そのままにするか、メソッドに分離するか、ローカル関数を使用する

私がこのような長い方法を持っていると仮定します:

public void SomeLongMethod()
{
    // Some task #1
    ...

    // Some task #2
    ...
}

このメソッドには、個別のメソッドまたはローカル関数に移動する必要がある繰り返し部分はありません。

長いメソッドはコードのにおいだと思っている人はたくさんいます(私を含めて)。また、私は#region(s)ここと これがなぜ悪いのかを説明する非常に人気のある答えがあります


しかし、このコードをメソッドに分離すると

public void SomeLongMethod()
{
    Task1();

    Task2();
}

private void Task1()
{
    // Some task #1
    ...
}

private void Task2()
{
    // Some task #1
    ...
}

次の問題が発生します。

  • 単一のメソッドによって内部的に使用される定義でクラス定義スコープを汚染しているということは、どこかにTask1およびTask2SomeLongMethodの内部のみを対象としています(または、私のコードを読むすべての人がこのアイデアを推測する必要があります)。

  • 単一のSomeLongMethodメソッド内で一度だけ使用されるメソッドの汚染IDEオートコンプリート(たとえばIntellisense))。


したがって、このメソッドコードを local functions に分離すると、

public void SomeLongMethod()
{
    Task1();

    Task2();

    void Task1()
    {
        // Some task #1
        ...
    }

    void Task2()
    {
        // Some task #1
        ...
    }
}

これには個別のメソッドの欠点はありませんが、元のメソッドよりも(少なくとも私にとっては)見栄えがよくありません。


SomeLongMethodのどのバージョンがあなたにとってより保守可能で読みやすいですか、そしてなぜですか?

9

自己完結型のタスクは、自己完結型のメソッドに移動する必要があります。この目標を無効にするほど大きな欠点はありません。

それらはクラスの名前空間を汚染すると言いますが、それはあなたのコードを因数分解するときに見なければならないトレードオフではありません。クラス(たとえばあなた)の将来の読者は、「この特定のメソッドは何をし、どのように変更する必要があるか」と尋ねる可能性が高くなります。変更された要件は何を言っているのですか?」 「クラスのすべてのメソッドの目的と意味は何ですか?」したがって、各メソッドを理解しやすくします(要素が少ない)のほうが、クラス全体を理解しやすくする(メソッドの数を減らす)よりもはるかに重要です。

もちろん、タスクが完全に自己完結型ではないが、いくつかの状態変数を移動する必要がある場合は、代わりにメソッドオブジェクト、ヘルパーオブジェクト、または同様の構成を選択できます。そして、タスクが完全に自己完結型およびで、アプリケーションドメインにまったくバインドされていない場合(たとえば、文字列のフォーマットのみの場合、おそらくそれらはまったく異なる(ユーティリティ)クラスですが、「クラスごとのメソッドの保持」がせいぜい非常に副次的な目標であるという事実は変わりません。

13
Kilian Foth

私はどちらのアプローチも好きではありません。より広いコンテキストは提供していませんが、ほとんどの場合は次のようになります。

複数のサービスの力を単一の結果に結合することによって、いくつかの仕事をするクラスがあります。

class SomeClass
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;

    public void SomeLongMethod()
    {
        _service1.Task1();
        _service2.Task2();       
    }
}

各サービスは、ロジックの一部のみを実装します。このサービスだけができる特別なこと。メインAPIをサポートするメソッド以外のプライベートメソッドがあり、簡単に並べ替えることができ、いずれにせよそれらの数が多すぎないようにする必要がある場合。

class Service1
{
    public void Task1()
    {
        // Some task #1
        ...
    }

    private void SomeHelperMethod() {}
}

class Service2
{
    void Task2()
    {
        // Some task #1
        ...
    }
}

要点は、メソッドを破壊するためにコード内に領域を作成し始めたら、新しいサービスでそれらのロジックをカプセル化することを考え始めるときです。

6
t3chb0t

メソッド内で関数を作成する場合の問題は、メソッドの長さが減らないことです。

追加レベルの保護「プライベートメソッド」が必要な場合は、新しいクラスを作成できます。

public class BigClass
{
    public void SomeLongMethod();

    private class SomeLongMethodRunner
    {
        private void Task1();
        private void Task2();
    }
}

しかし、クラス内のクラスは非常に醜いです:/

2
Ewan

変数やメソッドなどの言語構成要素のスコープを最小限に抑えることをお勧めします。例えば。グローバル変数を避けます。これにより、コードが理解しやすくなり、構造体にアクセスできる場所が少なくなるため、誤用を防ぐことができます。

これは、ローカル関数を使用することに賛成です。

2
fsl

長いメソッドは多くの場合コードのにおいがすることに同意しますが、それが全体像だとは思いません。むしろ、メソッドが長いのは問題を示す理由です。私の一般的なルールは、コードが複数の異なるタスクを実行している場合、それらのタスクのそれぞれが独自のメソッドであることです。私にとって、コードのこれらのセクションが反復的であるかどうかは重要です。将来的にこれらを個別に個別に呼び出すことは絶対にないと確信しているのでない限り(そしてそれを確認するのは非常に難しいことです!)それらを別のメソッドに入れてください(そしてそれをプライベートにしてください-これは非常に強力なインジケータになるはずです)あなたはそれがクラスの外で使用されることを期待していません)。

私はまだローカル関数を使用していないことを告白しなければならないので、私の理解はここでは完全にはほど遠いですが、提供されたリンクは、ラムダ式と同様の方法で使用されることが多いことを示唆しています(ただし、ここにはいくつかの使用例があります)それらがラムダよりも適切である場合、またはラムダを使用できない場合は、詳細については ローカル関数とラムダ式の比較 を参照してください)。ここでは、「その他の」タスクの細分性にまで及んでいると思います。それらがかなり些細なものであれば、おそらくローカル関数は大丈夫でしょうか?一方、巨大なラムダ式(おそらく開発者が最近LINQを発見した場所)の例を見てきました。これにより、コードが別のメソッドから単に呼び出された場合よりもコードの理解と保守がはるかに難しくなります。

Local Functions ページは次のように述べています:

'ローカル関数は、コードの意図を明確にします。コードを読んでいる人は誰でも、含まれているメソッド以外ではメソッドを呼び出せないことがわかります。チームプロジェクトの場合、クラスまたは構造体の他の場所から別の開発者が誤って直接メソッドを呼び出すこともできなくなります。

使用の理由として、これを実際にMSで使用しているのかどうかはわかりません。メソッドのスコープ(およびその名前とコメント)により、その時点での意図が明確になります。ローカル関数を使用して、私はそれを見ることができました、他の開発者はそれをもっと神聖なものとして見るかもしれませんが、将来的にその機能を再利用する必要がある変更が発生した場合、独自の手順を記述してローカル関数をそのままにしておく可能性があります;これにより、コードの重複の問題が発生します。上記の引用の後半の文は、呼び出す前にチームメンバーがプライベートメソッドを理解することを信頼せず、不適切に呼び出す場合に関連する可能性があります(そのため、決定が影響を受ける可能性がありますが、ここでは教育の方が適しています) 。

要約すると、一般的には、メソッドに分離することをお勧めしますが、MSは理由のためにローカル関数を作成したので、それらを使用しないとは言いませんが、

1
d219