Posts tagged ‘compilation’

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

Recykling nazw w kodzie

2011-06-26 20:24

W dziedzinie optymalizacji kodu pojawia się często pojęcie aliasowania. Z grubsza polega ono na tym, że jakaś komórka pamięci (lub bardziej ogólnie: zmienna) może być dostępna pod więcej niż jedną “nazwą” czyli np. wskaźnikiem lub referencją. Taka sytuacja sprawia, że kompilator ma mniejsze pole manewru w zakresie optymalizacji dostępu do niej. Typową konsekwencją jest konieczność ponownego odczytywania wartości tej komórki zamiast posłużenia się cache‘owanym rezultatem zapisanym w jednym z rejestrów procesora.
Aliasing w większym lub mniejszym stopniu dotyczy właściwie wszystkich języków kompilowanych. Jest on jednak brany pod uwagę głównie tam, gdzie jego wpływ na wydajność jest największy, tj. w kodzie kompilowanym bezpośrednio do instrukcji maszynowych konkretnego sprzętu. Kod w C i C++ to najważniejszy przykład tego rodzaju.

Niejako na przeciwnym biegunie – zwłaszcza pod względem liczby warstw abstrakcji pod spodem – sytuują się języki interpretowane z dynamicznym typowaniem. Javascript i Python są tutaj typowymi reprezentantami. W nich problem “aliasowania” przybiera według mnie formę dokładnie odwrotną i nie dotyczy już kwestii wydajnościowych – z których wspomniane języki przecież nie słyną :) Przeciwnie: ów symetryczny problem jest związany z ich wyróżniającą zaletą: prostotą i czytelnością kodu.
O jaki więc rodzaj aliasowania chodzi? Ano taki, który polega na używaniu jednej nazwy do wielu różnych wartości. Przy czym ‘nazwę’ rozumiem tu w sensie stricte programistycznym, obejmującym także jej zasięg i uwzględniającym wszystkie dodatkowe cechy tego pojęcia – jak choćby przesłanianie (name shadowing).
Od razu zastrzegam jednak, żeby nie rozumieć tego zbyt dosłownie. W tym sensie chociażby poniższa pętla:
for (int i = 0; i < 10; ++i) { /* ... */ }[/cpp] nie stanowi odpowiedniego przykładu mimo tego, iż zmienna i jest tutaj używana do “nazwania” aż dziesięciu formalnie różnych wartości (\{0, ..., 9\}). Koncepcyjnie każda z nich jest bowiem tym samym, tj. wartością licznika pętli w danej iteracji.

Wątpliwej słuszności jest dopiero praktyka recyklingu tych samych nazw do różnych celów. Jest tu oczywiście spory margines niepewności jeśli chodzi o definicję słowa ‘różny’. Być może dla niektórych osób coś podobnego do poniższego kodu jest już po złej stronie granicy między akceptowalną a niedobrą praktyką:

  1. def strip_html(data):
  2.     ''' Strips HTML tags and stuff from given text, making it plain text. '''
  3.     data = strip_html_tags(data)
  4.     data = strip_html_entities(data)
  5.     data = normalize_whitespace(data)
  6.     return data

Inni z kolei mogą nie widzieć nic złego w użyciu jednej zmiennej (np. node) do przekopania się przez pięciokrotnie zagnieżdżoną strukturę XML-a czy JSON-a. Wypada tylko mieć nadzieję, że osoba utrzymująca potem taki kod będzie prezentowała podobny poziom wrażliwości ;-)

Nietrudno zauważyć, że wielokrotne używanie tych samych nazw do różnych celów jest zupełnie możliwe także w językach kompilowanych ze statycznym typowaniem. Dynamiczne typowanie zapewnia jednak o wiele większą “zachętę” do takich praktyk, pozwalając chociażby na takie oto smaczki:

  1. if foo: foo = foo[0] # lista -> pojedynczy obiekt
  2. foo = [foo] # pojedynczy obiekt -> lista
  3. foo = str(foo) # obiekt -> string
  4. foo = str.join(foo, " ") # lista -> string
  5. foo = ["%s=%s\n" % i for i in foo.items()] # słownik -> "klucz=wartość"

Czy mieszczą się one w kategorii prostoty i czytelności, każdy pewnie oceni indywidualnie. Nie da się jednak ukryć, że kryją one w sobie wielki potencjał zarówno upraszczania, jak i zaciemniania kodu. To wielka moc, z którą naturalnie jest związana wielka odpowiedzialność :)

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…

Zamiast klawisza F7

2010-09-11 12:19

Kompilacja – rozumiana w najszerszym sensie “budowania” wynikowej aplikacji – to najwyraźniej całkiem serious business. Używając wyłącznie jednego IDE (zwłaszcza gdy jest nim Visual Studio), można długo nie zdawać sobie z tego sprawy. W końcu jedną z funkcji środowisk programistycznych jest właśnie maksymalne uproszczenie tego procesu. Idealnie ze strony użytkownika wymagane jest tylko wydanie polecenia zbudowania projektu, a czasem – w przypadku budowania w tle – nawet i to nie jest potrzebne.
Nie zawsze jednak tak było i także obecnie nie zawsze proces kompilacji programu może być czymś, co uważamy za automatycznie załatwione. Wiedzą o tym zwłaszcza aktywni uczestnicy projektów open source, a szczególnie tych pisanych w językach bez jedynie słusznych IDE czy kompilatorów – takich jak C, C++ czy Java. Nic dziwnego, że właśnie z ich otoczenia wywodzą się systemy zautomatyzowanego budowania projektów.

Starym, wysłużonym i trochę już niedzisiejszym, ale wciąż szeroko używanym jest GNU Make. To z niego prawdopodobnie wywodzą się takie podstawowe koncepcje automatycznej kompilacji jak choćby target, czyli rodzaju “punktu wejścia” całego procesu. Dwa polecenia typowo służące zainstalowaniu programu ze źródeł na systemach linuksowych:

  1. make
  2. make install

to właśnie wywołania GNU Make dla dwóch różnych targetów: domyślnego (uruchamiającego kompilację) i install (wykonującego inne czynności instalacyjne). Między targetami występują acykliczne zależności, określające wymaganą kolejność przeprowadzania poszczególnych kroków procesu budowy, a także pozwalające na pominięcie tych, które nie są aktualnie potrzebne. Dla przykładu, domyślny target może zajmować się linkowaniem, więc do działania wymagać skompilowanych modułów. Zależeć wtedy będzie od targetów, które zajmują się właśnie kompilacją. Przy dobrze napisanym pliku makefile wykonają się jedynie te odnoszące się do plików z kodem, w których faktycznie nastąpiły zmiany od czasu ostatniej budowy projektu.

Ponieważ zarówno składnia, jak i możliwości GNU Make (ograniczające się głównie do uruchamiania poleceń powłoki) pozostawiają wiele do życzenia, pojawiły się w końcu inne tego typu narzędzia. Potrzebowała ich głównie wieloplatformowa Java i stąd wziął się Apache Ant (skrót od Another Neat Tool). Nie straszy on już składnią opartą na tabulatorach; zamiast tego straszy XML-em ;-) Mimo większej przenośności międzysystemowej (abstrakcja operacji na plikach i katalogach) nadal brakuje mu jednak uniwersalności językowej, która zresztą nie wydaje się wcale celem istnienia Anta. Pomimo wyraźnego zorientowania na Javę da się go jednak z powodzeniem używać w projektach pisanych również w innych językach.

Te dwa rozwiązania to oczywiście nie jedyne systemy automatycznego budowania, z którymi możemy się spotkać. Z innych ciekawe są chociażby CMake czy Apache Maven. Warto się z nimi zapoznać, by na widok paczki, która oprócz kodu zawiera tylko plik build.xml lub makefile nie wpadać w popłoch :)

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

Zależności między projektami w Visual Studio

2010-05-29 0:40

Zdarza się, że pracuje nad złożonym systemem, na który składa się kilka osobnych projektów. IDE znają dobrze takie przypadki i potrafią je obsługiwać – stąd chociażby pojęcie solution (dawniej workspace) w Visual Studio. Dla pojedynczych aplikacji i bibliotek wydaje się ono zbędne, jednak staje się nieodzowne wtedy, gdy nasze projekty zależą od siebie.

Typowa sytuacja to wspólna biblioteka (framework, engine czy co jeszcze kto woli) rozwijana razem z programami, które z niej korzystają. (W najprostszym przypadku to może być po prostu jakaś aplikacja testowa). Wówczas pojawiają się zależności między projektami na etapie ich budowania: wynik szeroko pojętej “kompilacji” jednego jest wejściem do procesu budowania innego. Jeśli nie poświęcimy temu faktowi należytej uwagi, to mogą nas czekać kłopoty. W najlepszym razie jest to konieczność wciskania F7 (Build Solution) więcej niż raz, aż do zbudowania wszystkich projektów. W gorszym – uruchamianie (i debugowanie!) aplikacji korzystającej z nieaktualnej, bo nieprzekompilowanej wersji biblioteki.

Zależności między projektami w procesie budowania da się na szczęście określić. W Visual Studio służy do tego opcja Project Dependencies z menu – a jakże – Project. Możemy w niej określić dla każdego projektu, z jakimi innymi projektami z tego samego solution jest on powiązany, czyli które z nich powinny być już wcześniej od niego zbudowane. Na podstawie tak podanej sieci zależności da się następnie określić właściwą kolejności “kompilacji” dla wszystkich projektów w danym solution. VS oczywiście to czyni, używając do tego zapewne sortowania topologicznego w analogiczny sposób jak dla kompilacji jednego projektu składającego się z wielu plików.

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

To, czego oczekuje kompilator

2008-08-19 12:53

Język C++ jest tworem skomplikowanym i jego złożoność daje się we znaki nie tylko programistom. Dość często mają z nią problemy także kompilatory. Obecnie zdecydowana większość narzędzi tego typu spełnia prawie całkowicie wymagania aktualnego standardu. Istnieje jednak sporo miejsca na usprawnienia, chociażby w bodaj najbardziej niedopracowanej dziedzinie: jakości komunikatów o błędach generowanych przez kompilatory.
Jeśli na przykład trafi nam się taka deklaracja zmiennej:

  1. SomeType variable;

a typ SomeType z jakiegoś powodu (niedołączony nagłówek, kwestia zasięgu, itd.) będzie w jej miejscu nieznany, to treść wyprodukowanego przy okazji komunikatu o błędzie może nas trochę zaskoczyć. Zarówno CL (kompilator Microsoftu używany w Visual C++), jak i GCC wyplują bowiem coś w stylu:

error: expected ‘;’ before ‘variable’

To znaczy ni mniej, ni więcej, tylko to, że według tych kompilatorów właściwa jest “deklaracja”:

  1. SomeType;

Dość zaskakujące, nieprawdaż?

Takie zachowanie można częściowo wyjaśnić tym, że kompilator “nie patrzy” na kod tak, jak programista. My widzimy tutaj deklarację zmiennej, natomiast dla kompilatora jest to tylko sekwencja: ‘nieznany identyfikator’, po którym następuje kolejny ‘nieznany identyfikator’. Ponieważ mówi się, że C++ jest językiem kontekstowym (co swoją drogą nie jest do końca ścisłe), taki fragment może sugerować wiele różnych rodzajów pomyłek. To, co sugeruje kompilator, wbrew pozorom też pewnie ma sens, chociaż dość specyficzny. Postawienie średnika w “spodziewanym” miejscu sprawi bowiem, że zamiast dwóch nieznanych nazw w jednej instrukcji będziemy mieli tylko jedną. Cóż za postęp! :)
Na tym prostym przykładzie widać, że interpretacja wyników kompilacji zakończonej niepowodzeniem wymaga niestety pewnego doświadczenia. Dopiero po jakimś czasie nauczymy się trafnie(j) zgadywać, co tak naprawdę oznacza ten czy inny błąd. Najwyraźniej taki już urok C++…

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

Kopiowanie do katalogu wyjściowego w VS

2008-06-02 19:02

Kiedy program korzysta z zewnętrznych plików (np. obrazków, dźwięków, itp.), które docelowo mają się znaleźć razem z nim w tym samym katalogu, w Visual Studio pojawia się pewien problem. Otóż nie bardzo wiadomo, gdzie dokładnie pliki te należy umieścić, by aplikacja miała do nich dostęp. Zazwyczaj najwygodniej byłoby wrzucić je do katalogu głównego z projektem i/lub całym solution. Tyle że wersje skompilowane trafiają zwykle do katalogów w rodzaju Debug czy Release, czyli folderów pod- lub równorzędnych. Skutek jest często taki, że aplikacja nie potrafi znaleźć potrzebnych plików, gdyż nie znajdują się one we wspomnianych katalogach ze skompilowanymi wersjami programu.

Można je oczywiście ręcznie tam skopiować, ale to niezbyt dobre rozwiązanie – zwłaszcza, jeśli ręcznie musielibyśmy te pliki aktualizować przy każdej zmianie (i to w dwóch miejscach!). Alternatywą jest ustawienie w opcjach projektu odpowiedniego katalogu roboczego (working directory), dzięki czemu moglibyśmy otwierać nasze niezbędne pliki posługując się ścieżkami względnymi. W finalnej wersji programu należałoby jednak pozbyć się tej zależności od katalogu roboczego. Jej pozostawanie sprawiłoby bowiem, że nasz program nie odszukałby potrzebnych plików, jeśli tylko zostałby uruchomiony z innym katalogiem roboczym (np. z poziomu wiersza poleceń lub przy pomocy odpowiednio przygotowanego skrótu). Byłby to ewidentny błąd.
Dlatego też lepiej, aby wszystkie pliki “towarzyszące” otwierać przy pomocy ich pełnych ścieżek, złożonych przy pomocy ścieżki do katalogu z plikiem EXE. Ją zaś można pobrać na wiele sposobów, w zależności od języka i platformy. W Windows API można na przykład użyć funkcji GetModuleFileName (do uzyskania pełnej ścieżki do pliku wykonywalnego) oraz PathRemoveFileSpec (do wyrzucenia z niej nazwy pliku). W .NET będzie to pole System.Windows.Forms.Application.ExecutablePath w połączeniu np. z metodą System.IO.Path.GetDirectoryName.

Wówczas jednak dochodzimy do punktu wyjścia, jako że podczas uruchamiania w debuggerze program będzie szukał swoich plików w katalogach Debug/Release. Można temu w prosty sposób zaradzić. Wystarczy mianowicie:

  1. Dodać potrzebne pliki do projektu w Visual Studio – zupełnie tak samo, jak pliki z kodem źródłowym.
  2. Odpowiednio ustawić dla nich właściwość Copy to Output Directory.

Właściwość Copy to Output DirectoryDomyślną wartością dla tej właściwości jest Do not copy. Wybranie wariantu Copy always sprawi, że zawsze po kompilacji Visual Studio skopiuje wskazany plik do katalogu z plikami wykonywalnymi (np. Debug). Możemy też wybrać Copy if newer, przez co kopiowanie zostanie wykonane tylko wtedy, jeśli od ostatniego zbudowania projektu nasz plik uległ zmianie. Ten wariant jest zwykle najrozsądniejszy, bo oszczędza niepotrzebnego kopiowania tych samych wersji plików.

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


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