Fragensteller: Vorgehensweise zur Typ-Erkennung

Guten Morgen,

in Javascript gibt es ja dieses bekannte Pattern Object.prototype.toString.call(o).slice(8, -1), mit dem man an den Typ einer Variable kommt. Das kann man dann noch mit einem typeof-Check paaren, um zusätzlich vom Typ auch noch Primitives unterscheiden zu können. Schließlich kommt in etwa sowas raus:

let type = function(o) {
    if (o === null)
      return 'null';
    if (Number.isNaN(o))
      return 'NaN';
    let t = typeof o;
    if (t !== 'object')
      return t;
    return Object.prototype.toString.call(o).slice(8, -1);
};

Diese Funktion erkennt im Grunde genommen alles, was man ihr zum fressen gibt:
new Number(10) // "Number"
10 // "number"
NaN // "NaN"
[] // "Array"
{} // "Object"
/x/ // "RegExp"
new Date() // "Date"
function(){} // "function"
Symbol() // "symbol"
undefined // "undefined"
null // "null"

Mich würde nun interessieren, ob etwas gegen einen breiten Einsatz eines solchen Typ-Checks spricht. Wenn ich an einer Library oder einem API schreibe ist es nicht wegzudenken, den Parameter-Input-Typ zu kennen - bspw. wenn man unterschiedliche Signaturen für eine Funktion zur Verfügung stellen möchte oder eine aussagekräftige Exception werfen. Oder wie regelt ihr sowas?

  1. Tach!

    in Javascript gibt es ja dieses bekannte Pattern Object.prototype.toString.call(o).slice(8, -1), mit dem man an den Typ einer Variable kommt.

    Mich würde nun interessieren, ob etwas gegen einen breiten Einsatz eines solchen Typ-Checks spricht. Wenn ich an einer Library oder einem API schreibe ist es nicht wegzudenken, den Parameter-Input-Typ zu kennen - bspw. wenn man unterschiedliche Signaturen für eine Funktion zur Verfügung stellen möchte oder eine aussagekräftige Exception werfen. Oder wie regelt ihr sowas?

    Gar nicht. Ich kann mich nicht erinnern, jemals einen Typcheck benötigt zu haben, ich schreibe aber auch keine öffentliche Software. Garbage in, garbage out. Wenn jemand meint, andere Werte als im dokumentierten Bereich übergeben zu müssen, dann muss er halt mit den Konsequenzen leben. Eine aussagekräftige Fehlermeldung muss auch nicht unbedingt sagen, was da gekommen ist. Das bekommt man auch selbst mit einem Debugger raus. Wichtiger ist eine Hilfestellung, wie man das Problem beheben kann, also nicht (nur) sagen was falsch ist, sondern was richtig wäre.

    In vielen Fällen wird es auch reichen, eine Inhaltskontrolle vorzunehmen. Wenn es dabei implizit typecastet, dann macht es das eben. Kann ja auch vom Verwender so gewollt sein und ein zu enges Einschränken als kontraproduktiv empfunden werden. Zur Not gibt es auch noch die typsicheren Vergleichsoperatoren.

    Wenn es denn unbedingt typsicher sein soll, bietet es sich auch an, Typescript zu verwenden.

    dedlfix.

    1. Wenn es denn unbedingt typsicher sein soll, bietet es sich auch an, Typescript zu verwenden.

      Du wolltest vermutlich auf statische Typisierung hinaus und nicht auf Typsicherheit. Typsicher bedeutet nämlich, dass (alle!) Typfehler spätestens zur Laufzeit erkannt werden. Das ist in JavaScript der Fall - im Gegensatz zu C++ oder Basic. Statische Typisierung dagegen meint, dass (einige!) Typfehler bereits vor der Laufzeit erkannt werden können. Das reicht allerdings noch nicht zur Charakterisierung, da man mittels Typinferenz für so ziemlich jede Programmiersrapche gewisse Typeigenschaften vor der Laufzeit sicherstellen kann. Man verlangt deshalb oft zusätzlich, dass man Typen explizit angeben kann. TypeScript erweitert JavaScript um Typannotationen und gilt deshalb als statisch typisiert (hat aber auch ein Laufzeit-Typsystem). Ich habe die Begriffe auch schon durcheinander gebracht und auch andere, deshalb habe ich diesmal darauf aufmerksam gemacht.

  2. Mich würde nun interessieren, ob etwas gegen einen breiten Einsatz eines solchen Typ-Checks spricht.

    Wenn man das merkwürdige aber für viele gut riechende, jedermann auf den Straßen Lissabons angebotene landwirtschaftliche Produkt geraucht hat sollte man nichts programmieren und darf auch kein KFZ führen.

    Anders ausgedrückt: Die Antwort auf Deine Frage fällt je nach Definition dessen, was ein "breiter Einsatz" sein soll, anders aus. Im Übrigen schließe ich mich weitgehend dem Dedlfix an. Bei jedem Aufruf einer Funktion oder eines Setters in einem Objekt würde ich das nicht machen wollen.

    1. Hallo,

      Im Übrigen schließe ich mich weitgehend dem Dedlfix an. Bei jedem Aufruf einer Funktion oder eines Setters in einem Objekt würde ich das nicht machen wollen.

      Natürlich. Bei jedem Funktionsaufruf wäre das wahnsinn. Aber was ist mit dem von außen zugänglichen Teil?

  3. Mich würde nun interessieren, ob etwas gegen einen breiten Einsatz eines solchen Typ-Checks spricht. Wenn ich an einer Library oder einem API schreibe ist es nicht wegzudenken, den Parameter-Input-Typ zu kennen - bspw. wenn man unterschiedliche Signaturen für eine Funktion zur Verfügung stellen möchte oder eine aussagekräftige Exception werfen.

    Ja - das ist aber auch der einzige Fall, wo solche Typechecks meiner Meinung nach sinnvoll sind.

    Aber selbst dort habe ich nie mit der [[Class]] arbeiten müssen (das ist der interne Typ, der Object.prototype.toString zurückgibt). Meist reicht ein simples typeof oder ein Truthy-Check aus, um die Signaturrn eine Funktion zu unterscheiden.

    Allgemein setze ich in JavaScript auf "Duck Typing". Sobald ein Parameter für eine Operation nutzbar ist, sollte er auch einfach verwendet werden. Strenge Typechecks schränken die Flexibilität nur ein. Meist muss ich lediglich truthy/falsy, String, Number, Liste/Collections und ggf. Objekte erkennen.

    Grüße Sebastian

    1. Hallo,

      Wenn ich an einer Library oder einem API schreibe ist es nicht wegzudenken, den Parameter-Input-Typ zu kennen - bspw. wenn man unterschiedliche Signaturen für eine Funktion zur Verfügung stellen möchte oder eine aussagekräftige Exception werfen.

      Ja - das ist aber auch der einzige Fall, wo solche Typechecks meiner Meinung nach sinnvoll sind.

      Aber selbst dort habe ich nie mit der [[Class]] arbeiten müssen (das ist der interne Typ, der Object.prototype.toString zurückgibt). Meist reicht ein simples typeof oder ein Truthy-Check aus, um die Signaturrn eine Funktion zu unterscheiden.

      Da stimme ich dir eigentlich zu, aber

      Allgemein setze ich in JavaScript auf "Duck Typing". Sobald ein Parameter für eine Operation nutzbar ist, sollte er auch einfach verwendet werden. Strenge Typechecks schränken die Flexibilität nur ein. Meist muss ich lediglich truthy/falsy, String, Number, Liste/Collections und ggf. Objekte erkennen.

      typeof regelt nicht alles. Arrays könnte man noch mit foo instanceof Array finden. Bei Object wird es schwieriger (vielen Dank an die Pfeife, die für typeof foo === 'object' für foo === null verantwortlich ist). Man könnte nun

      Object.isObject = function isObject(value) {
          return typeof value === 'object' && value !== null;
      }
      

      implementieren, da es für Arrays ja bereits Array.isArray() gibt. Oder wie pflegst du es, diese Kandidaten zu erkennen?

      1. vielen Dank an die Pfeife, die für typeof foo === 'object' für foo === null verantwortlich ist

        Die "Pfeife" war Tony Hoare und er hat sich schon dafür entschuldigt:

        I call it my billion-dollar mistake. It was the invention of the null reference in 1965.

        In TypeScript hat null übrigens den Typ Null und man kann strikte Nullchecks aktivieren, um damit auszuschließen, dass man null anstelle eines Objekts bekommt.

      2. Tach!

        Man könnte nun

        Object.isObject = function isObject(value) {
            return typeof value === 'object' && value !== null;
        }
        

        implementieren, da es für Arrays ja bereits Array.isArray() gibt. Oder wie pflegst du es, diese Kandidaten zu erkennen?

        Ich versuche nicht den Typ zu erkennen sondern prüfe nur den Inhalt. Dazu reicht im Falle von null oftmals schon der einfache ==, denn nur null und undefined sind sich auf diese Weise gleich. Geht natürlich nur, falls man nicht auch noch null und undefined unterscheiden möchte.

        Ob der Kandidat nun ein Objekt ist oder nicht, ist mir auch egal, denn das sagt noch nichts darüber aus, ob er Eigenschaften oder Methoden hat, die ich ansprechen möchte. Da ist mir lieber zu prüfen, ob die Ente quakt.

        dedlfix.

      3. typeof regelt nicht alles.

        Ja richtig. Es muss auch nicht alles regeln. Es muss nur Primitives von Nicht-Primitives unterscheiden.

        [Arrays, Objects] Oder wie pflegst du es, diese Kandidaten zu erkennen?

        Gar nicht wenn ich es nicht aus besonderen Gründen muss.

        Als Objekt in JavaScript verhält sich praktisch alles, was nicht null und nicht undefined ist. Meine Funktionen erwarten üblicherweise Objekte, um dort Eigenschaften nachzuschlagen. Das ist bei allen Werten möglich, die nicht null oder undefined nicht. Warum einen strengen Typecheck machen? Höchstens um grob fehlerhafte Benutzung zu erkennen.

        Ich prüfe auch nicht explizit auf Arrays. Wenn überhaupt prüfe ich auf Listen. Denn viele Listen in JavaScript sind keine Arrays. Eine Liste ist ein Wert, der nicht null und nicht undefined ist, length-Eigenschaft sowie Eigenschaften 0, 1 … n hat. Das trifft auch auf Strings zu, aber das ist i. d. R. von Vorteil und kein Problem.

        Sebastian