Ungenutzte Variablen ermitteln

  • Jeder wird es kennen, ein Skript wächst und wächst. Variablen werden deklariert und manchmal auch entfernt. Und zum Schluß weiß man gar nicht, ob alle deklarierten auch verwendet werden.
    Mit diesem Skript könnt ihr:
    - einmalig genutze Variablen (Standard) oder
    - alle verwendeten Variablen ausgeben lassen
    Die Ausgabe enthält:
    - Funktionsbereich (Funktionsname für Variablen in Funktionen, sonst 'Out_Of_Func')
    - Variablenname
    - Anzahl Vorkommen
    - Zeile(n) des Vorkommens
    Die Ausgabe erfolgt als:
    - String in die Konsole (Standard)
    - Array


    Ignoriert werden Variablen in Kommentarzeilen/-blöcken und ebenso Zählervariablen in For-Schleifen.


    Ich prüfe (noch) nicht gegen includierte Konstanten, sodass diese bei einmaliger Verwendung auch als 'ungenutzt' erscheinen. Diese Prüfung werde ich später noch einbauen. Im Moment testet mal, ob es bei diversen Skripten auch funktioniert.
    Es gibt eine bekannte Konstellation, in der ein Kommentar nicht erkannt wird:
    Bsp: $sVariable = 'irgendwas;' ; Kommentar dazu und dieselben 'Stringbegrenzer', wie in der Zuweisung, $sVariable enthält beliebigen StringIn dieser Zeile kann der Kommentar nicht lokalisiert werden, da das letzte Semikolon als Stringbestandteil interpretiert wird: ' ; Kommentar dazu und dieselben ' und somit der Variablenname im Kommentar mitgezählt wird. Da dieser Fall sicher sehr unwahrscheinlich ist sehe ich das aber als unkritisch an.


    Die Array-Ausgabe sieht z.B. so aus
    autoit.de/wcf/attachment/13135/


    Am einfachsten die Funktion in das SciTE-Menü einbinden "SciTEUser.properties"
    # END => DO NOT CHANGE ANYTHING BEFORE THIS LINE #-#-#-#-#-#
    # 39 Unused Vars
    command.39.*.au3="$(autoit3dir)\autoit3.exe" "$(SciteDefaultHome)\GetUnusedVars.au3" "$(FilePath)" "1" "0"
    command.name.39.*.au3=GetUnusedVars
    command.save.before.39.*.au3=1
    command.is.filter.39.*.au3=1
    command.shortcut.39.*.au3=Ctrl+Shift+Alt+U

    Den Shortcut könnt ihr natürlich anpassen, ebenso, wie den Pfad zur Datei.


    Die Aufrufzeile enthält zwei optionale Parameter, hier: "1" "0"
    Diese stehen für
    Alle-Variablen: "0" (Standard, einmalig) oder "1" (alle Vorkommen) und
    Ausgabe: "1" (Standard, in Konsole) oder "0" (als Array)


    Edit: Kleiner 'Guttenberg-Error' :D (gefixt)


    Hier der Code:


    ToDoList:
    - auf includierte Konstanten prüfen
    - auf includierte Globale Variablen prüfen
    - Globale Variablen gegen Variablen in Funktionen abgleichen

  • Schöne Idee. Ich hätte hier eine funktionierende Lösung, um Die Kommentare besser zu parsen ;)
    #include<Array.au3>


    $sString = " $sVariable = 'irgendwas;' ; Kommentar dazu und dieselben 'Stringbegrenzer', wie in der Zuweisung, $sVariable enthält beliebigen String"
    $a = StringRegExp($sString, "^\h*(\$\S+)\h*([-+*/&]?=)\h*((?:[^;'""]+|(['""])(?:[^\4]|\4\4)*?\4(?!\4))+)\h*(;?.*)$", 1)
    _ArrayDisplay($a)


    Wenn man nur Befehl und Kommentar trennen will, ist es so ausreichend: $a = StringRegExp($sString, "^\h*((?:[^;'""]+|(['""])(?:[^\2]|\2\2)*?\2(?!\2))*)\h*(;?.*)$", 1)
    ; [0] ist Befehl, [1] der letzte gefundene Stringbegrenzer, [2] der Kommentar
    _ArrayDisplay($a)

  • Ich bin nicht so fit in SciTE: wohin muss die GetUnusedVars.au3 kopiert werden und ist das so richtig in der SciTEUser.properties?


    Mein erstes Programm bestand aus ca. 2.000 Lochkarten und nachdem sie mir das erste mal runtergefallen sind, habe ich die letzten beiden der 80 Byte für eine Numerierung benutzt :rofl:

  • wohin muss die GetUnusedVars.au3 kopiert werden

    $(SciteDefaultHome) ist das Installationsverzeichnis von SciTE. Das würde ich nicht zum Speichern eigener Anwendungen empfehlen. Verwende besser einen Ordner im Bereich USER, indem du Skripte (au3 und lua) für SciTE ablegst. Z.B. C:\Users\USER\MySciTE.

    Am Besten speicherst du diesen Pfad in der SciTEUser.properties (SciTE-Optionen-Benutzereinstellungen) als:

    User.Scripts.Path=C:\Users\USER\MySciTE

    Dann kannst du später in den SciTE.properties auf den Pfad immer zugreifen in der Form $(User.Scripts.Path).


    In deinem Fall also entweder:

    command.39.*.au3="$(autoit3dir)\autoit3.exe" "C:\Users\USER\MySciTE\GetUnusedVars.au3" "$(FilePath)" "1" "0"

    oder:

    command.39.*.au3="$(autoit3dir)\autoit3.exe" "$(User.Scripts.Path)\GetUnusedVars.au3" "$(FilePath)" "1" "0"

  • Hallo BugFix,


    wie gut sieht es mit der geplanten Nutzung von Variablendeklarationen in Include Dateien aus?


    Bei mir habe ich dein Script mal auf ein (zugegebenermassen extrem umfangreiches) Projekt angesetzt und es gibt Variablen aus als 'Out_Of_Func' aus die in einer geschachtelten include Ebene enthalten sind. Bei dem Projekt sind die Variablen nicht in der Hauptroutine deklariert, sondern die Dekalaration erfolgt über eine include Datei die die Variablendeklaration enthält. Um es noch komplizierter zu machen, gibt es für die Internationalisierung der GUI auch Include-dateien in zweiter Schachtelungsebene (komme nicht dazu die Meldungen in eine ini-Datei zu verfrachten). Schachtelung ist also etwa in der Form Hauptcode => Include Globale Variablendeklaration => Include Internationale GUI-Meldung . Bin mir jetzt nicht sicher, ob eine solche Stuktur bei der Überprüfung überhaupt vorgesehen war.


    Bei normalen Skripten ohne Variablendeklaration in Include-Dateien funktioniert es bei mir prima.


    Danke PapaPeter

  • wie gut sieht es mit der geplanten Nutzung von Variablendeklarationen in Include Dateien aus?

    Kennst du vielleicht: Bei der Entwicklung des Skriptes fällt dir auf, dass sich noch ein ständig wachsender Haufen an Folgemaßnahmen erforderlich macht und dann schiebst du das erst mal vor dir her. :whistling: Ein wesentlicher Punkt (der mich auch bei meinem ManageIncludes gebremst hat) ist eine Routine zum rekursiven Auslesen/Vergleichen der Includes. Da habe ich schon etliche Ansätze probiert aber noch nichts (geschwindigkeitsmäßig) zufriedenstellendes gefunden. Dadurch dass viele Includes Querverweise auf andere Includes haben, landet man da schnell in einem InfinityLoop. <X

    Ich werde vermutlich das Parsen der Includes in ein externes (Nicht-AutoIt) Programm auslagern, das mit der notwendigen Geschwindigkeit arbeitet.

    Bei dem Projekt sind die Variablen nicht in der Hauptroutine deklariert, sondern die Dekalaration erfolgt über eine include Datei die die Variablendeklaration enthält. Um es noch komplizierter zu machen, gibt es für die Internationalisierung der GUI auch Include-dateien in zweiter Schachtelungsebene

    He, da ticken wir ähnlich. ;)

    In meinen (größeren) Projekten baue ich das so auf:

    - Mainskript (meist nur ein paar Zeilen: Includes, Initialisierungsaufrufe(Variablen, GUI), Mainloop)

    - getrennte Includes: Variablendeklaration, Variableninitialisierung, GUI-Erstellung, GUI-Funktionen, Programmfunktionen

    Aber ich halte das alles in einer Ebene.


    Bei derartigen Konstrukten ist das Problem, dass ein Include auf Variablen zugreift, welche in einem anderen Include deklariert sind, aber dieses Include noch nicht geparst wurde.

    Hier die richtigen Abhängigkeiten zu finden ist nicht ohne. Eigentlich müsste man dasselbe tun, wie der Interpreter: Alle Includes in einen gemeinsamen Kontext zusammenführen und dann parsen.

    Aber, wie schon oben beschrieben, wird eine Lösung für ManageIncludes auch hier ein bessere Umsetzung ermöglichen.

  • Da gebe ich Dir vollkommen recht, eine gute Idee zieht manchmal viel nach was dann ach noch alles berücksichtigt werden kann und das man im ersten Anlauf gar nicht gesehen hat.

    Wenn Du dich durch alle Dateien hangeln willst, würde ich folgendermassen vorgehen - aber das wäre nur mein Ansatz:
    Starte mit dem ursprünglichen Script arbeite dich Zeile für Zeile durch das Skript und kopiere den gescannten Teil eine Tempdatei.
    Sobald Du auf ein Include triffst - trage es in eine Liste ein. Wenn das Include vorher schon in der Liste war überspringe es, Wenn das Include vorher nicht in der Liste war, arbeite das Include jetzt analog zeilenweise ab, bis Du auf das nächste include triffst oder oder das Ende des includes erreichst.
    Hast Du das Ende des Includes erreicht mache mit dem Sourcecode in der übergeordneten Ende weiter bis du irgendwann das Ende der Haupdatei erreicht hast.
    Am Ende hast Du eine temporäre Datei die allen Sourcecode einmal in chronlogischer Reihenfolge enthält und den du dann parsen kannst.

    Include-Once würde ich ignorieren und Includes auch nicht mehr mehrfach einbinden (wegen der Gefahr von Schleifen).
    Das einzige Problem was Du jetzt noch hast ist die Zuordnung der Zeilen in der Tempdatei zu den eigentlichen Sourcecodedateien.

    Hier fallen mir zwei Ansätze ein, entweder Du führst eine Liste die Zuordnung jeder Zeile in der Tempdatei zu der Zeile und den Namen der Sourcecodedatei enthält (u.U. schneller aber Resoucen fressender) oder du erfasst nur eine Liste der Sprünge zwischen den Soucecodedateien mit jeweiligenj Zeilennummern (weniger Resourcen, aber schwieriger zu tracken). Diese Hilfslisten sollten sich auch parallel auch dazu nutzen zu lassen, wo man am Ende des Parsen einer Sourcecodedatei weiterfortsetzen muss.


    Am Ende musst Du also sogar noch mehr tun als der Interpreter, da es ja nicht reicht einen Fehler festzustellen, sondern man muß ihn ja auch noch lokalisieren.

    Die Umsetzung könnte ein interessante Herausforderung werden, aber fürchte Dir bleibt dazu vermutlich genauso wenig Zeit wie mir.
    Wenn ich in näherer Zeit (Sommerferien vielleicht) etwas Luft haben sollte und Du auch noch Unterstüzung möchtest, könnte ich Dich gerne unterstützen. Aber ich verstehe auch wenn das Du gerne das Heft in der Hand halten möchtest und Programmierstile sind auch nicht immer kompatibel. Sehe es einfach als Angebot das Du auch unbegründet ablehnen kannst - ich bin Dir in keinem Fall böse.


    PapaPeter

  • Zitat

    In meinen (größeren) Projekten baue ich das so auf:

    - Mainskript (meist nur ein paar Zeilen: Includes, Initialisierungsaufrufe(Variablen, GUI), Mainloop)

    - getrennte Includes: Variablendeklaration, Variableninitialisierung, GUI-Erstellung, GUI-Funktionen, Programmfunktionen

    Aber ich halte das alles in einer Ebene.

    Das ist bei mir ähnlich kurzes Hauptprogramm, das dann die Haupbestandteile also aufruft, also:
    * Globale Variablen und Allgemeine Autoit Includes (hier oder nach der Initialisierung ggf. Einbindung von länderspezifischen Parameter für Internationalisierung als getrenntes Include)

    * Programmspezifische Funktionsbibliotheken als includes definiert
    * Includes für die Initialisierung (Parameter aus SQL Datenbanken oder ini-dateien einlesen), ggf. auch Auto-Update des kompilierten Programms
    * Funktionen für die GUI (deren oberste Ebene) aus dem Hauptprogramm aufgerufen werden,

    * Include für das Gui-Handling und die Auswertung der Gui und zwar die Teile die nicht direkt aus dem Hauptprogramm aufgerufen werden. Gegebenfalls - je nach Umfang - noch getrennt wie es thematisch passt.


    Manchmal kann - zumindestens ich - es ablauftechnisch aufgrund der Deklarations-Abhängigkeiten nicht vermeiden die Includes zu schachteln, andererseits versuche ich halt thematische Blöcke pro include zu finden um sich nicht einen Wolf bein Debugging zu suchen.


    In meinen größten Projekt stecken einige 10.000 Zeilen eigener Code. Deshalb dokumentiere und modularisiere ich lieber extrem, als Code auf Teufel komm raus zu optimieren, Schliesslich will ich einen Fehler auch noch 3 Jahre später finden können, nur weil ich gerade mal was angepasst habe und jetzt der Fehler nicht auf Anhieb zu finden ist sondern durch die andere Nutzung ganz wo anders vergraben ist.


    PapaPeter

  • Hallo Bugfix,

    Wenn ich noch genauer nachdenke fallen 2 Dinge ein die ich noch variieren würde:

    Zitat

    ... Sobald Du auf ein Include triffst - trage es in eine Liste ein. Wenn das Include vorher schon in der Liste war überspringe es, Wenn das Include vorher nicht in der Liste war, arbeite das Include jetzt analog zeilenweise ab, bis Du auf das nächste Include triffst oder oder das Ende des Includes erreichst....

    Da man in der Regel nicht an "unnötig" deklarierten Variablen/Konstanten in Standard-Includes interessiert ist und um diese nicht jedes Mal durchsuchen zu müssen, würde ich eine Option vorsehen diese zu ignorieren. In dem Fall würde das Include einfach so übernommen, wie es im Sourcecode steht.


    Eine weitere Variation wurde ich ebenfalls [optional] noch in diesem Punktt vornehmen:

    Zitat

    ... Starte mit dem ursprünglichen Script arbeite dich Zeile für Zeile durch das Skript und kopiere den gescannten Teil eine Tempdatei....

    Im Sinne einer schnelleren Abarbeitung und in Anbetracht heutiger Speichergrößen, sollte es in der Regel reichen das Ergebnis in ein Array zu schreiben.


    Papa Peter

  • PapaPeter

    Dein letzter Post war 4:20 Uhr ?! - Und was machst du tagsüber? :rofl:


    Danke für deine Gedanken und das Hilfsangebot.

    Momentan erledige ich erstmal einiges "drumherum". Da ich die ManageIncludes endlich fertigstellen will schreibe ich mir erst mal externe Parser, in der Hoffnung dann auf das Abspeichern von Funktionen/Konstanten etc. verzichten zu können und tatsächlich On-The-Fly zu arbeiten. Mal sehen ob das performancemäßig möglich ist. Wird evtl. davon abhängen, wie ich IPC hier umsetzen kann.

    Ggf. werde ich dann auf dein Angebot zurückkommen.


    Und jetzt geh erst mal schlafen. :P

  • Und was machst du tagsüber?

    Im Augenblick machen wir in unserem Betrieb Schichtdienst - wegen Coronna und Reduzierungs des Ansteckungsrisiko. Auch wenn ich viel von zuhause arbeiten kann, muß ich dann auch in dem entsprechenden Zeitschema arbeiten.

    Durch die Schichtwechsel ist der Schlafrythmus immer etwas gestört, das wäre die Zeit wo ich für die Frühschicht aufstehen müßte - auch wenn ich diese Woche Spätschicht habe.


    PapaPeter