ヘルパースタイルの「ユーティリティバッグ」静的クラスを回避し、「フリー関数」をクリーンに処理するC#パターン


9

最近、私が使用するいくつかの大きなC#コードベースの周りに浮かぶいくつかのヘルパースタイルの「ユーティリティバッグ」静的クラスを確認していました。

// Helpers.cs

public static class Helpers
{
    public static void DoSomething() {}
    public static void DoSomethingElse() {}
}

私がレビューした特定の方法は

  • 主に互いに無関係です
  • 呼び出し間で持続する明示的な状態なしで、
  • 小さい、そして
  • それぞれが、関連のないさまざまなタイプによって消費されます。

編集:上記は申し立てられた問題のリストを意図したものではありません。これは、私が検討している特定のメソッドの一般的な特性のリストです。回答がより関連性の高いソリューションを提供するのに役立つコンテキストです。

この質問のためだけに、この種のメソッドをGLUM(一般的な軽量ユーティリティメソッド)と呼びます。「glum」の否定的な意味合いは部分的に意図されています。これが馬鹿げた言い回しとして出くわしたらすみません。

GLUMについての私自身のデフォルトの懐疑論はさておき、私はこれについて以下のことを好きではありません。

  • 静的クラスは名前空間としてのみ使用されています。
  • 静的クラス識別子は基本的に無意味です。
  • 新しいGLUMが追加されると、(a)この「バッグ」クラスは理由もなく触れられるか、または(b)新しい「バッグ」クラスが作成されます(通常、それ自体は問題ではありません。悪いのは、新しい静的クラスは多くの場合、無関係な問題を繰り返すだけですが、メソッド数は少なくなります)。
  • メタ命名は、それはだかどうか、必然的に、ひどい非標準、通常は内部的に矛盾しているHelpersUtilitiesまたは何でも。

これをリファクタリングするための合理的に良い&単純なパターンは何ですか?

私はおそらく強調する必要があります私が扱っているすべての方法は、互いにペアで関係がありません。それらをよりきめ細かく、それでもマルチメンバーの静的クラスのメソッドバッグに分解するための合理的な方法はないようです。


1
GLUMs™について:メソッドはベストプラクティスに従って軽量にする必要があるため、「軽量」の省略形を削除できると思います;)
Fabio

@ファビオ同意する。私はこの質問を、この質問の記事の省略形として機能する(恐ろしくおかしくなく、おそらく混乱させる可能性がある)冗談としてこの用語を含めました。問題のコードベースが長持ちし、レガシーで、少し厄介であるため、ここでは「軽量」が適切であるように思われます。現時点で(エヘムなど)予算が多ければ、私は間違いなくより積極的なアプローチをとり、後者に取り組みます。
ウィリアム

回答:


17

お互いに密接に関連している二つの問題があると思います。

  • 静的クラスには論理的に無関係な関数が含まれています
  • 静的クラスの名前は、含まれている関数の意味/コンテキストに関する有用な情報を提供しません。

これらの問題は、論理的に関連するメソッドのみを1つの静的クラスに結合し、他のメソッドを独自の静的クラスに移動することで修正できます。問題ドメインに基づいて、あなたとあなたのチームは、メソッドを関連する静的クラスに分離するために使用する基準を決定する必要があります。

懸念事項と考えられる解決策

メソッドはほとんど互いに無関係です - 問題。 関連するメソッドを1つの静的クラスにまとめ、関連しないメソッドを独自の静的クラスに移動します。

メソッドには呼び出し間で永続化される明示的な状態がない - 問題ではありません。 ステートレスメソッドは機能であり、バグではありません。静的な状態はとにかくテストが困難であり、メソッド呼び出し全体で状態を維持する必要がある場合にのみ使用する必要がありますが、状態マシンやyieldキーワードなど、より良い方法があります。

メソッドは小さいです - 問題ありません。-メソッド小さくする必要があります。

メソッドはそれぞれ、無関係なさまざまなタイプによって消費されます - 問題ではありません。関連のない多くの場所で再利用できる一般的な方法を作成しました。

静的クラスは単に名前空間として使用されている - 問題ありません。これが静的メソッドの使用方法です。このスタイルの呼び出し方法が気になる場合は、拡張メソッドまたはusing static宣言を使用してみてください。

静的クラス識別子は、基本的には無意味である - 問題。開発者が無関係なメソッドを入れているので意味がありません。無関係なメソッドを独自の静的クラスに入れ、それらに特定のわかりやすい名前を付けます。

新しいGLUMが追加されると、(a)この「バッグ」クラスが影響を受ける(SRP悲しみ) - 論理的に関連するメソッドのみが1つの静的クラスに含まれるべきであるという考えに従う場合は問題ありませんSRP原則はクラスまたはメソッドに適用する必要があるため、ここでは違反していません。静的クラスの単一の責任は、1つの一般的な「アイデア」に対して異なるメソッドを含むことを意味します。そのアイデアは、機能や変換、反復(LINQ)、データの正規化、検証などです...

またはそれほど頻繁ではない(b)新しい「バッグ」クラスが作成される(バッグが増える) - 問題ではない。 クラス/静的クラス/関数-ソフトウェアの設計に使用する私たちの(開発者向け)ツールです。あなたのチームはクラスの使用にいくつかの制限がありますか?「より多くのバッグ」の最大制限は何ですか?プログラミングはすべてコンテキストに関するものであり、特定のコンテキストでのソリューションの場合、論理的に名前空間/フォルダーに論理的に配置された、わかりやすい名前の200の静的クラスが存在する場合は問題ありません。

メタ命名は、それがヘルパー、ユーティリティ、または任意のかどうか、必然的に、ひどい非標準、通常は内部的に一貫性がない - 問題。 予想される動作を説明する、より適切な名前を提供します。関連するメソッドのみを1つのクラスに保持します。これにより、ネーミングを改善できます。


これはSRP違反ではありませんが、明らかにオープン/クローズド原則違反です。それは私が、一般的に、OCPはそれの価値よりも多くのトラブルであることがわかってきた、と述べた
ポール

2
@ Paul、Open / Closeの原則は静的クラスには適用できません。それが私のポイントでした、SRPまたはOCPの原則は静的クラスには適用できないということです。静的メソッドに適用できます。
Fabio、

ここで混乱がありました。質問の項目の最初のリストは、疑わしい問題のリストではありません。これは、私が検討している特定のメソッドの一般的な特性のリストです。回答がより適切なソリューションを提供するのに役立つコンテキストです。明確にするために編集します。
ウィリアム

1
「静的クラスを名前空間として」と同じく、それが一般的なパターンであるという事実は、それがアンチパターンではないことを意味するものではありません。この特定の低凝集性静的クラス内のメソッド(基本的にはC / C ++から用語を借用するための「フリー関数」)は、この場合に意味のあるエンティティです。この特定のコンテキストでは、単一のファイルに100のメソッドを持つA静的クラスよりも、100の単一メソッドの静的クラス(100ファイル内)を持つサブ名前空間の方が構造的に優れているようAです。
ウィリアム

1
私はあなたの答えにかなり大規模なクリーンアップを行いました。最後の段落が何であるかわかりません。だれも、Math静的クラスを呼び出すコードをどのようにテストしているかについて心配していません。
Robert Harvey

5

このようなC#の状況では、静的クラスが名前空間のように使用されるという規則を採用してください。名前空間には適切な名前が付けられるため、これらのクラスも同様です。using staticC#6機能により、生活も少し楽になります。

臭いのようなクラス名はHelpers、可能であればメソッドをより適切な名前の静的クラスに分割する必要があることを示しますが、強調している前提の1つは、処理しているメソッドが「ペアワイズに関連しない」ことです。既存のメソッドごとに1つの新しい静的クラス。もしそうなら、それはおそらく大丈夫です

  • 新しい静的クラスには良い名前があり、
  • プロジェクトの保守性に実際に役立つと判断します。

不自然な例として、のようHelpers.LoadImageなものになるかもしれませんFileSystemInteractions.LoadImage

それでも、静的クラスのメソッドバッグになる可能性があります。これが発生するいくつかの方法:

  • おそらく、元のメソッドの一部のみ(またはまったく)が、新しいクラスに適切な名前を付けていません。
  • おそらく、新しいクラスは後で他の関連するメソッドを含むように進化します。
  • おそらく、元のクラス名は臭くなく、実際には大丈夫でした。

これらの静的クラスメソッドバッグは、実際のC#コードベースではそれほど珍しくはないことを覚えておくことが重要です。いくつかの小さなものを持っていることはおそらく病的ではありません。


それぞれの「フリー関数」のようなメソッドを独自のファイルに含めることの利点が本当にある場合(これは、プロジェクトの保守性に実際に役立つと正直に判断すると価値があり)、静的クラスではなく静的クラスにすることを検討できます。部分クラスusing static。名前空間と同様の静的クラス名を使用し、それをを介して使用します。例えば:

このパターンを使用したダミープロジェクトの構造

Program.cs

namespace ConsoleApp1
{
    using static Foo;
    using static Flob;

    class Program
    {
        static void Main(string[] args)
        {
            Bar();
            Baz();
            Wibble();
            Wobble();
        }
    }
}

Foo / Bar.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Bar() { }
    }
}

Foo / Baz.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Baz() { }
    }
}

Flob / Wibble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wibble() { }
    }
}

Flob / Wobble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wobble() { }
    }
}

-6

一般的に、「一般的なユーティリティメソッド」は必要ありません。あなたのビジネスロジックで。別のハードな外観を取り、適切な場所に置きます。

数学ライブラリなどを書いているのであれば、私は通常は嫌いですが、拡張メソッドをお勧めします。

拡張メソッドを使用して、標準のデータ型に関数を追加できます。

たとえば、Helpers.MySortの代わりにMySort()の一般的な関数があるか、新しいListOfMyThings.MySortを追加すると、IEnumerableに追加できます


ポイントは、さらに別の「ハードルック」をとらないことです。ポイントは、ユーティリティの「バッグ」を簡単にクリーンアップできる再利用可能なパターンを持つことです。ユーティリティは臭いだと知っています。質問はこれを述べています。目標は、賢明これらの独立した(あまりにも密接に共同設置)ドメインエンティティ隔離することである今のを、プロジェクトの予算にできるだけ簡単に行きます。
ウィリアム

1
あなたがいる場合はありません持っている「一般的なユーティリティメソッドを、」あなたはおそらく、あなたのロジックは十分の残りの部分からロジックの部分を分離していません。たとえば、私が知る限り、.NETには汎用のURLパス結合関数はないため、私はしばしばそれを記述してしまいます。その種のくだらないものは標準ライブラリの一部としてより良いでしょうが、すべての標準ライブラリには何かが欠けています。代替は、あなたの他のコード内で直接繰り返しロジックを残して、それが作る大幅に読みにくく。
jpmc26 2017年


1
@Ewanは関数ではなく、汎用のデリゲートシグネチャです...
TheCatWhisperer 2017年

1
@EwanそれがTheCatWhispererの全体的なポイントでした。ユーティリティクラスは、クラスにメソッドを配置する必要があることについての骨の折れるものです。同意してください。
jpmc26 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.