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)? :)
No podle mě jde jen o další z
No podle mě jde jen o další z idiomů tohoto jazyka, stejně jako se např. kus kódu
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í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ř.
mi přijde úplně stejně čitelné jako
Každý názor musí mít titulek :p
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:
Nepoužívají se magické konstanty a kód je self-documented.
Jo, while (*q++ = *p++); je
Jo,
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
se mi třeba nezamlouvá v tom ohledu, že existuje-li konstanta
STRCMP_EQ
, očekával bych přirozeně i konstantySTRCMP_LT
aSTRCMP_GT
s patřičnou sémantikou.Re: while (*q++ = *p++);
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:
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...
Operátor !
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:
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í :).
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í.