'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

예, @David, 언어를 언급하지 않아서 죄송합니다. 대답에 어떤 영향을 줄 수 있습니까?
Saeed Neamati

일부 언어에는 약간의 코드를 고려하는 데 도움이되는 특정 구문이있을 수 있습니다. C #을 모르지만 Ruby에서는 블록을 사용하여 사용 부분을 고려할 수 있다고 생각합니다.
David

사용하는 문은 실제로와 자원 처리를 관리하면서 코딩 도움 않도록 반복적으로 DRY 원칙을 적용하는 C # 언어 지원 제공 폐기 디자인 패턴을 . 그것은 우리가 물건을 건조시키는 방법을 찾을 수 없다는 것을 의미하지는 않습니다! 개인적으로 DRY는 재귀 프로세스라고 생각합니다.
John Tobler

답변:


24

하나의 아이디어는 그것을 취하는 함수로 감싸는 것입니다 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 당신이 이것을하는 두 가지 이상의 유형을 가지고 있다고 가정하기 때문에 유형 매개 변수도 . 그렇지 않은 경우 제거하고 반환 유형에 type 매개 변수를 사용할 수 있습니다.

솔직히 말해서 이런 종류의 코드는 가독성에 전혀 도움이되지 않습니다. 내 경험상 더 많은 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가 아닌 동료)의 경우 코드의 기능에 대한 인식이 느려진다 고 생각합니다.
Brook

+1이 방법은 괜찮다고 생각합니다. 가독성을 향상시킬 수 있습니다. 예를 들어, "ToList"를 WithEntities에 넣고을 Func<T,IEnumerable<K>>대신 사용 Func<T,K>하고 "WithEntities"에 더 나은 이름 (예 : SelectEntities)을 지정하십시오. "엔티티"가 일반적인 매개 변수 일 필요는 없다고 생각합니다.
Doc Brown

1
이 작업을하려면 제약 할 필요가 where T : IDisposable, new()같이 using필요로 IDisposable작업하기 위해.
Anthony Pegram

1
@Anthony Pegram : 고마워요! (브라우저 창에서 C #을 코딩하기 위해 얻는 것입니다)
Brook

23

나에게 이것은 같은 컬렉션에 대해 여러 번 foreaching을 걱정하는 것과 같습니다. 그것은 당신이해야 할 일입니다. 더 추상화하려고 시도하면 코드를 훨씬 덜 읽을 수 있습니다.


훌륭한 비유 @Ben. +1
Saeed Neamati

3
동의하지 않습니다. 죄송합니다. 트랜잭션 범위에 대한 OP의 의견을 읽고 이러한 종류의 코드를 500 배 작성했을 때 수행해야 할 작업에 대해 생각한 다음 500 번 동일한 것을 변경해야합니다. 이러한 종류의 코드 반복은 10 개 미만의 기능이있는 경우에만 정상일 수 있습니다.
Doc Brown

아, 그리고 각 컬렉션마다 비슷한 모양의 코드를 사용하여 동일한 모음을 10 회 이상 매우 비슷한 방식으로 수집하는 경우, 일반화에 대해 확실히 생각해야합니다.
Doc Brown

1
나에게 당신이 하나의 라이너로 3 개의 라이너를 만들고있는 것처럼 보입니다 ... 당신은 여전히 ​​자신을 반복하고 있습니다.
Jim Wolff

예를 들어 foreach매우 큰 컬렉션에 있거나 foreach루프 내 논리 가 시간이 오래 걸리면 상황에 따라 다릅니다 . 내가 채택한 모토 : 집착하지 말고 항상 당신의 접근 방식을 염두에 두십시오
Coops

9

"한 번만"원칙과 DRY 원칙을 혼동하는 것 같습니다. DRY 원칙은 다음과 같이 말합니다.

모든 지식은 시스템 내에서 하나의 명백하고 권위있는 표현을 가져야합니다.

그러나 Once and Only Once 원칙은 약간 다릅니다.

[DRY] 원칙은 OnceAndOnlyOnce와 유사하지만 목적이 다릅니다. OnceAndOnlyOnce를 사용하면 중복 코드 및 기능을 제거하기 위해 리팩토링하는 것이 좋습니다. DRY를 사용하면 시스템에 사용 된 모든 지식 조각의 단일 결정적인 소스를 식별 한 다음 해당 소스를 사용하여 해당 지식의 적용 가능한 인스턴스 (코드, 문서, 테스트 등)를 생성 할 수 있습니다.

DRY 원칙은 일반적으로 실제 논리와 관련하여 사용되며 명령문을 사용하여 너무 많이 중복되지 않습니다.

프로그램의 구조를 DRY로 유지하는 것은 어렵고 가치는 낮습니다. 비즈니스 규칙, if 문, 수학 공식 및 메타 데이터가 한 곳에만 표시되어야합니다. HTML 페이지, 중복 테스트 데이터, 쉼표 및 {} 구분 기호 등의 WET 항목은 모두 무시하기 쉬우므로 DRY를 사용하는 것이 덜 중요합니다.

출처


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())트랜잭션의 범위를 정의하기 위해 코드에서 코드 블록을 사용하는 곳이 많이 있습니다 . 단일 객체로 묶을 수 없으며 트랜잭션을 사용하려는 모든 곳에서 블록을 작성해야합니다. 이제부터 그 거래의 유형을 변경하는 것을 원하는 경우 CustomTransactionBuiltInTransaction500 개 이상의 방법에? 이것은 반복적 인 작업이며 DRY 교장의 위반 사례 인 것 같습니다.
Saeed Neamati 12

3
여기에 "사용"하면 데이터 컨텍스트가 닫히므로 이러한 메소드 외부에서는 게으른 로딩이 불가능합니다.
Steven Striga

@ 사이드 : 의존성 주입을 살펴볼 때입니다. 그러나 그것은 질문에 명시된 것과는 상당히 다른 것처럼 보입니다.
CVn

@Saeed : 게시물이 업데이트되었습니다.
back2dos

@WeekendWarrior using(이 문맥에서) 여전히 더 알려지지 않은 "편의 구문"입니까? =)를 사용하는 것이 좋은 이유
Coops

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 번 반복되고 점점 더 반복되고 있습니다.
Saeed Neamati 12

DRY는 주로 여러 번 테스트 케이스를 작성해야하며 한 곳에서 버그가 발생하면 100 곳에서 수정해야한다는 사실에서 비롯됩니다. using (Entities ...)은 너무 간단한 코드입니다. 분할하거나 다른 클래스에 넣을 필요는 없습니다. 여전히 단순화를 주장한다면. 루비의 Yeild 콜백 기능을 제안합니다.
arunmur

1
@arnumur-사용하기가 항상 너무 간단하지는 않습니다. 데이터 컨텍스트에서 사용할 옵션을 결정하는 좋은 논리가 종종 있습니다. 잘못된 연결 문자열이 전달 될 수 있습니다.
Morgan Herlocker

1
@ironcode-그러한 상황에서는 기능으로 밀어 넣는 것이 좋습니다. 그러나이 예에서는 깨지기 매우 어렵습니다. 수정이 실패하더라도 종종 Entities 클래스 자체의 정의에 있으며 별도의 테스트 사례로 다루어야합니다.
arunmur

@arunmur +1-동의합니다. 나는 보통 그 일을한다. 해당 함수에 대한 테스트를 작성하지만 using 문에 대한 테스트를 작성하는 것이 약간 위에 보입니다.
Morgan Herlocker

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>에 대한 유형 매개 변수의 순서가 잘못되었습니다. (기본적으로 같은 것을 내 대답 참조)
Brook

글쎄, 쉽게 수정할 수 있습니다. 나는 올바른 순서가 무엇인지 결코 기억하지 못한다. 나는 Func충분히 사용해야합니다.
트래비스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.