Obwohl ich nicht erst seit gestern JavaScript schreibe, gibt es noch ein paar „Features“ in der Sprache, die ich nie in ernst gemeintem Code eingesetzt habe. Dabei gehöre ich nicht mal zur Spanische-Inquisition-Fraktion, die jeden, der mal new Function()
geschrieben hat, in den Kerker werfen möchte – wenn etwas funktioniert und in einem speziellen Fall keine Nachteile hat, dann verwende ich es! Nur new Number()
hatte ich noch nie eingesetzt … bis vor kurzem.
Wir erinnern uns: JavaScript kennt Wrapper-Objekt für die Datentypen String, Number und Boolean. Diese werden mit new String(x)
, new Number(x)
und new Boolean(x)
erzeugt. Durch diese Funktionsaufrufe wird der Parameter x
in den jeweiligen Datentyp konvertiert (also zu String, Number und Boolean, je nachdem) und in ein Wrapper-Objekt eingepackt. Ohne new
machen die Funktionen nur die Konvertierung und erzeugen keinen Wrapper. Das ist eigentlich immer die sinnvollere Variante, denn die Wrapper-Objekte sind zu nichts gut:
- Die Wrapper-Objekte bieten keine zusätzliche wünschenswerte Funktionalität. Methoden wie z.B.
toExponential()
bei Number können auch auf den primitiven Werten verwendet werden – diese verhalten sich diesbezüglich immer wie ein Objekt, auch wenn sie selbst keins sind (denn sie wissen, was sie für ein Objekt sie wären, wenn sie eins wären). - Der
typeof
-Operator identifiziert jedes Wrapper-Objekt (egal ob für String, Number oder Boolean) alsobject
, was korrekt, aber nicht hilfreich ist. - Alle Wrapper-Objekte sind ausnahmelos truthy, keins ist jemals falsy. Ja,
new Boolean(false)
ist truthy, denn alle Objekte sind truthy, auch wenn sie einfalse
wrappen oder einfach nur leer sind.
Demnach sollte eigentlich niemand jemals new Number()
verwenden. Aber kürzlich sah ich mich mit folgender API eines Third-Party-Moduls konfrontiert:
connectToServer({ reconnectionAttempts: <number> });
Mein Ziel war, einmal einen Verbindungsversuch zum Server zu unternehmen und nach erstmaligem Fehlschlagen sofort aufzugeben, um die Applikation in den permanenten Offline-Modus zu versetzen. Ich wollte also genau 0 reconnectionAttempts
haben. Die Implementierung der connectToServer()
-Funktion sah allerdings so aus:
export default function connectToServer(options){ if(!options.reconnectionAttempts){ options.reconnectionAttempts = 9000; } ... }
Dieser Code lässt 0 reconnectionAttempts
nicht zu! Der Wert 0 wäre falsy, was dazu führen würde, dass die Bedingung if(!options.reconnectionAttempts)
zutrifft und reconnectionAttempts
mit irgendeinem sehr hohen Standardwert ersetzt wird:
connectToServer({ reconnectionAttempts: 0 // tatsächlich 9001 Verbindungsversuche });
Das ist natürlich ein Bug im Code der connectToServer()
-Funktion. Bis dieser repariert ist, ist new Number(0)
eine gute Zwischenlösung, denn das Wrapper-Objekt ist truthy, taugt aber trotzdem auch als Zahl 0. Wo immer etwas zahlenhaftes mit einem Number-Objekt unternommen wird (Addition, nicht-strikter Vergleich usw.) wird die gewrappte Zahl aus dem Objekt mittels valueOf() „ausgepackt“.
connectToServer({ reconnectionAttempts: new Number(0) // Wirklich kein zweiter Verbindungsversuch! });
Die Nachteile des Wrapper-Objekts werden an dieser Stelle zum Vorteil! Es könnten freilich auch Dinge schiefgehen – ein strikter Vergleich new Number(0) === 0
wäre z.B. false
, aber für das, was ich mit besagter Library vorhabe, funktioniert es.
Als bizarres Extra kommt in meinem Fall noch hinzu, dass ich TypeScript statt JavaScript verwende und das TS-Typsystem (berechtigterweise) ein Problem damit hat, wenn man ein Number-Objekt dorthin schiebt, wo ein Number-Primitive erwartet wird. Also ergänze ich meinem Code noch durch eine Type Assertion, die dem Typsystem einredet, das Number-Objekt sei ein Number-Primitive. Im Endeffekt sieht der Code schon so aus, als hätte hier jemand unter dem Einfluss potenter Drogen gestanden:
connectToServer({ // Mit dem Brecheisen wird ein an sich nutzlos-gefährlicher Wrapper // um eine 0 in eine API gesteckt, die das alles nicht haben will. reconnectionAttempts: new Number(0) as number });
Bis eines fernen Tages mal die connectToServer()
-Funktion repariert ist, ist das meine eigentlich ganz zufriedenstellende Zwischenlösung. Was lernen wir daraus?
- Es lohnt sich, auch die bizarreren oder gar gefährlichen Teile einer Programmiersprache gut zu kennen. Sei es, um genau zu wissen, warum man sie nicht verwendet oder, wenn es hart auf hart kommt, um sie einmal im Jahr doch aus dem Keller zu holen um ein ganz bestimmtes Problem zu lösen.
- Schräg aussehender Code kann zwei Ursachen haben: entweder weiß jemand überhaupt nicht, was er da schreibt, oder dieser jemand weiß sehr genau Bescheid. Die letztgenannte Variante, so selten sie auch sein mag, darf man bei der Code-Lektüre nie ganz ausschließen.
Bleibt zu hoffen, dass der Bug in der connectToServer()
-Funktion bald repariert wird.