WernerK: Javascript Syntaxprüfung mit JSLint, Unused variable usw.

Hallo,

in einem Editor prüft JSLint den Javascript Code. Irgendwie meldet dies manchmal ungewöhnliche Fehler. z.b. hier: Unnecessary 'else' after disruption. Was ist an der Schreibweise mit dem "else" so schlimm?

if(anzahl === ""){
  alert('Mach was...');
  return false;
}else{
  //weiterer code hier.
}

Dann meldet JSLint bei diesem Code:

Unused 'erledigt'.

var erledigt = "";
if(val_defekt == '1'){
   erledigt = "Anwender";
}
else{
  erledigt = "";
}

Was ist daran falsch?

Gruss

Werner

akzeptierte Antworten

  1. Tach!

    Unnecessary 'else' after disruption. Was ist an der Schreibweise mit dem "else" so schlimm?

    if(anzahl === ""){
      alert('Mach was...');
      return false;
    }else{
      //weiterer code hier.
    }
    

    Es ist nicht schlimm, sonst wäre es ein Fehler und nicht nur ein Hinweis vom Linter. Das else ist schlicht nicht nötig, wenn der if-Zweig mit return beendet wird. Der "weiterer code" muss nicht mehr ein einem eigenen else-Block stehen, weil es keinen Unterschied gibt zwischen Code im else und Code, der nach dem kompletten if-Statement kommt.

    Dann meldet JSLint bei diesem Code:

    Unused 'erledigt'.

    var erledigt = "";
    if(val_defekt == '1'){
       erledigt = "Anwender";
    }
    else{
      erledigt = "";
    }
    

    Was ist daran falsch?

    In dem Ausschnitt ist nur eine Zuweisung zu einer lokalen Variable zu sehen, aber keine Nutzung derselben. Vielleicht ist das ein Tippfehler oder ein logischer, und du solltest das mal genauer untersuchen.

    dedlfix.

    1. Hallo,

      nun wenn man den Code so abändert dann meldet JSlint, dass die Variabel bereits initialisiert ist.

      if(val_defekt == '1'){
         var erledigt = "Anwender";
      }
      else{
        var erledigt = "";
      }
      

      Wie also, ist es richtig?

      Gruss

      Werner

      1. Hallo,

        Variablen haben un Javascript Function-Scope und sind implizit am Funktionsanfang deklariert. D.h., dass du die Variable "erledigt" zweimal deklarierst.

        Beim anderen wird es wahrscheinlich ähnlich sein.

        Beispielcode:

        function bla(val_defekt) {
          log.debug("Start der Funktion");
          var erledigt = "";
          if(val_defekt == '1'){
            erledigt = "Anwender";
          }
          else{
            erledigt = "";
          }
        }
        

        bedeutet implizit:

        function bla(val_defekt) {
          var erledigt;
          log.debug("Start der Funktion");
          erledigt = "";
          if(val_defekt == '1'){
            erledigt = "Anwender";
          }
          else{
            erledigt = "";
          }
        }
        

        Jetzt siehst du, dass die Zuweisung des leeren Strings an erledigt unnötig ist, da du die Variable in jedem Fall überschreibst.

        Viele Grüße Matti

      2. Tach!

        nun wenn man den Code so abändert dann meldet JSlint, dass die Variabel bereits initialisiert ist.

        if(val_defekt == '1'){
           var erledigt = "Anwender";
        }
        else{
          var erledigt = "";
        }
        

        Ja, weil das var die Variable anlegt, egal ob das in einem bedingten Block passiert oder nicht.

        Wie also, ist es richtig?

        Der Punkt ist nicht das Anlegen, sondern dass außer diesem Anlegen keine Verwendung stattfindet (jedenfalls nicht im gezeigten Teil). In anderen Worten, es gibt nur Schreibzugriffe und keine lesenden. Die Variable ist also entweder überflüssig oder bei der lesenden Verwendung wurde getippfehlert und demzufolge irgendwas anders verwendet als beabsichtigt.

        dedlfix.

        1. Tach!

          Der Punkt ist nicht das Anlegen, sondern dass außer diesem Anlegen keine Verwendung stattfindet (jedenfalls nicht im gezeigten Teil). In anderen Worten, es gibt nur Schreibzugriffe und keine lesenden.

          Achja, der Matti hat genauer hingeschaut. Mehrfache Zuweisung ohne dass zwischendrin gelesen wurde, damit sind die früheren Zuweisungen überflüssig, in dem Fall die Initialisierung mit Leerstring.

          dedlfix.

        2. Hallo,

          Die Variable "erledigt" wird schon noch weiter hinten im Code ausgewertet.

          Wäre es dann so syntaktisch richtig?

          var erledigt;
          if(val_defekt == '1'){
              erledigt = "Anwender";
          }
          else{
           erledigt = "";
          }
          .…
          …
          //machedbEintrag(erledigt,,,);
          
          1. Hallo Werner

            var erledigt;
            if(val_defekt == '1'){
              erledigt = "Anwender";
            }
            else{
              erledigt = "";
            }
            

            Das kann man so schreiben, ist aber auf diese Situation bezogen für meinen Geschmack unnötig umständlich. In dem Fall, dass du einer Variable abhängig von einer Bedingung einen von zwei Werten zuweisen willst, wobei die Werte wie hier nur kurze Literale sind und die Bedingung kein komplizierter Term ist, bietet sich die Verwendung des ternären Operators an:

            var erledigt = val_defekt == '1' ? 'Anwender' : '';
            

            Hast du komplexere Bedingungen die aus mehreren Teilausdrücken zusammengesetzt werden, oder müssen für die Werte längere Ausdrücke aufgelöst werden, wird die Zeile also zu lang, dann würde ich allerdings aufgrund der im Vergleich dann schlechteren Lesbarkeit auf die Verwendung des ternären Operators verzichten.

            val_defekt == '1'
            

            Davon abgesehen würde ich anraten, grundsätzlich typsichere Vergleiche mit === durchzuführen, es sei denn, du kennst dich gut mit den Regeln für Coercion, also für implizite Typumwandlungen aus und weißt was du tust. Selbst in diesem Fall würde ich aber nach Möglichkeit auf == zugunsten eines typsicheren Vergleichs verzichten, da dies die Lesbarkeit des Codes verbessert. Es ist dann unmittlbar ersichtlich was du zu tun beabsichtigst, welche Fällte du berücksichtigen möchtest und welche nicht.

            if(val_defekt == '1'){
              erledigt = "Anwender";
            }
            

            Zum Stil wäre noch anzumerken, dass es in Sprachen in denen single und double quotes für die Begrenzung von Stringliteralen semantisch gleich sind, gute Praxis ist, sich zumindest lokal für eine Art Stringbegrenzer zu entscheiden und dies konsequent beizubehalten. Eine Ausnahme für diese Konvention wäre, wenn die Begrenzer für die man sich zuvor entschieden hat selbst als Zeichen in dem String vorkommen. In diesem Fall ist die Verwendung der jeweils anderen Stringbegrenzer dem Escapen der Zeichen im String vorzuziehen.

            val_defekt

            Darüber hinaus ist die Verwendung von snake case für Bezeichner in JavaScript, anders als beispielsweise in Python, eher ungewöhnlich. Hier ist es üblich camel case zu verwenden. Das bleibt aber natürlich dir überlassen, wobei ich aber anraten würde auch hier eine einmal getroffene Entscheidung konsequent beizubehalten.

            if(val_defekt == '1'){
              var erledigt = "Anwender";
            }
            else{
              var erledigt = "";
            }
            

            Zu diesem Beispiel von dir hat Matti ja schon etwas gesagt. Mehrfachdeklarationen in JavaScript sind irreführend und darum schlechte Praxis. Es gehört zu den Altlasten der Sprache, dass dies überhaupt möglich ist, und es ist einer der Gründe dafür, weshalb auf das Schlüsselwort var für die Deklaration von Variablen verzichtet werden sollte.

            Wann immer in JavaScript ein neuer Ausführungskontext erzeugt wird, für ein Skript, ein Modul oder eine Funktion, werden alle Bezeichner die in diesem Gültigkeitsbereich mit var deklariert wurden implizit mit dem primitiven Wert undefined initialisiert. Das passiert also noch vor der Ausführung der ersten Anweisung in dem jeweiligen Codeabschnitt. Es ist darum ohne weiteres möglich, eine Variable vor ihrer Deklaration im Code anzusprechen, selbst dann, wenn der Code in dem die Variable deklariert wird nie ausgeführt wird.

            function test() {
              console.log(identifier); // undefined
            
              if (false) {
                var identifier = 'value';
              }
            }
            

            Dieses Beispiel hier produziert also keinen ReferenceError, da der Name innerhalb der Funktion aufgelöst werden kann. Wann immer eine Variable mit var deklariert wird, dann wird — sofern es sich auch um einen Zuweisungsausdruck handelt — also tatsächlich nur der Wert zugewiesen. Die Bekanntmachung des Namens im lokalen Scope erfolgt schon vorher. Und da es sich nur um Wertzuweisungen handelt, schert sich der Interpreter entsprechend auch nicht um mehrfache Deklarationen innerhalb des selben Gültigkeitsbereichs.

            Anzumerken ist allerdings, dass die allermeisten Probleme die aus der mehrfachen Deklaration von Variablen in JavaScript entstehen auf den Umstand zurückzuführen sind, dass die mit var vorgenommenen Deklarationen gehoisted, also wie beschrieben implizit an den Anfang des jeweiligen Codeabschnitts gezogen werden. Andere Programmiersprachen wie beispielsweise Rust haben diese Probleme in der Praxis nicht, obwohl auch dort Mehrfachdeklarationen im selben Scope erlaubt sind.

            fn test() {
              // scope for first variable starts
              let number = 10;
            
              // scope for first variable ends
              let number = 20;
            }
            

            In Rust können Variablen zwar mehrfach im selben Scope deklariert werden, aber es ist anders als in JavaScript nicht möglich auf eine Variable vor ihrer Deklaration im Code zuzugreifen. Zum Beispiel würde ein println!("{}", number) in der ersten Zeile der oben definierten Funktion zu einem Fehler führen. Die zweite Deklaration beendet hier einfach den Gültigkeitsbereich der ersten Variable. Die Zuordnung ist immer eindeutig.

            Glücklicherweise wurden auch in JavaScript schon vor längerer Zeit Alternativen für die Deklaration von Variablen eingeführt, die nicht so problembehaftet sind wie var. Es ist heute gute Praxis die Keywords let und const zu verwenden, um in JavaScript Variablen beziehungsweise symbolische Konstanten zu deklarieren.

            function test() {
            
              if (true) {
                let identifier = 'value';
              }
            
              console.log(identifier); // ReferenceError
            }
            

            Ein wesentliches Merkmal von let und const ist, dass es sich hier um lexikalische Deklarationen handelt, die Sichtbarkeit von auf diese Weise deklarierten Variablen und Konstanten also nicht wie bei var vom jeweiligen Ausführungskontext abhängt, sondern immer an den Anweisungsblock geknüpft ist, in dem die Deklaration vorgenommen wurde.

            Wird eine Variable also wie in dem Beispiel oben im Anweisungsblock eines conditional statements deklariert, dann ist sie auch nur innerhalb dieses Anweisungsblocks sichtbar, nicht jedoch außerhalb im Körper der Funktion. Der Versuch von außerhalb des Blocks auf die Variable zuzugreifen erzeugt einen Fehler, da sie dort nicht sichtbar ist und die Referenz entsprechend nicht aufgelöst werden kann.

            Auf diese Weise kann der Code besser gekapselt werden. Bezeichner die nur lokal in einem Anweisungsblock gebraucht werden leaken nicht in den umgebenden Scope. Das verbessert einerseits die Lesbarkeit des Codes und hilft andererseits dabei Namenskonflikte und daraus resultierende Programmfehler zu vermeiden.

            let number = 10;
            
            // SyntaxError
            let number = 20;
            

            Mehrfachdeklarationen im selben Gültigkeitsbereich von Variablen die mit let deklariert werden führen immer zu einen Syntaxfehler. Bei const verhält es sich genauso. Das macht den Code transparenter. Man kann sich beim Lesen des Codes darauf verlassen, dass es für einen bestimmten Namen immer nur genau eine Deklaration innerhalb des jeweiligen Gültigkeitsbereichs gibt.

            const number = 10
            
            
            function test() {
              // temporal dead zone starts
            
              console.log(number); // ReferenceError
            
              // temporal dead zone ends
              const number = 20;
            }
            

            Darüber hinaus können weder mit let deklarierte Variablen noch mit const deklarierte Konstanten vor ihrer Deklaration im Code angesprochen werden. Dies erzeugt in jedem Fall einen ReferenceError. Zwar erfolgt auch bei solchen Deklarationen die Bindung des Namens schon bei der Erzeugung der lexikalischen Umgebung des jeweiligen Anweisungsblocks, wie das Beispiel mit der globalen Variable oben zeigt, jedoch werden die Variablen beziehungsweise Konstanten nicht mit einem Standardwert initialisiert.

            Was die Entscheidung angeht ob let oder const zu verwenden ist würde ich folgendes empfehlen: Wenn während der Lebenszeit einer Variable nur ein und derselbe Wert an den Bezeichner gebunden wird, dann sollte man statt einer Variable eine Konstante deklarieren. Das transportiert einerseits eine nützliche Information an den Leser des Codes, schützt andererseits vor dem unbeabsichtigten Überschreiben mit einem anderen Wert, und erlaubt darüber hinaus dem Interpreter Optimierungen am Code vorzunehmen. Variablendeklarationen mittels let würde ich für den Fall reservieren, dass wirklich mehr als einmal Werte zugewiesen werden sollen.

            Wohl alle modernen Ausführungsumgebungen für JavaScript unterstützen let und const, und solltest du auf die Unterstützung älterer Browser angewiesen sein, bestünde immernoch die Möglichkeit, modernes JavaScript zu schreiben und den Code vor der Auslieferung an den Client mit einem Transpiler wie Babel in ein kompatibles Format zu übersetzen.

            Viele Grüße,

            Orlok

            1. Hallo Orlok,

              wieder mal ein sehr ausführlicher und zielführender Beitrag von dir. 😀

              var erledigt;
              if(val_defekt == '1'){
                erledigt = "Anwender";
              }
              else{
                erledigt = "";
              }
              

              Das kann man so schreiben, ist aber auf diese Situation bezogen für meinen Geschmack unnötig umständlich.

              var erledigt = val_defekt == '1' ? 'Anwender' : '';
              

              Lesbarer ist ersteres für Hobbyprogrammierer (nicht abwertend gemeint, da ich selbst einer bin) imho auf jeden Fall. 😉

              Auch in der kurzen Variante könnten Klammern die Lesbarkeit erhöhen.

              var erledigt = (val_defekt == '1' ? 'Anwender' : '');
              

              Bis demnächst
              Matthias

              --
              Rosen sind rot.
              1. Tach!

                Lesbarer ist ersteres für Hobbyprogrammierer (nicht abwertend gemeint, da ich selbst einer bin) imho auf jeden Fall. 😉

                Für Anfänger gibt es Tutorials. In produktivem Code darf man ruhig schreiben, der für fortgeschrittene Leser gedacht ist.

                Es ist auch semantisch ein Unterschied, ob man einer Variable den einen oder den anderen Wert zuweist oder ob man in Abhängigkeit der Bedingung das eine oder das andere tut, was auch grundverschiedene Dinge sein können. Im Zweifelsfall muss man als Leser erstmal herausfinden, dass das doch die gleiche Variable oder vielleicht zwei verschiedene sind.

                Auch in der kurzen Variante könnten Klammern die Lesbarkeit erhöhen.

                var erledigt = (val_defekt == '1' ? 'Anwender' : '');
                

                Finde ich nicht. Vermutlich kommt dann aber der Linter um die Ecke und erzählt dir, dass die Klammern da überflüssig sind.

                Wenn man bei ternären Operator klammert, sollte man das bei komplexeren Ausdrücken tun, wenn die Bedingung mehr als einen Ausdruck enthält und man die Eindeutigkeit der Operatorenrangfolge technisch notwendigerweise angeben muss oder das zeigen möchte. Im letzteren Fall hat man aber wieder potentielles Lintergemecker.

                dedlfix.