Was ist der Vorteil von "Keine Laufzeitausnahmen", wie Elm behauptet?


16

Einige Sprachen behaupten, "keine Laufzeitausnahmen" zu haben, was einen klaren Vorteil gegenüber anderen Sprachen darstellt, in denen sie vorkommen.

Ich bin in der Sache verwirrt.

Die Laufzeitausnahme ist meines Wissens nur ein Werkzeug und wird gut verwendet:

  • Sie können "schmutzige" Zustände kommunizieren (auf unerwartete Daten werfen)
  • Wenn Sie einen Stapel hinzufügen, können Sie auf die Fehlerkette verweisen
  • Sie können zwischen Unordnung (z. B. Rückgabe eines leeren Werts bei ungültiger Eingabe) und unsicherer Verwendung unterscheiden, die die Aufmerksamkeit eines Entwicklers erfordert (z. B. Ausnahmefehler bei ungültiger Eingabe).
  • Sie können Details zu Ihrem Fehler hinzufügen, indem Sie die Ausnahmemeldung mit weiteren hilfreichen Details versehen, die Ihnen (theoretisch) beim Debuggen helfen.

Andererseits finde ich es wirklich schwierig, eine Software zu debuggen, die Ausnahmen "verschluckt". Z.B

try { 
  myFailingCode(); 
} catch {
  // no logs, no crashes, just a dirty state
}

Die Frage ist also: Was ist der starke theoretische Vorteil, "keine Laufzeitausnahmen" zu haben?


Beispiel

https://guide.elm-lang.org/

Keine Laufzeitfehler in der Praxis. Nein null. Nein undefiniert ist keine Funktion.


Ich denke, es wäre hilfreich, ein oder zwei Beispiele für die Sprachen, auf die Sie sich beziehen, und / oder Links zu solchen Behauptungen bereitzustellen. Dies kann auf verschiedene Arten interpretiert werden.
JimmyJames

Das neueste Beispiel, das ich gefunden habe, war Ulme im Vergleich zu C, C ++, C #, Java, ECMAScript usw. Ich habe meine Frage @JimmyJames
am

2
Dies ist das erste Mal, dass ich davon gehört habe. Meine erste Reaktion ist, BS anzurufen. Beachten Sie die
Wieselwörter

@atoth Ich werde Ihren Fragentitel bearbeiten, um ihn übersichtlicher zu gestalten, da es mehrere unabhängige Fragen gibt, die ähnlich aussehen (wie "RuntimeException" vs "Exception" in Java). Wenn Ihnen der neue Titel nicht gefällt, können Sie ihn jederzeit erneut bearbeiten.
Andres F.

OK, ich wollte, dass es allgemein genug ist, aber ich kann dem zustimmen, wenn das hilft, danke für deinen Beitrag @AndresF.!
1.

Antworten:


28

Ausnahmen haben eine extrem einschränkende Semantik. Sie müssen genau dort gehandhabt werden, wo sie geworfen werden, oder im direkten Aufrufstapel nach oben, und es gibt beim Kompilieren keine Anzeige für den Programmierer, wenn Sie dies vergessen.

Vergleichen Sie dies mit Elm, wo Fehler als Ergebnisse oder Maybes codiert werden , die beide Werte sind . Das bedeutet, dass Sie einen Compilerfehler erhalten, wenn Sie den Fehler nicht behandeln. Sie können sie in einer Variablen oder sogar in einer Sammlung speichern, um ihre Bearbeitung auf einen geeigneten Zeitpunkt zu verschieben. Sie können eine Funktion erstellen, um die Fehler anwendungsspezifisch zu behandeln, anstatt sehr ähnliche Try-Catch-Blöcke überall zu wiederholen. Sie können sie zu einer Berechnung verketten, die nur dann erfolgreich ist, wenn alle ihre Teile erfolgreich sind, und sie müssen nicht in einen Try-Block gepackt werden. Sie sind nicht an die eingebaute Syntax gebunden.

Dies ist nichts anderes als "Ausnahmen schlucken". Es macht Fehlerbedingungen im Typsystem explizit und bietet viel flexiblere alternative Semantik, um damit umzugehen.

Betrachten Sie das folgende Beispiel. Sie können dies in http://elm-lang.org/try einfügen, wenn Sie es in Aktion sehen möchten.

import Html exposing (Html, Attribute, beginnerProgram, text, div, input)
import Html.Attributes exposing (..)
import Html.Events exposing (onInput)
import String

main =
  beginnerProgram { model = "", view = view, update = update }

-- UPDATE

type Msg = NewContent String

update (NewContent content) oldContent =
  content

getDefault = Result.withDefault "Please enter an integer" 

double = Result.map (\x -> x*2)

calculate = String.toInt >> double >> Result.map toString >> getDefault

-- VIEW

view content =
  div []
    [ input [ placeholder "Number to double", onInput NewContent, myStyle ] []
    , div [ myStyle ] [ text (calculate content) ]
    ]

myStyle =
  style
    [ ("width", "100%")
    , ("height", "40px")
    , ("padding", "10px 0")
    , ("font-size", "2em")
    , ("text-align", "center")
    ]

Beachten Sie, dass String.toIntin der calculateFunktion die Möglichkeit eines Fehlers besteht. In Java kann dies zu einer Laufzeitausnahme führen. Wenn es Benutzereingaben liest, hat es eine ziemlich gute Chance. Elm zwingt mich stattdessen, mit a umzugehen Result, aber ich muss feststellen, dass ich mich nicht sofort darum kümmern muss. Ich kann die Eingabe verdoppeln und in eine Zeichenfolge konvertieren und dann die getDefaultFunktion auf fehlerhafte Eingaben überprüfen . Dieser Ort ist für die Prüfung viel besser geeignet als der Punkt, an dem der Fehler aufgetreten ist, oder aufwärts im Aufrufstapel.

Die Art und Weise, wie der Compiler unsere Hand erzwingt, ist auch viel feiner als die von Java geprüften Ausnahmen. Sie müssen eine sehr spezielle Funktion verwenden Result.withDefault, um den gewünschten Wert zu extrahieren. Technisch gesehen könnte man diese Art von Mechanismus missbrauchen, aber es macht nicht viel Sinn. Da Sie die Entscheidung verschieben können, bis Sie eine gute Standard- / Fehlermeldung kennen, gibt es keinen Grund, sie nicht zu verwenden.


8
That means you get a compiler error if you don't handle the error.- Nun, das war der Grund für Checked Exceptions in Java, aber wir alle wissen, wie gut das geklappt hat.
Robert Harvey

4
@RobertHarvey In gewisser Weise waren Javas geprüfte Ausnahmen die Version des armen Mannes davon. Leider könnten sie "verschluckt" werden (wie im Beispiel des OP). Sie waren auch keine echten Typen, was sie zu einem zusätzlichen Pfad zum Code-Fluss machte. Sprachen mit einer besseren Art Systeme ermöglichen es Ihnen zu kodieren Fehler als erstklassige Werte, das ist , was Sie tun in (sagen wir) Haskell mit Maybe, Eitheretc. Elm sieht aus wie es ist eine Seite aus Sprachen wie ML, OCaml oder Haskell nehmen.
Andres F.

3
@ JimmyJames Nein, sie zwingen dich nicht. Sie müssen den Fehler nur "behandeln", wenn Sie den Wert verwenden möchten. Wenn ich das tue x = some_func(), kann ich nicht habe , etwas zu tun , wenn ich den Wert untersuchen will x, in diesem Fall kann ich überprüfen , ob ich einen Fehler oder einen „gültigen“ Wert habe; Darüber hinaus ist es ein statischer Typfehler, zu versuchen, einen anstelle des anderen zu verwenden, daher kann ich das nicht tun. Wenn die Typen von Elm wie andere funktionale Sprachen funktionieren, kann ich tatsächlich Werte aus verschiedenen Funktionen zusammensetzen, bevor ich überhaupt weiß, ob es sich um Fehler handelt oder nicht! Dies ist typisch für FP-Sprachen.
Andres F.

6
@atoth Aber Sie tun haben signifikante Gewinne und es ist ein sehr guter Grund (wie schon in mehreren Antworten auf Ihre Frage erklärt). Ich empfehle Ihnen aufrichtig, eine Sprache mit ML-ähnlicher Syntax zu lernen, und Sie werden sehen, wie befreiend es ist, C-ähnliche Syntax-Cruft (ML wurde übrigens in den frühen 70er-Jahren entwickelt, was es grob macht) loszuwerden ein Zeitgenosse von C). Die Leute, die diese Art von Typsystemen entworfen haben, betrachten diese Art von Syntax im Gegensatz zu C als normal :) Während Sie dabei sind, würde es nicht schaden, auch ein Lisp zu lernen :)
Andres F.

6
@atoth Wenn Sie eine Sache aus all dem herausholen möchten, nehmen Sie diese: Achten Sie immer darauf, dass Sie nicht dem Blub-Paradoxon zum Opfer fallen . Sei nicht irritiert über die neue Syntax. Vielleicht liegt es an mächtigen Funktionen, mit denen Sie nicht vertraut sind :)
Andres F.

10

Um diese Aussage zu verstehen, müssen wir zunächst verstehen, was uns ein statisches Typensystem einkauft. Im Grunde ist das, was uns ein statisches Typsystem bietet, eine Garantie: Wenn der Programmtyp überprüft wird, kann eine bestimmte Klasse von Laufzeitverhalten nicht auftreten.

Das klingt bedrohlich. Nun, ein Typprüfer ähnelt einem Theoremprüfer. (Nach dem Curry-Howard-Isomorphismus sind sie dasselbe.) Eine Besonderheit bei Theoremen ist, dass Sie, wenn Sie einen Theorem beweisen, genau beweisen, was der Theorem sagt, nicht mehr. (Das ist zum Beispiel der Grund, warum, wenn jemand sagt "Ich habe dieses Programm als richtig erwiesen", Sie immer fragen sollten "Bitte definieren Sie" richtig ".) Dasselbe gilt für Typsysteme. Wenn wir sagen "ein Programm ist typsicher", meinen wir nicht, dass kein möglicher Fehler auftreten kann. Wir können nur sagen, dass die Fehler, die das Typensystem zu verhindern verspricht, nicht auftreten können.

Programme können also unendlich viele verschiedene Laufzeitverhalten haben. Von diesen sind unendlich viele nützlich, aber auch unendlich viele sind "falsch" (für verschiedene Definitionen von "Korrektheit"). Mit einem statischen Typsystem können wir nachweisen, dass ein bestimmter endlicher, fester Satz von unendlich vielen falschen Laufzeitverhalten nicht auftreten kann.

Der Unterschied zwischen verschiedenen Typsystemen besteht grundsätzlich darin, in welchen, wie vielen und wie komplexen Laufzeitverhalten sie sich als nicht vorkommend erweisen können. Schwache Systeme wie das von Java können nur sehr grundlegende Dinge beweisen. Beispielsweise kann Java beweisen, dass eine Methode, die als "returning a" eingegeben wurde, Stringkein "return a" zurückgeben kann List. Aber zum Beispiel kann es nicht beweisen, dass die Methode nicht zurückkehrt. Es kann auch nicht nachgewiesen werden, dass die Methode keine Ausnahme auslöst. Und es kann nicht beweisen, dass es nicht das Falsche zurückgibt String  - jedes Stringwird die Typprüfung erfüllen. (Und natürlich, auch nullwird es genügen auch.) Es gibt auch ganz einfache Dinge , dass Java nicht beweisen können, weshalb wir Ausnahmen haben wie ArrayStoreException, ClassCastExceptionoder jedermanns Liebling, der NullPointerException.

Leistungsstärkere Typsysteme wie Agda's können auch Dinge wie "die Summe der beiden Argumente zurückgeben" oder "die sortierte Version der als Argument übergebenen Liste zurückgeben" beweisen.

Was die Entwickler von Elm unter der Aussage verstehen, dass sie keine Laufzeitausnahmen haben, ist, dass das Typsystem von Elm das Fehlen (eines signifikanten Teils) von Laufzeitverhalten nachweisen kann, das in anderen Sprachen nicht nachgewiesen werden kann und daher möglicherweise nicht auftritt zu fehlerhaftem Verhalten zur Laufzeit (was im besten Fall eine Ausnahme bedeutet, im schlimmsten Fall einen Absturz und im schlimmsten Fall überhaupt keinen Absturz, keine Ausnahme und nur ein stillschweigend falsches Ergebnis).

So sind sie nicht sagen : „Wir implementieren keine Ausnahmen“. Sie sagen, "Dinge, mit denen Laufzeitausnahmen in typischen Sprachen, mit denen typische Programmierer bei Elm Erfahrung haben würden, vom Typensystem erfasst werden". Natürlich wird jemand, der aus Idris, Agda, Guru, Epigramm, Isabelle / HOL, Coq oder ähnlichen Sprachen kommt, Elm im Vergleich als ziemlich schwach ansehen. Die Anweisung richtet sich eher an typische Java-, C♯-, C ++ -, Objective-C-, PHP-, ECMAScript-, Python-, Ruby-, Perl- usw. Programmierer.


5
Hinweis für potenzielle Redakteure: Die Verwendung von Doppel- und sogar Dreifachnegativen tut mir sehr leid. Ich habe sie jedoch absichtlich belassen: Typsysteme garantieren das Fehlen bestimmter Arten von Laufzeitverhalten, dh sie garantieren, dass bestimmte Dinge nicht auftreten. Und ich wollte diese Formulierung "Beweise, dass sie nicht auftritt" intakt halten, was leider zu Konstruktionen wie "Es kann nicht bewiesen werden, dass eine Methode nicht zurückkehrt" führt. Wenn Sie einen Weg finden, diese zu verbessern, fahren Sie fort, aber beachten Sie bitte die obigen Punkte. Vielen Dank!
Jörg W Mittag

2
Insgesamt eine gute Antwort, aber ein kleiner Trottel: "Ein Typprüfer ähnelt einem Theorembeweiser." Tatsächlich ähnelt ein Typprüfer eher einem Theoremprüfer: Beide führen eine Überprüfung durch , nicht eine Ableitung .
Gardenhead

4

Elm kann aus demselben Grund keine Laufzeitausnahme garantieren. C kann keine Laufzeitausnahme garantieren: Die Sprache unterstützt das Konzept der Ausnahmen nicht.

Elm hat eine Möglichkeit, Fehlerzustände zur Laufzeit zu signalisieren, aber bei diesem System handelt es sich nicht um Ausnahmen, sondern um "Ergebnisse". Eine möglicherweise fehlgeschlagene Funktion gibt ein "Ergebnis" zurück, das entweder einen regulären Wert oder einen Fehler enthält. Elms ist stark typisiert, daher ist dies im Typensystem explizit. Wenn eine Funktion immer eine Ganzzahl zurückgibt, hat sie den Typ Int. Wenn es jedoch entweder eine Ganzzahl zurückgibt oder fehlschlägt, lautet der Rückgabetyp Result Error Int. (Die Zeichenfolge ist die Fehlermeldung.) Dies zwingt Sie dazu, beide Fälle an der Anrufstelle explizit zu behandeln.

Hier ist ein Beispiel aus der Einführung (etwas vereinfacht):

view : String -> String 
view userInputAge =
  case String.toInt userInputAge of
    Err msg ->
        text "Not a valid number!"

    Ok age ->
        text "OK!"

Die Funktion toIntkann fehlschlagen, wenn die Eingabe nicht syntaktisch analysiert werden kann. Daher lautet der Rückgabetyp Result String int. Um den tatsächlichen ganzzahligen Wert zu erhalten, müssen Sie ihn per Mustervergleich "entpacken", was Sie wiederum dazu zwingt, beide Fälle zu behandeln.

Ergebnisse und Ausnahmen machen grundsätzlich das Gleiche, der wichtige Unterschied sind die "Vorgaben". Ausnahmen sprudeln und beenden das Programm standardmäßig, und Sie müssen sie explizit abfangen, wenn Sie sie behandeln möchten. Das Ergebnis ist der andere Weg - Sie sind gezwungen, standardmäßig damit umzugehen, sodass Sie sie explizit ganz nach oben übergeben müssen, wenn Sie möchten, dass sie das Programm beenden. Es ist leicht zu erkennen, wie dieses Verhalten zu robusterem Code führen kann.


2
@atoth Hier ist ein Beispiel. Stellen Sie sich vor, Sprache A lässt Ausnahmen zu. Dann haben Sie die Funktion gegeben doSomeStuff(x: Int): Int. Normalerweise erwarten Sie, dass es eine zurückgibt Int, aber kann es auch eine Ausnahme auslösen? Ohne einen Blick auf den Quellcode zu werfen, kann man es nicht wissen. Im Gegensatz dazu kann eine Sprache B, die Fehler über Typen codiert, dieselbe Funktion haben, die folgendermaßen deklariert wurde: doSomeStuff(x: Int): ErrorOrResultOfType<Int>(In Elm wird dieser Typ tatsächlich benannt Result). Anders als im ersten Fall ist jetzt sofort ersichtlich, ob die Funktion fehlschlagen kann, und Sie müssen sie explizit behandeln.
Andres F.

1
Wie @RobertHarvey in einem Kommentar zu einer anderen Antwort impliziert, scheint dies im Grunde genommen wie in Java geprüfte Ausnahmen zu sein. Was ich aus der Arbeit mit Java in den frühen Tagen, als die meisten Ausnahmen überprüft wurden, gelernt habe, ist, dass Sie wirklich nicht gezwungen werden möchten, Code zu schreiben, um immer Fehler an dem Punkt zu machen, an dem sie auftreten.
JimmyJames

2
@JimmyJames Es ist nicht wie bei geprüften Ausnahmen, weil Ausnahmen nicht erstellt werden, ignoriert ("verschluckt") werden können und keine erstklassigen Werte sind :) Ich empfehle wirklich, eine statisch typisierte funktionale Sprache zu lernen, um dies wirklich zu verstehen. Das ist nicht irgendeine neumodische Sache Elm aus - das ist , wie Sie in Sprachen programmieren wie ML oder Haskell, und es unterscheidet sich von Java.
Andres F.

2
@AndresF. this is how you program in languages such as ML or HaskellIn Haskell ja; ML, nein. Robert Harper, ein wichtiger Mitarbeiter von Standard ML und Programmiersprachenforscher, hält Ausnahmen für nützlich . Fehlertypen können die Funktionszusammensetzung beeinträchtigen, wenn Sie sicherstellen können, dass kein Fehler auftritt. Ausnahmen haben auch unterschiedliche Leistung. Sie zahlen nicht für Ausnahmen, die nicht ausgelöst werden, sondern für die Überprüfung der Fehlerwerte jedes Mal, und Ausnahmen sind eine natürliche Methode, um das Zurückverfolgen in einigen Algorithmen auszudrücken
Doval,

2
@JimmyJames Hoffentlich sehen Sie jetzt, dass geprüfte Ausnahmen und tatsächliche Fehlertypen nur oberflächlich ähnlich sind. Überprüfte Ausnahmen lassen sich nicht ordnungsgemäß kombinieren, sind umständlich in der Verwendung und nicht ausdrucksorientiert (und können daher einfach "verschluckt" werden, wie dies bei Java der Fall ist). Ungeprüfte Ausnahmen sind weniger umständlich, weshalb sie überall, außer in Java, üblich sind. Sie können Sie jedoch mit größerer Wahrscheinlichkeit stören, und Sie können anhand einer Funktionsdeklaration nicht erkennen, ob sie ausgelöst werden oder nicht, was Ihr Programm schwieriger macht verstehen.
Andres F.

2

Beachten Sie zunächst, dass Ihr Beispiel für das "Verschlucken" von Ausnahmen im Allgemeinen eine schreckliche Praxis ist und keinerlei Beziehung dazu hat, keine Laufzeitausnahmen zu haben. Wenn Sie darüber nachdenken, ist ein Laufzeitfehler aufgetreten, Sie haben ihn jedoch ausgeblendet und nichts dagegen unternommen. Dies führt häufig zu schwer verständlichen Fehlern.

Diese Frage kann auf verschiedene Arten interpretiert werden, aber da Sie Elm in den Kommentaren erwähnt haben, ist der Kontext klarer.

Elm ist unter anderem eine statisch typisierte Programmiersprache. Einer der Vorteile dieser Art von Typsystemen besteht darin, dass viele Fehlerklassen (wenn auch nicht alle) vom Compiler abgefangen werden, bevor das Programm tatsächlich verwendet wird. Einige Arten von Fehlern können in Typen codiert werden (z. B. Elms ResultundTask ) , anstatt als Ausnahmen geworfen zu werden. Dies ist, was die Designer von Elm meinen: Viele Fehler werden zur Kompilierungszeit statt zur Laufzeit abgefangen, und der Compiler wird Sie zwingen, mit ihnen umzugehen, anstatt sie zu ignorieren und auf das Beste zu hoffen. Es ist klar, warum dies ein Vorteil ist: Es ist besser, dass der Programmierer auf ein Problem aufmerksam wird, bevor der Benutzer es tut.

Beachten Sie, dass Fehler auf andere, weniger überraschende Weise codiert werden, wenn Sie keine Ausnahmen verwenden. Aus Elms Dokumentation :

Eine der Garantien von Elm ist, dass in der Praxis keine Laufzeitfehler auftreten. NoRedInk verwendet Elm seit ungefähr einem Jahr in der Produktion und sie haben immer noch keine! Wie bei allen Garantien in Elm kommt es auch hier auf die grundlegenden Auswahlmöglichkeiten für das Sprachdesign an. In diesem Fall hilft uns die Tatsache, dass Elm Fehler als Daten behandelt. (Ist Ihnen aufgefallen, dass wir hier viel Daten machen?)

Elm-Designer behaupten ein bisschen kühn, dass es keine Laufzeitausnahmen gibt , obwohl sie es mit "in der Praxis" qualifizieren. Was sie wahrscheinlich bedeuten, ist "weniger unerwartete Fehler als wenn Sie in Javascript codieren".


Verstehe ich das falsch oder spielen sie nur ein semantisches Spiel? Sie verbieten den Namen "Laufzeitausnahme", ersetzen ihn aber einfach durch einen anderen Mechanismus, der Fehlerinformationen über den Stapel überträgt. Das klingt so, als würde man nur die Implementierung einer Ausnahme in ein ähnliches Konzept oder Objekt ändern, das die Fehlermeldung anders implementiert. Das ist kaum erderschütternd. Es ist wie jede statisch typisierte Sprache. Vergleichen Sie das Wechseln von einem COM HRESULT zu einer .NET-Ausnahme. Unterschiedlicher Mechanismus, aber dennoch eine Laufzeitausnahme, egal wie Sie es nennen.
Mike unterstützt Monica

@ Mike Ehrlich gesagt habe ich mir Elm nicht im Detail angesehen. Gemessen an der docs, haben sie Typen Resultund Taskdie sehen sehr ähnlich dem bekannteren Eitherund Futureaus anderen Sprachen. Im Gegensatz zu Ausnahmen können Werte dieser Typen kombiniert werden, und Sie müssen sie irgendwann explizit behandeln: Stellen sie einen gültigen Wert oder einen Fehler dar? Ich lese keine Gedanken, aber dieser Mangel an Überraschung für den Programmierer ist wahrscheinlich das, was Elm-Designer mit "keine Laufzeitausnahmen" meinten :)
Andres F.

@ Mike Ich stimme zu, es ist nicht erderschütternd. Der Unterschied zu Laufzeitausnahmen besteht darin, dass sie in den Typen nicht explizit sind (dh Sie können nicht wissen, ob ein Teil des Codes ausgelöst werden darf, ohne den Quellcode zu betrachten). Das Codieren von Fehlern in Typen ist sehr explizit und verhindert, dass der Programmierer sie übersieht, was zu sichererem Code führt. Dies wird von vielen FP-Sprachen durchgeführt und ist in der Tat nichts Neues.
Andres F.

1
Nach Ihren Kommentaren gibt es meiner Meinung nach mehr als "statische Typprüfungen" . Sie können es mit Typescript zu JS hinzufügen. Dies ist weitaus weniger einschränkend als ein neues "Make-or-Break" -Ökosystem.
1.

1
@AndresF .: Technisch gesehen ist das neueste Merkmal von Javas Typsystem der parametrische Polymorphismus, der aus den späten 60er Jahren stammt. In gewisser Weise ist es also etwas richtig, "modern" zu sagen, wenn Sie "nicht Java" meinen.
Jörg W Mittag

0

Elm behauptet:

Keine Laufzeitfehler in der Praxis. Nein null. Nein undefiniert ist keine Funktion.

Aber Sie fragen über Laufzeit Ausnahmen . Es besteht ein Unterschied.

In Elm gibt nichts ein unerwartetes Ergebnis zurück. Sie können KEIN gültiges Programm in Elm schreiben, das Laufzeitfehler erzeugt. Sie brauchen also keine Ausnahmen.

Die Frage sollte also lauten:

Was ist der Vorteil, wenn "keine Laufzeitfehler" auftreten?

Wenn Sie Code schreiben können, der niemals Laufzeitfehler aufweist, werden Ihre Programme niemals abstürzen.

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.