Code Contracts. Еще небольшой пример использования и общие впечатления
- modified:
- reading: 3 minutes
Прошло уже более месяца с того момента, как я начал использовать 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, то будете видеть все эти условия вот так:
Ну и соответственно при выполнении такого кода:
IUserRepository repository = new UserRepository();
repository.AddUser(null);
Получим ошибку:
Вместо использования методов 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();
}
Пишите безопасный код.
Кстати, я в прошлый раз упоминал еще, что в .Net базовых классах используются контракты. Открыл недавно ASP.NET проект старенький, но переведенный на ASP.NET 4 и вот что я там увидел: