@CSL:
Mit Exceptions nicht für Flow-Control misbrauchen ist IMO sowas gemeint:
void foo()
{
blubb();
try
{
for (size_t i = 0; i < 100; i++)
{
for (size_t j = 0; j < 100; j++)
{
if (blah())
throw 0;
}
}
}
catch (int)
{
blobber();
}
bloink();
}
Das ist böse.
Was falsche User-Eingaben angeht: warum sollte es böse/falsch/schlecht sein da Exceptions zu verwenden?
Was dein Beispiel angeht: das finde ich etwas blauäugig, realer Code sieht anders aus, ist tiefer verschachtelt, und da zeigt sich schnell warum Exceptions praktisch sind. Auch bei falschen User-Eingaben.
Exceptions nur für "unerwartete" Fehler:
void XApp::UserSaidPrintFileOnPrinter(string pathToDocument, string printerName)
{
try
{
XDocument xdox;
ErrorDescription ed = xdox.TryRead(pathToDocument); // falscher pfad wäre "normal"
if (!ed)
{
XPrinter xprn;
ed = xprn.TryOpenPrinter(printerName); // falscher druckername wäre "normal"
if (!ed)
ed = xprn.PrintDocument(xdoc);
}
// "normale" fehler anzeigen
if (ed)
ShowErrorMessage(ed);
}
catch (exception const& e)
{
// "aussergewöhnliche" fehler - müssen auch behandelt und dem user angezeigt werden
ShowErrorMessage(e);
}
}
ErrorDescription XDocument::TryRead(string path)
{
if (!File::Exists(path))
return ErrorDescription(...);
File f(path);
return TryRead(f); // file ist vom falschen typ wäre "normal"
}
ErrorDescription XDocument::TryRead(File& f)
{
size_t l = f.GetLength();
Header h;
if (f.TryRead(&h, sizeof(h)) != sizeof(h)) // file ist vom falschen typ wäre "normal"
return ErrorDescription(...);
if (!ValidateHeader(h)) // file ist vom falschen typ wäre "normal"
return ErrorDescription(...);
for (size_t i = 0; i < h.SectionCount; i++)
{
ErrorDescription ed = TryReadSection(f, i); // file ist vom falschen typ wäre "normal"
if (ed)
return ed;
}
return NoError();
}
ErrorDescription XDocument::TryReadSection(File& f, size_t sectionNr)
{
// ...
}
ErrorDescription XPrinter::TryOpenPrinter(string name)
{
if (!Win32::Printer::Exists(name))
return ErrorDescription();
m_printer.reset(new Win32::Printer(name));
return ErrorDescription();
}
ErrorDescription XPrinter::PrintDocument(XDocument const& xdoc)
{
Win32::PrinterDocumentScope ds(m_printer); // spooler error wäre "aussergewöhnlich"
for (size_t i = 0; i < xdoc.GetPageCount() && (!ed); i++)
{
Win32::PrinterPageScope ps(m_printer); // spooler error wäre "aussergewöhnlich"
XRawPageData pd(xdoc, i); // fehler beim rendern der daten wäre "aussergewöhnlich"
m_printer->Write(pd.GetDataPtr(), pd.GetDataSize()); // spooler error wäre "aussergewöhnlich"
}
}
Mit Exceptions für alles:
void XApp::UserSaidPrintFileOnPrinter(string pathToDocument, string printerName)
{
try
{
XPrinter(printerName).PrintDocument(XDocument(pathToDocument));
}
catch (exception const& e)
{
// "aussergewöhnliche" fehler - müssen auch behandelt und dem user angezeigt werden
ShowErrorMessage(e);
}
}
XDocument::XDocument(string path)
{
File f(path);
Read(f);
}
void XDocument::Read(File& f)
{
Header h;
f.Read(&h, sizeof(h));
if (!ValidateHeader(h))
throw FileFormatException(...);
for (size_t i = 0; i < h.SectionCount; i++)
ReadSection(f, i);
}
void XDocument::TryReadSection(File& f, size_t sectionNr)
{
// ...
}
XPrinter::XPrinter(string name) :
m_printer(new Win32::Printer(name))
{
}
ErrorDescription XPrinter::PrintDocument(XDocument const& xdoc)
{
Win32::PrinterDocumentScope ds(m_printer);
for (size_t i = 0; i < xdoc.GetPageCount() && (!ed); i++)
{
Win32::PrinterPageScope ps(m_printer);
XRawPageData pd(xdoc, i);
m_printer->Write(pd.GetDataPtr(), pd.GetDataSize());
}
}
Und jetzt sag mir warum ich in diesem Beispiel keine Exceptions für "normale" Fehler verwenden sollte.
Ich würde es eher so sagen:
* wenn es häufig vorkommt, dass das Fehlschlagen von einer Operation *lokal* (d.h. direkt in der aufrufenden Funktion) sinnvoll behandelt werden kann, dann sollte man eine Exception-freie Variante anbieten
* wenn es häufig vorkommt, dass das Fehlschlagen von einer Operation nicht sinnvoll lokal behandelt werden kann, dann sollte man Exceptions verwenden
Die meisten Fälle, die ich bisher hatte, waren derart, dass die Behandlung des Fehlers mehrere Ebenen höher erfolgt, als die Stelle wo man den Fehler erkennt. Auch wenn der Fehler auf falsche User-Eingaben zurückzuführen ist. Daher ist es *praktisch* dort Exceptions zu verwenden.
Meine Frage wäre nun: was spricht denn dagegen?
Ein Argument welches man häufig hört, ist dass Exceptions in manchen Implementierungen langsam sind. Das stimmt, aber langsam ist relativ. Und ich kenne keine Implementierung, wo das Werfen + Fangen + Behandeln einer Exception *so* langsam ist, dass es wirklich etwas ausmacht.
p.S.:
Noch ein Argument gegen "Pre-Validation": der sicherste (und auch einfachste) Weg, zu sehen ob etwas geht (gehen wird), ist meist es einfach zu versuchen. Bei Pre-Validation kann man einfach zu viele Dinge übersehen. Ich kann z.B. fragen ob ein File existiert. Dass ich es "sehe", heisst aber noch lange nicht, dass ich es auch lesen darf/kann. Bzw. könnte es sogar sein, dass ich *nicht* feststellen kann dass es existiert, es aber trotzdem mit dem richtigen Pfad öffnen könnte. (Je nachdem was das File-System für Berechtigungen verwendet, und je nachdem was für den "File::Exists()" Check verwendet wird.) D.h. es könnte sein, dass das "bessere" Programm mit Pre-Validation mich ein File nicht aufmachen lässt, obwohl es existiert und ich auch die nötigen Berechtigungen hätte. Tolle Wurst.
Mal ganz davon abgesehen, dass ein File jederzeit verschwinden kann. Nur dass es jetzt existiert, heisst nicht, dass es in 2ns auch noch existiert. Und wieso wäre das plötzliche Verschwinden von Files weniger "normal" als eine falsche User-Eingabe?
EDIT: ich sehe wir sind hier im C# Forum und nicht im C++/RudP. Ändert allerdings an meiner Argumentation nichts. Beim Code möge man sich die entsprechenden C# Konstrukte denken, also using() Blöcke statt Klasse mit Destruktoren etc.