Jste zde

if (!strcmp(str1, str2))

Poměrně často se při prohlížení cizých zdrojových kódů setkávám s konstrukcemi, se kterými tak úplně nesouhlasím a o kterých si myslím, že snižují čitelnost kódu (a tudíž schopnosti porozumění daného kódu), jsou špatnou praktikou (nejen v daném programovacím jazyce) či jejichž použití může být dokonce nebezpečné. Nad jednou takovou konstrukcí (spadající do kategorie špatných praktik a snižování čitelnosti kódu) bych se v tomto příspěvku chtěl zamyslet.

Musím se přiznat, že následující konstrukci jsem dříve používal i já, ale v současné době se jí snažím vyhýbat.

if (!strcmp(str1, str2)) {
    // ...
}

Funkce strcmp() porovná dva řetězce a vrátí číslo:

  • menší než 0, pokud je první řetězec "menší" než ten druhý
  • 0, pokud jsou oba řetězce "stejné"
  • větší než 0, pokud je první řetězec "větší" než ten druhý

Pokud jsou tedy oba řetězce "stejné", pak funkce vrátí 0 a operátor ! způsobí změnu hodnoty výrazu na 1, což je v C vyhodnoceno jako pravda a tudíž se provede tělo bloku. Z funkčního hlediska zde žádný problém není a tato konstrukce je naprosto legální. Takže, v čem je tedy problém?

Z teoretického hlediska je operátor ! logickým operátorem a měl by se tudíž aplikovat pouze na logické výrazy. V jazyce C ovšem původně žádný typ bool není (podporu datového typu bool přinesl až standard ISO C99) a jedná se o slabě typovaný jazyk, takže jsou povoleny konstrukce jako !-645 apod. (např. v jazyku Java, který je silně typovaný, lze operátor ! opravdu aplikovat jen na logické výrazy). Funkce strcmp() ovšem obecně nevrací pravdivostní hodnotu (pravda/nepravda či 0/1, jak by ji vracela např. neexistující funkce strequal(str1, str2)), nýbrž existují celkem tři návratové hodnoty (menší než 0, 0, větší než 0) s různým významem.

Z praktického hlediska může být tato konstrukce hůře čitelná, především pro lidi, kteří nemají tento jazyk "v malíku" a může to svádět k pochopení jako "pokud nejsou řetězce stejné...". Jako jedinou výhodu vidím to, že tato konstrukce je kratší, než níže zmíněná konstrukce, za kterou se přimlouvám já a která je srozumitelná pro každého, kdo zná význam funkce strcmp():

if (strcmp(str1, str2) == 0) {
    // ...
}

Co na toto téma říkáte vy? Znáte nějakou konstrukci, která vás "vytáčí" a se kterou nesouhlasíte? Používáte sami tuto (či podobnou) konstrukci (pokud ano, co vás k tomu vede)? :)

Komentáře

No podle mě jde jen o další z idiomů tohoto jazyka, stejně jako se např. kus kódu

   while (*q++ = *p++) ;

může použít pro kopírování řetězce. Osobně mi tyto všechny konstrukce přijdou úděsné (strcmp(str1, str2) == 0? co to sakra má znamenat?? jasně, všichni známe výstup funkce strcmp, ale nás přeci nezajímá, jaká je mezi danými řetězci relace lexikografického uspořádání — nás zajímá, jestli jsou ekvivalentní, ne? tak proč tak obfuskovaně?) a spíš bych se dal cestou konzistentního používání

inline int str_equal(const char* str1, const char* str2) { return strcmp(str1, str2) == 0; }
  // ...
if (str_equal(str1, str2))
{
   // ...
}

což ale taky není úplně výhra (došlo k zavedení nového aliasu). Osobně si myslím, že je jedno, která z Tebou uvedených variant je použita, pokud je tato varianta používána konzistentně a pakliže je daná podmínka dobře komentovaná (mnou preferovaná varianta), např.

if (!strcmp(str1, str2))
{ // strings are equal
  printf("Strings are equal.\n");
  // do something else
}

mi přijde úplně stejně čitelné jako

if (strcmp(str1, str2) == 0)
{ // strings are equal
  printf("Strings are equal.\n");
  // do something else
}

while (*q++ = *p++);

Tohle je otřesna konstrukce, kterou bych radši v programu nikdy nepoužil, protože bych ji považoval za potenciální zdroj chyby a jejim laděním bych strávil víc času, než kolik bych si ho dříve ušetřil.

A abych pravdu řekl, tak už jsem z C docela vypadl a od !strcmp() jsem očekával přesný opak, než jaký ve skutečnosti dělá.

Mě příjde nejlepší používat něco ve na způsob:

/// Return code of strcmp() when the strings are equal
const int STRCMP_EQ = 0;
 
//...
 
if (strcmp(str1, str2) == STRCMP_EQ)
{
  // ...
}

Nepoužívají se magické konstanty a kód je self-documented.

Jo,

while (*q++ = *p++);

je pěkná prasárna, zdroj? Bjarne Stroustrup: The C++ Programming Language. Komentář k tomu v porovnání s jinými způsoby zápisu:

Is this version less readable than the previous versions? Not to an experienced C or C++ programmer.

Zápis pro porovnání řetězců na rovnost jako

/// Return code of strcmp() when the strings are equal
const int STRCMP_EQ = 0;
 
//...
 
if (strcmp(str1, str2) == STRCMP_EQ)
{
  // ...
}

se mi třeba nezamlouvá v tom ohledu, že existuje-li konstanta STRCMP_EQ, očekával bych přirozeně i konstanty STRCMP_LT a STRCMP_GT s patřičnou sémantikou.

Tohle mi připomnělo následující úryvek z S. McConnell, Code Complete, 2nd edition, Microsoft Press, 2004, ISBN 978-0735619678:

Try to figure out what this routine does:

strcpy(char * t, char * s) {
    while (*++t = *++s)
       ;
}

Some experienced C programmers don’t see the complexity in that example because it’s a familiar function; they look at it and say, “That’s strcpy().” In this case, however, it’s not quite strcpy(). It contains an error. If you said, “That’s strcpy()” when you saw the code, you were recognizing the code, not reading it. This is exactly the situation you’re in when you debug a program: The code that you overlook because you “recognize” it rather than read it can contain the error that’s harder to find than it needs to be.

Clearly, t and s are incremented before *s is copied to *t. The first character is missed...

Díky za komentáře. Jak už jsem naznačil v příspěvku, tak if (!expression) ... ve mě při čtení kódu evokuje když neplatí expression ..., takže by expression měla buď dávat logickou hodnotu, nebo maximálně dva možné výsledky, které lze interpretovat ve smyslu platný/neplatný. Např. v PHP je poměrně standardní konstrukce:

$result = mysql_query($sql_statement);
if (!$result) {
    // Handle database error
    // ...
}
// Process each row of the result table
while ($row = mysql_fetch_array($result)) {
    // ...
}

takže i když nevrací mysql_query() booleovskou hodnotu, je tento kód v pořádku (pokud je výsledek neplatný...). Ale to už asi zabíhám jinam (vzhledem k tomu, že PHP je v zásadě netypovaný jazyk)...

A když už jsem u toho operátoru !, tak jsem v kódu viděl i podobné konstrukce jako je ta následující :).

assert(!"Program should never get here.");

Každopádně souhlasím s tím, že pokuď je daný úsek kódu vhodně komentovaný, tak to snižuje riziko jeho špatného pochopení.

Přidat komentář