Posts tagged ‘errors’

Go is Like Better C, Mostly

2013-01-09 22:55

The Go programming language is was on my (long) list of things to look into for quite some time now. Recently, at last, I had the opportunity to go through the most part of a comprehensive tour of Go from the official website, as well as write few bits of some Go code by myself.

Go-pherToday I’d like to recap on some of my impressions. You can treat it as “unboxing” of the Go language, much like when people post movies of their first hands-on experiences with new devices. Except, it will be just text – I’m not cool enough to do videos yet ;)

Some trivia

We all like to put stuff into our various mental buckets, so let’s do that with Go too.

Go is a compiled, statically typed programming language that runs directly on the hardware, without any underlying virtual machine or other bytecode-based runtime. That sounds good from the speed viewpoint and indeed, Go comes close to C in raw performance of equivalent programs.

Syntax of Go is C-like, at least in the fact that it’s using curly braces to delimit blocks of code. Some visual clutter is intentionally omitted, though. Semicolons are optional, for example, and idiomatic Go code omits them at all times.
But more surprisingly, parentheses around if and for conditions are straight out forbidden. As a result, it’s mandatory to use curly braces even for blocks that span just one line:

  1. if obj == nil {
  2.     return
  3. }

If you’re familiar with reasoning that suggests doing that in other C-like languages, you shouldn’t have much problems adapting to this requirement.

No-fuss static typing

Go is type-safe and requires all variables to be declared prior to use. For that it provides very nice sugar in the form of := operator, coupled with automatic type inference:

  1. s := "world"
  2. fmt.Printf("Hello %s!\n", s)

But of course, function arguments and return values have to be explicitly typed. Coming from C/C++/Java/etc. background, those type declarations might look weird at first, for they place the type after the name:

  1. func Greet(whom string) string {
  2.     return fmt.Sprintf("Hello, %s! How are you?", whom)
  3. }

As you can see, this also results in putting return type at the end of function declarations – something that e.g. C++ also started to permit.

But shorthand variable declarations are not the only way Go improves upon traditional idioms of static typing. Its interfaces are one of the better known features here. They essentially offer the support for duck typing (known from Python, among others) in a compiled language.
The trick is that objects do not specify which interfaces they implement: it’s just apparent by their methods. We can, however, state what interfaces we require for our parameters and variables, and those constraints will be enforced by the compiler. Essentially, this allows for accepting arbitrary values, as long as they “quack like a duck”, while retaining the overall type safety.

As an example, we can have a function that accepts a very general io.Writer:

  1. func SendGreetings(w io.Writer, name string) {
  2.     fmt.Fprintf(w, "Hello, %s!", name)
  3. }

and use it with anything that looks like something you could write into: file objects, networked streams, gzipped HTTP responses, and so on. Those objects won’t have to declare or even know about io.Writer; it’s sufficient that they implement a proper Write method.

Pointers on steroids

Talking about objects and interfaces sounds a bit abstract, but we shall not forget that Go is not a very high level language. You still have pointers here like in C, with the distinction between passing an object by address and copying it by value like in C++. Those two things are greatly simplified and made less error prone, however.

First, you don’t need to remember all the time whether you interact with object directly or through a pointer. There’s no -> (“arrow”) operator in Go, so you just use dot (.) for either. This makes it much easier to change the type of variable (add or remove *) if there’s need.

Second, most common uses for pointers from C (especially pointer arithmetic) are handled by dedicated language mechanism. Strings, for example, are distinct type with syntactic support and not just arrays of chars, neither a standard library class like in C++. Arrays (called slices) are also well supported, including automatic reallocation based on capacity, with the option of reserving the exact amount of memory beforehand.

Finally, the common problems with pointer aliasing don’t really exist in Go. Constraints on pointer arithmetic (i.e. prohibiting it outright) mean that compiler is able to track how each and every object may be used throughout the program. As a side effect, it can also prevent some segmentation faults, caused by things like local pointers going out of scope:

  1. func Leak() *int {
  2.     i := 42
  3.     return &i
  4. }

The i variable here (or more likely: the whole stack frame) will have been preserved on heap when function ends, so the pointer does not become immediately invalid.

Packages!

If you ever coded a bit in some of the newer languages, then coming to C or C++ you will definitely notice (and complain about) one thing: lack of proper package management. This is an indirect result of the header/implementation division and the reliance on #include‘ing header files as means of specifying dependencies. Actually, #includes are not even that: they work only for compiler and not linker, and are in some sense abused when working with precompiled headers.

What about Go?… Turns out it does the right thing. There are no separate header and implementation units, only modules (.go files). Unless you are using GCC frontend or interfacing with C code, the compiler itself is also unified.

But most importantly, there are packages and normal import statements. You can have qualified and unqualified imports, and you can alias things you’re importing into different names. Packages themselves are based on directory structure rooted in $GOROOT, much like e.g. Python ones are stored under $PYTHONPATH.

The only thing you can want at this point is the equivalent of virtualenv. Note that it’s not as critical as in interpreted languages: standalone compiled binaries do not have dependency problems, after all. But it’s still a nice thing to have for development. So far, people seem to be using their own solutions here.

Tags: , , , , , ,
Author: Xion, posted under Programming » Comments Off on Go is Like Better C, Mostly

Interesting Problem with MySQL & SQLAlchemy

2012-06-12 14:31

While at work a few days ago, I had an interesting albeit weird problem which started with the following cryptic error message:

ERROR 1005: can’t create table `qtn_formdisplay_product` (errno: 150)

It was produced by a local MySQL server running on my development machine when I tried to rebuild test database to accommodate for some model changes happening in the codebase. As you might have noticed, it’s not terribly informative, with the errno number as the only useful tidbit. A cursory glance at top search result for this message said that the most probable cause was a malformed FOREIGN KEY constraint inside the offending CREATE TABLE query.

Upon reading this, I blinked several times; something here was definitely off. The query wasn’t of course written by hand – if it was, we could at least consider an actual mistake to be a problem here. But no, it came from ORM – and not just an ORM, but the best ORM known to mankind. While obviously nothing is perfect, I would think it’s extremely unlikely that I found a serious bug in a widely used library just by doing something as innocent as creating a table with foreign key. I’m not that good, after all ;)

Well, except that it could totally be such a bug. The before mentioned search results also pointed to MySQL issue tracker where it was suggested that the error might happen after trying to create foreign key constraint with duplicate name. Supposedly, this could “corrupt” the parent table and no new FOREIGN KEYs could reference it anymore, yielding the errno 150 if we attempted to create one. While it could not explain the behavior I observed (the parent table was freshly created), it raised some doubts whether MySQL itself may be to blame here.

These were exacerbated when one of my colleagues tried out the same procedure, and it worked for him just fine. He turned out to use newer version of MySQL, though: 5.5 versus 5.1. This appeared to support the hypothesis about a possible bug in MySQL but it didn’t seem to help one bit to get the thing running on the older version.

However, it was an important clue that something relevant changed in between, that had an influence on the whole issue. It was not really any particular bugfix or new feature: it was a change of defaults.

Tags: , , , ,
Author: Xion, posted under Computer Science & IT » 1 comment

Najboleśniejszy strzał w stopę

2011-03-13 20:14

Znanych jest mnóstwo kruczków w języku C++, o których trzeba pamiętać, jeśli chcemy efektywnie w nim programować i nie tracić zbyt dużo czasu na odczytywanie niespecjalnie zrozumiałych komunikatów kompilatora czy – gorzej – drapanie się po głowie podczas debugowania naszego programu. O wielu z nich miałem okazję pisać, ale oczywiście ten temat-rzeka daleki jest od wyczerpania :) W jego nurcie wyróżnia się jednak jeden wybitnie złośliwy przypadek, który przez długi czas uważałem aczkolwiek za ciekawostkę, na którą w praktyce raczej nikt się nie natknie…
Rzecz jasna, myliłem się. Okazuje się, że okaz ten jak najbardziej występuje w rzeczywistym świecie. Nie objawia się wprawdzie zbyt często, ale dzięki temu jest jeszcze bardziej podstępny, posiadając tym większą siłę rażenia, gdy w końcu na kogoś trafi. Dobrze więc wiedzieć, co robić, aby się przed nim bronić :] I tym właśnie chciałbym się dzisiaj zająć.

Pewną pomocą jest tutaj fakt, iż opisywany błąd zdaje się zazwyczaj pojawiać w pewnym konkretnym scenariuszu. Jego wystąpienie w owym kontekście jest przy tym tak zaskakujące, że zetknięcie się z nim skutecznie “uodparnia” na wszelkie inne, podobne okoliczności w których problem może wystąpić. Rzeczony scenariusz jest przy tym bardzo typowy: chodzi o wczytanie zawartości pliku (otwartego jako strumień std::ifstream) do kolekcji albo zmiennej, na przykład łańcucha znaków typu std::string.
Są naturalnie tacy, co bawiliby się tutaj w bufor pomocniczy, pętlę i funkcję getline lub coś w tym guście. Programiści lubiący operować na nieco wyższym poziomie wiedzą jednak, że std::string możemy inicjalizować zakresem znaków określonym – jak każdy zakres w C++ – parą iteratorów. Trochę mniej znanym faktem jest z kolei to, że iterować można również po strumieniach. Mamy na przykład coś takiego jak std::istream_iterator, który potrafi automatycznie dekodować ze strumienia egzemplarze ustalonego typu danych:

  1. // odczytanie 10 liczb int z stdio
  2. vector<int> numbers(10);
  3. copy (istream_iterator< int >(cin),
  4.       istream_iterator< int >(), numbers);

Jeśli nam to nie odpowiada i wolimy dostęp do “surowych” bajtów, wtedy z pomocą przychodzi bardziej wewnętrzny std::istreambuf_iterator. To właśnie przy jego pomocy możemy szybko przejść po zawartości strumienia plikowego i umieścić ją w stringu:

  1. ifstream file("plik.txt");
  2. string fileContents(istreambuf_iterator< char >(file),
  3.                     istreambuf_iterator< char >());

Jak pewnie można się domyślić, zakres zdefiniowany przez te iteratory w obu przypadkach zaczyna się na początku strumienia. Kończy się zaś w miejscu, gdzie odczyt następnej porcji danych nie jest już możliwy. W naszym “rozwiązaniu” problemu wczytywania całego pliku będzie to więc koniec tego pliku, czyli to co nam chodzi.

Nieprzypadkowo jednak słowo ‘rozwiązanie’ ująłem w cudzysłów. Powyższe dwie instrukcje zawierają bowiem ów wyjątkowo perfidny błąd, o którym wspominałem na początku. Dość powiedzieć, że jeśli spowoduje on wygenerowanie przez kompilator bardzo tajemniczego komunikatu, będzie to lepszy z jego dwóch możliwych rezultatów. Drugim jest kompilacja zakończona powodzeniem i… kod, który robi dokładnie nic. Nie tylko nic nie wczytuje, ale nawet nie tworzy zmiennej typu string! To raczej zaskakujące, nieprawdaż? ;)

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

Wątpliwości na właściwym poziomie

2010-11-16 19:36

Nikt nie lubi być w błędzie. Niestety, to niezbyt przyjemne uczucie jest częstym doświadczeniem dla programisty. Ciągle popełniamy błędy, będąc przekonanym o poprawności napisanego przez siebie kodu, i nieustannie musimy korygować swoje przekonania na ten temat. W pewnym sensie jest to podobne do pracy naukowców, zmuszonych do korygowania swoich teorii w obliczeniu dowodów eksperymentalnych.

Trzeba jednak zawsze wiedzieć, w co tak naprawdę powinniśmy wątpić, bazując na obserwacjach, doświadczeniu i przydatnej cesze umysłu znanej jako zdrowy rozsądek. To zwłaszcza ona mówi o jednej prostej regule odnoszącej się do poszukiwania źródeł błędów; mianowicie:

Im “dalej” on naszego własnego kodu szukamy przyczyn błędów, tym mniejsze jest prawdopodobieństwo, że je tam odnajdziemy.

Przez ‘odległość’ rozumiem tutaj – z grubsza – wysokość przekroju przez kolejne poziomy abstrakcji, na których oparta jest tworzona przez nas aplikacja. Może być ich niewiele albo bardzo dużo, ale jedno pozostaje prawdą: szansa na to, że powód niepożądanego zachowania programu leży gdzieś w niższych warstwach jest bardzo, bardzo, bardzo mała. Ponadto wraz z zagłębianiem się w owe warstwy niższe prawdopodobieństwo nie tylko spada, ale też spada niezwykle szybko. Mam podejrzenia, że gdyby ktoś pokusił się o opracowanie jakiegoś matematycznego modelu dla tego zjawiska (analizując ogromną ilość błędów i ich przyczyn z wielu rzeczywistych projektów o różnym charakterze), to okazałoby się, że spadek ten jest wykładniczy.

Z tego wynikają przynajmniej dwa proste wnioski. Po pierwsze: jeśli właśnie zastanawiasz się, czy twój kod nie spowodował objawienia się ukrytego błędu w kompilatorze, to… nie, nie spowodował ;P
Po drugie: błędy w niższych warstwach abstrakcji – jakkolwiek rzadkie – to jednak zdarzają się. i zawsze istnieje szansa, że akurat my będziemy mieli z tego powodu bardzo zły dzień. Lecz zanim odważymy się zejść w debugowaniu o krok w dół, powinniśmy być bardzo pewni, że wykluczyliśmy wszystkie możliwości odnoszące się do aktualnego poziomu – tym pewniejsi, im niżej chcemy zejść. A i na tak na 99% pominiemy coś, co wykraczało poza naszą przestrzeń hipotez, czym nie należy się jednak zbytnio przejmować. Błądzić bowiem, to rzecz bardzo koderska :)

Tags: , ,
Author: Xion, posted under Programming, Thoughts » 5 comments

Python, wcięcia i bardzo wredny błąd

2010-09-04 17:13

Niektórzy programiści, rozmawiając z innymi, znajdują przyjemność we wskazywaniu na specyficzne cechy różnych języków programowania, które im nie odpowiadają. Nie uważam tego za specjalnie produktywne i samemu staram się tego unikać. W końcu co za różnica, że w Javie czy C++ mamy nawiasy klamrowe, w Pascalu begin i end, a w Pythonie bloki wyróżniane wcięciami? Dla sprawnego programisty nie powinno to mieć żadnego znaczenia.
I faktycznie jest – dopóki nie okaże się, że pozornie nieistotna cecha języka prowadzi do błędów. Nieprzyjemnych, wrednych i trudnych do wykrycia błędów. Tak, wtedy dyskutowanie o podobnych składniowych błahostkach może być choć częściowo usprawiedliwione…

A skoro sam ten temat podjąłem, to wymaga mi przedstawić moje usprawiedliwienie. Nie jest ono długie. Wygląda bowiem tak:

  1. def escape_chars(text):
  2.     result = []
  3.     for c in text:
  4.         if ord(c) > 255:
  5.             result.append ("&#%s;" % ord(c))
  6.     else:
  7.         result.append (c)
  8.     return ''.join(result)

Ta prosta pythonowa funkcja ma za zadanie zamienić w podanym tekście znaki spoza zakresu ANSI na ich XML-owe odpowiedniki w postaci encji numerycznych (np. &#456;). Niezbyt skomplikowane, prawda? A jednak całkiem długo zajęło mi dojście do tego, czemu dla tekstu z samymi znakami ANSI wynikiem jest… łańcuch pusty.

Jest oczywiście bardzo prawdopodobne, że ktoś patrząc teraz na powyższy kod znajdzie przyczynę w mniej niż dziesięć sekund – zwłaszcza, że niemal bezpośrednio zasugerowałem ją na samym początku. To naturalnie nie świadczy o niczym, bo napady specyficznego rodzaju ślepoty na rzeczy oczywiste są nieodłączną częścią zajęcia zwanego programowaniem :) Z tym nie ma sensu polemizować.
Ale jak najbardziej można dyskutować o tym, dlaczego “wrodzona” cecha składni języka uważanego za nieskomplikowany, efektywny i nowoczesny (cokolwiek to znaczy) może być bezpośrednią przyczyną powstawania błędów, o którym użytkownikom C, Javy czy Pascala nawet się nie śniło! Nie widzę innego wytłumaczenia oprócz krótkowzroczności projektantów języka, którzy nie potrafili uświadomić sobie, że jego feature‘y mogą wchodzić ze sobą nie tylko w pożyteczne, ale czasem i niepożądane interakcje.

Dla jasności wytłumaczę jeszcze dokładniej, w czym rzecz. Mianowicie w Pythonie koniec bloku kodu rozpoznawany jest nie obecnością terminatora w rodzaju } czy end, lecz zmianą poziomu wcięcia następnej linijki – czyli czymś, co w innych językach pełni rolę wyłącznie estetyczną. Wiersze mające tę samą liczbę początkowych spacji leżą więc na tym samym poziomie zagłębienia.
Stąd zaś wynika fakt, iż w powyższej funkcji fraza else nie jest wcale dołączona do instrukcji if, lecz do… pętli for. Prawidłowe zagnieżdżenie wygląda bowiem tak:

  1. def escape_chars(text):
  2.     result = []
  3.     for c in text:
  4.         if ord(c) > 255:
  5.             result.append ("&#%s;" % ord(c))
  6.         else:
  7.             result.append (c)
  8.     return ''.join(result)

Ale chwilka – jak pętla for może mieć else‘a?… Ano to już jest rzecz specyficznie pythonowska (a biorąc pod uwagę okoliczności, wręcz monty-pythonowska), którą zresztą kiedyś zdarzyło mi się opisać. Wówczas to określiłem ją tylko jako zawracanie głowy. Dzisiaj porównałbym ją raczej z możliwością wpisywania stałych ósemkowych w kodzie C. Oba feature‘y używane są świadomie średnio raz na trzy lata, przez resztę czasu potencjalne powodując jedynie błędy.

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

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

Co się rzuca, czyli natywne klasy wyjątków

2010-05-16 13:46

W wielu językach wyjątki to niemal obowiązkowa metoda zgłaszania i obsługi błędów. W innych (tj. w C++) jest ona w dużym stopniu opcjonalna. W obu przypadkach istnieją zwykle dostępne out-of-the-box klasy wyjątków, których obiekty można wyrzucać, bez tworzenia swoich własnych. Ich używanie jest zdecydowanie wskazanie. Warto więc przyjrzeć się, jakie predefiniowane obiekty wyjątków przewidują standardowej biblioteki różnych języków.

I tak w C++ mamy bazową klasę exception, która zawiera w sumie niewiele więcej ponad komunikat o błędzie. Zresztą dotyczy to tak naprawdę wszystkich wbudowanych w C++ klas wyjątków; tym, co je rozróżnia, jest po prosty typ. Mamy tu prostą hierarchię dziedziczenia, wyróżniającą klasy bazowe: runtime_error dla niskopoziomowych błędów czasu wykonania (jak na przykład overflow_error) oraz logic_error dla błędów logicznych aplikacji. Z tej drugiej, bardziej dla nas interesującej, wyprowadzone są też wyjątki nieco bardziej szczegółowe, jak np.: invalid_argument, range_error, domain_error czy lenght_error. Wszystkie cztery są zresztą podobne do siebie pojęciowo i wydają się służyć głównie do sygnalizowania nieprawidłowych wartości argumentów funkcji. Do innego rodzaju błędów sensowniej jest używać klas bazowych lub niestety napisać własne (najlepiej dziedziczące z nich).

Na platformie .NET i w Javie jest z tym dużo lepiej – tam typów wyjątków jest niemal za dużo. Nie ma tu miejsca na omawianie ich wszystkich i nie jest to zresztą konieczne, gdyż nazwy klas są zwykle wystarczające do zorientowania się, z jakim rodzajem błędu mamy tu do czynienia. Według mnie najczęstszymi sytuacjami, w których można wyrzucić obiekt którejś z natywnych klas, są:

  • ogólnie pojęte niepoprawne wartości argumentów funkcji – System.ArgumentException / java.lang.IllegalArgumentException
  • argument będący null-em w sytuacji, gdy wymagany jest obiekt – System.ArgumentNullException
  • wykroczenie poza zakres argumentu działającego jak indeks – System.IndexOutOfRangeException / java.lang.IndexOutOfBoundsException
  • próba wykonania operacji na obiekcie, którego stan jest niepoprawny – System.InvalidOperationException / java.lang.IllegalStateException
  • wywołanie niezaimplementowanej metody wirtualnej – System.NotImplementedException
  • próba wykonania operacji, której obiekt nie wspiera – System.NotSupportedException / java.lang.UnsupportedOperationException
  • błąd podczas operacji wejścia/wyjścia – System.IO.IOException / java.io.IOException
  • przekroczenie czasu operacji (timeout) – System.TimeoutException / java.util.concurrent.TimeoutException

Można zauważyć, że dla większości pozycji odpowiadające klasy wyjątków istnieją na obu platformach i nawet nazywają się podobnie. Zapewne to kolejny znak wzajemnej inspiracji ich twórców ;)

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


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