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


97

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

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

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

回答:


152

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

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

シングルトンパターンなど、「安全でない」静的要素の一般的な使用方法はいくつかありますが、それらを呼び出すきれいな名前があっても、グローバル変数を変更するだけであることに注意してください。安全でない静力学を使用する前に注意深く考えてください。


これはまさに私が解決しなければならない問題でした-シングルトンオブジェクトの使用、またはむしろ誤用。
09

その最も優れた答えをありがとう。私の質問は、シングルトンが静的メソッドにパラメーターとして渡された場合、静的メソッドは安全ではないのですか?
トニーD

1
「純粋な関数」および「純粋でない関数」という用語は、関数型プログラミングで「安全」および「安全でない」静的と呼ばれるものに付けられた名前です。
Omnimike 2015

19

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

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

それ以外の場合は、オブジェクト言語で行われる手続き型の設計です。


6
あなたの言っていることを聞きましたが、Mathオブジェクトのようなものは、動作以外のものをどのようにカプセル化できますか?
JonoW 2009

10
彼は疑わしいと言っただけで、間違いではありません。
ビルK

2
@JonoW:数学は、多くのステートレス関数が存在する非常に特殊なケースです。もちろん、Javaで関数型プログラミングをしている場合は、多くのステートレス関数があります。
S.Lott、2009

13

これは、実際にはJohn Millikinのすばらしい回答のフォローアップにすぎません。


ステートレスメソッド(ほとんどの関数)を静的にしても安全ですが、変更が難しい結合が発生する場合があります。次のような静的メソッドがあるとします。

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

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

StaticClassVersionOne.doSomeFunkyThing(42);

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

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

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


5
これはありそうなシナリオであるだけでなく、静力学を最後の手段にしていると私は主張します。静力学はTDDを悪夢にもさせます。staticをどこで使用しても、モックアップすることはできません。無関係なクラスをテストするには、入出力が何であるかを知る必要があります。これで、静的の動作を変更すると、その静的を使用する無関係のクラスでのテストが失敗します。また、潜在的に重要な依存関係を開発者に通知するためにコンストラクターに渡すことができない非表示の依存関係になります。
DanCaveman

6

もう1つのオプションは、元のオブジェクトの非静的メソッドとしてそれらを追加することです。

つまり、変更:

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

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

ただし、多くの状況ではこれは不可能です(たとえば、XSD / WSDLなどからの通常のクラスコード生成)。または、クラスが非常に長くなり、変換メソッドは、多くの場合、複雑なオブジェクトにとって本当に苦痛であり、単にそれらを必要とするだけです。独自のクラスで。ええ、私はユーティリティクラスに静的メソッドを持っています。


5

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

つまり、「リーフ」メソッドであるメソッド(状態を変更せず、入力を何らかの形で変換するだけです)。この良い例は、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を初期化する必要があります。今、この依存関係の悪夢を拡大し、おそらく何が起こるかを推測できます。初期化の明示的な順序がない場所でコードを維持しようとしていると想像してください。これをインスタンスの依存性注入と比較してください。その場合、依存関係が満たされない場合、コードはコンパイルされません。

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

最後に、そして同じくらい重要なことですが、静的を使用すると、プログラムが特定の実装に結び付けられます。静的コードは、テスト容易性のための設計の正反対です。静的な問題に悩まされているコードのテストは悪夢です。静的呼び出しは、(静的型を模擬するように特別に設計されたテストフレームワークを使用しない限り)テストdoubleと入れ替えることはできません。そのため、静的システムは、それを使用するすべてを即時統合テストにします。

簡単に言えば、静的なものは問題のないものもあれば、小さなツールや使い捨てのコードの場合は使用しないでください。ただし、それを超えると、保守性、優れた設計、およびテストの容易さにとって、悪夢のような悪夢になります。

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


4

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

とはいえ、内部データを使用しない場合は、内部データを使用しても問題なく、実行速度がわずかに速くなります。ただし、グローバルデータには触れないでください。

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

4

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

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に賛成です。


2
静的メソッドを使用することは関数型プログラミングとしての資格があるとは思わないので、構造化プログラミングを意味していると思います。
Torsten Marek、

3

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

だから、私の答えは、静的クラスは本質的に悪いわけではありませんが、今すぐインスタンスを作成し始めて、後でリファクタリングする必要があるかもしれないということです。


3

デザインのにおいだと思います。主に静的なメソッドを使用している場合、おそらく非常に優れたOO設計はありません。それは必ずしも悪いわけではありませんが、すべてのにおいがそうであるように、それは私を止めて再評価させます。これは、オブジェクト指向の設計を改善できる可能性があること、またはこの問題に対してオブジェクト指向を完全に回避して別の方向に進む必要があることを示唆しています。


2

多くの静的メソッドとシングルトンを持つクラスの間を行ったり来たりしていました。どちらも問題を解決しますが、シングルトンは簡単に複数に置き換えることができます。(プログラマーは常に1つしかないと確信しているようであり、非常に限られた場合を除いて、静的メソッドを完全に放棄するのに十分なほど間違っていることに気付きました)。

とにかく、シングルトンを使用すると、後で何かをファクトリに渡して別のインスタンスを取得することができ、リファクタリングなしでプログラム全体の動作が変更されます。静的メソッドのグローバルクラスを別の「バッキング」データまたは動作がわずかに異なるもの(子クラス)に変更することは、お尻の大きな痛みです。

また、静的メソッドには同様の利点はありません。

そう、彼らは悪いです。


1

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


1

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


1

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

これに対する私の意見は純粋に実用的です。それがアプリのコードである場合、静的メソッドは通常、最善の方法ではありません。静的メソッドには、ユニットテストに重大な制限があります。簡単にモックすることはできません。モックした静的機能を他のテストに挿入することはできません。通常、機能を静的メソッドに挿入することもできません。

したがって、私のアプリロジックには、通常、静的なユーティリティのような小さなメソッド呼び出しがあります。すなわち

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

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


1

もちろん、特効薬はありません。静的クラスは、小さなユーティリティ/ヘルパーには問題ありません。しかし、ビジネスロジックプログラミングに静的メソッドを使用することは確かに悪です。次のコードを検討してください

   public class BusinessService
   {

        public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
        {
            var newItemId = itemsRepository.Create(createItem, userID, ownerID);
            **var searchItem = ItemsProcessor.SplitItem(newItem);**
            searchRepository.Add(searchItem);
            return newItemId;
        }
    }

ItemsProcessor.SplitItem(newItem);It への静的メソッド呼び出しが発生する

  • 明示的な依存関係が宣言されていないため、コードを詳しく調べないと、クラスと静的メソッドコンテナーの結合を見落とす可能性があります。
  • BusinessService分離からテストすることはできませんItemsProcessor(ほとんどのテストツールは静的クラスをモックしません)。これにより、単体テストが不可能になります。単体テストなし==低品質

0

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

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