Welche Schule der Berichterstattung Funktionsstörungen ist besser

8

Sehr oft haben Sie eine Funktion, die für gegebene Argumente kein gültiges Ergebnis erzeugen kann oder einige Aufgaben nicht ausführen kann. Abgesehen von Ausnahmen, die in der C / C ++ - Welt nicht so häufig verwendet werden, gibt es grundsätzlich zwei Schulen, die ungültige Ergebnisse melden.

Der erste Ansatz mischt gültige Rückgabewerte mit einem Wert, der nicht zum Codomain einer Funktion gehört (sehr oft -1) und einen Fehler anzeigt

%Vor%

Der zweite Ansatz besteht darin, einen Funktionsstatus zurückzugeben und das Ergebnis innerhalb einer Referenz zu übergeben

%Vor%

Welchen Weg bevorzugen Sie und warum? Verursachen zusätzliche Parameter in der zweiten Methode einen bemerkenswerten Leistungsaufwand?

    
doc 08.07.2010, 14:44
quelle

17 Antworten

14

Ignorieren Sie keine Ausnahmen für außergewöhnliche und unerwartete Fehler.

Wenn Sie jedoch nur Ihre Punkte beantworten, ist die Frage letztlich subjektiv. Das Hauptproblem besteht darin, zu überlegen, was für Ihre Kunden einfacher zu handhaben ist, während Sie sie ruhig an die Fehlerbedingungen erinnern. Meiner Meinung nach ist dies fast immer der "Rückgabe eines Statuscodes, und lege den Wert in eine separate Referenz", aber dies ist völlig eine persönliche Ansicht. Meine Argumente dafür ...

  1. Wenn Sie sich dafür entscheiden, einen gemischten Wert zurückzugeben, dann haben Sie das Konzept von return mit "entweder ein nützlicher Wert oder ein Fehlercode" überladen. Das Überladen eines einzelnen semantischen Konzepts kann zu Verwirrung darüber führen, was damit zu tun ist.
  2. Sie können Werte in der Codemain-Funktion häufig nicht finden, um sie als Fehlercodes zu verwenden. Daher müssen Sie die beiden Arten der Fehlerberichterstattung innerhalb einer einzigen API kombinieren und abgleichen.
  3. Es besteht fast keine Möglichkeit, dass, wenn sie vergessen, den Fehlerstatus zu überprüfen, sie einen Fehlercode verwenden, als ob es tatsächlich ein nützliches Ergebnis wäre. Man kann einen Fehlercode zurückgeben und ein Null-ähnliches Konzept in die Rückkehrreferenz einkleben, das bei Verwendung leicht explodiert. Wenn man das Fehler / Wert-Mixed-Return-Modell verwendet, ist es sehr einfach, es in eine andere Funktion zu übergeben, in der der Fehlerteil der Co-Domäne eine gültige Eingabe ist (aber bedeutungslos im Kontext).

Argumente für die Rückgabe des gemischten Fehlercodes / Wertmodells könnten Einfachheit sein - keine zusätzlichen Variablen, die umher fließen. Aber für mich sind die Gefahren schlimmer als die begrenzten Gewinne - man kann leicht vergessen, die Fehlercodes zu überprüfen. Dies ist ein Argument für Ausnahmen - Sie können buchstäblich nicht vergessen, mit ihnen umzugehen (Ihr Programm wird ausgelöscht, wenn Sie das nicht tun).

    
Adam Wright 08.07.2010, 14:57
quelle
8

boost optional ist eine brillante Technik. Ein Beispiel wird helfen.

Angenommen, Sie haben eine Funktion, die ein Double zurückgibt, und Sie möchten dies angeben ein Fehler, wenn das nicht berechnet werden kann.

%Vor%

was zu tun ist, wenn b 0 ist;

%Vor%

benutze es wie unten.

%Vor%     
bradgonesurfing 08.07.2010 16:04
quelle
6

Die Idee spezieller Rückgabewerte fällt vollständig aus, wenn Sie mit der Verwendung von Vorlagen beginnen. Überlegen Sie:

%Vor%

Es gibt keinen offensichtlichen Sonderwert, den wir in diesem Fall zurückgeben können. Daher ist das Auslösen einer Ausnahme der einzige Weg. Das Zurückgeben boolescher Typen, die überprüft werden müssen, und das Zurückgeben der wirklich interessanten Werte durch Referenz führt zu einem horrenden Codierungsstil.

    
anon 08.07.2010 15:28
quelle
4

Eine ganze Reihe von Büchern usw. empfehlen dringend die zweite, also mischen Sie keine Rollen und zwingen den Rückgabewert dazu, zwei völlig unabhängige Informationen zu tragen.

Während ich mit dieser Vorstellung sympathisiere, finde ich, dass das erste typischerweise in der Praxis besser funktioniert. Für einen offensichtlichen Punkt, im ersten Fall können Sie die Zuordnung zu einer beliebigen Anzahl von Empfängern verketten, aber in der zweiten, wenn Sie das Ergebnis mehr als einem Empfänger zuordnen möchten / müssen, müssen Sie den Aufruf durchführen und dann separat ausführen eine zweite Aufgabe. I.e.,

%Vor%

gegen:

%Vor%

oder:

%Vor%

Der Beweis des Puddings ist im Essen davon. Microsofts COM-Funktionen (für ein Beispiel) wählten ausschließlich die letztere Form. IMO, es liegt hauptsächlich an dieser Entscheidung allein, dass im Wesentlichen alle Code, der die native COM-API direkt verwendet, hässlich und fast nicht lesbar ist. Die involvierten Konzepte sind nicht besonders schwierig, aber der Stil der Schnittstelle macht praktisch jeden Code, der einfacher Code sein sollte, in eine fast unlesbare Unordnung.

Ausnahmebehandlung ist normalerweise ein besserer Weg, um mit Dingen umzugehen als mit beiden. Es hat drei spezifische Effekte, die alle sehr gut sind. Erstens hält es die Mainstream-Logik davon ab, mit der Fehlerbehandlung verschmutzt zu werden, so dass die wirkliche Absicht des Codes viel klarer ist. Zweitens entkoppelt es den Fehler handling von der Fehler Erkennung . Code, der ein Problem erkennt, befindet sich oft in einer schlechten Position, um diesen Fehler sehr gut zu behandeln . Drittens ist es im Gegensatz zu jeder Form der Rückgabe eines Fehlers im Wesentlichen unmöglich , eine ausgelöste Ausnahme einfach zu ignorieren. Bei den Return-Codes gibt es eine fast konstante Versuchung (der Programmierer allzu oft erliegen), einfach nur Erfolg zu haben und gar kein Problem zu machen - zumal der Programmierer nicht wirklich weiß, wie er mit dem Fehler umgehen soll Teil des Codes sowieso, und ist sich bewusst, dass, selbst wenn er es fängt und einen Fehlercode von seiner Funktion zurückgibt, Chancen gut sind, dass es sowieso ignoriert wird.

    
Jerry Coffin 08.07.2010 15:05
quelle
2

In C ist eine der gebräuchlichsten Techniken, die ich gesehen habe, die, dass eine Funktion bei einem Erfolg null zurückgibt (normalerweise ein Fehlercode). Wenn die Funktion Daten an den Aufrufer zurückgeben muss, geschieht dies über einen Zeiger, der als Funktionsargument übergeben wird. Dies kann auch dazu führen, dass Funktionen, die mehrere Daten an den Benutzer zurückgeben, einfacher zu verwenden sind (statt einige Daten über einen Rückgabewert und einige über einen Zeiger zurückzugeben).

Eine weitere C-Technik, die ich sehe, ist, dass bei Erfolg 0 zurückgegeben wird, und bei Fehler wird -1 zurückgegeben und errno wird gesetzt, um den Fehler anzuzeigen.

Die Techniken, die Sie vorgestellt haben, haben Vor- und Nachteile, so dass es immer (zumindest teilweise) subjektiv ist, zu entscheiden, welcher der "besten" ist. Allerdings kann ich das ohne Vorbehalte sagen: Die beste Technik ist die Technik, die im gesamten Programm konsistent ist . Die Verwendung verschiedener Stile von Fehlerberichtscodes in verschiedenen Teilen eines Programms kann schnell zu einem Albtraum für die Wartung und das Debugging werden.

    
bta 09.07.2010 21:22
quelle
1

Es sollte nicht viel, wenn überhaupt, Leistungsunterschied zwischen den beiden geben. Die Auswahl hängt von der jeweiligen Verwendung ab. Sie können die erste nicht verwenden, wenn kein geeigneter ungültiger Wert vorhanden ist.

Wenn Sie C ++ verwenden, gibt es viel mehr Möglichkeiten als diese beiden, einschließlich Ausnahmen und Verwendung von etwas wie boost :: optional als Rückgabewert.

    
KeithB 08.07.2010 14:47
quelle
1

C verwendet traditionell den ersten Ansatz der Codierung von magischen Werten in gültigen Ergebnissen - deshalb erhalten Sie lustige Sachen wie strcmp (), die false (= 0) auf eine Übereinstimmung zurückgeben.

Neuere sichere Versionen vieler Standardbibliotheksfunktionen verwenden den zweiten Ansatz, bei dem explizit ein Status zurückgegeben wird.

Und keine Ausnahmen sind hier keine Alternative. Ausnahmen sind für außergewöhnliche Umstände, mit denen der Code möglicherweise nicht umgehen kann - Sie verursachen keine Ausnahme für eine Zeichenfolge, die nicht in strcmp () übereinstimmt

    
Martin Beckett 08.07.2010 15:00
quelle
1

Es ist nicht immer möglich, aber unabhängig davon, welche Methode zur Fehlerberichterstattung Sie verwenden, sollten Sie nach Möglichkeit eine Funktion so entwickeln, dass keine Fehlerfälle auftreten. Wenn dies nicht möglich ist, minimieren Sie die möglichen Fehlerbedingungen . Einige Beispiele:

  • Statt einen Dateinamen tief in vielen Funktionsaufrufen zu übergeben, könnten Sie Ihr Programm so gestalten, dass der Aufrufer die Datei öffnet und den% desc_de% - oder Dateideskriptor übergibt. Auf diese Weise werden Prüfungen auf "Datei konnte nicht geöffnet werden" entfernt und bei jedem Schritt an den Aufrufer gemeldet.

  • Wenn es eine kostengünstige Möglichkeit gibt, die Menge an Speicher zu prüfen, die eine Funktion für die Datenstrukturen reservieren muss, die sie erstellen und zurückgeben wird, stellen Sie eine Funktion zur Verfügung, die diesen Betrag zurückgibt und hat der Anrufer reserviert den Speicher. In einigen Fällen kann dies dem Aufrufer ermöglichen, den Stapel einfach zu verwenden, wodurch die Speicherfragmentierung erheblich reduziert und Sperren in FILE * vermieden werden.

  • Wenn eine Funktion eine Aufgabe ausführt, für die Ihre Implementierung viel Arbeitsraum benötigt, fragen Sie, ob es einen alternativen (möglicherweise langsameren) Algorithmus mit malloc Speicherplatzanforderungen gibt. Wenn die Leistung nicht kritisch ist, verwenden Sie einfach den Algorithmus O(1) space. Andernfalls implementieren Sie einen Fallback Fall, um es zu verwenden, wenn die Zuweisung fehlschlägt.

Dies sind nur ein paar Ideen, aber die Anwendung des gleichen Prinzips kann die Anzahl der Fehlerbedingungen reduzieren, mit denen Sie umgehen müssen, und sich über mehrere Aufrufstufen verbreiten.

    
R.. 08.07.2010 15:01
quelle
1

Für C ++ bevorzuge ich eine Template-Lösung, die die Fugheit der Out-Parameter und der Fugheit von "magischen Zahlen" in kombinierten Antworten / Return-Codes verhindert. Ich habe dies erklärt, während beantwortet eine andere Frage . Schau es dir an.

Für C finde ich die fugly out Parameter weniger beleidigend als fugy "magische Zahlen".

    
JUST MY correct OPINION 08.07.2010 15:40
quelle
1

Sie haben eine Methode verpasst: Zurückgeben einer Fehleranzeige und eines zusätzlichen Aufrufs, um die Details des Fehlers zu erhalten.

Es gibt eine Menge zu sagen.

Beispiel:

%Vor%

Bearbeiten

Diese Antwort hat einiges an Kontroversen und Downvoting hervorgebracht. Um ehrlich zu sein, bin ich von den abweichenden Argumenten absolut nicht überzeugt. Die Trennung von ob ein Aufruf von warum fehlgeschlagen ist, hat sich als wirklich gute Idee erwiesen. Kombinieren Sie die beiden Kräfte in das folgende Muster:

%Vor%

Vergleichen Sie dies mit:

%Vor%

(Die GetLastError-Version ist konsistent mit der Funktionsweise der Windows-API allgemein . Die Version, die den Code direkt zurückgibt, funktioniert jedoch so, weil die Registrierungs-API diesem Standard nicht folgt.)

In jedem Fall würde ich vorschlagen, dass das Fehler zurückkehrende Muster es allzu leicht macht, warum die Funktion zu vergessen, was zu Code wie folgt führt:

%Vor%

Bearbeiten

Mit Blick auf Rs Anfrage habe ich ein Szenario gefunden, in dem es tatsächlich befriedigt werden kann.

Für eine allgemeine C-style-API, wie die Windows SDK-Funktionen, die ich in meinen Beispielen verwendet habe, gibt es keinen nicht-globalen Kontext, in dem sich Fehlercodes befinden. Stattdessen haben wir keine gute Alternative zur Verwendung eine globale TLV, die nach einem Fehler überprüft werden kann.

Wenn wir jedoch das Thema erweitern, um Methoden für eine Klasse einzuschließen, ist die Situation anders. Es ist völlig in Ordnung, wenn eine Variable reg eine Instanz der Klasse RegistryKey für einen Aufruf von reg.Open ist, um false zurückzugeben, dann müssen wir reg.ErrorCode aufrufen, um die Details abzurufen.

Ich glaube, dass dies die Forderung von R. erfüllt, dass der Fehlercode Teil eines Kontextes sein soll, da die Instanz den Kontext bereitstellt. Wenn wir anstelle einer RegistryKey -Instanz eine statische Open -Methode für RegistryKeyHelper aufgerufen haben, müsste das Abrufen des Fehlercodes bei einem Fehler ebenfalls statisch sein, was bedeutet, dass es eine TLV sein müsste nicht ein ganz globaler. Die Klasse wäre im Gegensatz zu einer Instanz der Kontext.

In beiden Fällen bietet die Objektorientierung einen natürlichen Kontext zum Speichern von Fehlercodes. Abgesehen davon, dass es keinen natürlichen Kontext gibt, würde ich trotzdem auf einem globalen bestehen, anstatt zu versuchen, den Aufrufer dazu zu zwingen, einen Ausgabeparameter oder einen anderen künstlichen Kontext zu übergeben oder den Fehlercode direkt zurückzugeben.

    
Steven Sudit 08.07.2010 14:56
quelle
0

Ich glaube, darauf gibt es keine richtige Antwort. Es hängt von Ihren Bedürfnissen, vom gesamten Anwendungsdesign usw. ab. Ich persönlich benutze jedoch den ersten Ansatz.

    
PeterK 08.07.2010 14:46
quelle
0

Ich denke, ein guter Compiler würde fast den gleichen Code mit der gleichen Geschwindigkeit erzeugen. Es ist eine persönliche Vorliebe. Ich würde zuerst gehen.

    
pcent 08.07.2010 14:47
quelle
0

Wenn Sie Referenzen und den bool-Typ haben, müssen Sie C ++ verwenden. In diesem Fall eine Ausnahme auslösen. Dafür sind sie da. Für eine allgemeine Desktop-Umgebung gibt es keinen Grund, Fehlercodes zu verwenden. Ich habe Argumente gegen Ausnahmen in einigen Umgebungen gesehen, wie doggy Sprache / Prozess Interop oder eng eingebettete Umgebung. Nehmen wir an, dass keines davon immer eine Ausnahme auslöst.

    
Puppy 08.07.2010 15:33
quelle
0

Nun, der erste wird entweder in C und C ++ kompilieren, also ist portabler Code in Ordnung. Der zweite, obwohl es "menschlicher lesbar" ist, Sie wissen nie wahrheitsgemäß, welchen Wert das Programm zurückgibt, indem Sie es wie im ersten Fall spezifizieren, gibt Ihnen mehr Kontrolle, das ist, was ich denke.

    
aitorkun 08.07.2010 15:34
quelle
0

Ich bevorzuge Rückkehrcode für den Typ des aufgetretenen Fehlers. Dies hilft dem Aufrufer der API, geeignete Fehlerbehandlungsschritte zu ergreifen.

Betrachten Sie GLIB-APIs, die meistens den Fehlercode und die Fehlermeldung zusammen mit dem booleschen Rückgabewert zurückgeben.

Wenn Sie also eine negative Rückkehr zu einem Funktionsaufruf erhalten, können Sie den Kontext von der Variable GError überprüfen.

Ein Fehler in dem von Ihnen angegebenen zweiten Ansatz hilft dem Aufrufer nicht, die richtigen Maßnahmen zu ergreifen. Der andere Fall, wenn Ihre Dokumentation sehr klar ist. In anderen Fällen ist es jedoch ein Problem, den API-Aufruf zu verwenden.

    
Praveen S 08.07.2010 16:07
quelle
0

Für eine "try" -Funktion, bei der ein "normaler" Fehlertyp vernünftigerweise erwartet wird, wie wäre es, entweder einen Standard-Rückgabewert oder einen Zeiger auf eine Funktion zu akzeptieren, die bestimmte Parameter in Bezug auf den Fehler akzeptiert und einen solchen Wert zurückgibt der erwartete Typ?

    
supercat 09.07.2010 21:03
quelle
-4
  

Abgesehen davon, dass Sie den richtigen Weg gewählt haben, welche dieser dummen Wege bevorzugen Sie?

Ich bevorzuge es, Ausnahmen zu verwenden, wenn ich C ++ benutze und einen Fehler ausgeben muss, und im Allgemeinen, wenn ich nicht alle aufrufenden Funktionen zwingen will, den Fehler zu erkennen und zu behandeln. Ich bevorzuge es, dumme Sonderwerte zu verwenden, wenn nur eine mögliche Fehlerbedingung vorliegt, und diese Bedingung bedeutet, dass der Anrufer nicht fortfahren kann und jeder denkbare Anrufer in der Lage ist, damit umzugehen. Das ist selten. Ich bevorzuge es, beim Modifizieren von altem Code dumme Out-Parameter zu verwenden, und aus irgendeinem Grund kann ich die Anzahl der Parameter ändern, aber nicht den Rückgabetyp ändern oder einen speziellen Wert identifizieren oder eine Exception werfen, was bisher noch nie geschehen ist.

  

Enthält zusätzliche Parameter in der   zweite Methode bringt bemerkenswert   Performance Overhead?

Ja! Zusätzliche Parameter führen zu einer Verlangsamung des Computers um mindestens 0 Nanosekunden. Am besten verwenden Sie das Schlüsselwort "no-overhead" für diesen Parameter. Es ist eine GCC-Erweiterung __attribute__((no-overhead)) , also YMMV.

    
John 08.07.2010 22:36
quelle

Tags und Links