VS Tipps & Tricks: Heap Bugs finden (Teil 4)

In meine ersten Artikeln über Heap-Bugs habe ich bereits erwähnt, dass die CRT aber auch Windows selbst Speicher unter bestimmten Umständen vorbelegt bzw. beim Freigeben des Speichers mit einem festen Wert löscht.

Für einen Entwickler ist es gut zu wissen welche Werte durch wen gesetzt werden. Zudem erleichtert einem dieses Wissen auch das Debuggen und die Identifikation von Problemen im Zusammenhang mit dem Heap, deshalb habe ich hier mal diese Magic-Bytes, die von Microsoft verwendet werden hier zusammengetragen.

Würde man im Debugger zum Beispiel eine Variable mit dem Wert 0xCCCCCCCC entdecken, dann ist davon auszugehen, dass man diese Variable auf dem Stack nicht initialisiert hat.

  • 0xABABABAB
    Wird von HeapAlloc als Wert für die Guard Bytes („no man’s land“) vor und hinter Speicherblöcken verwendet.
  • 0xBAADF00D
    Wird von LocalAlloc(LMEM_FIXED) verwendet um nicht nicht initialisierten Speicher im Heap zu kennzeichnen.
  • 0xCCCCCCCC
    Wird von der Debug-CRT verwendet um nicht initialisierten Stack zu kennzeichnen.
  • 0xCDCDCDCD
    Wird von der Debug-CRT verwendet um nicht initialisierten Speicher im Heap zu kennzeichnen.
  • 0xDDDDDDDD
    Wird von der Debug-CRT verwendet um freigegebenen Speicher im Heap zu kennzeichnen.
  • 0xFDFDFDFD
    Wird von vom Debug-Heap verwendet für Guard Bytes („no man’s land“), vor und hinter Speicherblöcken.
  • 0xFEEEFEEE
    Wird von HeapFree() verwendet um freigegebenen Speicher zu kennzeichnen.

Siehe auch: http://en.wikipedia.org/wiki/Magic_number_(programming)

HOWTO:Remote Debugging fast and easy

Vor Jahren habe ich dazu bereits mal einen Artikel für VC6 veröffentlicht: Remote Debugging for Dummies! BTW: Es war überhaupt mein erster Artikel!
Also Zeit für ein kleines Upgrade.
Anmerkung: Mir geht es hier nur um natives Debuggen von C++ (also unmanaged Code), wen wird es wundern ? 😉

Für mich das ultimative Mittel der Qualitätssicherung und die beste Waffe für die Jagd auf den gemeinen Software-Bug ist und bleibt Remote Debugging.

Remote Debugging ist seit es Visual Studio 2002 gibt so einfach geworden wie noch nie. Und seit es Visual Studio 2005 ist es noch einen Tick einfacher geworden. Einziger Wermuthstropfen: Remote Debugging ist erst ab der Professional Version verfügbar.
Um so erstaunlicher wie wenig Entwickler dieses Werkzeug einsetzen.

Nehmen wir einfach mal den Fall, wir haben auf einer Maschine einen Crash, oder wir haben einen Zustand, den wir sofort Debuggen wollen. Geht das On-the-fly?

JA ❗

Wenn die Rechner in einer Domäne sind, gibt es meistens gar keine Probleme und man kann einfach wie folgt vorgehen. 

  1. Wir kopieren die Datei MSVSMON.EXE auf den Rechner auf dem wir Debuggen wollen:
    „C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\Remote Debugger\x86\msvsmon.exe“
    Ja! Erstaunlich, mehr ist nicht notwendig.
  2. Beim ersten Start erhalten wir eine Warnung, dass wir die Firewall von XP SP2 oder Vista noch freischalten müssen. Gesagt getan.
  3. Nun wählen wir einfach die schnelle etwas unsichere Methode:
    Menü Tools -> Options
    In dem entsprechenden Dialog, wählen wir nun einfach:
    No Authentication (native only)
    Allow any user to Debug
    Port 4015 bleibt unverändert.
    Am Besten jetzt noch die Maximum idle time (seconds) von 900 auf was Brauchbares hoch setzen, sonst wird die Session einfach beendet nach 15 Minuten.
  4. Ja ❗ Das wars. Nun einfach im Visual Studio Tools -> Attach to Process wählen (Strg+Alt+P)
    Transport: Remote (Native only with no authentication)
    Qualifier: <Name des PCs den es zu debuggen gilt, oder IP-Adresse>
    Refresh
  5. WOW! Und schon sieht man seinen Prozess den man debuggen möchte.
  6. Jetzt nur noch auswählen und Attach

So einfach geht es.
Wenn man die passenden PDB Dateien in Reichweite hat, kann man sofort Breakpoints setzen und am „lebenden Patienten“ operieren, als nur „post-mortem“ mit Crashdumps zu arbeiten.

Wie man gezielt auch die Projekteinstellungen nutzen kann, habe ich mir für einen späteren Artikel vorgemerkt. 😉

Siehe auch:
How to: Set Up Remote Debugging

VS Tipps & Tricks: Heap Bugs finden (Teil 3)

Mancher Bug macht einem nicht den Gefallen und lässt sich in der Debug-Version finden. Ursache ist oft genug eine Variable, die in der Debug-Version initialisiert (0xCC) wird aber in der Release-Version zu einem Crash führt, wenn zufällige Daten auf dem Stack für undefiniertes Verhalten sorgen.

Also macht man sich an das debuggen der Release Version und kann keinen Fehler finden.
Kaum startet man das Programm ohne Debugger dann kracht es wieder. Warum?

Manch einer könnte jetzt denken: Der Debugger verändert das Memory Layout! Das tut er schon, aber entscheidend für ein anderes Verhalten ist der Debug Heap!
Die wenigsten Entwickler wissen überhaupt, dass es ihn gibt. Ich meine hier nicht die Debug Funktionen, die die CRT zur Verfügung stellt, denn mein Thema heute ist ja das Debuggen eines Release-Programms, und die Debug-CRT hat ja bekanntlich in einem Release Programm nichts zu suchen!

Machen wir es praktisch und nehmen wieder mein kleines Crashtest-Programm:

#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[])
{
  char *pCorrupt = new char[100];
  ZeroMemory(pCorrupt,104);
  char *pOther = new char[100];
  ZeroMemory(pOther,100);
  delete [] pOther;
  delete [] pCorrupt;
  return 0;
}

Wenn wir dieses Programm als Release Version kompilieren und ausführen, dann erhalten wir keine Fehlermeldung ❗ Interessant. Der Heap ist nicht soweit zerstört, dass es zu einer Zugriffsverletzung kommt. Starten wir aber unser Programm mit dem Debugger, dann wird der so genannte Debug Heap des Systems verwendet, der wie die Debug-CRT Guardbytes setzt und kontrolliert.

Ein weiteres Problem entsteht dadurch, dass der Debug Heap den allokierten Speicher auf feste Werte initialisiert genau wie die Debugversion der CRT. Wenn also nicht initialisierter Speicher genutzt wird, dann ist das Verhalten mit dem Debug-Heap deterministisch, ohne Debug Heap eher zufällig.

Das im Debugger alles etwas anders sein kann ist sogar dokumentiert 😉

Processes that the debugger creates (also known as spawned processes) behave slightly differently than processes that the debugger does not create.
Instead of using the standard heap API, processes that the debugger creates use a special debug heap. On Microsoft Windows XP and later versions of Windows, you can force a spawned process to use the standard heap instead of the debug heap by using the _NO_DEBUG_HEAP environment variable or the -hd command-line option.

In diesem Text steht auch, wie man den Debug-Heap ausschalten kann, mit:

SETX _NO_DEBUG_HEAP 1

Diese Environment-Variable sorgt dafür, dass sich auch bei geladenem Debugger, das Programm so verhält wie ohne Debugger (hoffentlich). Führt man mein Testprogramm nun im Debugger aus, wenn die Environment-Variable _NO_DEBUG_HEAP auf 1 gesetzt ist, erhält man keinen Debug-Break mehr. Denn in diesem Fall gibt es keine Guardbytes, die geprüft werden.
Löscht man den Eintrag _NO_DEBUG_HEAP wieder, dann erhält man im Debugger wieder wie erwartet einen Break.

Will man also wirklich realitätsnah eine Release-Version debuggen, dann kommt man um das Ausschalten des Debug-Heaps nicht herum.

PS: Man kann es auch etwas einfacher haben, wenn man sich nachträglich an den Prozess mit dem Debugger attached (wenn das geht). Ideal ist dieses Verfahren auch beim Remote-Debugging (dazu demnächst mehr).

VS Tipps & Tricks: Heap Bugs finden (Teil 2)

Einige Hilfsmittel um einen Heap-Fehler zu finden habe ich in meinem letzten Beitrag ja beschrieben.

Eigentlich wünscht sich der Entwickler nichts mehr, als dass ein falscher Zugriff auf den Heap, sofort einen Break im Debugger auslöst. Die Methoden, die ich bisher gezeigt habe (AfxCheckMemory, _CrtCheckMemory, _CrtSetDbgFlag) können das nicht direkt , aber zumindest helfen sie den Fehler einzukreisen.

Ein unverzichtbarer Helfer, der sofort solch einen Break auslösen kann, ist der Application Verifier, den ich bereits in einem älteren Artikel als Freund und Helfer vorgestellt habe.

Seit Visual Studio 2005 kann man direkt Parameter für den Application Verifier im Projekt einstellen und auch direkt den Debug-Prozess mit dem Application Verifier starten (Umschalt+Alt+F5).
An den Standardeinstellungen im Projekt braucht man hier gar nichts zu ändern:
Conserve Memory – No
Protection Location – Je nach Testfall (man sollte mit beiden Einstellungen mal debuggen)
Alle anderen Einstellungen Verification Layers Settings – auf Enable

Mit dem Application Verifier lässt sich der so genannte Paged Heap nutzen, der Guard Pages anlegt hinter oder vor den allokierten Speicherbereichen (siehe auch GFLAGS.EXE). Der Vorteil: Man erhält sofort eine Access Violation, wenn man den Speicherbereich überschreitet.

Mein kleines Demoproramm

#include <windows.h>
#include <tchar.h>
#include <crtdbg.h>
int _tmain(int argc, _TCHAR* argv[])
{
  char *pCorrupt = new char[100];
  ZeroMemory(pCorrupt,106); // -- This will corrupt the heap
  char *pOther = new char[100];
  ZeroMemory(pOther,100);
  delete [] pOther;
  delete [] pCorrupt;
  return 0;
}

crashed mit der Nutzung des Application Verifiers sofort und man kann im Call Stack die Zeile 7 ausmachen.
Genial ist besonders, dass der Application Verifier auch mit der Release Version sofort die Zeile 7 als Ursache identifiziert. Gerade wenn man also nicht auf die Debug-CRT zurückgreifen kann, ist der Application Verifier ein super Hilfsmittel.

Der Nachteil: Die Guard Pages liegen nicht exakt und direkt hinter dem allokierten Bereich, sondern auf der nächsten Page Boundary. Deshalb crashed mein Sample auch nicht wenn man den Speicher um nur 1 Byte überschreitet.

Aber der Application Verifier ist zum Testen ein absolutes Muss, weil auch falsche Handles erkannt werden und auch der Lock Verfification Layer für die Qualitätssicherung einfach nützlich zum entwanzen sind. (siehe auch Application Verifier Einstellungen in der MSDN).

Hinweis ❗

Auf Windows XP und Windows Server 2003 erhält man ohne administrative Rechte die folgende Fehlermeldung:

Access denied. You need administrative credentials to use Application Verifier on image <App_Name.exe> on machine <Machine_Name>. Contact your system administrator for assistance

Unter Windows Vista oder Windows Server 2008 erhält man die flogende Fehlermeldung wenn der Application Verifier nicht elevated gestartet wird:

Access denied. You need administrative credentials to use Application Verifier on image <App_Name.exe> on machine <Machine_Name> or per user verifier settings should be enabled by the administrator. Please refer to documentation for more information.

Durch einen simplen Eintrag in der Registry lässt sich aber auch als normaler Benutzer, ohne administrative Rechte, der Application Verifier nutzen, man erzeugt einen DWORD Eintrag in der Registry mit dem Wert 1
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manger\ImageExecutionOptions
Nach einem Reboot kann man nun einfach den Application Verifier auch non-elevated, als normaler Benutzer nutzen.

Manche ungenaue Dokumentation nervt einfach

Die MSDN ist eines der größten und besten Nachschlagewerke, die ich kenne, allerdings gehen mir auch seine Unzulänglichkeiten ab und zu auf die Nerven (und hier meine ich nicht die funktionellen Defizite, sondern die inhaltlichen)!

Das sind dann ganz besonders die kleinen Sachen, die einem das Leben leicht machen könnten, die dann nicht „vollständig“ dokumentiert sind.

Beispiel:
Die MFC List-Container (z.B. CObList). Wir lesen in der Doku zu InsertAfter:

position
A POSITION value returned by a previous GetNext, GetPrev, or Find member function call.

Gut! Aber da steht nichts zu NULL. 😕
Aber wenn ich genauer darüber nachdenken, kann GetNext und Find auch NULL zurückgeben…
Was passiert also wenn ich NULL als Argument für position?
Mein Verstand sagt mir: „Es wird ein Element am Ende eingeführt!“ und ein Blick in den Code bestätigt den Verdacht.

Warum steht in der Doku nicht gleich, dass InsertAfter mit NULL als position ein AddTail ausführt?
Wer weiß wieviele Entwicler solchen „unnützen“ Code aufgrund der mangelnden Doku geschrieben haben:

CStringList lst;
FillMyList(lst);
POSITION pos = lst.Find(_T("Anything"));
if (pos==NULL)
    lst.AddTail(_T("Something to insert"));
else
    lst.InsertAfter(pos,_T("Something to insert"));

Es würde ja genügen wie folgt zu schreiben:

CStringList lst;
FillMyList(lst);
POSITION pos = lst.Find(_T("Anything"));
lst.InsertAfter(pos,_T("Something to insert"));

Korrespondierend ist die Doku von CObjList::InsertBefore genauso unvollständig.
Wird NULL als Position verwendet wird hier ein AddHead ausgeführt.

PS: Mich ärgert auch jedesmal wenn einer meiner Kollegen solchen Code schreibt:

CSomeObject *p = NULL;
...
// Conditional create
if (SomeThing())
     p = new CSomeObject();
...
// Cleanup
if (p)		// Category meaningless
    delete p;
  1. Warum der Test auf !=NULL?
  2. Warum wird kein Autopointer verwendet, der das auch Exception-Save gemacht hätte?

Dr. Watson starb unter Vista…

… und es war nicht etwa wieder Prof. Dr. Moriarty :mrgreen:

Wenn man unter Vista einen Crashdump benötigt dann sucht man DRWTSN32.EXE unter Vista vergeblich. Für die Qualitätssicherung sind mir Minidumps immer extrem wichtig.

Es stellt sich die nette Frage: Wie kommt man von einem Kunden dann an einen informativen Minidump, wenn man keinen WER-Account hat, oder das Programm nicht signiert war, oder gar der WER Server diese Dumps nicht anfordert?

Unter Vista ist alles noch einfacher und schwieriger geworden. Vista speichert leider nicht grundsätzlich Minidumps, aber es legt für Crashs unter Problemberichte und Lösungen einen eigenen Eintrag an. Zu Crashs von WER registrierten Programme werden hier evtl. direkt Lösungen oder Updates angeboten. Und auch Crashdumps werden hier mit abgelegt, wenn sie erzeugt werden..

So ist es unter Vista aus:

  1. Das WER System in Vista erzeugt normalerweise nur Minidumps für signierte Programme bzw. wenn der WER Server einen Crash abruft.
  2. Um immer einen Minidump zu erhalten muss in der Registry der folgende Wert gesetzt werden:
    HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\ (DWORD)  mit Namen ForceQueue  erzeugen und auf 1 setzen.
  3. Dumps werden im Benutzerverzeichnis unter C:\Users\TheUserName\AppData\Local\Temp und hier C:\ProgramData\Microsoft\Windows\WER\ReportQueue abgelegt und sind meistens gepackt. Sie erhalten die Endung *.mdmp.
  4. Der Zugriff kann ganz einfach erfolgen mit Systemsteuerung -> System und Wartung -> Problemberichte und Lösungen -> Probleme anzeigen und überprüfen
  5. Dort findet man das entsprechende Programm und die dazugehörigen entsprechenden Crashs.
  6. Weitere Infos einfach über Details anzeigen lassen.
  7. Wenn es einen Dump gibt, dann findet man hier wieder einen Eintrag Temporäre Kopie dieser Dateien anzeigen
  8. Damit lässt sich der Explorer öffnen und man erhält Zugriff auf die gespeicherten Daten.
  9. Diese kann man sich nun als Entwickler zusenden lassen.

Siehe auch:

Was tun gegen Fehler in statischen Tabellen bzw. Variablen Deklarationen

Ich habe es immer wieder mit langen Listen von statischen Datenbeschreibungen zu tun. Das können Parserlisten, oder Sprachbeschreibungen, die baumartig vorliegen oder einfach nur simple Arrays sein.

In den meisten Fällen versuche ich solche Strukturen als konstante PODs (Plain Old Data) aufzubauen. D.h. also in einer Form:

  1. sodass kein weiterer Code notwendig ist um diese zu initialisieren (z.B. vermeinden von Konstruktoren/Destruktoren)
  2. dass die Daten sogar möglichst als const deklariert werden und so in einem Code Segment in Modulen geshared werden können.
  3. dass sie sich leicht aus einer „Art Datenbank“ in C/C++ Code erzeugen lassen.

Oft haben diese Datenstrukturen eine komplexe Struktur und auch einen Aufbau nach bestimmten Regeln, wie z.B. dass alle Daten eines Arrays sortiert sein müssen. Dadurch kann der Algorithmus, der diese Daten nutzt natürlich effektiver geschrieben werden, als wenn er auf einem unsortierten Array aufsetzt.

Eine Erfahrung, die ich heute weitergeben will, lautet nun:

Vertraue niemals darauf, dass die Daten in statischen konstanten Strukturen so vorliegen wie Du es glaubst… zumindest nicht in der Debug Version! 😉

Hintergrund für diese Erfahrung ist ein Bug, der sich genau aus einer Annahme einschlich, das statische Daten in einer bestimmten Regel vorliegen. In dem aktuellen Fall war es die bereits erwähnte Regel: „Der Array ist sortiert, nach einer ID“. Das Ganze war sogar am Anfang der besagten Struktur und im Code korrekt und ausführlich dokumentiert. Allerdings ist der Sourcecode dieser Tabelle mehrere 1000 Zeilen lang und ein Entwickler fügte nun einfach ein paar neue Einträge an das Ende der Liste an.
<ironiemode>Außerdem, wer liest schon interne Programmdokumentationen, wo wir alle (insb. der Autor und die geschätzten Leser dieses Artikels) doch fähig sind, den Sinn und Zweck von Code mit einem Blick zu erfassen?</ironiemode>

Die Folge war: „Nun ist der Array nicht mehr sortiert“ und die weitere Folge war, dass ein Algorithmus, der auf einer binären Suche basierte, nicht fand, was er finden sollte.

Dabei hätte Stück simpler Validierungscode, vermeiden können, dass dieser Code in Produktion ging:

class CSomeMightyDataHandler
{
public:
    CSomeMightyDataHandler();

    ...

private:
    // complex static data
    struct S_COMPLEXDATA
    {
        ...
    };
    static const S_COMPLEXDATA *m_pComplexData;

    // Validator for Debug mode
#ifdef _DEBUG
    static const bool m_bValidated;
    static bool ValidateData();
#endif
...
};

#ifdef _DEBUG
// Validation for debug code only!
const bool CSomeMightyDataHandler::m_bValidated = CSomeMightyDataHandler::ValidateData();

bool CSomeMightyDataHandler::ValidateData()
{
    // Do some validation and reset bResult on any failure
    bool bResult = true;

    ...

    ASSERT(bResult);
    return bResult;
}
#endif

Mit solch einem Code wäre sofort ein ASSERT nach der ersten inkorrekten Code Ergänzung geflogen.

PS: Folgende Annahmen stimmen:

  1. Der Programmierer, der den Code falsch hinzugefügt war ich selbst.
  2. Der Programmierer, der beim Design der Klasse keinen Validierungscode geschrieben hat war auch ich selbst.
  3. Ich hatte wirklich vergessen, wie dieser Algorithmus ursprünglich mal von mir geplant war (sprich, dass eine Sortierung beachtet werden muss).
  4. Ich kann mir in den Hintern beißen für soviel selbst gemachte Dummheit.

Der Application Verifier, mein neuer Freund

Durch die Vista Zertifizierung habe ich den Application Verifier von Microsoft als neuen guten Freund kennengelernt. Warum? Schauen wir uns mal den nachfolgenden Code an:

{
  CImageList il;
  il.Create(IDB_IMAGELIST,16,0,RGB(255,0,255));
  m_lcList1.SetImageList(&il,LVSIL_SMALL);
  m_lcList2.SetImageList(&il,LVSIL_SMALL);
  il.Detach();
}

Dieser Code ist natürlich fehlerhaft! Allerdings nicht ersichtlich auf den ersten Blick. Er erzeugt korrekt eine Image List. Setzt diese in ein List View 1 und auch in ein List View 2. Sofern LVS_SHAREIMAGELISTS nicht gesetzt wird die Image List beim Zerstören des List Views 1 freigegeben. Aber eben auch noch mal wenn List View 2 zerstört wird! Nicht gut.

Oder noch ein übles Beispiel:

{
  CImageList il;
  il.Create(IDB_IMAGELIST,16,0,RGB(255,0,255));
  m_lcList.SetImageList(&il,LVSIL_SMALL);
  ImageList_Destroy(il.m_hImageList);
}

Er erzeugt auch korrekt eine Image List. Setzt diese in ein List View und zerstört dann einmal die Image List über das Handle und anschließend noch einmal durch den Destruktor von CImageList. Und ein drittes Mal wird die Image List beim zerstören des List Views freigegeben. Übel übel!

Was passiert wenn dieser Code ausgeführt wird? Nichts…
Natürlich würde keiner so etwas programmieren 😉 . Aber man kann sich vorstellen, dass so etwas ablauftechnisch und programmiertechnisch schon mal passieren kann.
Alles ist scheinbar in Ordnung, obwohl hier eine Zeitbombe tickt.

Wie kommt man einem solchen Bug auf die Spur?
Der Titel dieses Beitrages gibt die Antwort: Der Application Verifier.

Man installiert den Verifier und fügt die Anwendung zu den Verifier Einstellungen hinzu. Man belässt es bei den Default Einstellungen und ergänzt am Besten noch unter Miscellaneous die Checkboxen für DangerousAPIs und DirtyStacks.

Sobald man nun den obigen Code im Kontext eines Debuggers ausführt bekommt man eine Exception! Wow… und wenn man einen entsprechenden Symbolserver hat und den Stacktrace betrachtet sieht man

comctl32.dll!CImageListBase::IsValid()+0x2a bytes
comctl32.dll!_HIMAGELIST_QueryInterface@12()+0x29 bytes
comctl32.dll!_ImageList_Destroy@4()+0x19 bytes

Das Ausgabefenster des Debuggers zeigt zusätzlich:

03D8F964 : Invalid address causing the exception.
75C273A8 : Code address executing the invalid access.
0012F08C : Exception record.
0012F0A8 : Context record.

Aus den Informationen kann man sich leicht denken was hier faul ist, oder sein könnte. Es empfiehlz sich die Anwendung auch im Release Mode mit Debug-Infos zu erzeugen. Das sollte sowieso Standard sein!

Es lohnt sich seine Applikation mal mit dem Application Verifier auszuführen, wenn man mal gerade nichts zu tun hat und man wundert sich dann, an welchen Stellen einem sein – so gut programmierter oder gut gemeinter – Code um die Ohren fliegt :mrgreen:

Für wen Qulitätssicherung kein Fremdwort ist, der kommt an diesem sehr nützlichen Tool nicht vorbei. Der Application Verifier und weitere brauchbare Infos findet sich hier auf der entsprechenden Produktseite von Microsoft:
http://www.microsoft.com/technet/prodtechnol/windows/appcompatibility/appverifier.mspx

Steve McConnell: Code Complete – Deutsche AusgabeDer Second Edition

Dieses Buch ist das Beste was ich jemals zum Thema Softwarentwicklung gelesen habe. Zumindest für mich als aktiven Entwickler und Leiter einer Entwicklungsabteilung. Als Entwickler will man am liebsten programmieren, aber das Buch zeigt anschaulich und in einem netten Stil, dass man ohne ein gewisses Mass an Planung, Architektur und Regeln schnell ein Projekt gegen die Wand fährt. Eindringlich wird der gesamt wirtschaftliche Zusammenhang in einem Projekt beleuchtet und ich konnte bei vielen negativen Beispielen nur nicken, weil ich genau in solche Projekt-Fettnäpfchen mit „Erfolg“ rein getreten bin. Es zeigt wie man ein Projekt gut angeht, ohne Software-Entwurf-Overkill zu betreiben. Es gibt gute Tipps wie man sich selbst Coding-Richtlinien geben kann, die einen vor manchem Bug bewahren. Typische Fallen werden angesprochen und die Möglichkeiten sie zu umschiffen werden gezeigt. Es geht darum, bessere, fehlerfreiere, wartungsärmere, billigere Programme zu schreiben. Das Buch gibt viele Anregungen und Ideen, die auch selbst einem alten Hasen das Leben leichter machen können. Und das ganze ist so praxisnah, dass man viele Dinge sofort übernehmen kann. Die meisten Code Beispiele sind in C++ Code geschrieben, aber im Endeffekt ist es komplett programmiersprachenunabhängig. Das Buch ist einem guten humorvollem Stil geschrieben, der nicht langweilig wird. Wenn ich es nicht besser wüsste, würde ich fast sagen, das Buch wurde ursprünglich auf Deutsch geschrieben. Der Text liest sich flüssig ohne Haken. Auch der gut platzierte Humor ist erhalten geblieben. Ein absolutes Muss und den Preis ist es allemal wert.

5 Sterne

http://www.amazon.de/Code-Complete-Deutsche-AusgabeDer-Second/dp/386063593X