Objekte um Properties erweitern (Ein JavaScript-Rant)

In letzter Zeit habe ich ein bißchen versucht, was mit JavaScript zu machen. Im naiven Bestreben, Dinge „gut“ zu machen, bedeutet(e) das auch, viel zu lesen - z.B. über best practices. Dass dazu im Detail jeder etwas anderes schreibt, ist klar - aber bei JavaScript schreibt jeder zu den elementarsten Dingen etwas anderes. Vielleicht verbessert sich mein (bisher recht schlechter) Eindruck noch, wenn sich das Verständnis etwas vertieft.

Aber eine Sache erscheint mir im Moment haarsträubend:

Man kann „„Objekte““ um Properties erweitern. Man kann also sowas machen wie


var called = function(someObject)
{
    someObject.canYouRelyOnThis = undefined;
    someObject.whereDoesThisComeFrom = "you never know";
}

var caller = function()
{
    var someObject = {};
    someObject.canYouRelyOnThis = "yes";

    called(someObject);

    console.log(someObject.whereDoesThisComeFrom); // Gibt "you never know" aus...
    if (typeof someObject.canYouRelyOnThis === "undefined") console.log("No, you cannot"); // Gibt "no, you cannot" aus...
}

Ja, man kann sich Objekte wie eine Große „Map“ vorstellen, und damit ist klar, warum das geht.
Aber…

— (hier geht der Rant-Teil los) —
[ot]

Ähm. Mal im Ernst:

:sick: :suspect: WTFITS!? :verzweifel: :confused:

Üblicherweise lege ich (in Java und C++, würde das aber auch in anderen Sprachen (gerne) versuchen) Wert auf ein klares Datenmodell. Datenmodellklassen sind bei mir üblicherweise interfaces. Eine public class verursacht bei mir schon Bauchschmerzen. Wenn irgendeine Methode sichtbar ist, die nicht Teil eines idealisierten Modells ist, fühle ich mich unwohl. Wenn jemand etwas aufrufen kann oder verändern kann, was auch nur ansatzweise dazu beiträgt, dass der Zustandsraum der Objekte umklarer wird, ist das IMHO ein Problem.

Und in JavaScript kann jeder beliebig und völlig frei an Objekten rumpfuschen, properties und sogar Funktionen hinzufügen oder rauswerfen oder durch eigene ersetzen. Auf Javaisch würde das bedeuten:

Object object = new Object();
magic(object); // Macht... was auch immer
System.out.println(object.toString()); // Startet MineSweeper und formatiert die Festplatte

Wie soll man mit so einer Sprache vernünftige, nachhaltige Software entwickeln? Wie kommt man damit klar, dass man NIE weiß, was mit einem Objekt passiert, das man an irgendeine Library-Funktion übergibt (wenn man weiß, dass mit diesem Objekt ALLES passieren kann - inklusive kann es komplett „kaputt“ gemacht werden!)

[/ot]
— (hier endet der Rant-Teil) —

Gibt es irgendwelche Konventionen dazu, ob Funktionen übergebene Objekte verändern dürfen? Speziell ob irgendwelche Funktionen „ihre“ Datein einfach mit in Objekte reinpacken dürfen? Oder, als ganz konkretes (wenn daher auch eingeschränkt repräsentatives) Beispiel: Auf WebGL Lesson 1 – A triangle and a square | Learning WebGL steht folgende Funktion:


  var gl;
  function initGL(canvas) {
    try {
      gl = canvas.getContext("experimental-webgl");
      gl.viewportWidth = canvas.width;
      gl.viewportHeight = canvas.height;
    } catch(e) {
    }
    if (!gl) {
      alert("Could not initialise WebGL, sorry :-( ");
    }
  }

Wer weiß nun woher, dass „gl.viewportWidth“ einen Wert zugewiesen bekommt? Was IST gl in diesem Fall? Ja, ersmal ein GL-Kontext - aber eben mit irgendwelchen willkürlich reingemischten Properties, die der Autor an dieser Stelle zufällig praktisch fand?! :sick: Und woher weiß man, dass eine Library-Funktion wie


  function renderWith(gl) {
      ...
  }

nicht sowas macht wie
gl.viewportWidth = "Ätsch, ich bin jetzt ein String!";
?!? :suspect:

(Wenn ich es richtig verstanden habe, kann man für letzteres wohl mit „defineProperty“ da einige Einschränkungen vornehmen, aber die grundsätzlichen Möglichkeiten sind ja sprachinhärent…)

Dein genanntes Problem haben auch andere schwach typisierte Sprachen. Siehe dieses Python-Beispiel:
[PYTHON]

Damit es ähnlicher deinem Beispiel aussieht, habe ich eine Klasse verwendet. Nichtsdestotrotz würde es auch mit dem dict Datentyp gehen der ähnlicher der Java Map ist.

class SomeClass:
pass

def called(someObject):
someObject.canYouRelyOnThis = None
someObject.whereDoesThisComeFrom = ‘you never know’

def caller():
someObject = SomeClass()
someObject.canYouRelyOnThis = ‘yes’

called(someObject)

print (someObject.whereDoesThisComeFrom)

if (someObject.canYouRelyOnThis == None):
    print('No, you cannot')

[/PYTHON]

Nun zu deiner Frage: “Wie soll man mit so einer Sprache vernünftige, nachhaltige Software entwickeln?” Möchte ich gerne mit einer Gegenfrage beantworten: Wie soll man überhaupt mit einer Programmiersprache vernünftige, nachhaltige Software entwickeln? Ich würde sagen, Konventionen, Dokumentation und nebenbei ein bisschen Vernunft sichern ab, dass Software vernünftig und nachhaltig funktioniert, ansonsten kann man die Software gleich schmeißen (Compiler wird schon alles richten, halte ich für Blödsinn siehe ). Auch könnte eine vertrauenswürdige Auslieferung von Code helfen, um XSS zu vermeiden (Tipp: Subresource Integrity). Ansonsten lässt sich da nicht viel weiter machen.


Noch eine Antwort zur Frage: “Gibt es irgendwelche Konventionen dazu, ob Funktionen übergebene Objekte verändern dürfen?” Die einfache Konvention ist, wenn die Funktion das Objekt verändert gibt sie auch nichts zurück. Wenn die Funktion ein Objekt zurückgibt, gibt sie das veränderte Objekt zurück. Beides sollte nicht gleichzeitig passieren. (Ich habe keine JavaScript API gesehen, die so etwas macht).


Zu LearningWebGL bin ich überfragt: Für mich sieht das auch seltsam aus, da ich nichts davon in den Mozilla Docs finde. Dort wird zumindest geraten wie folgend den Viewport zu setzen:
[JAVASCRIPT]gl.viewport(0, 0, canvas.width, canvas.height);[/JAVASCRIPT]
Sieht für mich zumindest sicherer aus als der direkte Zugriff auf die Variablen.

Aja noch etwas Objekte in JavaScript entsprechen eher den Java Datentyp Map, hingegen willst du Instanzen von Klassen in Javscript erstellen bieten sich Prototypen an:

[JAVASCRIPT]
function SomeClass() {
this.x = 5
}
SomeClass.prototype.square = function() {
return this.x*this.x
}

sc = new SomeClass()
console.log(sc.square())
[/JAVASCRIPT]
Es gibt auch Java-Style Klassen, aber die gibt es erst seit ECMAScript 6.

Aber so wie ich das verstanden habe, ist das nur syntaktischer Zucker, und man kann Instanzen dieser Klassen genauso manipulieren wie normale Objekte?

Stimmt, bis auf den auffälligen Unterschied ist, dass man Klassen (bzw. Prototypen) durch neudefinieren nicht überschreiben kann und die Vererbung besser ausieht als mit Prototypen.


Da ich auf meinen vorvorherigen Post keine Schreibrechte habe, hier ist noch eine Ergänzung:

Die Aussage, dass das in anderen Sprachen auch geht, führt am Ziel vorbei. Dass in der https://en.wikipedia.org/wiki/List_of_programming_languages einige vielleicht nicht der große Wurf sind, ist wohl klar. Aber JavaScript ist wohl in mehrerer Hinsicht die verbreitetste Sprache der Welt, und im Moment scheint es mir, etwas theatralisch formuliert, als würde sich da die IT-Welt ein seeehr tiefes Grab schaufeln.

Ob das mit “Konvention, Dokumentation, Vernunft” eine Art Witz sein sollte, weiß ich niicht genau. Falls nicht, kurz abhaken:

  • Konventionen scheint es keine zu geben - jeder scheint zu machen, was er will (und die Sprache erlabut das auch - das ist ja auch das Problem)
  • Dokumentation: Die einzige JavaScript-Lib, bei der ich bisher zumindest einen HAUCH Dokumentation gesehen habe, war JSDoc :rolleyes:
  • Vernunft. spöttisch: Hah. KEINE Programmiersprache wird von so vielen Dilettanten verwendet, wie JavaScript (Und mit mir ist es jetzt einer mehr :o) Aber ich mache mir wenigstens Gedanken zu Fragen wie Schnittstellen, Zustandsraum, Sichtbarkeit, Veränderbarkeit etc…). Gerade da wäre eigentlich ein enges Korsett nötig, damit man keinen Mist machen kann.

Auch die Frage, mit welcher Sprache man überhaupt vernünftige, nachhaltige Software entwickeln kann, erscheint rethorisch, aber welche Rethorik damit verbunden ist, ist mir nicht klar. Java, zum Beispiel. Oder auch C++. Oder viele andere.

Ich habe überlegt, inwieweit das damit zusammenhängt, dass die Sprache eben ungetypt ist. Aber ich denke, “ungetypt” müßte nicht notwendigerweise heißen, dass jeder mit jedem Objekt machen kann, was er will. Man könnte, bewußt verallgemeinernd, sagen, dass ALLE “Softwarefehler” (im weitesten Sinne!) darauf zurückzuführen sind, dass irgendein Objekt sich in einem falschen Zustand befindet. (Das ist höchstens (aber nicht notwendigerweise) beschränkt auf OOP). Und dass bei JS praktisch JEDER JEDES Objekt BELIEBIG verändern darf, ist absurd.

Nochmal: Mir ist klar, dass man das verhindern kann. Dieses “defineProperty” scheint ja ein Weg zu sein, ein bißchen Sicherheit da rein zu bringen. Ich selbst muss die genaue Anwendung davon noch besser grokken. Es gibt auch sowas wie Private Members in JavaScript (was ja der Josh Bloch des JavaScripts zu sein scheint), aber das ist von 2001, und das kafkaeske Chaos rund um die Fragen, ob es da mit ECMAScript3.2 nicht bessere Möglichkeiten gibt, ob das auf IE8.1 richtig funktioniert, und ob man nicht statt mit JavaScript vielleicht lieber gleich mit jQuery programmieren sollte :sick: macht es nicht gerade leicht, systematisch auf gute Ergebnisse hinzuarbeiten.

Vielleicht kann ich die eigentliche Frage etwas konkreter fassen. Das Problem ist, dass konkrete Beispiele oft konkrete Lösungen haben, und man sehr viele Beispiele bräuchte, um sich “Best Practices” selbst zu erschließen. Aber ich versuch’s:

Ich lese ein Objekt aus JSON-Daten ein. Das Objekt hat (soweit die Sprache das eben erlaubt :rolleyes: ) einen konkreten Typ. Genaugenommen ist das ein sehr klarer, sehr spezifischer Typ: https://github.com/KhronosGroup/glTF/blob/master/specification/schema/buffer.schema.json Nun werden von der URI, die da drin steht, Daten gelesen - z.B. als ein ArrayBuffer. Soll ich jetzt einfach irgendwo reinschreiben
someBuffer.theArrayBuffer = readDataFrom(someBuffer.uri);
? Das wäre eigentlich praktisch und sinnvoll. Aber das Datenmodell enthält diese Property eigentlich nicht. Und dass jede Library, der ich dieses Objekt in die Hand drücke, sowas machen könnte wie
someBuffer.theArrayBuffer = "Hey, I also thought that 'theArrayBuffer' was a nice name for this string property :-)"
erweckt bei mir den Eindruck, dass vemeintlich einfache Lösungen einen direkt in die Hölle führen (und die “besseren” Lösungen, mit defineProperty etc., EXTREM aufwändig und zumindest “unkonventionell” sind)

Seit es Typescript gibt, verwende ich dies. Damit bekommt man zwar nicht alle Probleme in den Griff, aber es fühlt sich einfach wie eine “richtige” Programmiersprache an. Schon alleine für das Klassenkonstruct und die Typsicherheit lohnt es sich. Natürlich kannst Du dann da mit Plain-Javascript trotzdem die Dinge tun, die sich “nicht richtig” anfühlen.

[QUOTE=Marco13]
Ich habe überlegt, inwieweit das damit zusammenhängt, dass die Sprache eben ungetypt ist. Aber ich denke, “ungetypt” müßte nicht notwendigerweise heißen, dass jeder mit jedem Objekt machen kann, was er will.[/QUOTE]

Inzwischen hat JavaScript const, meiner Meinung nach eine der wichtigsten Neuerungen in ES6. Nur muss das erst mal zum Einsatz kommen.

WebGL funktioniert erst ab IE 11, also kannst du IE 8 schmeißen und defineProperty gibt es erst seit ECMAScript 5. Vielleicht deswegen ist defineProperty nicht konventionell.

Einfach nur WTF!?!

(von https://twitter.com/weitzelb/status/718623065480019968 )

Auch in Java kann man via Reflections jegliche Felder ändern.

Und mit genug Bösartigkeit bekommt man via bytecode-manipulation auch noch alles andere hin.

So hat man letztendlich in Java auch nur Konventionen ala final => you should not change this field anstelle von must.

Ja, hat es ja auch auf What is happening in this JavaScript snippet? - Stack Overflow geschafft. Mir ist das ziemlich suspekt.

[QUOTE=ionutbaiu]Auch in Java kann man via Reflections jegliche Felder ändern.

Und mit genug Bösartigkeit bekommt man via bytecode-manipulation auch noch alles andere hin.

So hat man letztendlich in Java auch nur Konventionen ala final => you should not change this field anstelle von must.[/QUOTE]

Da vergleichst du Äpfel mit Birnen: Niemand verwendet in Java “versehentlich” Reflection oder Byte-Code-Manipulation, aber in JavasScript kann es eben wirklich versehentlich passieren. Anders ausgedrückt: Für Sicherheit in Java mag es ein “Opt-Out” geben, Sicherheit in JavaScript gibt es dagegen nur sehr eingeschränkt, und dann nur als “Opt-In”.

@Marco13 Wenn du JavaScript nicht verwenden willst, zwingt dich niemand dazu. Es gibt nämlich auch die Möglichkeit mit Emscripten deinen C++ Code zu JavaScript zu kompilieren. Da kannst du genauso auch auf die OpenGL API zugreifen.

@Landei trotzdem hindert es keinen Menschen mit JavaScript-Kentnissen eine Anwendung mit JavaScript zu entwickeln.

Genauso wenig wie fehlende Sicherheitsgurte verhindern, dass man mit einem Auto fahren kann :slight_smile:

Eben und wenn das Auto crasht, kann man versuchen den Crash nochmal durchzuführen ;-).

[QUOTE=mdickie]@Marco13 Wenn du JavaScript nicht verwenden willst, zwingt dich niemand dazu. Es gibt nämlich auch die Möglichkeit mit Emscripten deinen C++ Code zu JavaScript zu kompilieren. Da kannst du genauso auch auf die OpenGL API zugreifen.
[/QUOTE]

Ich „will“ es verwenden. Aber ich will, dass das, was ich da mache, „gut“ ist. Wer sich über die Implikationen dieses Bestrebens nicht im Klaren ist, sollte vieeeel nachdenken.

Da gibt es mehrere Probleme. Die wichtigsten sind auf JavaScript: The World’s Most Misunderstood Programming Language ja zusammengefasst.

Und ein Problem, das mit dieser Frage im Zusammenhang steht, ist „Amateurs“: Irgendein Webdesigner, der „HTML programmiert“, will einen „MouseOver-Effekt“ für einen Button … … … und Ruck-Zuck ist er jemand, der „jahrelange Erfahrung mit JavaScript“ hat, und damit dann (vermeintlich) Software entwickelt). Derjenige weiß nicht, was eine Klasse ist, geschweige denn die algebraische Interpretation eines ADT. Ist ja nicht schlimm. Am Ende sieht man nur das Ergebnis, und die verräterischen Konsolenausgaben werden durch if (something is undefined) return; unterdrückt.

(Aber unabhängig davon, ob jemand Ahnung vom Programmieren hat, oder nicht: )

Es gibt KEIN Sicherheitsnetz, das dafür sorgt, dass man nicht beliebigen Müll produziert.

Man kann versehentlich Dinge extrem schlecht machen. Und es gibt genug Quellen im Netz, wo extrem schlechte Lösungen postuliert werden. Und schon dass meine Fragen

  • Wie modelliert man ein Datenobjekt, wenn man nie weiß, welche Eigenschaften und Funktionen es hat?
  • Wie kann man verhindern, dass andere Funktionen die eigenen Objekte „kaputt“ machen?
  • Ist es OK, wenn man SELBST Objekte um Properties erweitert, die „man gerade praktisch findet“?

nicht beantwortet wurden (und, das ist viel schlimmer: Auch nach längerem Websuchen praktisch nicht beantwortet werden können!) ist beunruhigend.

[QUOTE=Marco13]Ich „will“ es verwenden. Aber ich will, dass das, was ich da mache, „gut“ ist. Wer sich über die Implikationen dieses Bestrebens nicht im Klaren ist, sollte vieeeel nachdenken.

Da gibt es mehrere Probleme. Die wichtigsten sind auf JavaScript: The World’s Most Misunderstood Programming Language ja zusammengefasst.

Und ein Problem, das mit dieser Frage im Zusammenhang steht, ist „Amateurs“: Irgendein Webdesigner, der „HTML programmiert“, will einen „MouseOver-Effekt“ für einen Button … … … und Ruck-Zuck ist er jemand, der „jahrelange Erfahrung mit JavaScript“ hat, und damit dann (vermeintlich) Software entwickelt). Derjenige weiß nicht, was eine Klasse ist, geschweige denn die algebraische Interpretation eines ADT. Ist ja nicht schlimm. Am Ende sieht man nur das Ergebnis, und die verräterischen Konsolenausgaben werden durch if (something is undefined) return; unterdrückt.

[/QUOTE]
Ja, in dem Fall wäre es vielleicht schöner, man würde folgendes durchführen: var o = {x: 5}; if (!o.x) {throw new Error('x not defined');}.

Duck typing - Wikipedia (Das könnte zumindest sein, was du suchst.)

Bitteschön:
[JAVASCRIPT]
class Container {
constructor() {
var someProperty;
Object.defineProperty(this, ‚someProperty‘, {
get: function () {
return someProperty;
},
set: function(value) {
if (typeof value !== ‚number‘) return;
someProperty = value;
}
});
}
}

function machObjektKaputt(o) {
o.someProperty = ‚Ätsch, ich bin ein String‘;
}

var container = new Container();
container.someProperty = 8;

machObjektKaputt(container);

console.log(container.someProperty);
[/JAVASCRIPT]

Ich würde es vermeiden, aber wenn du schon ein Objekt mit Properties erweitern willst, kannst du ja Vererbung nutzen.

Wie gesagt, “defineProperty” scheint da einige Möglichkeiten zur Absicherung zu bieten. Aber nach dem, was ich bisher gesehen habe, verwendet das kaum jemand. Insbesondere irgendwelche (Einführungs-) Tutorials scheinen eher mit einer gefährlichen Mischung aus Begeisterung und Selbstverständlichkeit irgendwelche Properties an Objekte zu kleben. Abgesehen davon, scheint ein klares Definieren und Befolgen von Best Practices da recht schwierig zu sein.

Es kann durchaus Sinn machen, dass man irgendwelche Properties an Objekte kleben kann, weil man dadurch nicht gezwungen ist im Vorhinein ein Datenmodell zu definieren. Beispielsweise muss man, wenn man eine JSON-Datei parst, nicht genau wissen wie das Datenmodell aussehen soll, um direkten Zugriff auf die geparsten Elemente zu bekommen. Das ist eine Nutzungsmöglichkeit, aber es gibt sicher auch andere.

Hier gibt es auch eine Doku über defineProperty: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty Falls du keine Lust dich durch die ganzen Tutorials und Docs durchzustöbern, kann ich dir JavaScript - The Definitive Guide von David Flanagan empfehlen. Die sechste Auflage davon behandelt ECMAScript 5 und HTML5.