Пользовательские методы в Contract.Ensures

Я пытаюсь понять кодовые контракты немного подробнее. У меня есть следующий надуманный пример, где я пытаюсь утвердить инвариант шаблона try/get, что если он возвращает true, то объект out не является нулевым, в противном случае, если он возвращает false.

    public static bool TryParseFruit(string maybeFruit, out Fruit fruit)
    {
        Contract.Requires(maybeFruit != null);

        Contract.Ensures((Contract.Result<bool>() && Contract.ValueAtReturn(out fruit) != null) ||
                         (!Contract.Result<bool>() && Contract.ValueAtReturn(out fruit) == null));

        // Elided for brevity
        if (ICanParseIt())
        {
            fruit = SomeNonNullValue;
            return true;
        }
        else
        {
            fruit = null;
            return false;
        }
    }

Мне не нравится дублирование внутри Contract.Ensures, поэтому я хотел выделить для этого свой собственный метод.

[Pure]
public static bool Implies(bool a, bool b)
{
   return (a && b) || (!a && !b);
}

Затем я изменил свой инвариант в TryParseFruit на

Contract.Ensures(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null);

Но это генерирует предупреждения о том, что «гарантия не доказана». Если я затем выполню встроенный рефакторинг моего метода Implies, то все снова будет в порядке.

Может ли кто-нибудь объяснить мне, почему это происходит? Я предполагаю, что это потому, что Contract.ValueAtReturn каким-то волшебным образом используется, что означает, что я не могу просто передать результат другой функции и ожидать, что она сработает.

(Обновление №1)

Я думаю, что все следующие Contract.Ensures выражают одно и то же (а именно, что если функция возвращает true, то fruit не равно нулю, иначе fruit равно null). Обратите внимание, что я использую только один из них одновременно :)

Contract.Ensures(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null));           
Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn(out fruit) != null));
Contract.Ensures(Contract.Result<bool>() ^ (Contract.ValueAtReturn(out fruit) == null));

Однако ни одно из приведенных выше действий Contract.Ensures не приводит к чистой компиляции приведенного ниже кода. Я хочу, чтобы Code.Contracts выражал, что fruit.Name не может быть нулевой ссылкой.

    Fruit fruit;
    while (!TryGetExample.TryParseFruit(ReadLine(), out fruit))
    {
        Console.WriteLine("Try again...");
    }

    Console.WriteLine(fruit.Name);

Я могу получить полностью чистую компиляцию с кодовыми контрактами только в том случае, если я использую многословный способ выражения этого, подробно описанного выше. Мой вопрос почему!


person Jeff Foster    schedule 05.12.2011    source источник


Ответы (2)


Можно, конечно, и попробовать Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn(out fruit) != null)); Я смутно припоминаю, что анализатор предпочитает == другим операторам.

Мне удалось отследить эти вещи с помощью Contract.Assert, который поможет вам найти дыру в анализе. Я также нашел случаи, когда Contract.Assert позволяет провести анализ успешно. Другими словами, вы можете исправить это с помощью нескольких утверждений:

public static bool TryParseFruit(string maybeFruit, out Fruit fruit) 
{ 
    Contract.Requires(maybeFruit != null); 

    Contract.Ensures(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null);

    // Elided for brevity 
    if (ICanParseIt()) 
    { 
        fruit = SomeNonNullValue; 
        Contract.Assert(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null);
        return true; 
    } 
    else 
    { 
        fruit = null; 
        Contract.Assert(Implies(Contract.Result<bool>(), Contract.ValueAtReturn(out fruit) != null);
        return false; 
    } 
} 

Грязный, я знаю. С другой стороны, если какое-либо утверждение не выполняется, вы можете, например, посмотреть на другие аспекты логики. Например, вы можете засорить свой код Contract.Assert(SomeNonNullValue != null);, чтобы увидеть, где анализатор теряет уверенность в ненулевости SomeNonNullValue.

ИЗМЕНИТЬ

Если Assert недоказан, но вы знаете, что это должно быть доказуемо, вы можете использовать это, чтобы помочь изолировать проблему. Я подозреваю, что проблема (или, по крайней мере, ее часть) заключается в отсутствии у вас Ensures в методе Implies. Попробуйте добавить Contract.Ensures(Contract.Result<bool>() == (Contract.OldValue(a) == Contract.OldValue(b))); Кроме того, поскольку у меня снова остались смутные воспоминания о разной обработке разных логических операторов, попробуйте переформулировать возврат этого метода. Например: return a == b;

person phoog    schedule 05.12.2011
comment
Спасибо за предложения. Я пытался использовать Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn(out fruit) != null));, но это не помогло (я обновил вопрос, добавив еще несколько деталей). Я не смог использовать Contract.Assert, чтобы уменьшить количество ошибок, так как все ветки просто выдавали непроверенное сообщение assert. - person Jeff Foster; 06.12.2011
comment
Спасибо за ответ. Я переделал свой код, чтобы он имел единую точку возврата (return fruit != null). Кажется, это немного помогает доказательству теорем, так как теперь контракт в стиле Contract.Ensures(Contract.Result<bool>() == (Contract.ValueAtReturn(out fruit) != null)); теперь компилируется совершенно чисто. Мне кажется, что Code Contracts еще не совсем готова к прайм-тайму. Я хотел бы иметь возможность просто аннотировать инварианты моих методов и заставить их просто работать, но похоже, что это может быть немного амбициозно, по крайней мере, для этой версии! - person Jeff Foster; 07.12.2011
comment
@JeffFoster да, тот факт, что CC не совсем готов к прайм-тайму, объясняет мое незнание системы - я немного экспериментировал с ней ... несколько месяцев назад. Я бросил его вскоре после этого. Мое разочарование в значительной степени было вызвано тем, что в некоторых частях BCL отсутствовали очевидные контракты. Например, методы, которые декомпилируют в return new Something(..., не могут возвращать null, но CC не знает об этом, и в десятках случаев авторы фреймворка еще не добавили Contract.Ensures... - person phoog; 07.12.2011

Прежде всего, ваше условие может быть сжато без использования специального метода:

Contract.Ensures(Contract.Result<bool>() ^ (Contract.ValueAtReturn(out fruit) == null));

(Здесь ^ – это оператор XOR)

Теперь о вашем вопросе. Я думаю, очень сложно сказать, в чем причина, если вы точно не знаете, как работает статический верификатор. Ограничений может быть сотни. С моей точки зрения, верификатор Code Contracts как-то останавливается на границах методов. Я имею в виду, что верификатор не изучает метод Implies и не знает, что он делает. Поэтому он не может получить то, что возвращает в каждом случае. А когда вы встраиваете метод, он получает возможность полностью проверить код. Но, опять же, я думаю, что никто за пределами команды разработчиков точно не знает.

ОБНОВЛЕНИЕ

Как выяснилось в комментариях, оператор XOR, похоже, не поддерживается текущей версией CodeContracts. Повезет в следующий раз...

person Pavel Gatilov    schedule 05.12.2011
comment
Спасибо за комментарии. Я знаю об операторе xor, но если я его использую, верификатор также выдает ту же ошибку (несмотря на то, что xor логически эквивалентен оператору в вопросе). Возможно, он рассматривает только логические операторы && и || операторы? Похоже, мне нужно больше подробностей о внутренней работе верификатора! - person Jeff Foster; 05.12.2011
comment
Обратите внимание, что даже если я упрощу до единственного оператора return, Code Contracts не сможет обработать XOR. - person Jeff Foster; 07.12.2011