web-dev-qa-db-ja.com

多くの静的メソッドを使用するのは悪いことですか?

クラスが内部状態を追跡する必要がない場合、クラス内のすべてのメソッドを静的として宣言する傾向があります。たとえば、AをBに変換する必要があり、変化する可能性のある内部状態Cに依存しない場合、静的変換を作成します。調整できるようにする内部状態Cがある場合は、コンストラクターを追加してCを設定し、静的変換を使用しません。

私はさまざまな推奨事項(StackOverflowを含む)を読んで、静的メソッドを使いすぎないようにしましたが、上記の経験則で何が間違っているのか理解できません。

それは合理的なアプローチですか?

89
Lolo

一般的な静的メソッドには2種類あります。

  • 「安全な」静的メソッドは、同じ入力に対して常に同じ出力を提供します。グローバルを変更せず、クラスの「安全でない」静的メソッドを呼び出しません。基本的に、限られた種類の関数型プログラミングを使用しています。これらを恐れてはいけません。問題ありません。
  • 「安全でない」静的メソッドは、グローバルな状態、プロキシをグローバルなオブジェクト、またはその他のテスト不可能な動作に変更します。これらは手続き型プログラミングへの先祖返りであり、可能な限りリファクタリングする必要があります。

「安全でない」静的変数には、たとえばシングルトンパターンなど、いくつかの一般的な使用方法がありますが、きれいな名前を呼んでも、グローバル変数を変更しているだけであることに注意してください。安全でない静電気を使用する前に慎重に検討してください。

142
John Millikin

内部状態のないオブジェクトは疑わしいものです。

通常、オブジェクトは状態と動作をカプセル化します。動作のみをカプセル化するオブジェクトは奇妙です。時々LightweightまたはFlyweightの例です。

また、オブジェクト言語で行われる手続き型の設計もあります。

15
S.Lott

これは本当にジョン・ミリキンの素晴らしい答えのフォローアップにすぎません。


ステートレスメソッド(ほとんど関数)を静的にすることは安全ですが、変更が困難なカップリングにつながる場合があります。次のような静的メソッドがあると考えてください。

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

あなたは次のように呼びます:

StaticClassVersionOne.doSomeFunkyThing(42);

静的メソッドの動作を変更する必要がある場合に遭遇し、StaticClassVersionOneに緊密にバインドされていることがわかるまで、これはすべて良好であり、非常に便利です。コードを修正することもできますが、それでも問題ありませんが、古い動作に依存している他の呼び出し元がいる場合は、メソッドの本体で説明する必要があります。場合によっては、これらのすべての動作のバランスをとろうとすると、メソッド本体がかなりugい、または保守不能になる可能性があります。メソッドを分割する場合は、いくつかの場所でコードを変更してそれを考慮するか、新しいクラスを呼び出す必要があります。

しかし、メソッドを提供するインターフェイスを作成し、呼び出し元に提供した場合、動作を変更する必要があるときに、新しいクラスを作成してインターフェイスを実装することができます。それは代わりに発信者に与えられます。このシナリオでは、呼び出し元のクラスを変更したり、再コンパイルしたりする必要はなく、変更はローカライズされます。

それはありそうな状況かもしれないしそうでないかもしれないが、私はそれを検討する価値があると思う。

11
Grundlefleck

他のオプションは、元のオブジェクトに非静的メソッドとして追加することです:

つまり、変更:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

public class Bar {
    ...
    public Foo transform() { ...}
}

ただし、多くの場合、これは不可能です(例:XSD/WSDL/etcからの通常のクラスコード生成)。または、クラスが非常に長くなり、変換メソッドは複雑なオブジェクトにとっては本当に苦痛になることがあります。独自の個別のクラスで。そのため、ユーティリティクラスには静的メソッドがあります。

6
JeeBee

静的クラスは、適切な場所で使用される限り問題ありません。

すなわち、「リーフ」メソッドであるメソッド(状態を変更せず、単に何らかの方法で入力を変換するだけです)。これの良い例は、Path.Combineのようなものです。これらの種類のものは便利であり、簡潔な構文になります。

問題点私が静的に抱えている問題は数多くあります

まず、静的クラスがある場合、依存関係は非表示になります。以下を考慮してください。

public static class ResourceLoader
{
    public static void Init(string _rootPath) { ... etc. }
    public static void GetResource(string _resourceName)  { ... etc. }
    public static void Quit() { ... etc. }
}

public static class TextureManager
{
    private static Dictionary<string, Texture> m_textures;

    public static Init(IEnumerable<GraphicsFormat> _formats) 
    {
        m_textures = new Dictionary<string, Texture>();

        foreach(var graphicsFormat in _formats)
        {
              // do something to create loading classes for all 
              // supported formats or some other contrived example!
        }
    }

    public static Texture GetTexture(string _path) 
    {
        if(m_textures.ContainsKey(_path))
            return m_textures[_path];

        // How do we know that ResourceLoader is valid at this point?
        var texture = ResourceLoader.LoadResource(_path);
        m_textures.Add(_path, texture);
        return texture; 
    }

    public static Quit() { ... cleanup code }       
}

TextureManagerを見ると、コンストラクターを見ても、どの初期化ステップを実行する必要があるかわかりません。クラスを詳しく調べて、依存関係を見つけ、正しい順序で初期化する必要があります。この場合、実行する前にResourceLoaderを初期化する必要があります。この依存関係の悪夢を拡大すると、おそらく何が起こるか推測できます。初期化の明示的な順序がない場所でコードを維持しようとすることを想像してください。インスタンスとの依存性注入とは対照的です-その場合、依存性が満たされない場合、コードはcompileさえもしません!

さらに、状態を変更するスタティックを使用する場合、それはカードの家のようなものです。誰が何にアクセスできるかは決してわかりませんし、デザインはスパゲッティモンスターに似ている傾向があります。

最後に、同様に重要なこととして、静的関数を使用すると、プログラムが特定の実装に結び付けられます。静的コードは、テストしやすいように設計することのアンチテーゼです。静的な要素に満ちたコードのテストは悪夢です。静的な呼び出しは、(静的型をモックアウトするために特別に設計されたテストフレームワークを使用しない限り)テストダブルに交換することはできません。

要するに、静的なものはいくつかのことには問題なく、小さなツールや使い捨てのコードには使用を妨げないでしょう。ただし、それ以上に、保守性、優れた設計、テストの容易さという点で、悪夢です。

ここに問題に関する良い記事があります: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

4
Mark Simpson

それは合理的なアプローチのようです。あまり多くの静的クラス/メソッドを使用したくないのは、オブジェクト指向プログラミングから構造化プログラミングの領域に移行するためです。

単純にAからBに変換する場合は、テキストを変換してから

"hello" =>(transform)=> "<b>Hello!</b>"

次に、静的メソッドが理にかなっています。

ただし、オブジェクトでこれらの静的メソッドを頻繁に呼び出しており、多くの呼び出しに対して一意である傾向がある場合(たとえば、使用方法は入力によって異なります)、またはオブジェクトの固有の動作の一部である場合、オブジェクトの一部にしてその状態を維持するのが賢明です。これを行う1つの方法は、インターフェイスとして実装することです。

class Interface{
    method toHtml(){
        return transformed string (e.g. "<b>Hello!</b>")
    }

    method toConsole(){
        return transformed string (e.g. "printf Hello!")
    }
}


class Object implements Interface {
    mystring = "hello"

    //the implementations of the interface would yield the necessary 
    //functionality, and it is reusable across the board since it 
    //is an interface so... you can make it specific to the object

   method toHtml()
   method toConsole()
}

編集:静的メソッドの優れた使用の良い例の1つは、Asp.Net MVCまたはRubyのhtmlヘルパーメソッドです。これらは、オブジェクトの動作に関連付けられていないhtml要素を作成するため、静的です。

編集2:関数型プログラミングを構造化プログラミングに変更し(何らかの理由で混乱しました)、それを指摘してくれたTorstenの小道具。

4
MunkiPhD

静的メソッドを使用しないように警告される理由は、それらを使用するとオブジェクトの利点の1つが失われるためです。オブジェクトはデータのカプセル化を目的としています。これにより、予期しない副作用の発生を防ぎ、バグを回避できます。静的メソッドにはカプセル化されたデータ*がないため、この利点は得られません。

ただし、内部データを使用していない場合は、使用しても問題ありませんが、実行はわずかに高速です。ただし、それらのグローバルデータに触れないようにしてください。

  • 一部の言語には、データおよび静的メソッドのカプセル化を可能にするクラスレベル変数もあります。
4
Steve Rowe

最近、アプリケーションをリファクタリングして、最初は静的クラスとして実装されていたいくつかのクラスを削除/変更しました。時間が経つにつれて、これらのクラスは非常に多く取得され、人々は新しい関数を静的としてタグ付けし続けました。

したがって、私の答えは、静的クラスは本質的に悪いものではありませんが、今すぐインスタンスを作成し、後でリファクタリングしなければならない方が簡単かもしれないということです。

3
overslacked

デザインの匂いだと思います。ほとんど静的なメソッドを使用していることに気付いた場合、おそらく非常に良いOO=デザインはありません。必ずしも悪いわけではありません。より良いOOデザインを作成できる可能性があることを示唆しています。または、この問題については、他の方向に進んでOO.

2
patros

内部状態が機能しない限り、これは問題ありません。通常、静的メソッドはスレッドセーフであることが期待されるため、ヘルパーデータ構造を使用する場合は、スレッドセーフな方法で使用してください。

1
Lucero

never Cの内部状態を使用する必要があることがわかっている場合は、問題ありません。ただし、将来的に変更される場合は、メソッドを非静的にする必要があります。静的ではない場合、内部状態が必要ない場合は無視できます。

1
Adam Crume

ユーティリティメソッドの場合、静的にするのは良いことです。 GuavaとApache Commonsは、この原則に基づいて構築されています。

これに関する私の意見は純粋に実用的です。アプリコードの場合、一般的に静的メソッドは最適なものではありません。静的メソッドには単体テストの重大な制限があります-簡単に模擬することはできません。模擬の静的機能を他のテストに注入することはできません。通常、静的メソッドに機能を注入することもできません。

そのため、アプリロジックでは、通常、小さな静的ユーティリティのようなメソッド呼び出しがあります。つまり.

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

利点の1つは、このような方法をテストしないことです:-)

1
Andrey Chaschev

一般に、静的メソッドは、ステートレスコードであっても悪い選択です。代わりに、これらのメソッドを使用してシングルトンクラスを作成します。このインスタンスは、一度インスタンス化され、メソッドを使用するクラスに注入されます。このようなクラスは、モックとテストが簡単です。それらははるかにオブジェクト指向です。必要に応じて、プロキシでラップできます。 StaticsはOOをはるかに難しくし、ほとんどすべての場合にそれらを使用する理由はありません。100%ではなくほとんどすべて。

0
John