it-swarm-eu.dev

Ist es falsch, einen booleschen Parameter zu verwenden, um das Verhalten zu bestimmen?

Ich habe von Zeit zu Zeit eine Praxis gesehen, die sich falsch "anfühlt", aber ich kann nicht genau sagen, was daran falsch ist. Oder vielleicht ist es nur mein Vorurteil. Hier geht:

Ein Entwickler definiert eine Methode mit einem Booleschen Wert als einen seiner Parameter, und diese Methode ruft einen anderen usw. auf, und schließlich wird dieser Boolesche Wert nur verwendet, um zu bestimmen, ob eine bestimmte Aktion ausgeführt werden soll oder nicht. Dies kann beispielsweise verwendet werden, um die Aktion nur zuzulassen, wenn der Benutzer über bestimmte Rechte verfügt oder wenn wir uns im Testmodus oder Batch-Modus oder Live-Modus befinden (oder nicht) oder wenn sich das System in einem befindet bestimmter Zustand.

Nun, es gibt immer einen anderen Weg, dies zu tun, sei es durch Abfragen, wann es Zeit ist, die Aktion auszuführen (anstatt den Parameter zu übergeben) oder durch mehrere Versionen der Methode oder mehrere Implementierungen der Klasse usw. Meine Frage ist nicht Es geht nicht so sehr darum, dies zu verbessern, sondern darum, ob es wirklich falsch ist oder nicht (wie ich vermute), und wenn ja, was ist daran falsch?.

203
Ray

Ja, dies ist wahrscheinlich ein Codegeruch, der zu nicht wartbarem Code führen würde, der schwer zu verstehen ist und eine geringere Wahrscheinlichkeit hat, leicht wiederverwendet zu werden.

Wie andere Plakate angemerkt haben Kontext ist alles (gehen Sie nicht hartnäckig vor, wenn es sich um eine einmalige handelt oder wenn die Praxis als absichtlich entstandene technische Schulden anerkannt wurde, die später neu berücksichtigt werden sollen) Wenn jedoch ein Parameter an eine Funktion übergeben wird, die ein bestimmtes auszuführendes Verhalten auswählt, ist eine weitere schrittweise Verfeinerung erforderlich. Wenn Sie diese Funktion in kleinere Funktionen aufteilen, werden höher zusammenhängende Funktionen erzeugt.

Was ist also eine sehr zusammenhängende Funktion?

Es ist eine Funktion, die nur eins und eins tut.

Das Problem mit einem Parameter, der wie beschrieben übergeben wird, besteht darin, dass die Funktion mehr als zwei Dinge ausführt. Abhängig vom Status des Booleschen Parameters kann es die Zugriffsrechte der Benutzer überprüfen oder nicht. Abhängig von diesem Entscheidungsbaum wird dann eine Funktion ausgeführt.

Es wäre besser, die Anliegen der Zugangskontrolle von den Anliegen der Aufgabe, Aktion oder des Befehls zu trennen.

Wie Sie bereits bemerkt haben, scheint die Verflechtung dieser Bedenken nicht zu stimmen.

Der Begriff der Kohäsivität hilft uns also zu erkennen, dass die fragliche Funktion nicht sehr kohäsiv ist und dass wir den Code umgestalten könnten, um eine Reihe von kohäsiveren Funktionen zu erzeugen.

Die Frage könnte also neu formuliert werden. Angesichts der Tatsache, dass wir uns alle einig sind, dass das Übergeben von Verhaltensauswahlparametern am besten vermieden wird, wie können wir die Dinge verbessern?

Ich würde den Parameter vollständig loswerden. Die Möglichkeit, die Zugriffskontrolle auch zu Testzwecken auszuschalten, ist ein potenzielles Sicherheitsrisiko. Zu Testzwecken entweder stub oder mock die Zugriffsprüfung, um sowohl das Szenario mit zugelassenem als auch mit verweigertem Zugriff zu testen.

Ref: Zusammenhalt (Informatik)

112
Rob Kielty

Ich habe dieses Muster vor langer Zeit aus einem sehr einfachen Grund nicht mehr verwendet. Wartungskosten. Mehrmals stellte ich fest, dass ich eine Funktion namens frobnicate(something, forwards_flag) hatte, die in meinem Code viele Male aufgerufen wurde und alle Stellen im Code suchen musste, an denen der Wert false als Wert übergeben wurde von forwards_flag. Sie können nicht einfach nach diesen suchen, daher wird dies zu einem Wartungsproblem. Und wenn Sie an jeder dieser Sites einen Bugfix vornehmen müssen, haben Sie möglicherweise ein unglückliches Problem, wenn Sie eines verpassen.

Dieses spezielle Problem lässt sich jedoch leicht beheben, ohne den Ansatz grundlegend zu ändern:

enum FrobnicationDirection {
  FrobnicateForwards,
  FrobnicateBackwards;
};

void frobnicate(Object what, FrobnicationDirection direction);

Mit diesem Code müsste man nur nach Instanzen von FrobnicateBackwards suchen. Während es möglich ist, dass es einen Code gibt, der dies einer Variablen zuweist, so dass Sie ein paar Kontrollfäden befolgen müssen, finde ich in der Praxis, dass dies selten genug ist, dass diese Alternative in Ordnung funktioniert.

Es gibt jedoch ein weiteres Problem, wenn die Flagge zumindest im Prinzip auf diese Weise übergeben wird. Dies bedeutet, dass einige (nur einige) Systeme mit diesem Design möglicherweise zu viel Wissen über die Implementierungsdetails der tief verschachtelten Teile des Codes (der das Flag verwendet) an die äußeren Schichten weitergeben (die wissen müssen, welcher Wert übergeben werden soll) in dieser Flagge). Um die Terminologie von Larry Constantine zu verwenden, kann dieses Design eine zu starke Kopplung zwischen dem Setter und dem Benutzer des Booleschen Flags aufweisen. Franky, obwohl es schwierig ist, diese Frage mit Sicherheit zu beantworten, ohne mehr über die Codebasis zu wissen.

Um auf die konkreten Beispiele einzugehen, die Sie geben, hätte ich ein gewisses Maß an Besorgnis, aber hauptsächlich aus Gründen des Risikos/der Richtigkeit. Das heißt, wenn Ihr System Flags weitergeben muss, die angeben, in welchem ​​Zustand sich das System befindet, haben Sie möglicherweise Code, der dies hätte berücksichtigen sollen, aber den Parameter nicht überprüft (weil er nicht übergeben wurde diese Funktion). Sie haben also einen Fehler, weil jemand den Parameter nicht übergeben hat.

Es ist auch zuzugeben, dass ein Systemstatusindikator, der an fast jede Funktion übergeben werden muss, im Wesentlichen eine globale Variable ist. Viele der Nachteile einer globalen Variablen gelten. Ich denke, in vielen Fällen ist es besser, das Wissen über den Systemstatus (oder die Anmeldeinformationen des Benutzers oder die Systemidentität) in einem Objekt zusammenzufassen, das für das korrekte Handeln auf der Grundlage dieser Daten verantwortlich ist. Dann geben Sie einen Verweis auf dieses Objekt im Gegensatz zu den Rohdaten weiter. Das Schlüsselkonzept hier ist Kapselung .

153
James Youngman

Dies ist nicht unbedingt falsch, kann aber einen Code-Geruch darstellen.

Das grundlegende Szenario, das in Bezug auf boolesche Parameter vermieden werden sollte, ist:

public void foo(boolean flag) {
    doThis();
    if (flag)
        doThat();
}

Wenn Sie dann aufrufen, rufen Sie normalerweise foo(false) und foo(true) auf, je nachdem, welches Verhalten Sie genau wünschen.

Dies ist wirklich ein Problem, weil es sich um einen schlechten Zusammenhalt handelt. Sie erstellen eine Abhängigkeit zwischen Methoden, die nicht wirklich erforderlich ist.

In diesem Fall sollten Sie doThis und doThat als separate und öffentliche Methoden belassen und dann Folgendes tun:

doThis();
doThat();

oder

doThis();

Auf diese Weise überlassen Sie die richtige Entscheidung dem Aufrufer (genau so, als würden Sie einen booleschen Parameter übergeben), ohne eine Kopplung zu erstellen.

Natürlich werden nicht alle booleschen Parameter so schlecht verwendet, aber es ist definitiv ein Codegeruch und Sie werden zu Recht misstrauisch, wenn Sie das häufig im Quellcode sehen.

Dies ist nur ein Beispiel für die Lösung dieses Problems anhand der von mir geschriebenen Beispiele. Es gibt andere Fälle, in denen ein anderer Ansatz erforderlich ist.

Es gibt einen guten Artikel von Martin Fowler , der die gleiche Idee ausführlicher erklärt.

PS: Wenn die Methode foo anstelle des Aufrufs von zwei einfachen Methoden eine komplexere Implementierung hatte, müssen Sie lediglich ein kleines Refactoring anwenden Methoden extrahieren , damit der resultierende Code der Implementierung ähnelt von foo, das ich geschrieben habe.

39
Alex

Zunächst einmal: Programmieren ist weniger eine Wissenschaft als eine Kunst. Es gibt also sehr selten eine "falsche" und eine "richtige" Art zu programmieren. Die meisten Codierungsstandards sind lediglich "Präferenzen", die einige Programmierer für nützlich halten. aber letztendlich sind sie eher willkürlich. Daher würde ich niemals eine Auswahl von Parametern als an und für sich "falsch" bezeichnen - und schon gar nicht so allgemein und nützlich wie ein boolescher Parameter. Die Verwendung eines boolean (oder eines int) zur Kapselung des Zustands ist in vielen Fällen durchaus gerechtfertigt.

Codierungsentscheidungen sollten im Großen und Ganzen in erster Linie auf Leistung und Wartbarkeit basieren. Wenn die Leistung nicht auf dem Spiel steht (und ich kann mir nicht vorstellen, wie es jemals in Ihren Beispielen sein könnte), sollte Ihre nächste Überlegung lauten: Wie einfach wird es für mich (oder einen zukünftigen Redakteur) sein, dies aufrechtzuerhalten? Ist es intuitiv und verständlich? Ist es isoliert? Ihr Beispiel für verkettete Funktionsaufrufe scheint in dieser Hinsicht tatsächlich möglicherweise spröde zu sein: Wenn Sie sich entscheiden, Ihr bIsUp in bIsDown zu ändern, wie viele andere Stellen im Code müssen ebenfalls geändert werden? Steigt auch Ihre Parameterliste im Ballon? Wenn Ihre Funktion 17 Parameter hat, ist die Lesbarkeit ein Problem, und Sie müssen erneut prüfen, ob Sie die Vorteile einer objektorientierten Architektur schätzen.

30
kmote

Ich denke, Robert C Martins Clean Code Article besagt, dass Sie boolesche Argumente nach Möglichkeit entfernen sollten, da sie zeigen, dass eine Methode mehr als eine Sache tut. Eine Methode sollte eine Sache tun und eine Sache, die nur ich denke, ist eines seiner Mottos.

16
dreza

Ich denke, das Wichtigste hier ist, praktisch zu sein.

Wenn der Boolesche Wert das gesamte Verhalten bestimmt, erstellen Sie einfach eine zweite Methode.

Wenn der Boolesche Wert nur ein kleines Verhalten in der Mitte festlegt, möchten Sie ihn möglicherweise in einem behalten, um die Codeduplizierung zu verringern. Wenn möglich, können Sie die Methode möglicherweise sogar in drei Teile aufteilen: Zwei aufrufende Methoden für eine der beiden booleschen Optionen und eine, die den Großteil der Arbeit erledigt.

Zum Beispiel:

private void FooInternal(bool flag)
{
  //do work
}

public void Foo1()
{
  FooInternal(true);
}

public void Foo2()
{
  FooInternal(false);
}

In der Praxis haben Sie natürlich immer einen Punkt zwischen diesen Extremen. Normalerweise gehe ich einfach mit dem um, was sich richtig anfühlt, aber ich ziehe es vor, auf der Seite weniger Codeduplizierung zu irren.

8
Jorn

Ich mag den Ansatz, das Verhalten durch Builder-Methoden anzupassen, die unveränderliche Instanzen zurückgeben. So verwendet Guava Splitter es:

private static final Splitter MY_SPLITTER = Splitter.on(',')
       .trimResults()
       .omitEmptyStrings();

MY_SPLITTER.split("one,,,,  two,three");

Die Vorteile davon sind:

  • Überlegene Lesbarkeit
  • Klare Trennung von Konfigurations- und Aktionsmethoden
  • Fördert den Zusammenhalt, indem Sie gezwungen werden, darüber nachzudenken, was das Objekt ist, was es tun soll und was nicht. In diesem Fall ist es ein Splitter. Sie würden someVaguelyStringRelatedOperation(List<Entity> myEntities) niemals in eine Klasse namens Splitter einfügen, aber Sie würden darüber nachdenken, es als statische Methode in eine StringUtils -Klasse einzufügen.
  • Die Instanzen sind vorkonfiguriert und können daher leicht in Abhängigkeit injiziert werden. Der Client muss sich keine Gedanken darüber machen, ob er true oder false an eine Methode übergeben soll, um das richtige Verhalten zu erhalten.
7
oksayt

Auf jeden Fall ein Code-Geruch . Wenn es nicht gegen Prinzip der Einzelverantwortung verstößt, dann verstößt es wahrscheinlich gegen Tell, Don't Ask . Erwägen:

Wenn sich herausstellt, dass eines dieser beiden Prinzipien nicht verletzt wird, sollten Sie dennoch eine Aufzählung verwenden. Boolesche Flags sind das boolesche Äquivalent von magische Zahlen . foo(false) macht genauso viel Sinn wie bar(42). Aufzählungen können nützlich sein für Strategiemuster und die Flexibilität haben, dass Sie eine weitere Strategie hinzufügen können. (Denken Sie daran, benennen Sie sie entsprechend .)

Ihr besonderes Beispiel stört mich besonders. Warum wird dieses Flag so vielen Methoden unterzogen? Das klingt so, als müssten Sie Ihren Parameter in Unterklassen aufteilen .

5
Eva

TL; DR: Verwenden Sie keine booleschen Argumente.

Siehe unten warum sie sind schlecht und wie man sie ersetzt (fett gedruckt).


Boolesche Argumente sind sehr schwer zu lesen und daher schwer zu pflegen. Das Hauptproblem besteht darin, dass der Zweck im Allgemeinen klar ist, wenn Sie die Methodensignatur lesen, in der das Argument benannt ist. Die Benennung eines Parameters ist in den meisten Sprachen jedoch im Allgemeinen nicht erforderlich. Sie haben also Anti-Patterns wie RSACryptoServiceProvider#encrypt(Byte[], Boolean) , wobei der boolesche Parameter bestimmt, welche Art von Verschlüsselung in der Funktion verwendet werden soll.

Sie erhalten also einen Anruf wie:

rsaProvider.encrypt(data, true);

wo der Leser die Signatur der Methode nachschlagen muss, um festzustellen, was zum Teufel true tatsächlich bedeuten könnte. Eine ganze Zahl zu übergeben ist natürlich genauso schlecht:

rsaProvider.encrypt(data, 1);

würde dir genauso viel sagen - oder vielmehr: genauso wenig. Selbst wenn Sie Konstanten definieren, die für die Ganzzahl verwendet werden sollen, können Benutzer der Funktion diese einfach ignorieren und weiterhin Literalwerte verwenden.

Der beste Weg, dies zu lösen, ist die Verwendung einer Aufzählung . Wenn Sie eine Aufzählung RSAPadding mit zwei Werten übergeben müssen: OAEP oder PKCS1_V1_5, Können Sie den Code sofort lesen:

rsaProvider.encrypt(data, RSAPadding.OAEP);

Boolesche Werte können nur zwei Werte haben. Dies bedeutet, dass Sie Ihre Signatur neu gestalten müssen, wenn Sie eine dritte Option haben. Im Allgemeinen kann dies nicht einfach durchgeführt werden, wenn die Abwärtskompatibilität ein Problem darstellt. Daher müssten Sie jede öffentliche Klasse mit einer anderen öffentlichen Methode erweitern. Dies ist, was Microsoft schließlich tat, als sie RSACryptoServiceProvider#encrypt(Byte[], RSAEncryptionPadding) einführten, wo sie eine Aufzählung (oder zumindest eine Klasse, die eine Aufzählung nachahmt) anstelle eines Booleschen Werts verwendeten.

Es kann sogar einfacher sein, ein vollständiges Objekt oder eine Schnittstelle als Parameter zu verwenden, falls der Parameter selbst parametrisiert werden muss. Im obigen Beispiel könnte das OAEP-Padding selbst mit dem intern zu verwendenden Hash-Wert parametrisiert werden. Beachten Sie, dass es jetzt 6 SHA-2-Hash-Algorithmen und 4 SHA-3-Hash-Algorithmen gibt. Daher kann die Anzahl der Aufzählungswerte explodieren, wenn Sie nur eine einzige Aufzählung anstelle von Parametern verwenden (dies ist möglicherweise das nächste, was Microsoft herausfinden wird ).


Boolesche Parameter können auch darauf hinweisen, dass die Methode oder Klasse nicht gut entworfen wurde. Wie im obigen Beispiel: Jede andere kryptografische Bibliothek als die .NET-Bibliothek verwendet überhaupt kein Auffüllflag in der Methodensignatur.


Fast alle Software-Gurus, die ich mag, warnen vor booleschen Argumenten. Zum Beispiel warnt Joshua Bloch in dem hochgeschätzten Buch "Effective Java" vor ihnen. Im Allgemeinen sollten sie einfach nicht verwendet werden. Sie könnten argumentieren, dass sie verwendet werden könnten, wenn es einen Parameter gibt, der leicht zu verstehen ist. Aber selbst dann: Bit.set(boolean) wird wahrscheinlich besser mit zwei Methoden implementiert: Bit.set() und Bit.unset().


Wenn Sie den Code nicht direkt umgestalten können, können Sie Konstanten definieren, um sie zumindest lesbarer zu machen:

const boolean ENCRYPT = true;
const boolean DECRYPT = false;

...

cipher.init(key, ENCRYPT);

ist viel besser lesbar als:

cipher.init(key, true);

auch wenn Sie lieber hätten:

cipher.initForEncryption(key);
cipher.initForDecryption(key);

stattdessen.

4
Maarten Bodewes

Ich bin überrascht, dass niemand Named-Parameters erwähnt hat.

Das Problem, das ich bei Booleschen Flags sehe, ist, dass sie die Lesbarkeit beeinträchtigen. Zum Beispiel, was bedeutet true in

myObject.UpdateTimestamps(true);

tun? Ich habe keine Ahnung. Aber was ist mit:

myObject.UpdateTimestamps(saveChanges: true);

Jetzt ist klar, was der Parameter, den wir übergeben, tun soll: Wir weisen die Funktion an, ihre Änderungen zu speichern. In diesem Fall, wenn die Klasse nicht öffentlich ist, denke ich, dass der boolesche Parameter in Ordnung ist.


Natürlich können Sie die Benutzer Ihrer Klasse nicht zwingen, benannte Parameter zu verwenden. Aus diesem Grund sind normalerweise entweder eine enum oder zwei separate Methoden vorzuziehen, je nachdem, wie häufig Ihr Standardfall ist. Genau das macht .Net:

//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);

//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key); 

Ich kann nicht genau sagen, was daran falsch ist.

Wenn es wie ein Codegeruch aussieht, sich wie ein Codegeruch anfühlt und - nun ja - wie ein Codegeruch riecht, ist es wahrscheinlich ein Codegeruch.

Was Sie tun möchten, ist:

1) Vermeiden Sie Methoden mit Nebenwirkungen.

2) Behandeln Sie notwendige Zustände mit einer zentralen, formalen Zustandsmaschine (wie this ).

1
BaBu

Ich bin mit allen Bedenken einverstanden, Boolesche Parameter zu verwenden, um die Leistung nicht zu bestimmen, um; Verbesserung, Lesbarkeit, Zuverlässigkeit, Verringerung der Komplexität, Verringerung der Risiken durch schlechte Verkapselung und Kohäsion sowie Senkung der Gesamtbetriebskosten durch Wartbarkeit.

Ich habe Mitte der 70er Jahre angefangen, Hardware zu entwerfen, die wir jetzt SCADA (Überwachungssteuerung und Datenerfassung) nennen. Dies war eine fein abgestimmte Hardware mit Maschinencode im EPROM, auf der Makrofernbedienungen ausgeführt wurden und Hochgeschwindigkeitsdaten erfasst wurden.

Die Logik heißt Mealey & Moore Maschinen, die wir jetzt nennen Finite State Machines . Diese müssen ebenfalls nach den gleichen Regeln wie oben ausgeführt werden, es sei denn, es handelt sich um eine Echtzeitmaschine mit einer begrenzten Ausführungszeit, und dann müssen Verknüpfungen ausgeführt werden, um den Zweck zu erfüllen.

Die Daten sind synchron, aber die Befehle sind asynchron und die Befehlslogik folgt der speicherlosen Booleschen Logik, jedoch mit sequentiellen Befehlen, die auf dem Speicher des vorherigen, gegenwärtigen und gewünschten nächsten Zustands basieren. Damit dies in der effizientesten Maschinensprache (nur 64 KB) funktioniert, wurde große Sorgfalt darauf verwendet, jeden Prozess in einer heuristischen IBM HIPO-Weise zu definieren. Das bedeutete manchmal, boolesche Variablen zu übergeben und indizierte Verzweigungen durchzuführen.

Aber jetzt, mit viel Speicher und einfacher OOK, ist die Kapselung heute ein wesentlicher Bestandteil, aber eine Strafe, wenn der Code für Echtzeit und SCADA-Maschinencode in Bytes gezählt wurde.

Es ist nicht unbedingt falsch, aber in Ihrem konkreten Beispiel für die Aktion, die von einem Attribut des "Benutzers" abhängt, würde ich eher einen Verweis auf den Benutzer als ein Flag durchlaufen.

Dies verdeutlicht und hilft auf verschiedene Weise.

Jeder, der die aufrufende Anweisung liest, wird feststellen, dass sich das Ergebnis je nach Benutzer ändert.

In der Funktion, die letztendlich aufgerufen wird, können Sie einfach komplexere Geschäftsregeln implementieren, da Sie auf alle Benutzerattribute zugreifen können.

Wenn eine Funktion/Methode in der "Kette" abhängig von einem Benutzerattribut etwas anderes ausführt, ist es sehr wahrscheinlich, dass eine ähnliche Abhängigkeit von Benutzerattributen bei einigen anderen Methoden in der "Kette" eingeführt wird.

0
James Anderson

Meistens würde ich diese schlechte Codierung in Betracht ziehen. Ich kann mir jedoch zwei Fälle vorstellen, in denen dies eine gute Praxis sein könnte. Da es bereits viele Antworten gibt, die sagen, warum es schlecht ist, biete ich zwei Mal an, wenn es gut sein könnte:

Der erste ist, wenn jeder Aufruf in der Sequenz für sich genommen Sinn macht. Es wäre sinnvoll, wenn der aufrufende Code möglicherweise von wahr in falsch oder von falsch in wahr geändert würde, oder wenn die aufgerufene Methode möglicherweise in geändert würde Verwenden Sie den booleschen Parameter direkt, anstatt ihn weiterzugeben. Die Wahrscheinlichkeit von zehn solchen Aufrufen hintereinander ist gering, aber es könnte passieren, und wenn dies der Fall wäre, wäre dies eine gute Programmierpraxis.

Der zweite Fall ist etwas schwierig, da es sich um einen Booleschen handelt. Wenn das Programm jedoch mehrere Threads oder Ereignisse hat, ist die Übergabe von Parametern die einfachste Möglichkeit, thread-/ereignisspezifische Daten zu verfolgen. Beispielsweise kann ein Programm Eingaben von zwei oder mehr Sockets erhalten. Code, der für einen Socket ausgeführt wird, muss möglicherweise Warnmeldungen erzeugen, ein Code, der für einen anderen ausgeführt wird, möglicherweise nicht. Es ist dann (irgendwie) sinnvoll, dass ein Boolescher Satz auf einer sehr hohen Ebene viele Methodenaufrufe an die Stellen weiterleitet, an denen möglicherweise Warnmeldungen generiert werden. Die Daten können nicht (außer mit großen Schwierigkeiten) in irgendeiner Art von global gespeichert werden, da mehrere Threads oder verschachtelte Ereignisse jeweils ihren eigenen Wert benötigen würden.

Natürlich würde ich im letzteren Fall wahrscheinlich eine Klasse/Struktur erstellen, deren einziger Inhalt ein Boolescher Wert war, und stattdessen that around übergeben. Ich würde fast sicher sein, dass ich bald andere Felder brauche - wie zum Beispiel wo, um die Warnmeldungen zu senden.

0
RalphChapin