Ist es eine schlechte Praxis, break in einer for-Schleife zu verwenden? [geschlossen]


123

Ist es eine schlechte Praxis, breakAnweisungen in einer forSchleife zu verwenden ?

Angenommen, ich suche nach einem Wert in einem Array. Vergleichen Sie innerhalb einer for-Schleife und wenn ein Wert gefunden wird, break;um die for-Schleife zu verlassen.

Ist das eine schlechte Praxis? Ich habe die verwendete Alternative gesehen: Definieren Sie eine Variable vFoundund setzen Sie sie auf true, wenn der Wert gefunden wird, und überprüfen Sie vFounddie forAnweisungsbedingung. Aber ist es notwendig, nur zu diesem Zweck eine neue Variable zu erstellen?

Ich frage im Kontext einer normalen C oder C ++ for Schleife.

PS: Die MISRA-Codierungsrichtlinien raten von der Verwendung von break ab.


28
Platziere dich nicht breakin der gleichen Liga wie goto:)
BoltClock

2
Ähm, ich habe sie nicht in die gleiche Liga wie zu ... MISRA-Regeln legen Pause fest, wenn ich mich richtig erinnere.
Kiki

1
Der einzige Grund, warum ich mir vorstellen kann, break nicht innerhalb einer Schleife zu verwenden, ist, wenn Sie noch mehr Elemente verarbeiten müssen, die das Ergebnis Ihrer Arbeit ändern könnten ...
Younes

4
Die MISRA-Regeln wurden gelockert: misra.org.uk/forum/viewtopic.php?f=75&t=298
Johan Kotlinski

Antworten:


122

Viele Antworten hier, aber ich habe dies noch nicht erwähnt gesehen:

Die meisten "Gefahren", die mit der Verwendung breakoder continuein einer for-Schleife verbunden sind, werden negiert, wenn Sie ordentliche, leicht lesbare Schleifen schreiben. Wenn der Hauptteil Ihrer Schleife mehrere Bildschirmlängen umfasst und mehrere verschachtelte Unterblöcke enthält, können Sie leicht vergessen, dass nach der Unterbrechung kein Code ausgeführt wird. Wenn die Schleife jedoch kurz und präzise ist, sollte der Zweck der break-Anweisung offensichtlich sein.

Wenn eine Schleife zu groß wird, verwenden Sie stattdessen einen oder mehrere gut benannte Funktionsaufrufe innerhalb der Schleife. Der einzige wirkliche Grund, dies zu vermeiden, ist die Verarbeitung von Engpässen.


11
Ziemlich wahr. Wenn die Schleife so groß und komplex ist, dass es schwierig ist zu sehen, was in ihr passiert, ist das natürlich ein Problem, ob Sie eine Pause haben oder nicht.
Jay

1
Ein weiteres Problem ist, dass das Brechen und Fortfahren Probleme beim Refactoring verursacht. Dies könnte ein Zeichen dafür sein, dass dies eine schlechte Praxis ist. Die Absicht des Codes ist auch klarer, wenn if-Anweisungen verwendet werden.
Ed Greaves

Diese "gut benannten Funktionsaufrufe" können lokal definierte Lambda-Funktionen anstelle von extern definierten privaten Funktionen sein, die an keiner anderen Stelle verwendet werden.
DavidRR

135

Nein, Pause ist die richtige Lösung.

Das Hinzufügen einer booleschen Variablen erschwert das Lesen des Codes und fügt eine potenzielle Fehlerquelle hinzu.


2
einverstanden. Vor allem, wenn Sie die Schleife unter mehr als einer Bedingung verlassen möchten. Der Boolesche Wert, eine Schleife zu verlassen, kann dann sehr verwirrend werden.
Rontologe

2
Deshalb gotobreche ich aus verschachtelten Schleifen der Stufe 2+ aus - weil es keine gibtbreak(level)
25етър Петров

3
Ich würde lieber den Schleifencode in eine Funktion einfügen und einfach zurückkehren. Dies vermeidet das Goto und zerlegt Ihren Code in kleinere Teile.
Dave Branton

53

Sie können alle Arten von professionellem Code mit 'break'-Anweisungen finden. Es ist durchaus sinnvoll, dies bei Bedarf zu verwenden. In Ihrem Fall ist diese Option besser, als eine separate Variable zu erstellen, nur um aus der Schleife herauszukommen.


47

Die Verwendung breaksowie continuein einer forSchleife ist vollkommen in Ordnung.

Es vereinfacht den Code und verbessert seine Lesbarkeit.


Ja. Eine Zeit lang mochte ich die Idee nicht und codierte sie, aber es dauerte nicht lange, bis mir klar wurde, dass die "Problemumgehung" oft ein Alptraum für die Wartung war. Nun, kein Albtraum, aber fehleranfälliger.
MetalMikester

24

Python (und andere Sprachen?) Erweitert die forSchleifenstruktur , sodass ein Teil davon nur ausgeführt wird, wenn die Schleife dies nicht tut break .

for n in range(5):
    for m in range(3):
        if m >= n:
            print('stop!')
            break
        print(m, end=' ')
    else:
        print('finished.')

Ausgabe:

stop!
0 stop!
0 1 stop!
0 1 2 finished.
0 1 2 finished.

Äquivalenter Code ohne breakund so praktisch else:

for n in range(5):
    aborted = False
    for m in range(3):
        if not aborted:
            if m >= n:
                print('stop!')
                aborted = True
            else:            
                print(m, end=' ')
    if not aborted:
        print('finished.')

2
Oooh, das gefällt mir und ich habe es mir manchmal auch in C gewünscht. Schöne Syntax, die ohne Mehrdeutigkeit in eine zukünftige C-ähnliche Sprache umgewandelt werden könnte.
Supercat

"C-ähnliche Sprache": P
nirvanaswap

15

Es ist an sich nichts Falsches daran, eine break-Anweisung zu verwenden, aber verschachtelte Schleifen können verwirrend werden. Um die Lesbarkeit zu verbessern, unterstützen viele Sprachen ( zumindest Java ) das Aufbrechen in Labels, wodurch die Lesbarkeit erheblich verbessert wird.

int[] iArray = new int[]{0,1,2,3,4,5,6,7,8,9};
int[] jArray = new int[]{0,1,2,3,4,5,6,7,8,9};

// label for i loop
iLoop: for (int i = 0; i < iArray.length; i++) {

    // label for j loop
    jLoop: for (int j = 0; j < jArray.length; j++) {

        if(iArray[i] < jArray[j]){
            // break i and j loops
            break iLoop;
        } else if (iArray[i] > jArray[j]){  
            // breaks only j loop
            break jLoop;
        } else {
            // unclear which loop is ending
            // (breaks only the j loop)
            break;
        }
    }
}

Ich werde sagen, dass break (und return) -Anweisungen häufig die zyklomatische Komplexität erhöhen , was es schwieriger macht, zu beweisen, dass Code in allen Fällen das Richtige tut.

Wenn Sie eine Pause beim Durchlaufen einer Sequenz für ein bestimmtes Element in Betracht ziehen, sollten Sie die Datenstruktur, in der Ihre Daten gespeichert sind, überdenken. Die Verwendung eines Sets oder einer Karte kann zu besseren Ergebnissen führen.


4
+1 für zyklomatische Komplexität.
sp00m

.NET-Programmierer möchten möglicherweise die Verwendung von LINQ als mögliche Alternative zu komplexen forSchleifen untersuchen.
DavidRR

14

break ist eine völlig akzeptable Aussage (also weiter , übrigens). Es geht nur um die Lesbarkeit von Code - solange Sie keine überkomplizierten Schleifen und dergleichen haben, ist das in Ordnung.

Es ist nicht so, als wären sie die gleiche Liga wie goto . :) :)


Ich habe argumentiert , gesehen , dass eine break - Anweisung ist effektiv ein goto .
Glenatron

3
Das Problem mit goto ist, dass Sie es überall hin zeigen können. Beide brechen und behalten die Modularität des Codes bei. Dieses Argument befindet sich auf derselben Ebene wie ein einzelner Austrittspunkt für jede Funktion.
Zachary Yates

1
Ich habe gehört , argumentiert , dass für eine Funktion mehrerer Austrittspunkte ist effektiv eine goto . ; D
Glenatron

2
@glenatron Das Problem mit goto ist nicht die goto-Anweisung, sondern die Einführung des Labels, zu dem goto springt, ist das Problem. Wenn Sie eine goto-Anweisung lesen, besteht keine Unsicherheit über den Kontrollfluss. Wenn Sie ein Etikett lesen, besteht eine große Unsicherheit hinsichtlich des Kontrollflusses (wo sind alle Punkte, die die Kontrolle auf dieses Etikett übertragen?). Eine break-Anweisung führt kein Label ein, sodass keine neuen Komplikationen auftreten.
Stephen C. Steel

1
Die "Pause" ist ein spezielles Goto, das nur Blöcke verlassen kann. Die historischen Probleme mit "goto" waren (1) es konnte und wurde oft verwendet, um überlappende Schleifenblöcke auf eine Weise zu konstruieren, die mit geeigneten Kontrollstrukturen nicht möglich ist; (2) Ein üblicher Weg, ein "Wenn-Dann" -Konstrukt zu erstellen, das normalerweise "falsch" war, bestand darin, den wahren Fall an eine nicht verwandte Stelle im Code zu verzweigen und dann zurück zu verzweigen. Dies würde im seltenen Fall zwei Zweige und im allgemeinen Fall null kosten, aber der Code sah abscheulich aus.
Supercat

14

Allgemeine Regel: Wenn Sie nach dem Befolgen einer Regel etwas Unangenehmeres und Schwierigeres tun müssen, als die Regel zu brechen, dann brechen Sie die Regel.

Im Falle einer Schleife, bis Sie etwas finden, stoßen Sie beim Aussteigen auf das Problem, gefunden und nicht gefunden zu unterscheiden. Das ist:

for (int x=0;x<fooCount;++x)
{
  Foo foo=getFooSomehow(x);
  if (foo.bar==42)
    break;
}
// So when we get here, did we find one, or did we fall out the bottom?

Okay, Sie können ein Flag setzen oder einen "gefundenen" Wert auf null initialisieren. Aber

Deshalb ziehe ich es im Allgemeinen vor, meine Suche in Funktionen zu verschieben:

Foo findFoo(int wantBar)
{
  for (int x=0;x<fooCount;++x)
  {
    Foo foo=getFooSomehow(x);
    if (foo.bar==wantBar)
      return foo;
  }
  // Not found
  return null;
}

Dies hilft auch, den Code übersichtlicher zu gestalten. In der Hauptzeile wird "find" zu einer einzelnen Anweisung, und wenn die Bedingungen komplex sind, werden sie nur einmal geschrieben.


1
Genau das, was ich sagen wollte. Wenn ich mich selbst benutze break, versuche ich oft , einen Weg zu finden, den Code in eine Funktion umzuwandeln, damit ich ihn returnstattdessen verwenden kann.
Rene Saarsoo

Ich stimme Ihnen zu 100% zu, es ist an sich nichts Falsches daran, break zu verwenden. Im Allgemeinen bedeutet dies jedoch, dass der Code, in dem er sich befindet, möglicherweise in eine Funktion extrahiert werden muss, bei der break durch return ersetzt wird. Es sollte eher als "Code-Geruch" als als völliger Fehler betrachtet werden.
vascowhite

Ihre Antwort könnte einige dazu veranlassen, die Zweckmäßigkeit der Verwendung mehrerer return-Anweisungen zu untersuchen .
DavidRR

13

Das hängt von der Sprache ab. Während Sie möglicherweise eine boolesche Variable hier überprüfen können:

for (int i = 0; i < 100 && stayInLoop; i++) { ... }

Es ist nicht möglich, dies zu tun, wenn Sie über ein Array jucken:

for element in bigList: ...

Auf jeden Fall breakwürden beide Codes besser lesbar machen.


7

In Ihrem Beispiel kennen Sie die Anzahl der Iterationen für die for- Schleife nicht. Warum nicht stattdessen die while- Schleife verwenden, bei der die Anzahl der Iterationen zu Beginn unbestimmt sein kann?

Es ist daher nicht erforderlich, die Unterbrechungsanweisung im Allgemeinen zu verwenden, da die Schleife besser als while- Schleife angegeben werden kann.


1
Eine "for" -Schleife ist oft gut, wenn eine maximale Anzahl von Iterationen bekannt ist. Allerdings ist eine while (1) -Schleife manchmal besser in einem Szenario, in dem nach einem Element gesucht wird und geplant wird, es zu erstellen, wenn es nicht vorhanden ist. Wenn der Code das Element findet, führt er in diesem Fall eine direkte Unterbrechung durch. Wenn er das Ende des Arrays erreicht, erstellt er ein neues Element und führt dann eine Unterbrechung durch.
Supercat

2
In dem angegebenen Beispiel - Durchsuchen eines Arrays nach einem Schlüssel ist die for-Schleife das richtige idiomatische Konstrukt. Sie verwenden keine while-Schleife, um durch ein Array zu gehen. Sie verwenden eine for-Schleife. (oder eine für jede Schleife, falls verfügbar). Sie tun dies aus Höflichkeit gegenüber anderen Programmierern, da sie das Konstrukt "for (int i = 0; i <ra.length; i ++) {}" sofort als "Walk through the array" erkennen. Diesem Konstrukt wird eine Unterbrechung hinzugefügt einfach eine "Früh aufhören, wenn du kannst" -Anweisung.
Chris Cudmore

7

Ich stimme anderen zu, die die Verwendung empfehlen break. Die offensichtliche Folgefrage ist, warum jemand etwas anderes empfehlen würde. Nun ... wenn Sie break verwenden, überspringen Sie den Rest des Codes im Block und die verbleibenden Iterationen. Manchmal verursacht dies Fehler, zum Beispiel:

  • Eine am oberen Rand des Blocks erfasste Ressource kann am unteren Rand freigegeben werden (dies gilt auch für Blöcke innerhalb von forSchleifen). Dieser Freigabeschritt kann jedoch versehentlich übersprungen werden, wenn ein "vorzeitiger" Exit durch eine breakAnweisung verursacht wird (in "modern"). In C ++ wird "RAII" verwendet, um dies zuverlässig und ausnahmesicher zu handhaben: Grundsätzlich geben Objektzerstörer Ressourcen zuverlässig frei, unabhängig davon, wie ein Bereich beendet wird.

  • Jemand kann den bedingten Test in der forAnweisung ändern, ohne zu bemerken, dass andere delokalisierte Austrittsbedingungen vorliegen

  • In der Antwort von ndim wird festgestellt, dass einige Benutzer möglicherweise vermeiden break, eine relativ konsistente Schleifenlaufzeit aufrechtzuerhalten, aber Sie haben mit der breakVerwendung einer booleschen Steuervariablen für den frühen Ausgang verglichen , bei der dies nicht zutrifft

Hin und wieder erkennen Leute, die solche Fehler beobachten, dass sie durch diese "No Breaks" -Regel verhindert / gemildert werden können ... in der Tat gibt es eine ganze verwandte Strategie für "sicherere" Programmierung, die "strukturierte Programmierung" genannt wird, bei der jede Funktion haben soll ein einziger Ein- und Ausstiegspunkt (dh kein Goto, keine vorzeitige Rückkehr). Es kann einige Fehler beseitigen, aber es führt zweifellos andere ein. Warum machen sie das?

  • Sie haben einen Entwicklungsrahmen, der einen bestimmten Programmier- / Codestil fördert, und sie haben statistische Belege dafür, dass dies in diesem begrenzten Rahmen einen Nettovorteil bringt, oder
  • Sie wurden durch Programmierrichtlinien oder Erfahrungen in einem solchen Rahmen beeinflusst, oder
  • sie sind nur diktatorische Idioten oder
  • eine der oben genannten + historischen Trägheiten (relevant, da die Begründungen eher für C gelten als für modernes C ++).

Nun ja, aber andererseits habe ich Codefehler von Leuten gesehen, die religiös versucht haben, sie zu vermeiden break. Sie haben es vermieden, aber nicht über die Bedingungen nachgedacht, die die Verwendung von Pause ersetzten.
Tomáš Zato - Wiedereinsetzung Monica

5

Es ist absolut gültig zu verwenden break- wie andere betont haben, ist es nirgends in der gleichen Liga wie goto.

Obwohl Sie die vFoundVariable möglicherweise verwenden möchten, wenn Sie außerhalb der Schleife prüfen möchten, ob der Wert im Array gefunden wurde. Auch unter dem Gesichtspunkt der Wartbarkeit kann es nützlich sein, ein gemeinsames Flag zu haben, das die Austrittskriterien signalisiert.


5

Ich habe einige Analysen der Codebasis durchgeführt, an der ich gerade arbeite (40.000 Zeilen JavaScript).

Ich habe nur 22 breakAussagen gefunden, von denen:

  • 19 wurden in switchAnweisungen verwendet (wir haben insgesamt nur 3 switch-Anweisungen!).
  • 2 wurden in forSchleifen verwendet - ein Code, den ich sofort als in separate Funktionen umgestaltet und durch returnAnweisung ersetzt klassifizierte .
  • Was die letzte breakInsider- whileSchleife betrifft ... Ich bin gelaufen, um git blamezu sehen, wer diesen Mist geschrieben hat!

Also laut meiner Statistik: Wenn breakes außerhalb von verwendet wird switch, ist es ein Code-Geruch.

Ich habe auch nach continueAussagen gesucht . Keine gefunden.


4

Ich sehe keinen Grund, warum es eine schlechte Praxis wäre, vorausgesetzt, Sie möchten die STOP-Verarbeitung an diesem Punkt abschließen.


4

In der eingebetteten Welt gibt es viel Code, der das folgende Konstrukt verwendet:

    while(1)
    { 
         if (RCIF)
           gx();
         if (command_received == command_we_are_waiting_on)
           break;
         else if ((num_attempts > MAX_ATTEMPTS) || (TickGet() - BaseTick > MAX_TIMEOUT))
           return ERROR;
         num_attempts++;
    }
    if (call_some_bool_returning_function())
      return TRUE;
    else
      return FALSE;

Dies ist ein sehr allgemeines Beispiel, viele Dinge passieren hinter dem Vorhang, insbesondere Interrupts. Verwenden Sie dies nicht als Boilerplate-Code, ich versuche nur, ein Beispiel zu veranschaulichen.

Meine persönliche Meinung ist, dass es nichts Falsches ist, eine Schleife auf diese Weise zu schreiben, solange angemessen darauf geachtet wird, dass sie nicht auf unbestimmte Zeit in der Schleife bleibt.


1
Könnten Sie das näher erläutern? Es sieht für mich so aus, als ob die Schleife absolut nichts tut ... es sei denn, continuedie Anwendungslogik enthält Anweisungen. Gibt es?
Rene Saarsoo

1
continue-Anweisungen sind nicht erforderlich, da Sie bereits an eine Endlosschleife gebunden sind. Grundsätzlich führen Sie die zum Erreichen eines Ziels erforderliche Logik aus. Wenn dieses Ziel bei n Versuchen nicht erreicht wird oder eine Zeitüberschreitungsbedingung abläuft, verlassen Sie die Schleife über ein Unterbrechungskonstrukt und ergreifen Korrekturmaßnahmen oder informieren den aufrufenden Code über einen Fehler. Ich sehe diese Art von Code häufig in uCs, die über einen gemeinsamen Bus (RS-485 usw.) kommunizieren. Sie versuchen grundsätzlich, die Leitung zu nutzen und ohne Kollisionen zu kommunizieren. Wenn Kollisionen auftreten, warten Sie eine durch eine Zufallszahl bestimmte Dauer und versuchen es nochmal.
Nate

1
Umgekehrt, wenn die Dinge gut laufen, verwenden Sie auch die break-Anweisung, um zu beenden, sobald die Bedingung erfüllt ist. In diesem Fall wird der Code nach der Schleife ausgeführt und alle sind zufrieden.
Nate

"if (call_some_bool_returning_function ()) gibt TRUE zurück; sonst return FALSE;" könnte einfach "return call_some_bool_returning_function ()" sein
frankster

3

Hängt von Ihrem Anwendungsfall ab. Es gibt Anwendungen, bei denen die Laufzeit einer for-Schleife konstant sein muss (z. B. um einige zeitliche Einschränkungen zu erfüllen oder um Ihre Dateninternalen vor zeitbasierten Angriffen zu verbergen).

In diesen Fällen ist es sogar sinnvoll, ein Flag zu setzen und den Flag-Wert erst zu überprüfen, nachdem alle forSchleifeniterationen tatsächlich ausgeführt wurden. Natürlich müssen alle for-Schleifeniterationen Code ausführen, der immer noch ungefähr dieselbe Zeit benötigt.

Wenn Sie sich nicht für die Laufzeit interessieren ... verwenden Sie break;und continue;, um den Code leichter lesbar zu machen.


1
Wenn das Timing wichtig ist, ist es möglicherweise besser, etwas Schlaf zu machen und Systemressourcen zu sparen, als das Programm als nutzlos bekannte Operationen ausführen zu lassen.
Bgs

2

Nach den MISRA 98-Regeln, die in meinem Unternehmen in C dev verwendet werden, darf die break-Anweisung nicht verwendet werden ...

Bearbeiten: Pause ist in MISRA '04 erlaubt


2
Genau deshalb habe ich diese Frage gestellt! Ich finde keinen guten Grund, warum eine solche Regel als Kodierungsrichtlinie existiert. Auch, wie alle anderen hier sagten, brechen Sie; sieht besser aus.
Kiki

1
Ich weiß nicht genau, warum es verboten ist (klügere Person als ich darüber nachgedacht habe ...), aber Pause (und mehrfache Rückkehr) sind besser für die Lesbarkeit (meiner Meinung nach, aber meine Meinung ist keine Unternehmensregel ... )
Benoît

1
Äh, die MISRA-Regeln erlauben die Verwendung von break-Anweisungen: misra.org.uk/forum/viewtopic.php?f=75&t=298
luis.espinal

Wir haben MISRA 98 Regeln verwendet ... Es ist genau, dass MISRA 2004 die Regeln ändert
Benoît

0

Natürlich break;ist dies die Lösung, um die for-Schleife oder die foreach-Schleife zu stoppen. Ich habe es in PHP in foreach und for loop verwendet und festgestellt, dass es funktioniert.


0

Ich bin nicht einverstanden!

Warum sollten Sie die integrierte Funktionalität einer for-Schleife ignorieren, um Ihre eigene zu erstellen? Sie müssen das Rad nicht neu erfinden.

Ich denke, es ist sinnvoller, Ihre Schecks so oben in Ihrer for-Schleife zu haben

for(int i = 0; i < myCollection.Length && myCollection[i].SomeValue != "Break Condition"; i++)
{
//loop body
}

oder wenn Sie die Zeile zuerst verarbeiten müssen

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
//loop body
}

Auf diese Weise können Sie eine Funktion schreiben, um alles auszuführen und viel saubereren Code zu erstellen.

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

Oder wenn Ihr Zustand kompliziert ist, können Sie diesen Code auch verschieben!

for(int i = 0; i < myCollection.Length && BreakFunctionCheck(i, myCollection); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

"Professional Code", der mit Pausen durchsetzt ist, klingt für mich nicht wirklich nach professionellem Code. Es klingt nach fauler Codierung;)


4
Lazy Coding ist professioneller Code :)
Jason

Nur wenn es kein Schmerz im Hintern ist, es aufrechtzuerhalten :)
Biff MaGriff

2
Ich neige dazu, "GTFO ASAP" zu üben. Das heißt, wenn ich eine Struktur sauber verlassen kann, sollte ich dies so schnell wie möglich und so nahe wie möglich an der Bedingung tun, die das Beenden angibt.
Chris Cudmore

Nun, das funktioniert auch. Ich denke, das Argument, das ich vermitteln möchte, ist, dass es nicht schadet, wenn Sie eine for-Schleife verwenden, die integrierte Funktionalität zu verwenden, um Ihren Code lesbar und modular zu machen. Wenn Sie unbedingt break-Anweisungen in Ihrer Schleife haben müssen, würde ich sitzen und darüber nachdenken, warum diese Komplexität erforderlich ist.
Biff MaGriff

1
Haha. Bedenken Sie, dass die meisten Leute, die davon breakabraten, argumentieren, dass dies der Lesbarkeit von Code dient. Schauen Sie sich jetzt noch einmal den verrückten 5 Meilen langen Zustand in den Schleifen oben an ...
Tomáš Zato - Stellen Sie Monica
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.