Archive for Programming

Asercje, wyjątki i inne błędy

2008-07-16 17:09

W każdym programie większym niż Hello World istnieje możliwość wystąpienia błędów w czasie działania. Dotyczy to zwłaszcza takich, które nie są zależne od programisty piszącego kod aplikacji, lecz na przykład od danych zewnętrznych pochodzących od użytkownika czy z plików.
W zależności od typu i stopnia dolegliwości błędy mogą być obsługiwane na różne sposoby. Dość często nie jest wcale łatwo zdecydować się na któryś z nich. Dlatego należy znać typowe metody sygnalizowania błędów i przynajmniej ogólne zasady opisujące sytuacje, w których każda z tych metod jest najwłaściwsza.

Sam przez bardzo długi czas miałem na przykład pewne wątpliwości co do przydatności asercji jako mechanizmu powiadamiania o błędach. W szczególności, nie potrafiłem odróżnić sytuacji, w których właściwsze jest korzystanie właśnie z asercji niż chociażby z wyjątków. Ostatnio aczkolwiek całkiem przypadkowo nadrobiłem te niechlubne zaległości :)
Okazuje się bowiem, że różnica między asercją a wyjątkiem jest znaczna. Asercje służą do sprawdzania pewnych warunków, które uznajemy za obiektywnie prawdziwe. Są to po prostu założenia, które muszą być spełnione w każdym okolicznościach, gdyż warunkują poprawność kodu. Pisanie asercji jest więc częściowo sprawdzaniem samego siebie: dzięki nim łatwiej wykryjemy błędy programistyczne we wczesnej fazie powstawania kodu (czyniąc naturalnie optymistyczne założenie, że same asercje są w porządku :]).

Wniosek z tego taki, że w dobrze działającej aplikacji asercje zawsze powinny być spełnione i nigdy nie przerywać działania programu. W przeciwieństwie do nich wyjątki (exceptions) mogą pojawiać się w zupełnie poprawnym kodzie, bo dotyczą rzeczy, na które program nie ma wpływu. Jednocześnie jednak musi on przewidywać ich ewentualne wystąpienie i posiadać odpowiedni kod ich obsługi. W przeciwnym razie konsekwencje bywają nieprzyjemne.
Dlatego wyjątki (i podobne do nich mechanizmy, jak np. symbianowe leaves) służą do powiadamiania o sytuacjach mających potencjalnie poważne konsekwencje. Dla pozostałych należy stosować mniej radykalne metody informowania o błędach. Najpopularniejszym jest oczywiście wartość zwracana przez funkcję, w której wystąpił błąd (ewentualnie w połączeniu z czymś takim jak errno lub GetLastError w Windows API). Kluczową cechą tego sposobu powiadamiania jest to, że należy specjalnie zadbać o sprawdzenie, czy błąd wystąpił; inaczej zostanie on zignorowany.

Tak w skrócie przedstawia się sprawa od strony teoretycznej. W praktyce odróżnienie sytuacji podpadającej pod asercję od tej, gdzie uprawniony jest wyjątek – a już zwłaszcza wyjątku od zwykłego “return -1;” – bywa trudne, a często subiektywne. Zwykle jednak daje się stosować przynajmniej jedną zasadę: przynajmniej w fazie testów lepiej jest przesadzać i reagować nazbyt histerycznie niż przeoczyć lub zignorować jakiś ważny szczegół.

Tags: , ,
Author: Xion, posted under Programming » 1 comment

using nieco inaczej

2008-07-14 18:42

W C++ istnieje słowo kluczowe using, z którym zetknął się chyba każdy. Zwykle dotyczy to nieśmiertelnej linijki:

  1. using namespace std;

Dlatego też słowo to kojarzy się przede wszystkim z przestrzeniami nazw (namespaces), a najczęściej tylko i wyłącznie z nimi.

Jednak using ma też swoje zastosowanie – i to zapewne znacznie ciekawsze – przy definiowaniu klas. Formalnie rzecz ujmując, słówko to pozwala wprowadzić składową pochodzącą z klasy bazowej (metodę lub pole) do zasięgu klasy pochodnej. Niezbyt to ekscytujące na pierwszy rzut oka, ale faktem jest, że dzięki skorzystaniu z using możemy dokonać przynajmniej jednej koniecznej czasami operacji. Możliwa jest mianowicie zmiana kontroli dostępu do danego składnika klasy, czyli określenie, czy ma on być prywatny czy może publiczny.
Wyobraźmy sobie na przykład, że w klasie bazowej mamy jakieś funkcje niepubliczne, które chcemy udostępnić na zewnątrz w klasie pochodnej:

  1. class Base { protected: void Fun(); };
  2.  
  3. class Foo : public Base
  4. {
  5.     public:
  6.         using Base::Fun; // teraz metoda Fun() jest publiczna
  7. };

Sytuacja nie jest wcale taka hipotetyczna. Sam miałem ostatnio taki przypadek, gdy najwygodniej było po prostu wbudować w klasę bazową pewną ukrytą funkcjonalność, która była używana przez wiele z jej klas pochodnych do ich wewnętrznych celów. Jednak niektóre z tych klas musiały tę “bazową” funkcjonalność udostępnić na zewnątrz jako publiczną. I tu przydało się użycie using w sposób zaprezentowany wyżej. (Dla zainteresowanych wspomnę, że opisywana sytuacja praktycznie wystąpiła u mnie w kodzie systemu GUI :]).

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

Triki z PowerShellem #7 – Zliczanie wierszy

2008-07-12 10:10

Gdy zawzięcie i wytrwale tworzymy jakiś koderski projekt, chciałoby się od czasu do czasu zmierzyć, ile to już pracy zdołaliśmy w nim wykonać. Pewnym sposobem na to jest policzenie ilości wszystkich linii kodu, które udało nam się już napisać.
Istnieją do tego nawet oddzielne aplikacje, niekiedy z rozbudowanym interfejsem graficznym. Jednak takie proste zadanie można by przecież zrealizować przy pomocy równie prostego narzędzia. Jakiego? Oczywiście – niewielkiego skryptu PowerShella :) Chociażby takiego jak ten:

  1. # lines.ps1
  2. # Skrypt liczący wiersze kodu w plikach podanego katalogu
  3.  
  4. # Parametr: ścieżka do katalogu z plikami
  5. param ([string]$path = ".")
  6.  
  7. # Stałe
  8. $EXTENSIONS = @("cpp", "h", "hpp")  # Rozszerzenia interesujących plików
  9.  
  10. # Zmienne
  11. $filesCount = 0
  12. $totalLinesCount = 0
  13. $emptyLinesCount = 0
  14.  
  15. # Przeszukujemy podany katalog rekurencyjnie (wszerz)
  16. $dirQueue = New-Object Collections.Queue
  17. $dirQueue.Enqueue($path)
  18. while ($dirQueue.Count -gt 0)
  19. {
  20.     # Dodajemy podkatalogi do przeszukania
  21.     $dir = [string]$dirQueue.Dequeue()
  22.     foreach ($subDir in [IO.Directory]::GetDirectories($dir))
  23.         { $dirQueue.Enqueue($subDir) }
  24.        
  25.     # Pobieramy wszystkie pliki w aktualnym katalogu pasujące do filtra
  26.     $files = New-Object Collections.ArrayList
  27.     foreach ($ext in $EXTENSIONS)
  28.         { $files.AddRange([IO.Directory]::GetFiles($dir, "*." + $ext)) }
  29.    
  30.     # Liczymy w nich wiersze
  31.     foreach ($file in $files)
  32.     {
  33.         ++$filesCount
  34.         foreach ($line in [IO.File]::ReadAllLines($file))
  35.         {
  36.             ++$totalLinesCount
  37.             if ($line.Trim().Length -eq 0) { ++$emptyLinesCount }
  38.         }
  39.     }
  40. }
  41.  
  42. # Wyświetlamy statystyki
  43. "Files: " + $filesCount | Out-Host
  44. "Total lines: " + $totalLinesCount | Out-Host
  45. "Non-empty lines: " + ($totalLinesCount - $emptyLinesCount) | Out-Host

Podajemy mu tylko ścieżkę do katalogu z naszym kodem, a on przeglądnie wszystkie pliki z ustalonymi rozszerzeniami i wyświetli statystykę z liczbą linii: wszystkich oraz niepustych. Cała ta czynność ta nie zajmuje w kodzie specjalnie dużo miejsca, gdyż najważniejsze jej elementy: pobranie podkatalogów, plików w katalogu i wierszy tekstu w pliku dają się zrobić pojedynczymi wywołaniami funkcji (odpowiednio: GetDirectories i GetFiles z System.IO.Directory oraz System.IO.File.ReadAllLines). To aczkolwiek zasługa nie samego PowerShella, tylko niezawodnej platformy .NET ;)

Skrypt ten można rzecz jasna znacznie usprawnić, pozwalając chociażby na definiowanie filtra nazw plików z kodem w jego wywołaniu czy też umożliwiając wykluczanie niektórych katalogów (np. bin, Debug, Release) w celu przyspieszenia zliczania. Jeśli ktoś miałby na to ochotę, może się dodawaniem takich feature‘ów pobawić :)

Tags:
Author: Xion, posted under Applications, Programming » Comments Off on Triki z PowerShellem #7 – Zliczanie wierszy

Samowskaźnik i usuwanie siebie

2008-07-10 17:35

Bez używania mechanizmu odśmiecania istnieje zawsze ryzyko, że skończymy z odwołaniem do obiektu, który został już zniszczony. (Przy używaniu GC też istnieje taka możliwość, ale musielibyśmy niejako sami się postarać, aby wystąpiła). Dlatego zaleca się na przykład, by każdemu wywołaniu delete towarzyszyło zerowanie wskaźnika:

  1. delete p; p = NULL;

dzięki czemu można ochronić się przez problemem wiszących wskaźników (dangling pointers).

Można? No cóż, nie do końca. Istnieje jeszcze możliwość, że obiekt zniszczy się sam, używając po prostu instrukcji delete this (jest to jak najbardziej legalne). A wtedy wszelkie odnoszące się do niego odwołania będą już nieważne.
Chyba że… obiekt sam je wyzeruje. Istnieje mianowicie śmieszny trik, polegający na przekazaniu do niego w konstruktorze wskaźnika na siebie (self-pointer):

  1. Foo* pFoo = new Foo(/* parametry konstruktora */, &pFoo);

A mówiąc dokładniej: wskaźnika na ów wskaźnik (zaczyna się robić ciekawie, prawda? ;]). Musi być on oczywiście zapamiętany, a cała sztuczka polega na tym, że w destruktorze obiektu następuje jego zerowanie:

  1. class Foo
  2. {
  3.     public:
  4.         Foo(/* ... */, Foo** ppSelf = NULL) : m_ppSelf(ppSelf) { /* ... */ }
  5.         ~Foo() { if (ppSelf) *ppSelf = NULL; }
  6.     private:
  7.         Foo* m_ppSelf;
  8. };

I teraz obiekt może radośnie się usuwać kiedy tylko zechce. A tak swoją drogą, jeśli rzeczywiście może on zniknąć w każdej chwili, to może wypadałoby też, aby sprawdzał, czy przypadkiem… już nie istnieje:

  1. void Foo::SomeMethod(/* ... */)
  2. {
  3.     if (!this) return;
  4.     // ...
  5. }

Pokręcone i przekombinowane? Powiedziałbym wręcz, że szalone :) Ale do takich sztuczek trzeba się uciekać, jeśli nie korzystamy z mechanizmów odśmiecania pamięci i nie potrafimy jednoznacznie określić czasu życia obiektów i ich wzajemnej przynależności.
Albo po prostu: gdy nie mamy lepszych pomysłów. Bo przecież w ostateczności lepiej chyba sprawdzi się zwyczajna flaga logiczna z metodą typu IsAlive, jeśli obiekt rzeczywiście może popełnić nagłe samobójstwo. Najlepiej jednak, żeby takich emo-obiektów było jak najmniej ;)

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

Wskaźniki i referencje jako parametry

2008-07-09 19:07

Kiedy w C++ chcemy przekazać do funkcji odwołanie do obiektu (zezwalające na jego modyfikację wewnątrz funkcji), mamy do wyboru dwie metody. Ta alternatywa to posłużenie się wskaźnikiem albo referencją:

  1. void Function(Foo* pFoo);
  2. void Function(Foo& foo)

Czy istnieje uniwersalna odpowiedź na to, którą wybrać? Chyba nie. Jeśli chodzi o wskaźnik, to za jego użyciem może przemawiać:

  • Możliwość przekazania odwołania pustego, jeśli parametr nie jest obowiązkowy. Obejmuje to oczywiście zdefiniowanie NULL jako domyślnej wartości dla tego parametru w deklaracji funkcji. Nie jest to możliwe dla referencji (w C++).
  • Fakt, że w wywołaniu funkcji bardziej widoczne jest to, iż przekazany do niej za pośrednictwem wskaźnika obiekt może się zmienić. Jeśli na przykład obiekt ten jest zmienną lokalną, to konieczne jest posłużenie się operatorem &, który daje o tym jakąś widoczną wskazówkę (nie tak jasną jak ref/out w C#, ale zawsze). Nie jestem też wielkim fanem notacji węgierskiej, lecz w przypadku wskaźników stosowanie przedrostka p wydaje mi się akurat wskazane i w tym kontekście też zwiększa czytelność wywołania funkcji, wskazując, że przekazywany obiekt (alokowany na stercie) też może się zmienić.
    1. Foo foo; Foo* pFoo = new Foo();
    2. Function (&Foo); Function (pFoo); // funkcja może zmienić obiekt

Z kolei referencje mogą się popisać innymi zaletami:

  • Nie można do nich przekazać odwołania pustego. To może być zaletą, jeśli taka sytuacja jest niepożądana. Ponadto brak konieczności sprawdzania tego, czy referencja jest “pusta”, może lekko poprawić wydajność kodu generowanego przez kompilator.
  • Składnia użycia obiektu przekazywanego przez referencję zwykle bywa bardziej przejrzysta. Jest tak zwłaszcza wtedy, gdy używamy względem niego operatorów. Na przykład kolekcja dostępna przez wskaźnik musiałaby być indeksowana przez (*pArray)[i], zaś przez referencję po prostu jako array[i].

Widać więc, że jeśli kwestia odwołania pustego nie jest dla nas istotna, to decyzja może być trudna. Ale naturalnie jest tak tylko wtedy, gdy zechcemy się nad takimi sprawami zastanawiać ;]

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

Kolorowanie semantyczne

2008-07-07 19:04

W dzisiejszych czasach niemal każdy edytor plików tekstowych nieco bardziej zaawansowany od windowsowego Notatnika oferuje funkcję kolorowania składni (syntax highlighting) przynajmniej kilku najpopularniejszych języków programowania. W przypadku całych IDE jest to oczywiście funkcjonalność absolutnie oczywista i niezbędna, jednak coraz więcej środowisk oferuje też coś więcej. Coś, co dla odróżnienia nazywam kolorowaniem semantycznym; nie jestem bowiem pewien, czy ten mechanizm ma jakąś ogólnie przyjętą nazwę.


Kodowanie semantyczne w Visual Studio z pluginem AssistX

O co w nim chodzi? Standardowe kolorowanie potrafi między innymi odróżnić słowa kluczowe od identyfikatorów, wyróżniając zwykle te pierwsze innym stylem czcionki, zaś te drugie pozostawiając bez zmian. Wszystkie identyfikatory: nazwy zmiennych, funkcji, typów, stałych, szablonów, itp., są więc formatowane tak samo i wyglądają identycznie.
Trik kolorowania semantycznego polega właśnie na rozróżnieniu tych wszystkich kategorii identyfikatorów. Oczywiście jest to możliwe tylko wtedy, gdy IDE dysponuje bazą danych o całej strukturze projektu. Ale taka baza jest przecież coraz częściej tworzona, tyle że w innym celu: zapewnienia automatycznego uzupełniania poprzez mechanizmy w rodzaju IntelliSense w Visual Studio. Na szczęście ktoś kreatywny wpadł na pomysł, że można ją wykorzystać także w inny sposób – i chwała mu za to :)

Kolorowanie semantyczne ma przynajmniej dwie zalety. Ułatwia ono zorientowanie się w przeglądanym kodzie, zwłaszcza takim który widzimy po raz pierwszy. Ponadto zaś pozwala ono na wczesne wykrycie prostych acz uciążliwych błędów w rodzaju literówek. Sprawiają one bowiem, że dany identyfikator – jako nieznany – jest kolorowany inaczej, co pozwala na łatwiejsze dostrzeżenie pomyłki.
A wady? Jak każdy mechanizm działający w tle, kolorowanie semantyczne zjada trochę zasobów systemowych – m.in. dla niego opłaca się mieć w procesorze więcej niż jeden rdzeń. A poza tym można zwyczajnie nie lubić pstrokatego kodu i preferować bardziej jednolitą kolorystykę… O gustach się przecież nie dyskutuje, a teoretycznie kodować można i w Notatniku :)

Tags:
Author: Xion, posted under Applications, Programming » 8 comments

Wyliczenia kwalifikowane

2008-07-01 23:26

W C++ typy wyliczeniowe deklaruje się zwykle poprzez coś podobnego do poniższego kawałka kodu:

  1. enum Sides { Left = -1, Middle = 0, Right = 1 };

Jego skutkiem jest jednak to, że nazwy stałych typu (tutaj: Left, Middle i Right) będą widoczne w całej przestrzeni nazw zawierającej daną deklarację enum. Jeśli więc przypadkiem jest ona globalna, to całkiem łatwo może ona spowodować konflikt chociażby z innym typem w rodzaju:

  1. enum Keys { Left, Right, Up, Down, /* ... */ };

Aby zapobiegać takim sytuacjom, w Javie i C# stałe wyliczeniowe muszą być kwalifikowane nazwą odpowiedniego typu – używa się więc Sides.Left i Keys.Left. W C++ jest jednak inaczej, gdyż blok enum sam w sobie nie tworzy zasięgu (w przeciwieństwie np. do bloków class).

Można temu częściowo zaradzić w następujący sposób:

  1. struct Sides
  2. {
  3.     enum _Enum { Left = -1, Middle = 0, Right = 1 };
  4. };
  5. typedef Sides::_Enum Side;

dzięki czemu możemy z naszego enuma korzystać tak:

  1. Side foo;
  2. foo = Sides::Left;  // OK
  3. foo = 1; // błąd - nie można przypisać liczby
  4. foo = Right; // błąd - Right nie jest w przestrzeni globalnej

Różnica względem wspomnianych dwóch języków polega na tym, że nazwa typu wyliczeniowego (Side) oraz kwalifikator stałych (Sides) nie są takie same. Wydaje się jednak (przynajmniej mi się tak wydaje :]), że w tym przypadku takie rozróżnienie jest logicznie poprawne i wygląda nawet czytelniej niż gdyby obie nazwy były identyczne.
Trik ten można naturalnie opakować w makro, które umożliwi łatwe tworzenie typów wyliczeniowych z kwalifikowanymi nazwami stałych. Nie poprawi to oczywiście funkcjonalności enumów w C++, ale przynajmniej sprawi, że będą ładniej wyglądały :D

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


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