× Aktuálně z oboru

Vychází Game Ready ovladače pro Far Cry 5 [ clanek/2018040603-vychazi-game-ready-ovladace-pro-far-cry-5/ ]
Celá zprávička [ clanek/2018040603-vychazi-game-ready-ovladace-pro-far-cry-5/ ]

Code Contracts - Úvod

[ http://programujte.com/profil/19947-stepan-sindelar/ ]Google [ ?rel=author ]       [ http://programujte.com/profil/14523-martin-simecek/ ]Google [ ?rel=author ]       21. 2. 2011       24 978×

V úvodním článku krátkého seriálu o technologii Code Contracts, která vznikla v Microsoft Research, si ukážeme, v čem nám mohou kontrakty pomoci a jak je používat.

Pojem kontrakt se běžně používá i mimo svět programování. Pro jistotu uveďme, že kontrakt je dohoda dvou a více stran o vzájemných povinnostech. Jak si tedy představit takový kontrakt v kontextu programování? Například u následující metody třídy String

public string Substring(int startIndex)
parametr startIndex představuje index v zadaném řetězci. Formálně je typu Int32, takže i hodnota -42 sice projede kompilací, ale za běhu dostaneme výjimku. V tomto případě záporná hodnota neodpovídá kontraktu metody Substring, který říká, že bychom jí měli předat index do daného řetězce - nezáporné číslo menší než jeho délka, a tento fakt bychom se dozvěděli například z dokumentace [ http://msdn.microsoft.com/en-us/library/hxthx5h6.aspx ].

Dokumentace je sice hezká věc, ale ještě lepší by byla nějaká systematičtější metoda pro uvádění kontraktu. Možnost zapsat kontrakty tak, aby je mohl zpracovat nějaký automatizovaný nástroj, který by nás mohl upozornit na porušení kontraktu už při kompilaci. Přidání informace o takovém kontraktu do API dokumentace by pak bylo už hračkou. Technologií, která nám tohle všechno umožní, jsou právě Code Contracts.

Instalace Code Contracts

Instalace je přímočará. Code Contracts je možné stáhnout na stránkách Microsoft Dev Labs [ http://msdn.microsoft.com/en-us/devlabs/dd491992 ]. Pokud máte k dispozici nějakou z následujících edicí Visual Studia: Visual Studio 2008 Team System, Visual Studio 2010 Premium Edition nebo Visual Studio 2010 Ultimate Edition, pak doporučuji stáhnout Premium edici Code Contracts, protože ta obsahuje i integraci statického analyzátoru v IDE a právě statickou analýzou se v prvních dílech seriálu budeme nejvíce zabývat. Express edice Visual Studia neumožňuje integraci Code Contracts z toho prostého důvodu, že neposkytuje dostatečné rozšiřující mechanizmy. Pro všechny ostatní edice Visual Studia je určena verze Code Contracts Standard, která ale nepodporuje statickou analýzu přímo v IDE.

Code Contracts je teoreticky možné používat i bez jakékoliv podpory v IDE pouze přes příkazovou řádku. Více podrobností se dočtete v oficiální dokumentaci.

Už vám jde z těch všech edic a verzí hlava kolem? Máte jenom Express edici Visual Studia? Nebo se vám prostě nechce instalovat další add-in? Nezoufejte! Code Contracts (a nejen je) si můžete vyzkoušet online na rise4fun [ http://rise4fun.com/CodeContracts ].

První příklad

Pojďme se podívat, jak to bude s voláním metody Substring(-42) při použítí Code Contracts. Vytvoříme standardní konzolovou aplikaci. Když klikneme pravým tlačítkem na ikonu našeho nového projektu v Solution Exploreru a vybereme "Properties", objeví se známá obrazovka s vlastnostmi projektu. Novinkou pro některé ovšem může být záložka s názvem "Code Contracts".

Pro začátek si Code Contracts nastavte tak, jak je uvedeno na obrázku, a pojďme programovat.

public class Program {
	public static void Main(string[] args) {
		var x = "Hello world".Substring(-1);
	}
}

Když tento program přeložíte, zobrazí se u volání metody Substring modrá vlnovka. O toto označení se postaral právě statický analyzátor Code Contracts. Některé metody z .NET BCL (Base Class Library) totiž již Code Contracts podporují a statický analyzátor (součást Code Contracts) se v tomto případě podíval na kontrakt metody Substring, zjistil, že hodnota -1 není validní, a upozorňuje nás.

Vytváříme vlastní kontrakty

Teď už víme, že statický analyzátor nám zkontroluje kontrakt na standardních metodách z .NET BCL. Nyní si ukážeme jak přidat kontrakt vlastní metodě nebo celému typu a předvedeme si, že kromě tzv. pre-conditions, neboli požadavků na vstupní parametry metod (startIndex u metody Substring), je možné uvést kontrakt i například pro návratovou hodnotu metody a další elementy.

Budeme postupně vytvářet třídu Fractional (zlomek) s datovými položkami numerator (čitatel) a denominator (jmenovatel). Začněme deklarací typu a konstruktoru.

public class Fractional {	
	public double Numerator { get; private set; }
	public double Denominator { get; private set; }
	
	public Fractional(int numerator, int denominator) {		
		this.Numerator = numerator;
		this.Denominator = denominator;
	}	
}

public class Program {
	public static void Main(string[] args) {
		var x = new Fractional(4, 0);
	}
}

Jak je všeobecně známo, zlomek s nulou ve jmenovateli nedává dost dobrý smysl, takže bychom chtěli přidat kontrakt, kterým dáme "vnějšímu světu" najevo, že při volání konstruktoru musí pro parametr denominator platit nerovnost denominator != 0. Jedná se o zmiňovanou pre-condition a zapisujeme ji pomocí logického výrazu, který je parametrem statické metody Requires na statické třídě Contract ze jmenného prostoru System.Diagnostics.Contract.

using System.Diagnostics.Contracts;

public class Fractional {
	// ...
	public Fractional(int numerator, int denominator) {		
		Contract.Requires(denominator != 0);		
		this.Numerator = numerator;
		this.Denominator = denominator;
	}	

Po tom, co kód přeložíme a necháme proběhnout analyzátor Code Contracts, se samozřejmě objeví známá modrá vlnovka na řádku s new Fractional(4, 0). Poučeni z chybového hlášení jej opravíme na new Fractional(4, 1).

Post-conditions

Další metodou, kterou přidáme třídě Fractional, bude statická metoda FromInt, která ze zadaného celého čísla udělá instanci třídy Fractional.

public class Fractional {
	// ....
	
	public static Fractional FromInt(int i) {
		return new Fractional(i, 1);
	}
}

public class Program {
	public static void Main(string[] args) {
		var x = Fractional.FromInt(4);
		Console.WriteLine("{0} / {1}", x.Denominator, x.Numerator);
	}
}

Co nám teď po kompilaci poví statický analyzátor? Dozvíme se, že na řádku s WriteLine může dojít k NullReferenceException. Nikdo nám totiž nezaručuje, že to, co vrátí metoda Fractional.FromInt, nemůže být null a zavolání getteru vlastnosti Denominator by v takovém případě vyvolalo uvedenou výjimku.

Statický analyzátor Code Contracts v tomto případě kontroluje i implicitní (vestavěný) kontrakt jazykového konstruktu, jakým je volání instanční metody (getteru vlastnosti). Pokud v C# zavoláme metodu na null referenci, obdržíme výjimku typu NullReferenceException. Podobně statický analyzátor kontroluje i další jazykové konstrukty a upozorňuje nás na možné chyby, jako je například dělení nulou.

Analyzátor neví nic o tom, jak metoda FromInt funguje, a neví, že nikdy nemůže vrátit null referenci. Představte si, že metoda FromInt je součástí nějaké knihovny, k jejímž zdrojovým kódům nemáte přístup, v takovém případě si tím, že FromInt nikdy nevrací null, nemůžeme být jisti!

Nyní se nacházíme v opačné situaci. Po metodě FromInt chceme nějakou garanci ohledně jejího výsledku. Konkrétně že nebude null. Tento druh kontraktu se nazývá post-condition a zapisuje se logickým výrazem, podobně jako pre-condition, na začátku dané metody. Místo Requires, které bylo u pre-condition, ovšem musíme zavolat Ensures. Možná už v tom vidíte maličký háček. Jak se odvolat na výsledek, když jej na začátku těla metody ještě nevíme? Slouží k tomu speciální funkce Contract.Result<T>(). Použití snad bude jasné z následující ukázky:

public class Fractional {
	// ....
	
	public static Fractional FromInt(int i) {
		Contract.Ensures(Contract.Result<Fractional>() != null);
		return new Fractional(i, 1);
	}
}

Tímto kódem jsme "vnějšímu světu" sdělili, že se nemusí bát, že by snad metoda FromInt vrátila null. Pokud teď znovu program zkompilujete, modrá vlnovka na řádku s WriteLine by měla zmizet.

Objektový invariant

Poslední metodou, kterou v dnešním dílu přidáme, je ToInt. ToInt by měla převést daný zlomek na nejbližší celé číslo. Tato metoda už bude instanční a její implementace by měla být snadná.

public class Fractional {
	public Fractional(int numerator, int denominator) {		
		Contract.Requires(denominator != 0);		
                // ...
	}

	// ....

	public int ToInt() {
		return this.Numerator / this.Denominator;
	}
}

Jak jistě tušíte, tento kód statickou analýzou neprojde, jinak bychom se jím nezabývali. Code Contracts nám nahlásí možnost dělení nulou. Jak to? Přeci jsme v konstruktoru uvedli, že denominator != 0. Zaručuje nám to ale, že vždy před zavoláním ToInt() bude vlastnost Denominator různá od nuly? Odpověď je: ne zcela. Stačí jenom přidat do Fractional neškodnou veřejnou metodu:

public class Fractional {
	// ....
	public void InvalidateMe() {
		this.Denominator = 0;
	}
	
	public int ToInt() {
		return this.Numerator / this.Denominator;
	}
}

public class Program {
	public static void Main(string[] args) {
		var x = Fractional.FromInt(4);
		x.InvalidateMe();
		int i = x.ToInt();
	}
}

Uvedený sled volání InvalidateMe a potom ToInt by skončil právě na dělení nulou. Co nyní potřebujeme, je objektový invariant. Objektový invariant je tvrzení, které platí po celou dobu existence objektu. U typu DateTime by například mělo vždy platit, že vlastnost Month (měsíc) je menší nebo rovna 12. U zlomku je to už obligátní denominator != 0.

Invariant se v Code Contracts zapisuje pomocí libovolně pojmenované, zpravidla privátní, metody, která je ale označena atributem [InvariantMethod] a která se skládá pouze ze sekvence volání metody Contract.Invariant - ta podobně jako Contract.Requires nebo Contract.Ensures bere jako první parametr logický výraz. Následuje názorná ukázka.

public class Fractional {
	// ....
	public void InvalidateMe() {
		this.Denominator = 0;
	}
	
	public int ToInt() {
		return this.Numerator / this.Denominator;
	}
	
	[InvariantMethod]
	private void ContractInvariant() {
		Contract.Invariant(this.Denominator != 0);
	}
}

Po této úpravě a kompilaci zmizí vlnovka u metody ToInt, ale objeví se u metody InvalidateMe, která porušuje invariant objektu, což je přesně stav, který jsme si představovali.

Závěr

V dnešním díle jsme si představili technologii Code Contracts, ukázali jsme si její základní nastavení a použití. V dalším díle se podíváme na trochu pokročilejší možnosti Code Contracts, jako například kontrakty pro výstupní parametry.


Článek stažen z webu Programujte.com [ http://programujte.com/clanek/2011021200-code-contracts-uvod/ ].