W wielu API funkcje mają bardzo prosty sposób powiadamiania o tym, czy ich wykonanie zakończyło się sukcesem czy porażką. Albo więc wykorzystują typ bool
bezpośrednio, albo wpasowują się w konwencję, iż niezerowa wartość liczbowa jest tożsama z prawdą, a zero z fałszem. To sprawia, że możliwe jest pisanie warunków podobnych do poniższego:
Ładne to i opisowe – wręcz samodokumentujące się. Ale czasami tak się zrobić nie da, bo wartości zwracane nie chcą współpracować z tym modelem.
Przykład? To większość biblioteki runtime języka C oraz API systemów uniksowych. O ile tylko rezultatem funkcji należącej do któregoś z tych dwóch zbiorów nie jest wskaźnik, konwencja informowania o powodzeniu lub niepowodzeniu jest zwykle dość osobliwa. Według niej zero oznacza sukces, natomiast porażka wykonania jest sygnalizowana przez wartość mniejszą od zera – zazwyczaj -1
. Oczywiście nijak nie pasuje to sposobu interpretowania liczb jako wartości logicznych. Sprawia to, że sprawdzanie rezultatu takich funkcji może wyglądać cokolwiek enigmatycznie:
Powód, dla którego to działa, jest dość prosty. W standardowym sposobie zapisu liczb całkowitych, stosowanym na zdecydowanej większości typowych i nietypowych maszyn (zwanym uzupełnieniem do 2 – U2), wartość -1 to w zapisie binarnym same jedynki. Negując je bitowo, otrzymujemy same zera – czyli zero, a więc logiczny fałsz. A odwrotnością fałszu jest oczywiście prawda i wszystko działa poprawnie. Wygląda więc tak, jakby skromna tylda zdołała “naprawić” funkcję, by zachowywała się zgodnie z oczekiwaniami…
Tylko czy aby na pewno nowy zapis jest bardziej sugestywny? Mam nadzieję, że każdy potrafi poprawnie odpowiedzieć na to pytanie we własnym zakresie :) Na koniec jednak muszę – dla spokoju sumienia – ostrzec wszystkich: zdecydowanie nie róbcie tego w domu :D
Zdarza się że funkcje takie zwracają różne kody błędów mniejsze od 0. Dla -1 to faktycznie działa ale dla -2 już niebardzo :)
Bardzo podobnie do sprawdzanych magicznych wartości zwracanych przez f(). Używane są warunki w ifach
if( a && b*a && f()|c ||a+2<0 )
żyj();
else
foo();
Przy tworzeniu takich baboli, każdy myśli sobie: jestem zajebisty, to po co mam pisać nawiasy/komentarze/etc. Lubie oglądać ludzi i ich miny jak muszą własne wypociny później poprawiać.
Dobrze by było, gdyby oprócz programowania uczyli także dobrego stylu programowania.
Tak jest, to doskonały przykład na to, jak *nie* należy programować, bowiem ten przykład jest:
1) Ryzykowny – jak słusznie zauważył moriturius, działa tylko dla wartości -1. Akurat np. funkcje API uniksowego spełniają ten warunek, ale jeśli ktoś pomyśli sobie na przykład, aby w liczbie ujemnej zapisywać kod błędu, to już tak dobrze nie będzie.
2) Nieprzenośny – opiera się na fakcie, że -1 w kodzie U2 to 11..11. Jeśli mielibyśmy do czynienia na przykład z reprezentacją przy użyciu bitów znaku, to -1 miałby postać 100..001, co po negacji oczywiście nie daje zera.
3) Nieprzejrzysty – bez uważnego wpatrzenia się i/lub odwołania do dokumentacji, bardzo ciężko (bez komentarzy) zrozumieć, “co autor miał na myśli”.
4) Nie dający żadnych korzyści – pierwotne porównanie z 0 czy -1 jest po prostu lepsze.
Tak więc jedyną zaletą tego przykładu jest to, że świetnie nadaje się na zły przykład i w takim też charakterze go zaprezentowałem.
Inaczej mówiąc jest super! :D