outcoldman
outcoldman Denis Gladkikh

Code Contracts. Еще небольшой пример использования и общие впечатления

.NET, C#, and Code Contracts

Прошло уже более месяца с того момента, как я начал использовать Code Contracts в проекте нашей компании, и как я написал статью о них Небольшой пример использования Code Contract. Хочу порекомендовать документацию с сайта Microsoft Research, если еще не читали.

Вообще, как мне и сказали в комментариях к предыдущей статье – во-первых, с ними немного сложно: в целом все настроить, и чтобы все отлично работало. Во-вторых, увеличивается время билда из-за перезаписи. Я пока настроил контракты только для серверной части нашего проекта, пока не вводил их для клиентской части. Серверных библиотек у нас раза в 3 меньше, чем библиотек на Silverlight. На сервере контракты пока что оправдывают себя неплохо.

Мне понравилась возможность написания контрактов для интерфейсов (тоже будет работать и для абстрактных классов). Например, у нас есть интерфейс репозитария пользователей:

public interface IUserRepository
{    User GetUserByID(int id);
     void AddUser(User user);
     void UpdateUser(User user);
     void DeleteUser(User user);
}

Класс User самый обычный:

public class User
{    public int Id { get; set; }
    public string Name { get; set; }
}

Когда мы будем реализовывать этот репозитарий, то, скорее всего, мы будем в каждом методе писать проверки на то, что user != null, и выкидывать ArgumentNullException и т.п.:

class UserRepository : IUserRepository
{    public User GetUserByID(int id) 
    {        if (user.Id <= 0)
            throw new ArgumentException("Identify should be greater than 0", "id");
         /* ... */     
    }
     public void AddUser(User user)
    {        if (user == null)
            throw new ArgumentNullException("user");
         if (user.Id > 0)
            throw new ArgumentException("Should be new user", "user");
         /* ... */     
    }    public void UpdateUser(User user) 
    {        if (user == null)
            throw new ArgumentNullException("user");
         if (user.Id <= 0)
            throw new ArgumentException("Should be saved user", "user");
         /* ... */ 
    }
     public void DeleteUser(User user) 
    {        if (user == null)
            throw new ArgumentNullException("user");
         if (user.Id <= 0)
            throw new ArgumentException("Should be saved user", "user");
         /* ... */ 
    }
}

А если реализаций будет больше чем одна, тогда что? Писать в каждой? Вот тут приходят на помощь Code Contracts. Дописываем к интерфейсу контракты, и указываем аттрибутами ClassContract и ClassContractFor принадлежность этих контрактов к определенному интерфейсу:

[ContractClass(typeof(IUserRepositoryContract))]
public interface IUserRepository
{    User GetUserByID(int id);
     void AddUser(User user);
     void UpdateUser(User user);
     void DeleteUser(User user);
}
 [ContractClassFor(typeof(IUserRepository))]
abstract class IUserRepositoryContract : IUserRepository
{    User IUserRepository.GetUserByID(int id)
    {        Contract.Requires<ArgumentException>(id > 0, "Identify should be greater than 0.");
        Contract.Ensures(Contract.Result<User>() != null);
         return default(User);
    }
     void IUserRepository.AddUser(User user)
    {        Contract.Requires<ArgumentNullException>(user != null);
        Contract.Requires<ArgumentException>(user.Id == 0, "Should be new user.");
    }
     void IUserRepository.UpdateUser(User user)
    {        Contract.Requires<ArgumentNullException>(user != null);
        Contract.Requires<ArgumentException>(user.Id > 0, "Should be saved user.");
    }
     void IUserRepository.DeleteUser(User user)
    {        Contract.Requires<ArgumentNullException>(user != null);
        Contract.Requires<ArgumentException>(user.Id > 0, "Should be saved user.");
    }
}

Больше в самой реализации нам не нужно делать проверки. Более того, если вы установите плагин для VS с официального сайта Code Contracts, то будете видеть все эти условия вот так:

Capture

Ну и соответственно при выполнении такого кода:

IUserRepository repository = new UserRepository();
repository.AddUser(null);

Получим ошибку:

Capture2

Вместо использования методов Contract класса можно так же писать и свои проверки с бросанием исключений, вроде такого:

void IUserRepository.AddUser(User user)
{    if (user == null)
        throw new ArgumentNullException("user");
    if (user.Id == 0)
        throw new ArgumentException("Should be new user.", "user");
    Contract.EndContractBlock();
}

Пишите безопасный код. Winking smile

Кстати, я в прошлый раз упоминал еще, что в .Net базовых классах используются контракты. Открыл недавно ASP.NET проект старенький, но переведенный на ASP.NET 4 и вот что я там увидел:

CaptureASPNET

Have feedback or questions? Looking for consultation?

My expertise: MongoDB, ElasticSearch, Splunk, and other databases. Docker, Kubernetes. Logging, Metrics. Performance, memory leaks.

Send me an email to public@denis.gladkikh.email.

The content on this site represents my own personal opinions and thoughts at the time of posting.

Content licensed under the Creative Commons CC BY 4.0.