Posts tagged ‘return value’

Informacje o błędach w rezultatach funkcji

2010-07-25 11:34

Wczoraj naciąłem się na nieprzyjemny rodzaj błędu w kodzie. Opierając się na powiedzeniu “Najciemniej pod latarnią”, można by go określić jako nieprzenikniona ciemność tuż pod najjaśniejszą latarnią w całym mieście. Normalnie nikomu nie przyjdzie do głowy, by jej tam szukać. Chodzi bowiem o jakąś “oczywistość”, w którą wątpienie jest nie tyle nierozsądnie, co wręcz nie przychodzi nawet na myśl.
A wszystko dlatego, że lubię prostotę – przynajmniej w takich kwestiach, jak wyniki funkcji informujące o ewentualnych błędach. Prawie zawsze są to u mnie zwykłe boole, informujące jedynie o tym, czy dana operacja się powiodła lub nie. To wystarcza, bo do bardziej zaawansowanego debugowania są logi, wyjątki, asercje i inne dobrodziejstwo inwentarza. A same wywołania funkcji możemy sobie bez przeszkód umieszczać w ifach czy warunkach pętli.

Są jednak biblioteki, które “twierdzą”, że taka konwencja jest niedobra i proponują inne. Pół biedy, gdy chodzi tu o coś w rodzaju typu HRESULT, do którego w pakiecie dostarczane są makra SUCCEEDED i FAILED. Zupełnie nie rozumiem jednak, co jest pociągającego w pisaniu np.:

  1. int sock;
  2. if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == -1)
  3.     { /* błąd */ }

Usprawiedliwiać się mogę jednak tym, że… no cóż, to przecież Linux ;-) Ale kiedy dostaję do ręki porządną bibliotekę z klasami, wsparciem dla STL-a i CamelCase w nazwach funkcji, to przecież mogę się spodziewać czegoś rozsądniejszego, prawda? Skąd może mi przyjść do głowy, że rezultat zero ma oznaczać powodzenie wykonania funkcji?!

Najwyraźniej jednak powinienem taką możliwość dopuszczać. Okazuje się bowiem, że nawet dobrze zaprojektowane, estetyczne API może mieć takie kwiatki. Przekonałem się o tym, próbując pobrać wartość atrybutu elementu XML, używając biblioteki TinyXML i jej metody o nazwie QueryStringAttribute. W przypadku powodzenia zwraca ona stałą TIXML_SUCCESS; szkoda tylko, że jej wartością jest… zero ;P
Więc zaprawdę powiadam wam: nie ufajcie żadnej funkcji, której typem zwracanym nie jest bool!

Tags: , , ,
Author: Xion, posted under Programming » 14 comments

Dwukierunkowe funkcje i obiekty proxy

2010-06-01 16:07

Jedną z pierwszych rzeczy poznawanych w trakcie nauki programowania jest sposób działania funkcji. Mam tu na myśli zupełnie elementarny fakt, iż funkcje przyjmują zero lub więcej argumentów na wejściu i produkują co najwyżej jeden rezultat na wyjściu. Niezachwiana pewność w tę własność funkcji może być potem mocno nadwątlona, jeśli poznamy języki o bardziej egzotycznym sposobie działania, jak na przykład Prolog; tam wszystkie parametry funkcji mogą przekazywać dane w obu kierunkach, dzięki czemu np. za dzielenie i łączenie ciągów lub list odpowiada jedna i ta sam funkcja.
Nie trzeba jednak uciekać tak daleko od starego dobrego programowania imperatywnego, żeby zaobserwować odstępstwa od reguły. Parametry mogą przecież służyć do zwracania wartości – dzieje się to przy pomocy słów kluczowych ref/out w C# lub zwykłych wskaźników/referencji w C++. Często zdarza się wtedy, że “normalny” rezultat zwracany przez funkcję służy jedynie przekazaniu informacji o ewentualnym błędzie.

Rzadsza sytuacja polega na tym, że wynik funkcji staje się jej argumentem wejściowym. Technicznie nie jest nawet możliwe, ale można tak traktować sytuacje, gdy rezultatem jest l-wartość (l-value), tj. obiekt, do którego można przypisywać:

  1. vector<int> v(10);
  2. v.at(5) = 25;

Typowo jest to referencja (tutaj int&), a powyższa konstrukcja – poprzez swojej podobieństwo do indeksowania zwykłej tablicy – nie należy do wielce zaskakujących. Co jednak można powiedzieć o tej:

  1. Dim S as String = "Ala ma kota"
  2. Mid(S, 4, 2) = "nie ma" ' Ala nie ma kota

poza widocznym od razu faktem, że pochodzi ona z języka, którego rozwlekłość składni przyprawia o niestrawność? :) Mianowicie tutaj nie ma już oczywistych przesłanek co do tego, czym jest lewa strona. Widać bowiem, że możemy jej przypisać dłuższy podciąg niż oryginalny i zostanie on mimo wszystko poprawnie zastąpiony – nie jest to więc żaden prosty “wyrób wskaźnikopodobny”. Może więc to po prostu feature akurat tego języka, którego nie da się zreplikować?…

Odpowiedź jest – jak można się domyślać, skoro o tym piszę – oczywiście negatywna :) Ażeby podobny efekt osiągnąć w C++, konieczne jest jednak zastosowanie techniki znanej jako obiekt pośredniczącyproxy. Powinien on zachowywać się jak zwykły rezultat funkcji, ale w razie potrzeby dawać również możliwość przypisywania do siebie. Naturalnie efekt takiego działania jest specyficzny dla konkretnej funkcji, która swoją drogą może być często zredukowana do samego tworzenia obiektu proxy:

  1. inline Mid_Proxy Mid(std::string& s, size_t off, size_t len)
  2.     { return Mid_Proxy(s, off, len); }

Minimum, jakie rzeczony obiekt musi zapewniać, to operator przypisania oraz jakiś operator rzutowania, który pozwoli na “wyciągnięcie” rezultatu funkcji, gdy jest ona użyta w zwykły sposób:

  1. class Mid_Proxy
  2. {
  3.     private:
  4.         std::string& s;
  5.         size_t off, len;
  6.     public:
  7.         Mid_Proxy(std::string& _s, size_t _off, size_t _len)
  8.             : s(_s), off(_off), len(_len) { }
  9.  
  10.         void operator = (const std::string& str)
  11.             { s.replace (off, len, str); }
  12.         operator std::string () const
  13.             { return s.substr(off, len); }
  14. };

Widzimy tutaj, że przy takim proxy nasza funkcja Mid użyta w zwykły sposób zachowuje się jak metoda substr. Gdy jednak umieścimy jej wywołanie po lewej stronie przypisania, zadziała metoda replace, służąca do zastępowania podciągu innym. W sumie więc poniższy kod:

  1. std::string s("Ala ma kota");
  2. Mid(s, 4, 2) = "nie ma";

będzie działał analogicznie do prezentowanego wyżej fragmentu w języku Visual Basic.

Opisana tu sztuczka nie jest oczywiście doskonała. Do pełni możliwości potrzebna jest jeszcze wersja read-only funkcji Mid, przyjmująca stałą referencję do stringa i wywołująca substr bezpośrednio, z pominięciem obiektu proxy. To jednak da się łatwo zauważyć.
Mniej widoczny jest natomiast fakt, że obiekt proxy, dodając kolejną warstwę niejawnych konwersji (tutaj: z siebie na string) może spowodować kłopoty tam, gdzie jedna niestandardowa konwersja jest już wykorzystywana. Dobry przykład to interakcja ze strumieniem:
std::cout << Mid(s, 0, 3) << std::endl;[/cpp] która nie powiedzie, bo operator strumienowy << nie posiada wersji dla lewego prawego argumentu będącego std::stringiem, a jedynie dla const char* (co swoją drogą jest dość dziwne). Rozwiązaniem jest napisanie takowego dla samego obiektu proxy.

 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.