Персонализирани методи в 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 unproven съобщение. - 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
Обърнете внимание, че дори ако опростя, за да имам един оператор за връщане, договорите за код не могат да обработят XOR. - person Jeff Foster; 07.12.2011