Насколько свободным я могу быть в коде, инвариантном к объекту?
-
05-07-2019 - |
Вопрос
Я пытаюсь продемонстрировать инварианты в кодовых контрактах, и я подумал, что приведу пример отсортированного списка строк.Он поддерживает массив внутри, с запасным пространством для дополнений и т.д. - точно так же, как List<T>
, в принципе.Когда ему нужно добавить элемент, он вставляет его в массив и т.д.Я полагал, что у меня есть три инварианта:
- Граф должен быть благоразумен:неотрицательный и не более такого же размера, как размер буфера
- Все, что находится в неиспользуемой части буфера, должно быть нулевым
- Каждый элемент в используемой части буфера должен быть по крайней мере таким же "большим", как элемент перед ним
Теперь я попытался реализовать это таким образом:
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(count >= 0 && count <= buffer.Length);
for (int i = count; i < buffer.Length; i++)
{
Contract.Invariant(buffer[i] == null);
}
for (int i = 1; i < count; i++)
{
Contract.Invariant(string.Compare(buffer[i], buffer[i - 1]) >= 0);
}
}
К сожалению, ccrewrite
это запутывает петли.
В пользовательской документации говорится, что метод должен представлять собой просто серию вызовов Contract.Invariant
.Действительно ли мне нужно переписать код как-то вроде этого?
Contract.Invariant(count >= 0 && count <= buffer.Length);
Contract.Invariant(Contract.ForAll
(count, buffer.Length, i => buffer[i] == null));
Contract.Invariant(Contract.ForAll
(1, count, i => string.Compare(buffer[i], buffer[i - 1]) >= 0));
Это несколько некрасиво, хотя и работает.(Заметьте, это намного лучше, чем моя предыдущая попытка.)
Являются ли мои ожидания необоснованными?Являются ли мои инварианты необоснованными?
(Также заданный в качестве вопрос на форуме Code Contracts.Я сам добавлю сюда любые соответствующие ответы.)
Решение
На (предварительных) страницах MSDN это выглядит как Контракт.Участник ForAll мог бы помочь вам с контрактами на 2 диапазона.Однако в документации не очень четко описана его функция.
//untested
Contract.Invariant(Contract.ForAll(count, buffer.Length, i => buffer[i] == null));
Contract.Invariant(Contract.ForAll(1, count,
i => string.Compare(buffer[i], buffer[i - 1]) >= 0));
Другие советы
(Я собираюсь принять ответ Хенка, но я думаю, что стоит добавить это.)
Теперь на этот вопрос дан ответ в Форум MSDN, и результатом является то , что первая форма это не так ожидалось, что это сработает.Инварианты действительно, действительно должны представлять собой серию вызовов для Contract.Invariant
, и это все.
Это делает более выполнимым для статической проверки понимание инварианта и его принудительное применение.
Это ограничение можно обойти, просто поместив всю логику в другой элемент, напримеран IsValid
свойство, а затем вызывающий:
Contract.Invariant(IsValid);
Это, без сомнения, испортило бы статическую проверку, но в некоторых случаях это может быть полезной альтернативой в некоторых случаях.
Разве дизайнеры немного не изобретают велосипед?
Что было не так с старый добрый
bool Invariant() const; // in C++, mimicking Eiffel
?
Теперь в C # у нас нет const, но почему вы не можете просто определить Invariant
функция
private bool Invariant()
{
// All the logic, function returns true if object is valid i.e. function
// simply will never return false, in the absence of a bug
}
// Good old invariant in C#, no special attributes, just a function
а затем просто использовать Кодовые контракты в терминах этой функции?
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(Invariant() == true);
}
Может быть, я пишу бессмыслицу, но даже в этом случае это будет иметь некоторую дидактическую ценность, когда все скажут мне, что я неправ.