Posts tagged ‘debugging’

Essential Coding Activities (That I No Longer Do)

2012-09-13 18:32

Writing code is not everything there is in programming. But writing code comprises of much more than just typing it in. There is compiling or otherwise building it; running the application to see whether it works how it breaks; and of course debugging to pinpoint the issue and fix it. These are inherent parts of development process and we shouldn’t expect to be skipping them anytime soon…

Well, except that right now, I virtually pass on all of them. Your mileage may vary, of course, but I wouldn’t be surprised if many more developers found themselves in this peculiar position. I actually think this might be a sign of times, and that we should expect more changes in developer’s workflow that head in this very direction.

So, how do you “develop without developing”? Let’s look at the before mentioned activities one by one.

Compiling

Getting rid of the build step is not really inconceivable. There are plenty of languages that do not require additional processing prior to running their code. They are called interpreted languages, and are steadily gaining grounds (and hype) in the programming world for quite some time now.
Python, JavaScript and Ruby are probably the most popular among them. Given that I’m currently doing most of my development work in the first two, it’s no wonder I don’t find myself compiling stuff all that often.

But even if we’re talking about traditional, de facto compiled languages (like Java or C++), there’s still something missing. It’s the fact that you don’t often have to explicitly order your IDE to compile & build your project, because it’s already doing it, all the time.
I feel there’s tremendous productivity gain by shortening the feedback loop and having your editor/IDE work with you as your write the code. When you can spot and correct simple mistakes as you go, you end up having more time and cognitive power for more interesting problems. This background assistance is something that I really like to have at all times, therefore I’ve set it up in my editor for Python as well.

Running

The kind of programs I’m writing most often now – server-side code for web applications and backends – does not require another, seemingly necessary step all that often: running the app. As it stands, their software scaffolding is clever enough to detect changes in runtime and automatically reload program’s code without explicit prompting.

Granted, this works largely because we’re talking about interpreted languages. For compiled ones, there are usually many more hurdles to overcome if we want to allow for hot-swapping code into and out of a running program. Still, there are languages that allow for just that, but they are usually chosen because of reliability requirements for some mission critical systems.

In my opinion, there are also significant programming benefits if you can pull it off on your development machine. They are again related to making the cycle of writing code and testing it shorter, therefore making the whole flow more interactive and “real-time”. As of recently, we can see some serious pushes into this very direction. Maybe we will see this approach hitting mainstream soon enough.

Debugging

“Oh, come on”, you might say, “how can you claim you’ve got rid of debugging? Is all your code always correct and magically bug-free?…”

I wish this was indeed true, but so far reality refuses to comply. What I’m referring to is proactive debugging: stepping though code to investigate the state of variables and objects. This is done to verify whether the actual control flow of particular piece of code is the one that we’ve really intended. If we find a divergence, it might indicate a possible cause for a bug we’re trying to find and fix.

Unfortunately, this debugging ordeal is both ineffective and time consuming. It’s still necessary for investigating errors in some remote, test-forsaken parts of the code which are not (easily) traceable with other methods and tools. For most, however, it’s an obsolete, almost antiquated way of doing things. That’s mainly because:

  • Post-mortem investigation is typically more than enough. Between log messages and stacktraces (especially if they contain full frames with local variables), you’re often very capable of figuring out what’s wrong, and what part of the code you should be looking at. Once there, the fix should be evident… usually :)
  • Many (most?) fixes are made because of failed run of an automated test suite. In this scenario, you have even more information about the bug (like the exacting assert that fails), while the relevant part of the code is even easier to localize. You might occasionally drop into debugger to examine local variables of the test run, but you never really step through whole algorithms.
  • It’s increasingly easier to “try stuff” before putting it into project’s codebase, reducing the likelihood of some unknown factor making our program blow up. If we have a REPL (Read-Eval-Print Loop) to test small snippets of code and e.g. verify assumptions about API we’re going to use, we can eliminate whole classes of errors.

It’s not like you can throw away your Xdb completely. With generous logging, decent test coverage and a little cautiousness when adding new things, the usefulness of long debugging sessions is greatly diminished, though. It is no longer mandatory, or even typical part of development workflow.

Whatever else it may be, I won’t hesitate calling it a progress.

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

Nie tylko pisanie kodu

2011-01-21 19:25

Gdyby było to fizycznie możliwe, chętnie przeprowadziłbym następujący eksperyment. Z odległej przeszłości – na przykład z połowy lat 90. poprzedniego stulecia – sprowadziłbym w obecne czasy dowolnego ponadprzeciętnie uzdolnionego programistę. Jak szybko odnalazłby się we współczesnym koderskim środowisku pracy?… W celu redukcji złożoności problemu poczyńmy daleko idące uproszczenia i pomińmy wszelkiego typu zmiany związane z postępem technologicznym (jak choćby nieporównywalnie większe znaczenie Internetu wtedy i teraz), a także modyfikacje, które zachodzą w samych językach programowania. Interesuje mnie raczej to, czy ów przybysz z przeszłości doceniłby i uznał za przydatne różnego rodzaju czynności i narzędzia pomocnicze, niebędące edytorem tekstu (lub IDE) ani kompilatorem, i pozostające w luźniejszym związku z samym pisaniem kodu jako takiego.

Jakby bowiem przyjrzeć się dokładnie wachlarzowi tych narzędzi, okazuje się, że jest on już całkiem szeroki. Programowanie, zwłaszcza zespołowe (jeżeli w ogóle istnieje jeszcze jakieś inne) już od dawna przestało ograniczać do tworzenia kodu i czasami wydaje się nawet, że czynność ta stała się poboczną. Coraz więcej czasu zajmuje praca z takimi narzędziami jak systemy kontroli wersji, systemy śledzenia błędów (issue tracking systems), narzędzia automatyzujące proces kompilacji, programy do statycznej analizy kodu, systemy zdalnej kompilacji i pewnie mnóstwo jeszcze innych wynalazków, z którymi nie miałem dotąd okazji się zetknąć.
Między innymi dlatego nie potrafię jednoznacznie określić, czy uważam je wszystkie raczej za przydatne czy raczej za zbędne. Wiążące opinie mogę jak dotąd wyrazić tylko o niektórych.

Tym niemniej ciekawi mnie również, czy w dziedzinie wspomagania kodowania (czy ogólnie pracy nad projektami) od strony zautomatyzowanych narzędzi da się wymyślić coś jeszcze…

Nieoficjalne debugowanie na Androidzie

2010-12-22 22:00

System Android, jak powszechnie wiadomo, jest licencjonowany kategorią open source, więc istnieje możliwość w miarę łatwego umieszczenia go na urządzeniach mobilnych bez konieczności współpracy z Google. Wykorzystują to producenci różnego rodzaju mniej lub bardziej “nieoficjalnego” sprzętu, działającego pod kontrolą tego systemu. Mogą to być produkty, które trudno klasyfikować inaczej niż jako tanie imitacje, ale także sprzęt produkowany przez (u)znane firmy – jak choćby tablet stworzony przez Creative. Ich wspólną cechą jest domyślny brak wsparcia przez narzędzia wspomagające pisanie aplikacji dostępne w ramach SDK Androida – przede wszystkim Android Debug Bridge (adb), czyli usługę umożliwiającą między innymi debugowanie aplikacji bezpośrednio na urządzeniu. Dla zwykłego użytkownika to oczywiście żadna wada, ale co mają zrobić biedni programiści?…
Okazuje się jednak, że przeszkodę tę da się pokonać. Dzisiaj udała mi się ta sztuka ze wspomnianym tabletem Creative’a i dlatego czuję się upoważniony podzielić się swoim rozwiązaniem :) Wydaje się ono przy tym na tyle ogólne, że powinno stosować do dowolnego urządzenia.

Tags: , , , , ,
Author: Xion, posted under Programming » Comments Off on Nieoficjalne debugowanie na Androidzie

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

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

Ostatnia wartość zwracana przez funkcję

2010-01-19 9:23

Debugując program w pracy krokowej w Visual Studio, możemy podglądać wartości wszystkich zmiennych, jakie są dostępne w zasięgu punktu wykonania. Jest to możliwe za pośrednictwem okienka tzw. czujek (watches). Automatycznie wypełnia się ono zmiennymi lokalnymi oraz ewentualnym wskaźnikiem this, pozwalającym podejrzeć wartości pól obiektu, jeśli znajdujemy się akurat w jego metodzie.
Jednak oprócz rzeczywistych zmiennych możemy śledzić też pewne specjalne “pseudozmienne”, udostępniane przez debuger VS. Mają one nazwy zaczynające się od znaku dolara $ i potrafią być bardzo przydatne, co pokażę na przykładzie.

Powiedzmy, że mamy kod składający się z wielu następujących po sobie wywołań funkcji bibliotecznych. Każda z nich zwraca rezultat liczbowy, informujący o (nie)powodzeniu operacji, którego jednak w kodzie nie sprawdzamy – pewnie dlatego, że nie chciało nam się pisać tych wszystkich ifów :) Taka sytuacja może wystąpić chociażby przy ciągu wywołaniach DirectX: ogromna większość funkcji z tej biblioteki zwraca wynik typu HRESULT, który jest liczba zawierającą kod błędu, jeśli takowy wystąpił.
Zauważamy teraz, że ów ciąg wywołań najpewniej zawiera jakiś błąd. Objawem w DX może być chociażby słynne “nic nie widać” :) Jak teraz dojść, które z wielu wywołań zwraca błąd, jeśli w żadnym z nich nie tylko nie sprawdzamy wyniku, ale wręcz w ogóle go nie zapisujemy?…

Podejrzenie wartości EAX w VS

Istnieje na szczęście proste i wygodne rozwiązanie, które nie wymaga żadnych zmian w kodzie. Otóż przy pomocy debugera VS i jego pseudozmiennych możemy podejrzeć wartość rejestrów procesora. Wystarczy użyć symbolu $nazwa_rejestru, jak np. $eax. I właśnie $eax jest tutaj świetnym przykładem, bo rozwiązuje nasz problem. Otóż przez rejestr EAX przekazywany jest zawsze 32-bitowy rezultat zwracany przez każdą porządną funkcję. Przechodząc więc krokowo po naszym kodzie możemy sprawdzić, jak zmienia się ten rejestr po każdym wywołaniu, podglądając w ten sposób wartość, jaką zwróciła w nim funkcja. Wystarczy więc tylko znaleźć to, w którym rezultat informuje o niepowodzeniu… i voila – mamy nasz błąd :)

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

Podłączanie debugera VS do procesu

2009-06-27 17:48

Kiedy piszemy aplikację będącą już w na tyle zaawansowanym stadium, że nie objawia ona błędów przy pierwszym lepszym uruchomieniu, to zdarza się, iż uruchamiamy ją bez wsparcia debugera (co można zrobić standardowym skrótem klawiszowym Ctrl+F5 w Visual Studio). Mimo tego zawsze może się jednak zdarzyć jakiś nieprzewidziany wyjątek, błąd czy inna nieprawidłowość. Ba, może się tak zdarzyć w programie, który już dawno uznaliśmy za skończony!
Co wtedy? Przecież warunki powstania błędu mogą być trudne i pracochłonne do odtworzenia, jeślibyśmy uruchomili program ponownie – już w trybie debugowania. Nierzadko zresztą po takiej próbie błąd nagle w “magiczny” sposób zniknie, bo okaże się, że albo coś przeoczyliśmy, albo dany bug zależy od jakichś nieznanych jeszcze okoliczności, albo że całkiem niedeterministyczny (heisenbug).

Dlatego też lepiej posłużyć się już tą instancją programu, w której problem wystąpił, i przy jej pomocy poszukać błędu. W tym celu można użyć przydatnej opcji Visual Studio, pozwalającej na przyłączenie debugera do działającego procesu i dostępnej poprzez menu Debug > Attach to Process. Tam możemy wybrać po prostu naszą aplikację z listy działających procesów.
Jakie możliwości nam to daje? To zależy od tego, czy w pliku wykonywalnym docelowej aplikacji znajdują się odpowiednie symbole debugowe . W najlepszym wypadku będziemy mogli śledzić kod programu tak samo, jak przy uruchamianiu go z asystą debugera od samego początku (o ile, rzecz jasna, wcześniej otworzymy projekt, z którego kompilowaliśmy nasz program). Jeśli zaś nie dysponujemy źródłem aplikacji lub docelowy plik .exe nie posiada żadnych symboli debugowych, to oczywiście nadal możemy wykonywać działania typowe dla każdego debugera – jak krokowe wykonywanie instrukcji maszynowych czy ustawianie breakpointów na konkretnych adresach w pamięci.

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


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