Tvrzení (vývoj softwaru) - Assertion (software development)

V programování počítače , zejména při použití naléhavý programovací paradigma, An tvrzení je predikát (a booleovské hodnotou funkce přes stavovém prostoru , obvykle vyjádřené jako logické tvrzení pomocí proměnné programu) spojen s bodem v programu, který vždy by měl být vyhodnocen na hodnotu true v tomto bodě při provádění kódu. Asertions can help a programmer read the code, help a compiler compile it, or help the program detect its own nedostatky.

U posledně jmenovaných některé programy kontrolují tvrzení tím, že ve skutečnosti vyhodnocují predikát při běhu. Pak, pokud to ve skutečnosti není pravda - selhání tvrzení - program se považuje za poškozeného a obvykle záměrně havaruje nebo vyvolá výjimku selhání tvrzení .

Podrobnosti

Následující kód obsahuje dvě tvrzení x > 0a x > 1, a ta jsou skutečně v uvedených bodech během provádění pravdivá:

x = 1;
assert x > 0;
x++;
assert x > 1;

Programátoři mohou použít tvrzení k upřesnění programů ak odůvodnění správnosti programu. Například předpoklad - tvrzení umístěné na začátku části kódu - určuje sadu stavů, za kterých programátor očekává, že se kód spustí. Postcondition -placed na konci, popisuje očekávaný stav na konci provedení. Například: x > 0 { x++ } x > 1.

Výše uvedený příklad používá zápis pro zahrnutí tvrzení použitých CAR Hoare ve svém článku z roku 1969. Tento zápis nelze použít ve stávajících hlavních programovacích jazycích. Programátoři však mohou zahrnout nekontrolovaná tvrzení pomocí funkce komentáře jejich programovacího jazyka. Například v C :

x = 5;
x = x + 1;
// {x > 1}

Závorky zahrnuté v komentáři pomáhají odlišit toto použití komentáře od jiného použití.

Knihovny mohou také poskytovat funkce tvrzení. Například v C pomocí glibc s podporou C99:

#include <assert.h>

int f(void)
{
    int x = 5;
    x = x + 1;
    assert(x > 1);
}

Několik moderních programovacích jazyků obsahuje kontrolovaná tvrzení - příkazy, které jsou kontrolovány za běhu nebo někdy staticky. Pokud je vyhodnocení vyhodnoceno jako nepravdivé za běhu, dojde k selhání tvrzení, které obvykle způsobí přerušení provádění. To upoutá pozornost na místo, kde je detekována logická nekonzistence a může být vhodnější než chování, které by jinak mělo za následek.

Použití tvrzení pomáhá programátorovi navrhnout, vyvinout a zdůvodnit program.

Používání

V jazycích, jako je Eiffel , jsou tvrzení součástí procesu návrhu; jiné jazyky, například C a Java , je používají pouze ke kontrole předpokladů za běhu. V obou případech lze zkontrolovat jejich platnost za běhu, ale obvykle je lze také potlačit.

Tvrzení v návrhu na základě smlouvy

Tvrzení mohou fungovat jako forma dokumentace: mohou popsat stav, který kód očekává, než najde, než se spustí (jeho předpoklady ), a stav, v němž kód očekává, že bude mít za následek spuštění ( postkondice ); mohou také určit invarianty ze na třídě . Eiffel integruje taková tvrzení do jazyka a automaticky je extrahuje, aby dokumentoval třídu. To tvoří důležitou součást metody návrhu podle smlouvy .

Tento přístup je také užitečný v jazycích, které jej výslovně nepodporují: výhodou použití prohlášení o tvrzení místo tvrzení v komentářích je, že program může kontrolovat tvrzení při každém spuštění; pokud tvrzení již neplatí, lze nahlásit chybu. To zabrání tomu, aby se kód dostal mimo synchronizaci s tvrzeními.

Tvrzení pro kontrolu za běhu

Tvrzení lze použít k ověření, že předpoklad, který programátor učinil během implementace programu, zůstává v platnosti, i když je program spuštěn. Zvažte například následující kód Java :

 int total = countNumberOfUsers();
 if (total % 2 == 0) {
     // total is even
 } else {
     // total is odd and non-negative
     assert total % 2 == 1;
 }

V Javě , %je zbytek operátor ( modulo ), a v Javě, pokud je jeho první operand je negativní, může být výsledkem také negativní (na rozdíl od modulo použité matematiky). Zde programátor předpokládá, že totalje nezáporný, takže zbytek dělení s 2 bude vždy 0 nebo 1. Tvrzení činí tento předpoklad explicitní: pokud countNumberOfUsersvrátí zápornou hodnotu, program může mít chybu.

Hlavní výhodou této techniky je, že když dojde k chybě, je detekována okamžitě a přímo, spíše než později, prostřednictvím často nejasných efektů. Protože selhání tvrzení obvykle hlásí umístění kódu, lze často chybu přesně určit bez dalšího ladění.

Tvrzení jsou také někdy umisťována do bodů, kterých by provedení nemělo dosáhnout. Tvrzení lze například umístit na defaultklauzuli switchpříkazu v jazycích, jako je C , C ++ a Java . Jakýkoli případ, který programátor úmyslně neřeší, způsobí chybu a program se přeruší, než aby potichu pokračoval v chybném stavu. V D je takové tvrzení přidáno automaticky, pokud switchpříkaz neobsahuje defaultklauzuli.

V Javě jsou tvrzení součástí jazyka od verze 1.4. Selhání tvrzení mají za následek vyvolání nebo AssertionErrorspuštění programu s příslušnými příznaky, bez nichž jsou příkazy assert ignorovány. V C jsou přidány standardním záhlavím assert.hdefinujícím jako makro, které signalizuje chybu v případě selhání, obvykle ukončení programu. V C ++ , a to i hlavičky poskytnout makro. assert (assertion) assert.hcassertassert

Nebezpečí tvrzení je, že mohou způsobit vedlejší účinky buď změnou dat v paměti, nebo změnou načasování vláken. Tvrzení by měla být implementována opatrně, aby neměly žádné vedlejší účinky na programový kód.

Konstrukce tvrzení v jazyce umožňují snadný vývoj na základě testů (TDD) bez použití knihovny třetích stran.

Tvrzení během vývojového cyklu

Během vývojového cyklu programátor obvykle spustí program s povolenými tvrzeními. Dojde -li k selhání tvrzení, programátor je na problém okamžitě upozorněn. Mnoho implementací tvrzení také zastaví provádění programu: to je užitečné, protože pokud by program pokračoval v běhu i poté, co došlo k porušení tvrzení, mohlo by dojít k poškození jeho stavu a ztížení nalezení příčiny problému. Pomocí informací poskytnutých selháním tvrzení (jako je místo selhání a možná trasování zásobníku , nebo dokonce úplný stav programu, pokud prostředí podporuje jádrové skládky nebo pokud program běží v debuggeru ), může programátor obvykle opravit problém. Tvrzení tedy poskytují velmi účinný nástroj při ladění.

Tvrzení v produkčním prostředí

Když je program nasazen do produkce , tvrzení jsou obvykle vypnuta, aby se předešlo jakýmkoli režijním nebo vedlejším účinkům, které mohou mít. V některých případech v nasazeném kódu zcela chybí tvrzení, například v C/C ++ tvrzení přes makra. V jiných případech, jako je Java, jsou v nasazeném kódu přítomna tvrzení a lze je zapnout v poli pro ladění.

Tvrzení lze také použít ke slibování kompilátoru, že daná podmínka okraje není ve skutečnosti dosažitelná, což umožňuje určité optimalizace, které by jinak nebyly možné. V tomto případě by zakázání tvrzení mohlo ve skutečnosti snížit výkon.

Statická tvrzení

Assertions that are checked at compile time are called static assertions.

Statická tvrzení jsou zvláště užitečná při kompilaci metaprogramování časových šablon , ale lze je také použít v jazycích na nízké úrovni, jako je C, zavedením nezákonného kódu, pokud (a pouze pokud) tvrzení selže. C11 a C ++ 11 podporují statická tvrzení přímo prostřednictvím static_assert. V dřívějších verzích C lze implementovat statické tvrzení, například takto:

#define SASSERT(pred) switch(0){case 0:case pred:;}

SASSERT( BOOLEAN CONDITION );

Pokud je (BOOLEAN CONDITION)část vyhodnocena jako nepravdivá, pak se výše uvedený kód nezkompiluje, protože kompilátor nepovolí dva popisky případů se stejnou konstantou. Logický výraz musí být konstantní hodnotou v době kompilace, například by v tomto kontextu byl platným výrazem. Tato konstrukce nefunguje v rozsahu souboru (tj. Není uvnitř funkce), a proto musí být zabalena uvnitř funkce. (sizeof(int)==4)

Další populární způsob implementace tvrzení v C je:

static char const static_assertion[ (BOOLEAN CONDITION)
                                    ? 1 : -1
                                  ] = {'!'};

Pokud je (BOOLEAN CONDITION)část vyhodnocena jako nepravdivá, výše uvedený kód nebude kompilován, protože pole nemusí mít zápornou délku. Pokud kompilátor ve skutečnosti povoluje zápornou délku, pak by inicializační bajt ( '!'část) měl způsobit, že si i takoví přehnaní kompilátoři stěžují. Logický výraz musí být konstantní hodnotou v době kompilace, například (sizeof(int) == 4)by v tomto kontextu byl platným výrazem.

Obě tyto metody vyžadují metodu vytváření jedinečných jmen. Moderní kompilátory podporují __COUNTER__definici preprocesoru, který usnadňuje konstrukci jedinečných jmen, a to vrácením monotónně rostoucích čísel pro každou kompilační jednotku.

D poskytuje statická tvrzení použitím static assert.

Deaktivace tvrzení

Většina jazyků umožňuje, aby byla tvrzení aktivována nebo deaktivována globálně a někdy nezávisle. Tvrzení jsou často povolena během vývoje a zakázána během závěrečného testování a při vydání zákazníkovi. Nekontrolování tvrzení eliminuje náklady na vyhodnocení tvrzení, zatímco (za předpokladu, že tvrzení jsou bez vedlejších účinků ) stále produkují stejný výsledek za normálních podmínek. Za abnormálních podmínek může deaktivace kontroly tvrzení znamenat, že program, který by byl přerušen, bude pokračovat v běhu. To je někdy výhodnější.

Některé jazyky, včetně C a C ++ , mohou zcela odstranit tvrzení v době kompilace pomocí preprocesoru . Java vyžaduje, aby byla možnost předána modulu run-time, aby bylo možné povolit tvrzení. Absent the option, assertions are bypassed, but they always remain in the code unless optimized away by a JIT compiler at run-time or guaranteed by a if (false)condition at compile time, throughout they need not have a run-time space or time cost in Java buď.

Programátoři mohou do svého kódu zabudovat kontroly, které jsou vždy aktivní, obejitím nebo manipulací s běžnými mechanismy kontroly tvrzení jazyka.

Porovnání se zpracováním chyb

Tvrzení se liší od rutinního zpracování chyb. Tvrzení dokumentují logicky nemožné situace a objevují chyby v programování: pokud dojde k nemožnému, pak je v programu něco zásadního špatně. To se liší od zpracování chyb: většina chybových stavů je možná, i když některé mohou být v praxi extrémně nepravděpodobné. Použití tvrzení jako obecného mechanismu zpracování chyb je nerozumné: tvrzení neumožňují obnovu z chyb; selhání tvrzení normálně zastaví provádění programu náhle; a tvrzení jsou často zakázána v produkčním kódu. Tvrzení také nezobrazují uživatelsky přívětivou chybovou zprávu.

Zvažte následující příklad použití tvrzení ke zpracování chyby:

  int *ptr = malloc(sizeof(int) * 10);
  assert(ptr);
  // use ptr
  ...

Zde si programátor uvědomuje, že mallocvrátí NULLukazatel, pokud není přidělena paměť. To je možné: operační systém nezaručuje, že každé volání na mallocbude úspěšné. Pokud dojde k chybě paměti, program okamžitě přeruší činnost. Bez tvrzení by program pokračoval v běhu, dokud nebude ptrzrušen, a případně i déle, v závislosti na konkrétním používaném hardwaru. Dokud nejsou tvrzení deaktivována, je zajištěn okamžitý odchod. Pokud je ale požadována půvabná chyba, program musí selhání zvládnout. Server může mít například více klientů nebo může obsahovat prostředky, které nebudou uvolněny čistě, nebo může mít nepotvrzené změny pro zápis do úložiště dat. V takových případech je lepší selhat jedinou transakci, než náhle přerušit.

Další chybou je spoléhat se na vedlejší účinky výrazů použitých jako argumenty tvrzení. Vždy je třeba mít na paměti, že tvrzení nemusí být provedena vůbec, protože jejich jediným účelem je ověřit, zda podmínka, která by vždy měla být pravdivá, skutečně platí. Pokud je tedy program považován za bezchybný a uvolněný, mohou být tvrzení deaktivována a již nebudou vyhodnocována.

Zvažte jinou verzi předchozího příkladu:

  int *ptr;
  // Statement below fails if malloc() returns NULL,
  // but is not executed at all when compiling with -NDEBUG!
  assert(ptr = malloc(sizeof(int) * 10));
  // use ptr: ptr isn't initialised when compiling with -NDEBUG!
  ...

Může to vypadat jako chytrý způsob, jak přiřadit návratovou hodnotu mallock ptra zkontrolovat, zda je NULLv jednom kroku, ale mallocvolání a přiřazení k ptrje vedlejším efektem vyhodnocení výrazu, který tvoří assertpodmínku. Když je NDEBUGparametr předán kompilátoru, jako když je program považován za bezchybný a uvolněný, assert()příkaz je odebrán, takže malloc()se ptrneříká , vykreslování je neinicializováno. To by mohlo potenciálně vést k chybě segmentace nebo podobné chybě nulového ukazatele mnohem dále v řádku při provádění programu, což způsobuje chyby, které mohou být sporadické a/nebo obtížně dohledatelné. Programátoři někdy ke zmírnění tohoto problému používají podobnou definici VERIFY (X).

Moderní kompilátory mohou při setkání s výše uvedeným kódem vydat varování.

Dějiny

Ve zprávách von Neumanna a Goldstina z roku 1947 o jejich návrhu pro stroj IAS popsali algoritmy pomocí rané verze vývojových diagramů , do nichž zahrnuli tvrzení: „Může být pravda, že kdykoli C skutečně dosáhne určitého bodu toku diagram, jedna nebo více vázaných proměnných nutně bude mít určité zadané hodnoty, nebo bude mít určité vlastnosti, nebo navzájem uspokojí určité vlastnosti. Kromě toho můžeme v takovém bodě uvést platnost těchto omezení. Z tohoto důvodu označíme každou oblast, ve které je platnost takových omezení prosazována, zvláštním rámečkem, kterému říkáme tvrzení. "

Asertivní metodu prokazování správnosti programů prosazoval Alan Turing . V rozhovoru „Kontrola velké rutiny“ v Cambridgi, 24. června 1949, Turing navrhl: „Jak lze kontrolovat velkou rutinu ve smyslu ujistit se, že je správná? Aby muž, který kontroluje, nemusel mít příliš obtížné Úkol by měl programátor učinit řadu konkrétních tvrzení, která lze jednotlivě zkontrolovat a ze kterých snadno vyplývá správnost celého programu “.

Viz také

Reference

externí odkazy