「using」キーワードを使用する場合のDRY原則の実装方法


23

次の方法を検討してください。

public List<Employee> GetAllEmployees()
{
    using (Entities entities = new Entities())
    {
        return entities.Employees.ToList();
    }
}

public List<Job> GetAllJobs()
{
    using (Entities entities = new Entities())
    {
        return entities.Jobs.ToList();
    }
}

public List<Task> GetAllTasksOfTheJob(Job job)
{
    using (Entities entities = new Entities())
    {
        return entities.Tasks.Where(t => t.JobId == job.Id).ToList();
    }
}

ブロックの使用方法は同じで、ここでは3回繰り返されています(もちろん、実際のアプリケーションでは100回以上)。usingブロックにDRY(Do n't Repeat Yourself)プリンシパルを実装するにはどうすればよいですか?DRYプリンシパルの違反とみなされますか?

更新:usingブロック内に実装されているものについては話していない。ここで私が実際に意味するのは、using (Entities entities = new Entities())です。この行は100回以上繰り返されます。


2
これはC#ですか?あなたの質問への答えは言語に依存する可能性があります
デビッド

ええ、@ David、ごめんなさい。回答にどのように影響しますか?
サイードネアマティ

一部の言語では、コードの一部を分解するのに役立つ特定の構文を使用できます。私はC#を知りませんが、Rubyでは、ブロックを使用して使用部分を分解できると思います。
デビッド

usingステートメントは、実際にリソース処分を管理しながらコーディングヘルプ回避の繰り返しにDRY原則を適用するためのC#言語のサポートを提供処分デザインパターンを。それは物をより乾燥させる方法を見つけることができないという意味ではありません!個人的には、DRYは再帰的なプロセスだと考えています。
ジョントブラー

回答:


24

1つのアイデアは、を受け取る関数でラップすることFuncです。

このようなもの

public K UsingT<T,K>(Func<T,K> f) where T:IDisposable,new()
{
    using (T t = new T())
    {
        return f(t);
    }
}

次に、上記のコードは

public List<Employee> GetAllEmployees()
{
    return UsingT<Entities,List<Employee>>(e=>e.Employees.ToList());
}

public List<Job> GetAllJobs()
{
    return UsingT<Entities,List<Job>>(e=>e.Jobs.ToList());
}

public List<Task> GetAllTasksOfTheJob(Job job)
{
    return UsingT<Entities,List<Task>>(e=>e.Tasks.Where(t => t.JobId == job.Id).ToList());
}

私はEntitiesあなたがこれをやっている複数の型があると仮定しているので、型パラメータも作成しました。そうでない場合は、それを削除して、戻り値の型に型パラメーターを使用することができます。

正直言って、この種のコードは読みやすさにはまったく役立ちません。私の経験では、より多くのJrの同僚も同様に非常に苦労しています。

あなたが考慮するかもしれないヘルパーのいくつかの追加のバリエーションを更新

//forget the Entities type param
public T UsingEntities<T>(Func<Entities,T> f)
{
    using (Entities e = new Entities())
    {
        return f(e);
    }
}
//forget the Entities type param, and return an IList
public IList<T> ListFromEntities<T>(Func<Entities,IEnumerable<T>> f)
{
    using (Entities e = new Entities())
    {
        return f(e).ToList();
    }
}
//doing the .ToList() forces the results to enumerate before `e` gets disposed.

1
+1素敵な解決策。ただし、実際の問題(元の質問には含まれていません)、つまりの多くの発生に対処していませんEntities
back2dos

@Doc Brown:関数名であなたを打ち負かしたと思います。私は左のIEnumerableいずれかの非があった場合には機能のうちにIEnumerable、呼び出し元が返さたいとTの性質が、あなたがしている権利は、それは少しそれをクリーンアップするでしょう。おそらく、シングルとIEnumerable結果の両方のヘルパーがあればいいでしょう。私はまだ(:) SOにされていないなど、あなたの同僚)それは、特にジェネリック医薬品とラムダの多くを使用することに慣れていないユーザーのために、コードが何をするかの認識を遅くすると思う、と述べている
ブルック

+1このアプローチは問題ないと思います。そして読みやすさを改善できます。たとえば、「ToList」をに入れWithEntities、のFunc<T,IEnumerable<K>>代わりに使用し、「WithEntities」にFunc<T,K>(SelectEntitiesのような)より良い名前を付けます。そして、ここでは「エンティティ」を一般的なパラメータにする必要はないと思います。
Doc Brown

1
この作業を行うには、制約があることが必要となるwhere T : IDisposable, new()よう、using必要となるIDisposable作業のために。
アンソニーペグラム

1
@アンソニーペグラム:修正、ありがとう!(ブラウザウィンドウでC#をコーディングすると得られます)
ブルック

23

私にとって、これは同じコレクションを何度もforeach-ingすることを心配するようなものです。それはあなたがする必要があることです。さらに抽象化しようとすると、コードの可読性が大幅に低下します。


@Benの大きな類似点。+1
サイードネアマティ

3
同意しません、すみません。トランザクションスコープに関するOPのコメントを読み、これらの種類のコードを500回記述した場合に何をしなければならないかを考え、同じことを500回変更する必要があることに気づきます。この種のコードの繰り返しは、これらの関数が10個未満の場合にのみ問題ありません。
Doc Brown

忘れてはならないのは、同じコレクションを非常に似た方法で10回以上for-eachし、for-eachの中に似たようなコードがある場合、何らかの一般化について明確に考える必要があります。
Doc Brown

1
私には、3ライナーを1ライナーにしているように見えますが、あなたはまだ自分自身を繰り返しています。
ジムウォルフ

foreach非常に大きなコレクションにある場合や、foreachループ内のロジックに時間がかかる場合など、コンテキストに依存します。私が採用するようになったモットー:取りつかれているのではなく、常にあなたのアプローチに留意してください
-Coops

9

「一度だけ」の原則とDRYの原則を混同しているようです。DRYの原則は次のとおりです。

すべての知識は、システム内で単一の明確な権威ある表現を持たなければなりません。

ただし、1回限りの原則はわずかに異なります。

[DRY]の原則はOnceAndOnlyOnceに似ていますが、目的が異なります。OnceAndOnlyOnceを使用すると、重複したコードと機能を排除するためにリファクタリングすることをお勧めします。DRYを使用すると、システムで使用されるすべての知識の単一の明確なソースを特定し、そのソースを使用してその知識の適用可能なインスタンス(コード、ドキュメント、テストなど)を生成しようとします。

DRYの原則は通常、実際のロジックのコンテキストで使用され、ステートメントを使用してそれほど冗長ではありません。

プログラムの構造をDRYに保つのは難しく、価値も低くなります。ビジネスルール、ifステートメント、数式、メタデータが1か所にしか表示されません。WETのもの-HTMLページ、冗長なテストデータ、カンマ、{}区切り文字-はすべて無視するのが簡単なので、それらを乾燥させることはそれほど重要ではありません。

ソース


7

私はusingここの使用を見ていません:

どうですか:

public List<Employee> GetAllEmployees() {
    return (new Entities()).Employees.ToList();
}
public List<Job> GetAllJobs() {
    return (new Entities()).Jobs.ToList();
}
public List<Task> GetAllTasksOfTheJob(Job job) {
    return (new Entities()).Tasks.Where(t => t.JobId == job.Id).ToList();
}

または、さらに良いことに、毎回新しいオブジェクトを作成する必要はないと思います。

private Entities entities = new Entities();//not sure C# allows for that kind of initialization, but you can do it in the constructor if needed

public List<Employee> GetAllEmployees() {
    return entities.Employees.ToList();
}
public List<Job> GetAllJobs() {
    return entities.Jobs.ToList();
}
public List<Task> GetAllTasksOfTheJob(Job job) {
    return entities.Tasks.Where(t => t.JobId == job.Id).ToList();
}

DRYの違反に関しては、DRYはこのレベルでは適用されません。実際、読みやすさの原則を除いて、実際には原則はありません。そのレベルでDRYを適用しようとするのは、実際には単なるアーキテクチャのマイクロ最適化です。これは、すべてのマイクロ最適化と同じように自転車の脱落であり、問題は解決しませんが、新しいものを導入するリスクさえあります。
私自身の経験から、そのレベルでコードの冗長性を削減しようとすると、本当に明確でシンプルなものを難読化することで、コード品質に悪影響を与えることを知っています。

編集:
はい。したがって、問題は実際にはusingステートメントではなく、毎回作成するオブジェクトへの依存関係です。コンストラクターを注入することをお勧めします。

private delegate Entities query();//this should be injected from the outside (upon construction for example)
public List<Employee> GetAllEmployees() {
    using (var entities = query()) {//AFAIK C# can infer the type here
        return entities.Employees.ToList();
    }
}
//... and so on

2
しかし、@ back2dosでは、using (CustomTransaction transaction = new CustomTransaction())コード内でコードブロックを使用して、トランザクションのスコープを定義する場所がたくさんあります。それを単一のオブジェクトにバンドルすることはできず、トランザクションを使用するすべての場所でブロックを作成する必要があります。今、何からその取引の種類を変更したい場合CustomTransactionBuiltInTransaction、500の以上の方法で?これは、繰り返しの作業であり、DRYプリンシパルの違反例のように思えます。
サイードネアマティ

3
ここで「使用」すると、データコンテキストが閉じられるため、これらのメソッド以外では遅延読み込みはできません。
スティーブンストライガ

@Saeed:依存性注入を調べるときです。しかし、それは質問で述べられているケースとはまったく異なるようです。
CVn

@Saeed:投稿を更新しました。
back2dos

@WeekendWarrior using(このコンテキストでは)まだ未知の「便利な構文」ですか?なぜそれが使用に素敵なようだ=)
小屋

4

重複コードを使用するだけでなく(重複コードであり、実際にはtry..catch..finallyステートメントと比較される)、toListも使用します。次のようにコードをリファクタリングします。

 public List<T> GetAll(Func<Entities, IEnumerable<T>> getter) {
    using (Entities entities = new Entities())
    {
        return getter().ToList();
    }
 }

public List<Employee> GetAllEmployees()
{
    return GetAll(e => e.Employees);
}

public List<Job> GetAllJobs()
{
    return GetAll(e => e.Jobs);
}

public List<Task> GetAllTasksOfTheJob(Job job)
{
    return GetAll(e => e.Tasks.Where(t => t.JobId == job.Id));
}

3

ここには、最後のものを除いて、いかなる種類のビジネスロジックもありません。私の見解では、それは本当に乾燥していません。

最後のブロックでは、usingブロックにDRYがありませんが、where句は使用する場所を変更する必要があると思います。

これは、コードジェネレーターの典型的な仕事です。コードジェネレータを作成してカバーし、各タイプに生成させます。


@arunmurはありません。ここには誤解がありました。DRYとは、using (Entities entities = new Entities())ブロックを意味します。つまり、このコード行は100回繰り返され、ますます繰り返されています。
サイードネアマティ

DRYは主に、テストケースを何度も作成する必要があるという事実から発生し、1箇所でバグが発生すると、100箇所で修正する必要があります。using(Entities ...)は単純すぎて壊れないコードです。分割したり、別のクラスに入れる必要はありません。まだそれを簡素化することを主張すれば。Yeildのrubyからのコールバック関数を提案します。
-arunmur

1
@arnumur-使用することは、必ずしも簡単すぎるとは限りません。多くの場合、データコンテキストで使用するオプションを決定するロジックが少しあります。間違った接続文字列が渡されることができることが非常に可能性がある。
モルガンHerlocker

1
@ironcode-そのような状況では、関数にプッシュするのが理にかなっています。しかし、これらの例では、破るのはかなり難しいです。たとえそれが壊れても、修正はエンティティクラス自体の定義に含まれていることが多く、別のテストケースでカバーする必要があります。
-arunmur

+1 @arunmur-同意します。私は通常それを自分で行います。私はその関数のテストを作成しますが、usingステートメントのテストを作成するのは少々やり過ぎです。
モーガンハーロッカー

2

同じ使い捨てオブジェクトを何度も作成および破棄しているため、クラス自体がIDisposableパターンを実装するのに適しています。

class ThisClass : IDisposable
{
    protected virtual Entities Context { get; set; }

    protected virtual void Dispose( bool disposing )
    {
        if ( disposing && Context != null )
            Context.Dispose();
    }

    public void Dispose()
    {
        Dispose( true );
    }

    public ThisClass()
    {
        Context = new Entities();
    }

    public List<Employee> GetAllEmployees()
    {
        return Context.Employees.ToList();
    }

    public List<Job> GetAllJobs()
    {
        return Context.Jobs.ToList();
    }

    public List<Task> GetAllTasksOfTheJob(Job job)
    {
        return Context.Tasks.Where(t => t.JobId == job.Id).ToList();
    }
}

これにより、クラスのインスタンスを作成するときに「使用する」だけで済みます。クラスがオブジェクトを破棄することを望まない場合は、メソッドに引数として依存関係を受け入れさせることができます。

public static List<Employee> GetAllEmployees( Entities entities )
{
    return entities.Employees.ToList();
}

public static List<Job> GetAllJobs( Entities entities )
{
    return entities.Jobs.ToList();
}

public static List<Task> GetAllTasksOfTheJob( Entities entities, Job job )
{
    return entities.Tasks.Where(t => t.JobId == job.Id).ToList();
}

1

比類なき魔法の私のお気に入りのビット!

public class Blah
{
  IEnumerable<T> Wrap(Func<Entities, IEnumerable<T>> act)
  {
    using(var entities = new Entities()) { return act(entities); }
  }

  public List<Employee> GetAllEmployees()
  {
    return Wrap(e => e.Employees.ToList());
  }

  public List<Job> GetAllJobs()
  {
    return Wrap(e => e.Jobs.ToList());
  }

  public List<Task> GetAllTasksOfTheJob(Job job)
  {
    return Wrap(e => e.Tasks.Where(x ....).ToList());
  }
}

Wrapそのアウトまたはあなたが必要とする魔法を抽象化するためにのみ存在します。これを常に推奨するかどうかはわかりませんが、使用することは可能です。「より良い」アイデアは、StructureMapなどのDIコンテナーを使用し、Entitiesクラスを要求コンテキストにスコープし、コントローラーに挿入し、コントローラーを必要とせずにライフサイクルを処理することです。


Func <IEnumerable <T>、Entities>の型パラメーターの順序が間違っています。(基本的に同じことを持っている私の答えを参照してください)
ブルック

まあ、修正するのに十分簡単です。正しい順序が何であるか覚えていない。使用するFuncのに十分です。
トラビス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.