Teil 3: Eine Debug-Hilfe

Im letzten Programm gab es ein kleines Problem: sollte eine der Initialisierungsfunktionen fehlschlagen, wird das Programm ohne weitere Nachricht beendet.
Der Programmierer hat hier zwar noch die Möglichkeit, durch den Code zu steppen, der Endbenutzer weiß aber gar nicht mehr was los ist. Jedes Betriebssystem bietet hier Möglichkeiten an, dem Benutzer eine Fehlermeldung zu präsentieren, z.B. die MessageBox unter Windows. Wir wählen allerdings eine Alternative, die unter allen Systemen verwendet werden kann: Ein Log-File. Hier werden wir alle relevanten Nachrichten unseres Programms hineinschreiben, so dass man später den Ablauf nachvollziehen und auch eine Absturzursache eingrenzen kann.
Das System des Log-Files ist sehr einfach. Beim Programmstart wird eine Datei erstellt, an die während des Programmablaufs Zeichenketten angehängt werden. Wichtig hierbei ist, dass die Datei nicht die ganze Zeit geöffnet bleibt, sondern für jeden Schreibvorgang extra geöffnet und geschlossen wird, da sonst bei einem Absturz die letzte Nachricht evtl. nicht mehr auftaucht, weil sie intern gepuffert wurde. Das ständige Schliessen der Datei sorgt dafür, dass der Puffer direkt auf die Festplatte geschrieben wird.
Um die Nachrichten zu schreiben, entwickeln wir eine kleine Klasse, die den Operator << für alle möglichen Datentypen überlädt. Die übergebenen Daten werden einfach in einen Output-Filestream geschrieben.
Um etwas Struktur in den Quelltext zu bringen lege ich unterhalb des Projektverzeichnisses ein Verzeichnis debug an, in das ich die folgenden Dateien Log.h und Log.cpp einfüge und von hier dem Projekt hinzufüge.
Datei /debug/Log.h

#ifndef LOG_H
#define LOG_H
#include <fstream>
#include <ios>
#include <string>
// --------------------------------------------------------------------------------
class CLog
{
public:
   CLog( const char* pszFileName );
   CLog& operator<< ( const char* s );
   CLog& operator<< ( char* s );
   CLog& operator<< ( char c );
   CLog& operator<< ( short n );
   CLog& operator<< ( unsigned short n);
   CLog& operator<< ( int n );
   CLog& operator<< ( unsigned int n );
   CLog& operator<< ( long n );
   CLog& operator<< ( unsigned long n );
   CLog& operator<< ( float n );
   CLog& operator<< ( double n );
   CLog& operator<< ( long double n );
   CLog& operator<< ( void* n );
private:
   std::ofstream m_clStream;
   std::string m_strFileName;
};
#define ENDL "n"
extern CLog LOG;
#endif

Im Konstruktur wird ein Dateiname übergeben, der als Name der Log-Datei dient. Weiterhin wird im Header das Symbol ENDL definiert, welches einen Zeilenumbruch definiert. Ich werde im Hauptprogramm eine Instanz der Klasse CLog mit dem Namen LOG erstellen. Durch das "extern ..." kann ich dann später von jeder Datei aus, in der ich die Log.h include, auf die LOG Instanz zugreifen und so über LOG << "bla" << ENDL; einen Text in die Log-Datei schreiben. Alternativ könnte man hier z.B. ein Singleton-Design-Pattern verwenden.
Datei /debug/Log.cpp

#include "Log.h"
// --------------------------------------------------------------------------------
CLog::CLog( const char* pszFileName )
:   m_clStream( pszFileName ),
  m_strFileName( pszFileName )
{
  m_clStream.close();
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( const char* s )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << s;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( char* s )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << s;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( char c )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << c;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< (short n)
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< (unsigned short n)
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( int n )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( unsigned int n )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( long n )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( unsigned long n )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( float n )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( double n )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( long double n )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}
//------------------------------------------------------------------------------
CLog& CLog::operator<< ( void* n )
{
  m_clStream.open( m_strFileName.c_str(), std::ios::out | std::ios::app );
  m_clStream << n;
  m_clStream.close();
  return *this;
}

Ein derartiges Log-File ist nur ein minimaler Teil des Aufwandes, den man normalerweise vor der konkreten Implementierung eines Projekts betreibt. Einem professionellen Projekt liegt ein solides Framework zugrunde, das aus zahlreichen Debug-Hilfen, sowie anderen Teilsystemen, wie z.B. einem virtuellen Dateisystem, besteht. Wer einmal ein solches System geschaffen hat, kann dieses normalerweise problemlos für weitere Projekte verwenden.

Weiterführende Links

Aufgaben

  1. Erstelle für Initialisierung und Deinitialisierung zwei Funktionen, die jeweils aus der main Funktion aufgerufen werden, um die main Funktion übersichtlich zu halten.
  2. Erstelle eine globale Instanz der Logklasse namens LOG in der Main.cpp und übergib als Parameter den Dateinamen "ProjektE.log".
  3. Füge Informations- und Fehler-Nachrichten in den Quelltext ein.

Mit dieser kleinen Hilfe können wir beim nächsten Mal wieder mit der unmittelbaren Spiellogik weitermachen.

Leave a Reply

Your email address will not be published. Required fields are marked *