2013-10-26 3 views
4

정적 및 런타임 검사를 사용하여 C# 코드 계약을 사용하기 시작합니다. 문제는 일부 코드 계약 점검이 메소드마다 중복 될 수 있으므로이를 방지 할 좋은 방법이 없다는 것입니다.코드 계약 명세서의 코드 중복을 피하는 방법

내 생각에 정적 분석기 경고를 피하고 가능한 경우 억제하지 않는 것이 좋습니다.

는 예를 살펴 수 있습니다 :

다음의 간단한 클래스가 있습니다. 우리가 모델 속성에 대한 공공의 기본 생성자 및 공공 세터를 제공해야합니다 (MVC, 데이터베이스 매핑 바인딩 모델 같은) 일부 반사 기반 기술에 대한

class Category 
{ 
    public string Name { get; set; } 
} 

class Article 
{ 
    public string Title { get; set; } 
    public string Content { get; set; } 
    public Category Category { get; set; } 
} 

: 이것은 일반적인 비즈니스 로직 모델 클래스의 예이다. 따라서 Category에 대한 Contract.Invariant (! string.IsNullOrEmpty (Name))가 항상 true라는 것을 보장 할 수는 없습니다.

그런 다음 내부 CategoryRepository 클래스에서 다음 메소드를 만듭니다. 모든 유효성 검사가 이전에 통과되었고 유효한 범주 만 허용한다고 가정합니다.

public void Add(Category category) 
{ 
    Contract.Requires(category != null); 
    Contract.Requires(!string.IsNullOrEmpty(category.Name)); 
    ... 
} 

지금까지는 그렇게 좋았습니다. 그런 다음 우리는 ArticleRepository 유사한 방법을 추가

public void Add(Article article) 
{ 
    Contract.Requires(article != null); 
    Contract.Requires(!string.IsNullOrEmpty(article.Title)); 
    Contract.Requires(!string.IsNullOrEmpty(article.Content)); 
    Contract.Requires(article.Category != null); 
    Contract.Requires(!string.IsNullOrEmpty(article.Category.Name)); 
    ... 
} 

을 문제점은 다음과 같습니다

1) 우리는 우리가 같은 검사 중복 할 필요가 계약에 유효한 카테고리 기대 모든 장소에서 :

Contract.Requires(category != null); 
Contract.Requires(!string.IsNullOrEmpty(category.Name)); 

때로는 Contract.Assume 메소드에서 이러한 확인을 수행해야 할 수도 있습니다.

2) 외부 클래스 (예 : Article)는 Category 클래스의 계약을 확인해야합니다. LoW 위반 및 기본 캡슐화 원칙과 같습니다.

1)과 같이 분류 클래스의 순수한 방법으로 중복 된 코드를 추출 :이 방법

[Pure] 
public static bool Valid(Category category) 
{ 
    if (category == null) 
     return false; 

    return !string.IsNullOrEmpty(category.Name); 
} 

을하고 이용 계약 :

Contract.Requires(Category.Valid(category)); 

나는 다음 솔루션을 시도 별로 좋은 해결책이 아니며 작동하지 않습니다. 정적 분석기가 만족스럽지 않습니다.

2) 범주에 대한 불변의 정의 :

[ContractInvariantMethod] 
void Invariant() 
{ 
    Contract.Invariant(!string.IsNullOrEmpty(Name)); 
} 

이 솔루션은 꽤 좋은 및 Category 클래스에서 불필요한 검사를 제거 할 수 있지만, 실제로 불변는 더 유효하지 않습니다 (예를 들어, 기본 생성자에서). 그리고 정적 분석기는이 위반을 정확하게 감지합니다.

내가 잘못하고 있으며 정적 분석기와 함께 코드 계약을 사용하는 더 편리한 방법이 있습니까?

답변

1

당신은 popsicle immutability의 아이디어를 사용하여 무엇을 할 수 있는지 하나 아이스 유효를 호출 할 수 있습니다 즉, 객체가 항상 한 번에 모든 속성이 유효하게 한 후 설정 한 유효하지 않습니다 있지만, 그런 식으로 남는다.

그런 식으로 데이터가 포함 된 개체의 유효성 검사를 수행하고 해당 개체를 사용하는 코드의 계약을 간단하게 thing != null && thing.IsValid()으로 단순화 할 수 있습니다.

다음은이 접근법을 보여주는 코드입니다. 정적 체커는 여전히 속성이 독립적으로 설정 되었기 때문에 a이 유효하다는 것을 증명하기 위해 도움이 필요하지만 리플렉션을 통해 생성 된 후에는 개체에 대해 수행하려는 검사 일 가능성이 높습니다.

internal class Program 
{ 
    private static void Main() 
    { 
     var c = new Category(); 
     c.Name = "Some category"; 

     var categoryRepository = new CategoryRepository(); 
     categoryRepository.Add(c); 

     var a = new Article(); 
     a.Category = c; 
     a.Content = "Some content"; 
     a.Title = "Some title"; 

     var repository = new ArticleRepository(); 

     // give the static checker a helping hand 
     // we don't want to proceed if a is not valid anyway 
     if (!a.IsValid) 
     { 
      throw new InvalidOperationException("Hard to check statically"); 
      // alternatively, do "Contract.Assume(a.IsValid)" 
     } 

     repository.Add(a); 

     Console.WriteLine("Done"); 
    } 
} 

public class Category 
{ 
    private bool _isValid; 
    public bool IsValid 
    { 
     get { return _isValid; } 
    } 

    private string _name; 
    public string Name { 
     get { return _name; } 
     set 
     { 
      Contract.Requires(!string.IsNullOrEmpty(value)); 
      Contract.Ensures(IsValid); 

      _name = value; 
      _isValid = true; 
     } 
    } 

    [ContractInvariantMethod] 
    void Invariant() 
    { 
     Contract.Invariant(!_isValid || !string.IsNullOrEmpty(_name)); 
    } 
} 

public class Article 
{ 
    private bool _isValid; 
    public bool IsValid 
    { 
     get { return _isValid; } 
    } 

    private string _title; 
    public string Title 
    { 
     get { return _title; } 
     set 
     { 
      Contract.Requires(!string.IsNullOrEmpty(value)); 
      _title = value; 
      CheckIsValid(); 
     } 
    } 

    private string _content; 
    public string Content 
    { 
     get { return _content; } 
     set 
     { 
      Contract.Requires(!string.IsNullOrEmpty(value)); 
      _content = value; 
      CheckIsValid(); 
     } 
    } 

    private Category _category; 
    public Category Category { 
     get { return _category; } 
     set 
     { 
      Contract.Requires(value != null); 
      Contract.Requires(value.IsValid); 
      _category = value; 
      CheckIsValid(); 
     } 
    } 

    private void CheckIsValid() 
    { 
     if (!_isValid) 
     { 
      if (!string.IsNullOrEmpty(_title) && 
       !string.IsNullOrEmpty(_content) && 
       _category != null && 
       _category.IsValid) 
      { 
       _isValid = true; 
      } 
     } 
    } 

    [ContractInvariantMethod] 
    void Invariant() 
    { 
     Contract.Invariant(
      !_isValid || 
       (!string.IsNullOrEmpty(_title) && 
       !string.IsNullOrEmpty(_content) && 
       _category != null && 
       _category.IsValid)); 
    } 
} 

public class CategoryRepository 
{ 
    private readonly List<Category> _categories = new List<Category>(); 

    public void Add(Category category) 
    { 
     Contract.Requires(category != null); 
     Contract.Requires(category.IsValid); 

     Contract.Ensures(category.IsValid); 

     _categories.Add(category); 
    } 
} 

public class ArticleRepository 
{ 
    private readonly List<Article> _articles = new List<Article>(); 

    public void Add(Article article) 
    { 
     Contract.Requires(article != null); 
     Contract.Requires(article.IsValid); 

     Contract.Ensures(article.IsValid); 

     _articles.Add(article); 
    } 
} 
+0

감사합니다. Matthew에게 감사드립니다. 가능한 타협처럼 보입니다. –

+0

또 다른 옵션은, 생성 된 후에 객체를 변경할 필요가없는 경우 변경 가능한 객체에서 복사 생성 될 수 있고 생성시 유효성 검사를 수행 할 수있는 새로운 불변 ​​클래스를 만드는 것입니다. 그렇게하면 외부에서 유효성을 검사 할 필요가 없습니다. 하나를 통과하고 null이 아닌 경우 유효 함이 보장됩니다. –