Meine Frage ist, was die meisten Entwickler für Fehlerbehandlung, Ausnahmen oder Fehlerrückgabecodes bevorzugen. Bitte seien Sie sprachspezifisch (oder sprachfamilienspezifisch) und warum Sie einander vorziehen.
Ich frage das aus Neugier. Persönlich bevorzuge ich Fehlerrückgabecodes, da sie weniger explosiv sind und den Benutzercode nicht zwingen, die Ausnahmeleistungsstrafe zu zahlen, wenn sie dies nicht möchten.
Update: Danke für alle Antworten! Ich muss das sagen, obwohl ich die Unvorhersehbarkeit des Codeflusses mit Ausnahmen nicht mag. Die Antwort über den Rückkehrcode (und die Handles ihres älteren Bruders) fügt dem Code viel Rauschen hinzu.
language-agnostic
exception
Robert Gould
quelle
quelle
Antworten:
Für einige Sprachen (z. B. C ++) sollte ein Ressourcenleck kein Grund sein
C ++ basiert auf RAII.
Wenn Sie Code haben, der fehlschlagen, zurückgeben oder werfen kann (dh den meisten normalen Code), sollten Sie Ihren Zeiger in einen intelligenten Zeiger einschließen (vorausgesetzt, Sie haben einen sehr guten Grund, Ihr Objekt nicht auf einem Stapel zu erstellen).
Rückkehrcodes sind ausführlicher
Sie sind ausführlich und entwickeln sich zu etwas wie:
Am Ende ist Ihr Code eine Sammlung identifizierter Anweisungen (ich habe diese Art von Code im Produktionscode gesehen).
Dieser Code könnte gut übersetzt werden in:
Welche sauber separater Code und Fehlerverarbeitung, die kann sein , gute Sache.
Rückkehrcodes sind spröder
Wenn nicht eine obskure Warnung von einem Compiler vorliegt (siehe den Kommentar von "phjr"), können sie leicht ignoriert werden.
Nehmen wir bei den obigen Beispielen an, dass jemand vergisst, seinen möglichen Fehler zu behandeln (dies passiert ...). Der Fehler wird bei "Rückgabe" ignoriert und explodiert möglicherweise später (dh ein NULL-Zeiger). Das gleiche Problem wird mit Ausnahme nicht auftreten.
Der Fehler wird nicht ignoriert. Manchmal möchten Sie jedoch, dass es nicht explodiert ... Sie müssen also sorgfältig auswählen.
Rückkehrcodes müssen manchmal übersetzt werden
Nehmen wir an, wir haben folgende Funktionen:
Was ist die Art der Rückgabe von doTryToDoSomethingWithAllThisMess, wenn eine der aufgerufenen Funktionen fehlschlägt?
Rückkehrcodes sind keine universelle Lösung
Bediener können keinen Fehlercode zurückgeben. C ++ - Konstruktoren können das auch nicht.
Rückkehrcodes bedeuten, dass Sie Ausdrücke nicht verketten können
Die Folge des obigen Punktes. Was ist, wenn ich schreiben möchte:
Ich kann nicht, da der Rückgabewert bereits verwendet wird (und manchmal nicht geändert werden kann). Der Rückgabewert wird also zum ersten Parameter, der als Referenz gesendet wird ... oder nicht.
Ausnahmen werden eingegeben
Sie können für jede Art von Ausnahme verschiedene Klassen senden. Ressourcenausnahmen (dh nicht genügend Speicher) sollten leicht sein, aber alles andere kann so schwer wie nötig sein (ich mag die Java-Ausnahme, die mir den gesamten Stapel gibt).
Jeder Fang kann dann spezialisiert werden.
Verwenden Sie niemals catch (...), ohne erneut zu werfen
Normalerweise sollten Sie einen Fehler nicht verbergen. Wenn Sie den Fehler zumindest nicht erneut auslösen, protokollieren Sie ihn in einer Datei, öffnen Sie eine Nachrichtenbox, was auch immer ...
Ausnahme sind ... NUKE
Das Problem mit Ausnahme ist, dass eine Überbeanspruchung Code voller Versuche / Fänge erzeugt. Das Problem ist jedoch anderswo: Wer versucht, seinen Code mithilfe eines STL-Containers abzufangen? Diese Container können jedoch eine Ausnahme senden.
Lassen Sie in C ++ natürlich niemals eine Ausnahme einen Destruktor verlassen.
Ausnahme sind ... synchron
Stellen Sie sicher, dass Sie sie abfangen, bevor sie Ihren Thread auf die Knie zwingen oder sich in Ihrer Windows-Nachrichtenschleife verbreiten.
Die Lösung könnte darin bestehen, sie zu mischen?
Ich denke, die Lösung ist zu werfen, wenn etwas nicht passieren sollte. Und wenn etwas passieren kann, verwenden Sie einen Rückkehrcode oder einen Parameter, damit der Benutzer darauf reagieren kann.
Die einzige Frage ist also: "Was sollte nicht passieren?"
Dies hängt vom Vertrag Ihrer Funktion ab. Wenn die Funktion einen Zeiger akzeptiert, aber angibt, dass der Zeiger nicht NULL sein darf, ist es in Ordnung, eine Ausnahme auszulösen, wenn der Benutzer einen NULL-Zeiger sendet (die Frage ist in C ++, wann der Funktionsautor keine Referenzen verwendet hat von Zeigern, aber ...)
Eine andere Lösung wäre, den Fehler anzuzeigen
Manchmal ist Ihr Problem, dass Sie keine Fehler wollen. Die Verwendung von Ausnahmen oder Fehlerrückgabecodes ist cool, aber ... Sie möchten es wissen.
In meinem Job verwenden wir eine Art "Assert". Abhängig von den Werten einer Konfigurationsdatei wird unabhängig von den Debug- / Release-Kompilierungsoptionen Folgendes ausgeführt:
Auf diese Weise kann der Benutzer sowohl beim Entwickeln als auch beim Testen das Problem genau bestimmen, wenn es erkannt wird, und nicht danach (wenn sich ein Code um den Rückgabewert kümmert oder innerhalb eines Catch).
Es ist einfach, Legacy-Code hinzuzufügen. Beispielsweise:
führt eine Art Code ähnlich wie:
(Ich habe ähnliche Makros, die nur beim Debuggen aktiv sind).
Beachten Sie, dass die Konfigurationsdatei in der Produktion nicht vorhanden ist, sodass der Client das Ergebnis dieses Makros nie sieht. Es ist jedoch einfach, es bei Bedarf zu aktivieren.
Fazit
Wenn Sie mit Rückkehrcodes codieren, bereiten Sie sich auf einen Fehler vor und hoffen, dass Ihre Testfestung sicher genug ist.
Wenn Sie mit Ausnahme codieren, wissen Sie, dass Ihr Code fehlschlagen kann, und setzen normalerweise den Gegenfeuerfang an der ausgewählten strategischen Position in Ihrem Code. Aber normalerweise geht es in Ihrem Code mehr um "was es tun muss" als um "was ich fürchte".
Wenn Sie jedoch überhaupt codieren, müssen Sie das beste Tool verwenden, das Ihnen zur Verfügung steht. Manchmal lautet es "Verstecken Sie niemals einen Fehler und zeigen Sie ihn so schnell wie möglich an". Das Makro, das ich oben gesprochen habe, folgt dieser Philosophie.
quelle
if( !doSomething() ) { puts( "ERROR - doSomething failed" ) ; return ; // or react to failure of doSomething } if( !doSomethingElse() ) { // react to failure of doSomethingElse() }
doSomething(); doSomethingElse(); ...
besser, denn wenn ich hinzufügen muss, wenn / während / etc. Anweisungen für normale Ausführungszwecke, ich möchte nicht, dass sie mit if / while / etc. gemischt werden. Anweisungen, die für außergewöhnliche Zwecke hinzugefügt wurden ... Und da die eigentliche Regel bei der Verwendung von Ausnahmen darin besteht, zu werfen , nicht zu fangen , sind die try / catch-Anweisungen normalerweise nicht invasiv.Ich benutze eigentlich beide.
Ich verwende Rückkehrcodes, wenn es sich um einen bekannten, möglichen Fehler handelt. Wenn es ein Szenario ist, von dem ich weiß, dass es passieren kann und wird, wird ein Code zurückgesendet.
Ausnahmen werden nur für Dinge verwendet, die ich NICHT erwarte.
quelle
Gemäß Kapitel 7 mit dem Titel "Ausnahmen" in den Richtlinien für das Framework-Design: Konventionen, Redewendungen und Muster für wiederverwendbare .NET-Bibliotheken werden zahlreiche Gründe angegeben, warum die Verwendung von Ausnahmen über Rückgabewerte für OO-Frameworks wie C # erforderlich ist.
Vielleicht ist dies der überzeugendste Grund (Seite 179):
"Ausnahmen lassen sich gut in objektorientierte Sprachen integrieren. Objektorientierte Sprachen neigen dazu, Mitgliedssignaturen, die nicht durch Funktionen in Nicht-OO-Sprachen auferlegt werden, Einschränkungen aufzuerlegen. Beispielsweise bei Konstruktoren, Operatorüberladungen und Eigenschaften der Entwickler Der Rückgabewert hat keine Auswahl. Aus diesem Grund ist es nicht möglich, die auf Rückgabewerten basierende Fehlerberichterstattung für objektorientierte Frameworks zu standardisieren. Eine Fehlerberichterstattungsmethode, z. B. Ausnahmen, liegt außerhalb des Bereichs der Methodensignatur ist die einzige Option. "
quelle
Ich bevorzuge (in C ++ und Python) die Verwendung von Ausnahmen. Die von der Sprache bereitgestellten Funktionen machen es zu einem genau definierten Prozess, Ausnahmen auszulösen, zu fangen und (falls erforderlich) erneut auszulösen, wodurch das Modell einfach zu sehen und zu verwenden ist. Konzeptionell ist es sauberer als Rückkehrcodes, da bestimmte Ausnahmen durch ihre Namen definiert werden können und zusätzliche Informationen beigefügt sind. Mit einem Rückkehrcode sind Sie nur auf den Fehlerwert beschränkt (es sei denn, Sie möchten ein ReturnStatus-Objekt oder etwas anderes definieren).
Sofern der von Ihnen geschriebene Code nicht zeitkritisch ist, ist der mit dem Abwickeln des Stapels verbundene Overhead nicht signifikant genug, um sich Sorgen zu machen.
quelle
Ausnahmen sollten nur zurückgegeben werden, wenn etwas passiert, das Sie nicht erwartet haben.
Der andere Ausnahmepunkt in der Vergangenheit ist, dass Rückkehrcodes von Natur aus proprietär sind. Manchmal kann eine 0 von einer C-Funktion zurückgegeben werden, um den Erfolg anzuzeigen, manchmal -1 oder einer von beiden für einen Fehler mit 1 für einen Erfolg. Selbst wenn sie aufgezählt werden, können Aufzählungen mehrdeutig sein.
Ausnahmen können auch viel mehr Informationen liefern und insbesondere gut formulieren: "Etwas ist falsch gelaufen, hier ist was, eine Stapelverfolgung und einige unterstützende Informationen für den Kontext".
Abgesehen davon kann ein gut aufgezählter Rückkehrcode für eine bekannte Reihe von Ergebnissen nützlich sein, ein einfaches "Heres n Ergebnis der Funktion, und es lief einfach so".
quelle
In Java verwende ich (in der folgenden Reihenfolge):
Design-by-Contract (Sicherstellen, dass die Voraussetzungen erfüllt sind, bevor versucht wird, etwas zu tun , das fehlschlägt). Dies fängt die meisten Dinge ab und ich gebe einen Fehlercode dafür zurück.
Rückgabe von Fehlercodes während der Verarbeitung der Arbeit (und ggf. Durchführen eines Rollbacks).
Ausnahmen, aber diese werden nur für unerwartete Dinge verwendet.
quelle
assert
da es keine Probleme im Produktionscode auffängt, es sei denn, Sie werden ständig mit Zusicherungen ausgeführt. Ich neige dazu, Behauptungen als einen Weg zu sehen, um Probleme während der Entwicklung zu erkennen, aber nur für Dinge, die sich durchsetzen oder nicht konsistent behaupten (wie Dinge mit Konstanten, keine Dinge mit Variablen oder irgendetwas anderes, das sich zur Laufzeit ändern kann).Rückkehrcodes bestehen den " Pit of Success " -Test für mich fast jedes Mal nicht.
In Bezug auf die Leistung:
quelle
Ein großartiger Ratschlag, den ich von The Pragmatic Programmer erhalten habe, lautete: "Ihr Programm sollte in der Lage sein, alle seine Hauptfunktionen ohne Ausnahmen auszuführen."
quelle
Ich habe vor einiger Zeit einen Blog-Beitrag darüber geschrieben.
Der Leistungsaufwand beim Auslösen einer Ausnahme sollte bei Ihrer Entscheidung keine Rolle spielen. Wenn Sie es richtig machen, ist eine Ausnahme eine Ausnahme .
quelle
Ich mag keine Rückkehrcodes, weil sie dazu führen, dass das folgende Muster in Ihrem Code auftaucht
Bald wird ein Methodenaufruf, der aus 4 Funktionsaufrufen besteht, mit 12 Zeilen Fehlerbehandlung aufgebläht. Einige davon werden niemals auftreten. Wenn und Schalter Fälle gibt es zuhauf.
Ausnahmen sind sauberer, wenn Sie sie gut verwenden ... um außergewöhnliche Ereignisse zu signalisieren ... nach denen der Ausführungspfad nicht fortgesetzt werden kann. Sie sind oft aussagekräftiger und informativer als Fehlercodes.
Wenn Sie nach einem Methodenaufruf mehrere Zustände haben, die unterschiedlich behandelt werden sollten (und keine Ausnahmefälle sind), verwenden Sie Fehlercodes oder out-Parameter. Obwohl ich persönlich festgestellt habe, dass dies selten ist.
Ich habe ein bisschen über das Gegenargument der Leistungsstrafe gejagt. Mehr in der C ++ / COM-Welt, aber in den neueren Sprachen denke ich, dass der Unterschied nicht so groß ist. In jedem Fall, wenn etwas explodiert, werden Leistungsbedenken in den Hintergrund gedrängt :)
quelle
Bei anständigen Compiler- oder Laufzeitumgebungen verursachen Ausnahmen keine nennenswerten Nachteile. Es ist mehr oder weniger wie eine GOTO-Anweisung, die zum Ausnahmebehandler springt. Wenn Ausnahmen von einer Laufzeitumgebung (wie der JVM) abgefangen werden, können Sie einen Fehler viel einfacher isolieren und beheben. Ich werde jeden Tag eine NullPointerException in Java über einen Segfault in C durchführen.
quelle
finally
Blöcken und Destruktoren für vom Stapel zugewiesene Objekte fertig zu werden.Ich verwende Ausnahmen in Python sowohl unter außergewöhnlichen als auch unter nicht außergewöhnlichen Umständen.
Es ist oft schön, eine Ausnahme verwenden zu können, um anzuzeigen, dass "Anforderung nicht ausgeführt werden konnte", anstatt einen Fehlerwert zurückzugeben. Dies bedeutet, dass Sie / immer / wissen, dass der Rückgabewert der richtige Typ ist, anstatt willkürlich None oder NotFoundSingleton oder so. Hier ist ein gutes Beispiel dafür, wo ich lieber einen Ausnahmebehandler anstelle einer Bedingung für den Rückgabewert verwende.
Der Nebeneffekt ist, dass Sie beim Ausführen einer datastore.fetch (obj_id) nie überprüfen müssen, ob der Rückgabewert None ist. Sie erhalten diesen Fehler sofort kostenlos. Dies steht im Widerspruch zu dem Argument "Ihr Programm sollte in der Lage sein, alle Hauptfunktionen ohne Ausnahmen auszuführen".
Hier ist ein weiteres Beispiel dafür, wo Ausnahmen "außergewöhnlich" nützlich sind, um Code für den Umgang mit dem Dateisystem zu schreiben, das nicht den Rennbedingungen unterliegt.
Ein Systemaufruf statt zwei, keine Rennbedingung. Dies ist ein schlechtes Beispiel, da dies offensichtlich mit einem OSError unter mehr Umständen fehlschlägt, als das Verzeichnis nicht existiert, aber es ist eine "gute" Lösung für viele streng kontrollierte Situationen.
quelle
Ich glaube, dass die Rückkehrcodes das Code-Rauschen verstärken. Zum Beispiel hasste ich das Aussehen von COM / ATL-Code aufgrund von Rückkehrcodes immer. Für jede Codezeile musste eine HRESULT-Prüfung durchgeführt werden. Ich halte den Fehlerrückgabecode für eine der schlechten Entscheidungen der Architekten von COM. Dies macht es schwierig, den Code logisch zu gruppieren, sodass die Codeüberprüfung schwierig wird.
Ich bin mir über den Leistungsvergleich nicht sicher, wenn in jeder Zeile eine explizite Überprüfung des Rückkehrcodes erfolgt.
quelle
Ich bevorzuge es, Ausnahmen für die Fehlerbehandlung und Rückgabewerte (oder Parameter) als normales Ergebnis einer Funktion zu verwenden. Dies ergibt ein einfaches und konsistentes Fehlerbehandlungsschema, und wenn es richtig gemacht wird, sieht es viel sauberer aus.
quelle
Einer der großen Unterschiede besteht darin, dass Ausnahmen Sie zwingen, einen Fehler zu behandeln, während Fehlerrückgabecodes deaktiviert werden können.
Fehlerrückgabecodes können, wenn sie häufig verwendet werden, auch sehr hässlichen Code mit vielen if-Tests verursachen, die diesem Formular ähnlich sind:
Persönlich bevorzuge ich Ausnahmen für Fehler, auf die der aufrufende Code reagieren SOLLTE oder MUSS, und verwende Fehlercodes nur für "erwartete Fehler", bei denen die Rückgabe von etwas tatsächlich gültig und möglich ist.
quelle
Ich habe ein einfaches Regelwerk:
1) Verwenden Sie Rückkehrcodes für Dinge, auf die Ihr sofortiger Anrufer reagieren soll.
2) Verwenden Sie Ausnahmen für Fehler, deren Umfang breiter ist und von denen vernünftigerweise erwartet werden kann, dass sie von vielen Ebenen über dem Aufrufer behandelt werden, damit die Fehlererkennung nicht über viele Ebenen verteilt werden muss, wodurch der Code komplexer wird.
In Java habe ich immer nur ungeprüfte Ausnahmen verwendet, geprüfte Ausnahmen sind nur eine andere Form des Rückkehrcodes, und meiner Erfahrung nach war die Dualität dessen, was durch einen Methodenaufruf "zurückgegeben" werden könnte, im Allgemeinen eher ein Hindernis als eine Hilfe.
quelle
Es gibt viele Gründe, Ausnahmen dem Rückkehrcode vorzuziehen:
quelle
Ausnahmen gelten nicht für die Fehlerbehandlung, IMO. Ausnahmen sind genau das; außergewöhnliche Ereignisse, die Sie nicht erwartet hatten. Verwenden Sie mit Vorsicht, sage ich.
Fehlercodes können in Ordnung sein, aber die Rückgabe von 404 oder 200 von einer Methode ist schlecht, IMO. Verwenden Sie stattdessen Aufzählungen (.Net), um den Code lesbarer und für andere Entwickler einfacher zu verwenden. Außerdem müssen Sie keine Tabelle über Zahlen und Beschreibungen führen.
Ebenfalls; Das Try-Catch-Endlich-Muster ist ein Anti-Muster in meinem Buch. Try-Catch kann gut sein, Try-Catch kann auch gut sein, aber Try-Catch-Endlich ist nie gut. try-finally kann oft durch eine "using" -Anweisung (IDispose-Muster) ersetzt werden, was IMO besser ist. Und Try-Catch, bei dem Sie tatsächlich eine Ausnahme abfangen, die Sie behandeln können, ist gut, oder wenn Sie dies tun:
Solange Sie die Ausnahme weiter sprudeln lassen, ist dies in Ordnung. Ein weiteres Beispiel ist folgendes:
Hier behandle ich tatsächlich die Ausnahme; Was ich außerhalb des Try-Catch mache, wenn die Update-Methode fehlschlägt, ist eine andere Geschichte, aber ich denke, mein Punkt wurde klargestellt. :) :)
Warum ist Try-Catch-Endlich dann ein Anti-Pattern? Hier ist der Grund:
Was passiert, wenn das Datenbankobjekt bereits geschlossen wurde? Eine neue Ausnahme wird ausgelöst und muss behandelt werden! Das ist besser:
Wenn das Datenbankobjekt IDisposable nicht implementiert, gehen Sie folgendermaßen vor:
Das sind sowieso meine 2 Cent! :) :)
quelle
Ich benutze nur Ausnahmen, keine Rückkehrcodes. Ich spreche hier von Java.
Die allgemeine Regel, der ich folge, lautet: Wenn ich eine Methode habe, die aufgerufen
doFoo()
wird, folgt, dass, wenn sie sozusagen nicht "do foo" ist, etwas Außergewöhnliches passiert ist und eine Ausnahme ausgelöst werden sollte.quelle
Eine Sache, die ich bei Ausnahmen befürchte, ist, dass das Auslösen einer Ausnahme den Codefluss beeinträchtigt. Zum Beispiel, wenn Sie dies tun
Oder noch schlimmer, wenn ich etwas löschte, das ich nicht hätte haben sollen, aber geworfen wurde, um es zu fangen, bevor ich den Rest der Bereinigung erledigte. Werfen hat meiner Meinung nach viel Gewicht auf den armen Benutzer gelegt.
quelle
Meine allgemeine Regel im Argument "Ausnahme vs. Rückkehrcode":
quelle
Ich finde Rückkehrcodes nicht weniger hässlich als Ausnahmen. Mit der Ausnahme haben Sie das
try{} catch() {} finally {}
Wo wie bei den Rückkehrcodes, die Sie habenif(){}
. Ich habe aus den in der Post angegebenen Gründen Ausnahmen befürchtet. Sie wissen nicht, ob der Zeiger gelöscht werden muss, was haben Sie. Aber ich denke, Sie haben die gleichen Probleme, wenn es um die Rückkehrcodes geht. Sie kennen den Status der Parameter nur, wenn Sie einige Details zu der betreffenden Funktion / Methode kennen.Unabhängig davon müssen Sie den Fehler nach Möglichkeit behandeln. Sie können eine Ausnahme genauso einfach auf die oberste Ebene übertragen lassen, wie einen Rückkehrcode ignorieren und das Programm segfault lassen.
Ich mag die Idee, einen Wert (Aufzählung?) Für Ergebnisse und eine Ausnahme für einen Ausnahmefall zurückzugeben.
quelle
Für eine Sprache wie Java würde ich Exception verwenden, da der Compiler einen Fehler bei der Kompilierungszeit ausgibt, wenn Ausnahmen nicht behandelt werden. Dadurch wird die aufrufende Funktion gezwungen, die Ausnahmen zu behandeln / auszulösen.
Für Python bin ich mehr in Konflikt geraten. Es gibt keinen Compiler, daher ist es möglich, dass der Aufrufer die von der Funktion ausgelöste Ausnahme, die zu Laufzeitausnahmen führt, nicht verarbeitet. Wenn Sie Rückkehrcodes verwenden, kann es zu unerwartetem Verhalten kommen, wenn diese nicht ordnungsgemäß behandelt werden. Wenn Sie Ausnahmen verwenden, können Laufzeitausnahmen auftreten.
quelle
Im Allgemeinen bevorzuge ich Rückkehrcodes, da der Anrufer entscheiden kann, ob der Fehler außergewöhnlich ist .
Dieser Ansatz ist typisch für die Elixiersprache.
Die Leute erwähnten, dass Rückkehrcodes dazu führen können, dass Sie viele verschachtelte
if
Anweisungen haben, aber dies kann mit einer besseren Syntax behandelt werden. In Elixirwith
können wir mit der Anweisung auf einfache Weise eine Reihe von Happy-Path-Rückgabewerten von Fehlern trennen.Elixir hat immer noch Funktionen, die Ausnahmen auslösen. Zurück zu meinem ersten Beispiel: Ich könnte beides tun, um eine Ausnahme auszulösen, wenn die Datei nicht geschrieben werden kann.
Wenn ich als Anrufer weiß, dass ich einen Fehler auslösen möchte, wenn der Schreibvorgang fehlschlägt, kann ich
File.write!
anstelle von anrufenFile.write
. Oder ich kann wählen, ob ichFile.write
jeden der möglichen Fehlergründe anders anrufen und behandeln möchte.Natürlich ist es immer zu
rescue
einer Ausnahme möglich, wenn wir wollen. Aber im Vergleich zum Umgang mit einem informativen Rückgabewert erscheint es mir unangenehm. Wenn ich weiß, dass ein Funktionsaufruf fehlschlagen kann oder sogar fehlschlagen sollte, ist sein Fehler kein Ausnahmefall.quelle