Návrh podle kontraktu (anglicky Design by Contract, DbC), nebo též kontraktové programování je relativně dlouho známým, avšak stále nedoceněným přístupem k tvorbě spolehlivého programového vybavení.[1]
Předepisuje, aby návrháři softwaru definovali formální, přesné a verifikovatelné specifikace rozhraní pro softwarové komponenty, které rozšiřují běžnou definici abstraktních datových typů o předběžné podmínky, následné podmínky a invarianty. Tyto specifikace se označují jako „smlouvy“ („kontrakty“), v souladu s pojmovou metaforou s podmínkami a závazky obchodních kontraktů.
DbC přístup předpokládá, že všechny klientské komponenty, které volají operaci na serverové komponentě splní předběžné podmínky specifikované jako požadovaný pro tuto operaci.
Tam, kde je tento předpoklad považován za příliš riskantní (jako v případě vícekanálových nebo distribuovaných výpočtů), používá se inverzní přístup, což znamená, že serverová komponenta testuje, zda všechny relevantní předběžné podmínky platí (před zpracováním požadavku klientské komponenty' nebo v jeho průběhu), a pokud tomu tak není, odpoví vhodným chybovým hlášením.
Tento termín poprvé použil Bertrand Meyer v souvislosti se svým návrhem programovacího jazyka Eiffel a poprvé jej popsal od roku 1986 v různých článcích[2][3][4] a ve dvou po sobě následujících vydáních (v letech 1988 a 1997) své knihy Object-Oriented Software Construction. Společnost Eiffel Software požádala o registraci obchodní známky Design by Contract v prosinci 2003, a známku vlastní[5][6] od prosince 2004.[7][8]
Návrh podle kontraktu má kořeny v práci na formální verifikaci, formální specifikaci a Hoareho logice. Mezi původní příspěvky patří např:
Ústřední myšlenkou DbC je metafora o tom, jak prvky softwarového systému vzájemně spolupracují na základě vzájemných závazků a výhod. Tato metafora pochází z obchodního života, kde se „klient“ a „dodavatel“ dohodnou na „kontraktu“, která definuje např. toto:
Podobně, pokud metoda třídy v objektově orientovaném programování poskytuje určitou funkčnost, může:
kontrakt je sémanticky ekvivalentní s Hoareho trojicí, která formalizuje závazky. Lze ji shrnout do „tří otázek“, na které musí designer v kontraktu opakovaně odpovídat:
Mnoho programovacích jazyků má nástroje pro vytváření takovýchto tvrzení (asercí). DbC však považuje tyto kontrakty za natolik zásadní pro korektnost softwaru, že by měly být součástí procesu návrhu. DbC v podstatě prosazuje, aby se nejprve napsala tvrzení.[zdroj?] Kontrakty mohou být zapsány pomocí komentářů v kódu, vynuceny sadou testů nebo obojím, a to i v případě, že by pro kontrakty neexistovala žádná speciální jazyková podpora.
Pojem kontraktu se rozšiřuje až na úroveň metod a procedur; kontrakt pro každou metodu bude obvykle obsahovat následující informace:[zdroj?]
Podtřídy v hierarchii dědičnosti mohou oslabovat předběžné podmínky (ale ne je posilovat) a posilovat následné podmínky a invarianty (ale ne je oslabovat). Tato pravidla se blíží behaviourálnímu podtypování.
Všechny vztahy tříd jsou mezi klientskými a dodavatelskými třídami. Klientská třída se zavazuje volat dodavatelské funkce, pokud výsledný stav dodavatele není porušen klientským voláním. Dodavatel je následně povinen poskytnout návratový stav a data, která neporušují stavové požadavky klienta.
Například vyrovnávací paměť pro data dodavatele může vyžadovat, aby v ní byla data přítomna v okamžiku volání funkce mazání. Následně dodavatel klientovi zaručí, že když funkce mazání skončí svou práci, datová položka bude skutečně smazána z vyrovnávací paměti. Dalšími návrhovými kontrakty jsou koncepty invariantu třídy. Invariant třídy zaručuje (pro lokální třídu), že stav třídy bude v rámci specifikovaných tolerancí zachován na konci každého provedení funkce.
Při používání kontraktů by se dodavatel neměl pokoušet ověřit, zda jsou podmínky kontraktu splněny — tato praxe je známa jako ofenzivní programování — obecnou myšlenkou je, že kód musí „selhat natvrdo“, přičemž ověření kontraktu je záchrannou sítí.
Vlastnost „tvrdého selhání“ zjednodušuje ladění chování kontraktu, protože zamýšlené chování každé metody je jasně specifikováno.
Tento přístup se podstatně liší od defenzivního programování, kde dodavatel je odpovědný za vyhodnocení, co dělat, když předběžná podmínka není splněna. Nejčastěji dodavatel vyhodí výjimku, aby klienta informoval, že předběžná podmínka nebyla splněna, a v obou případech — u DbC i defenzivního programování — musí klient zjistit jak na to reagovat. V takových případech DbC dodavateli usnadňuje práci.
Návrh podle kontraktu také definuje kritéria korektnosti softwarového modulu:
Návrh podle kontraktu může také usnadňovat opětovné použití kódu, protože kontrakt pro každou část kódu je plně dokumentovaný. Kontrakty modulu lze považovat za určitou formu dokumentace chování modulu.
Během provádění bezchybného programu nesmí být podmínky kontraktu nikdy porušené. Kontrakty se proto při vývoji softwaru obvykle kontrolují pouze v režimu ladění. V sostré verzi softwaru jsou kontroly smluv vypnuty, aby se maximalizoval výkon.
V mnoha programovacích jazycích jsou kontrakty implementovány pomocí asercí. Aserce jsou implicitně vypuštěny při vytváření produkčního softwaru v jazycích C/C++, a podobně jsou deaktivovány v jazycích C#[9] a Java.
Spuštění interpretu Pythonu s parametrem „-O“ (jako „optimalizovat“) také způsobí, že generátor kódu nebude emitovat žádný bytecode pro aserce.[10]
Tám se efektivně odstraňují náklady na aserce v produkčním vydání softwaru - bez ohledu na počet a výpočetní náklady asertů použitých při vývoji — protože překladač žádné takové instrukce do produkčního kódu nezahrne.
Návrh podle kontraktu nenahrazuje obvyklé strategie testování, např. unit testing, integrační testování a systémové testování. Spíše doplňuje externí testování interními autotesty, které lze aktivovat jak pro izolované testy tak pro produkční kód během fáze testování.
Výhodou interních autotestů je, že mohou odhalit chyby dříve než se projeví jako neplatné výsledky pozorované klientem. To umožňuje dřívější a podrobnější detekci a odstraňování chyb.
Použití asercí lze považovat za formu testovacího orákula, způsob testování implementace návrhu podle kontraktu.
K jazykům, které implementují většinu DbC vlastností nativně patří:
Kromě toho má standardní kombinace metod v Common Lisp Object System kvalifikátory metod :before
, :after
a :around
, které mimo jiné umožňují zápis kontraktů jako pomocných metod.
Pro existující programovací jazyky bez nativní podpory návrhu podle kontraktu byly vyvinuty různé knihovny, preprocesory a jiné nástroje:
V tomto článku byl použit překlad textu z článku Design by contract na anglické Wikipedii.