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


9

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

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あり、そのTask2内部のみを対象としてSomeLongMethodいます(または、私のコードを読むすべての人がこのアイデアを推測する必要があります)。

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


このメソッドコードをローカル関数に分離すると

public void SomeLongMethod()
{
    Task1();

    Task2();

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

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

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


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



@gnat私の質問はjavaではなくc#に固有です。私の主な関心事は、単にメソッドに分離するかどうかではなく、古典的なメソッドとローカル関数についてです。
Vadim Ovchinnikov

@gnatまた、AFAIK Java(C#とは異なります)はネストされた関数をサポートしていません。
Vadim Ovchinnikov

1
privateキーワードは「クラス定義スコープ」を確実に保護しますか?
ユアン

1
待って……クラスの大きさは?
ユアン

回答:


13

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

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

もちろん、タスクが完全に自己完結型ではなく、移動するためにいくつかの状態変数を必要とする場合は、代わりにメソッドオブジェクト、ヘルパーオブジェクト、または同様の構成を選択できます。タスクは完全に自己完結している場合とし、すべてのアプリケーションドメインと密接に結びついていない(例えば、単に文字列の書式設定、そしておそらく彼らはまったく違う(ユーティリティ)クラスに行く必要があります。しかし、すべては「その事実を変更しないことクラスごとのメソッドを抑える」というのは、せいぜい非常に副次的な目標です。


つまり、ローカル関数ではなくメソッドを使用するのですか?
Vadim Ovchinnikov

2
@VadimOvchinnikov最近のコード折りたたみIDEでは、ほとんど違いがありません。ローカル関数の最大の利点は、パラメーターとして渡さなければならない変数にアクセスできることですが、それらがたくさんある場合、タスクは最初は本当に独立していませんでした。
キリアンフォス

6

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

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

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
        ...
    }
}

結論としては、コードにリージョンを作成してメソッドを破壊する場合は、新しいサービスでロジックをカプセル化することを検討するときがきました。


2

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

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

public class BigClass
{
    public void SomeLongMethod();

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

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


2

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

これは、ローカル関数を使用することを支持しています。


1
確かにコードを再利用する==多くの呼び出し元の間でメソッドを共有する。つまり、その範囲を最大化する
Ewan

1

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

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

ローカル関数のページのように述べています:

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.