Code zur Programmiertechnik in Classic Visual Basic
Zurückgeben eines Arrays aus einer Funktion
Es gibt zwei Möglichkeiten, ein Array aus einer Funktion zurückzugeben. Die eine Methode verwendet einen Rückgabewert vom Typ Variant
, die andere, die erst unter Visual Basic 6.0 funktioniert, gibt direkt ein Array zurück:
Verbesserung des Anwendungsverhaltens
Es gibt zahlreiche kleine Tips, mit denen man die Ausführungsgeschwindigkeit einer Anwendung verbessern kann. Allerdings gehen diese Möglichkeiten oft auf Kosten der Nachvollziehbarkeit und Einfachheit des Codes.
Performancezuwachs durch Weglassen von Steuervariablen
Nicht ganz der Logik eines Programmierers entsprechend, kann die Performance einer Anwendung durch Weglassen der Steuervariablen bei den Next
-Teilen von For
-Schleifen beschleunigt werden. Diese Steuervariablen sind nicht zwingend erforderlich, will man aber leicht nachvollziehbaren Code schreiben, sollte man sie trotzdem angeben:
Ganzzahlige Division
Wenn die Nachkommastellen eines Divisionsergebnisses nicht relevant sind, sollte eine ganzzahlige Division durchgeführt werden. Es wird also anstelle des /
-Operators der Operator \
verwendet.
Datentypen wählen und Vermeiden der Verwendung von Variant
Von der Verwendung des Universaldatentyps Variant
wird abgeraten. Einerseits sind Variablen dieses Datentyps speicherintensiv, andererseits sind Operationen auf Daten in diesem Datentyp langsam. Daher sollte immer in einem expliziten Typ deklariert werden, um Speicher zu sparen und die Ausführungsgeschwindigkeit zu erhöhen (abgesehen von einigen wenigen Fällen, in denen die Verwendung von Variant
sinnvoll ist).
Zwischenergebnisse wiederverwenden
Bei komplizierten Rechnungen sollte nicht der ganze Befehl in eine Anweisung geschrieben werden, sondern Zwischenergebnisse berechnet werden, die dann später kombiniert zum Gesamtresultat der Berechnung führen. Dadurch wird der Code etwas schneller. Zu beachten ist hier aber, daß es dabei zu Rechenfehlern in numerischem Sinn kommen kann, wenn nicht ordentlich aufgeteilt wird.
Erkennung leerer Zeichenfolgen
Oft ist es nötig, zu überprüfen, ob eine Zeichenfolge „leer“ ist, d. h., ihr Wert gleich ""
ist. Der direkte Vergleich mit einer Zeichenfolge der Länge 0 erscheint intuitiv als einfachste Lösung. Allerdings besteht eine bessere Möglichkeit: Man prüft, ob die Länge der Zeichenfolge gleich 0 ist. Nachstehendes Beispiel enthält den Code der beiden Varianten:
Logische Verknüpfungen
Man stelle sich das oft eintretende Problem vor, daß man eine boolesche Variable je nach Wert einer Variablen auf Wahr oder Falsch setzen will. In Gedanken würde der Code folgendermaßen aussehen:
Es geht aber noch besser, wie nachfolgend angegebener Code zeigt. Dabei wird der geklammerte Ausdruck evaluiert und der Variablen b
gleich der Wert, der bei der Evaluation herauskommt, zugewiesen. Die Klammern dabei sind optional, werden aber aus Konvention verwendet, damit nicht irrtümlicherweise eine doppelte Zuweisung interpretiert werden könnte (Visual Basic überlädt den Operator =
, weshalb eine doppelte Zuweisung in einer Anweisung nicht möglich ist).
Das gleiche funktioniert klarerweise auch mit Variablen anderer Datentypen. In die selbe Klasse fällt auch das nächste, noch einfachere Beispiel. Wir wollen zuerst den naiven Ansatz betrachten:
Besser wäre der folgende Code, bei dem einfach das Komplement von y
der Variablen x
zugewiesen wird:
Außerdem ist ein impliziter Vergleich intuitiver als ein expliziter. Der nachfolgende Code zeigt einen expliziten und einen impliziten Vergleich:
Die IIf
-Funktion
Die Funktion IIf
prüft, ob eine Bedingung erfüllt ist, und gibt dementsprechend einen von zwei Werten zurück. Der unten angegebene Code enthält die Version mit und ohne IIf
:
IIf
ist langsam, da es einige Typumwandlungen durchführt. Trotzdem erleichtert es in einigen Fällen die Programmierung, beispielsweise bei folgendem Code:
Zeichen in einer Zeichenfolge finden
Um ein Zeichen in einer Zeichenfolge zu finden, sollte die InStr
-Funktion verwendet werden. Das Durchlaufen aller Zeichen bis zum Ende im schlechtesten Fall ist in Visual Basic viel langsamer, als wenn dies von der Laufzeitbibliothek durchgeführt wird.
Alphanumerische Zeichen
Um zu prüfen, ob ein Zeichen alphanumerisch ist, könnte man z. B. den Zeichencode daraufhin prüfen, ob er sich in den Bereichen befindet, in denen die alphanumerischen Zeichen liegen. Allerdings ist diese Methode umständlich und langsam, schneller ist die Verwendung der API-Funktion IsCharAlphaNumeric
.
Verknüpfen von Zeichenfolgen
Der Operator &
wird verwendet, um Zeichenfolgen zu verknüpfen (konkatenieren). Wenn es möglich ist, sollte die Zeichenfolge als Konstante angegeben werden oder über die Funktion Mid
der entsprechende Teil fester Länge ausgetauscht werden.
Vergleichen von Zeichen
Nehmen wir an, wir hätten ein Zeichen in einer Zeichenfolge der Länge eins mit dem Namen s
vorliegen und den Zeichencode eines Zeichens in y
. Jetzt soll geprüft werden, ob der Zeichencode dem Zeichen entspricht. Dazu gibt es zwei Möglichkeiten, die im Folgenden angegeben werden. Die zweite Methode ist schneller als die erste:
Der Datentyp Object
Der Datentyp Object
sollte aus Gründen der Ausführungsgeschwindigkeit nicht verwendet werden. Stattdessen sollte eine Deklaration im erwarteten Typ erfolgen. Beispiel hierzu wäre eine Prozedur, die ein ListBox-Steuerelement übergeben bekommt, um darin Einträge hinzuzufügen. Viele Programmierer übergeben dies in einem Object
-Parameter, wohingegen einer vom Typ ListBox
in zweierlei Hinsicht von Vorteil sein würde: Der Programmierer, der die Funktion wiederverwenden will, weiß gleich, welchen Datentyp er übergeben muß und es können keine Fehler bei der versehentlichen Übergabe falscher Datentypen auftreten.
Objekte anlegen und löschen
Um eine Instanz eines Objektes anzulegen, stehen in Visual Basic zwei Möglichkeiten zur Verfügung, die jedoch eine unterschiedliche Semantik haben:
Die erste Deklaration mit As New
bewirkt im Gegensatz zur zweiten, daß eine Standardinstanz erstellt wird, auf die von der mit As New
deklarierten Variablen verwiesen wird. Es ist z. B. nicht möglich, Bill
auf Nothing
zu setzen.
Das Löschen einer Instanz erfolgt durch das Setzen auf den Wert Nothing
, sofern keine anderen Verweise auf das Objekt mehr existieren. Im nächsten Beispiel wird z. B. ein Collection
-Objekt geleert, indem die darauf zeigende Variable auf Nothing
gesetzt wird und dieser anschließed eine neue Instanz zugewiesen wird. Diese Vorgehensweise ist schneller, als alle Elemente „manuell“ zu entfernen:
Daten schnell kopieren
Da bei Visual Basic Variablen während der Laufzeit nicht im Speicher verschoben werden, ist es möglich, einen Zeiger auf die Speicherstelle zu ermitteln, an der eine Variable steht. Dieser kann dann in Verbindung mit der API-Funktion CopyMemory
bzw. RtlMoveMemory
dazu benutzt werden, Daten an diese Stelle zu kopieren. Bei Grafikoperationen findet diese Methode oft Anwendung, da sie sehr schnell ist. Das Kopieren von Arrays mit dieser Funktion ist wesentlich schneller, als wenn jedes Element einzeln, beispielsweise in einer For
-Schleife, kopiert werden würde.
Aufspalten von If
-Konstrukten
Visual Basic unterstützt nur vollständige Evaluation, d. h., wenn zwei Ausdrücke mit einem And
verknüpft sind und bereits der erste zu False
evaluiert, wertet Visual Basic trotzdem den zweiten Ausdruck aus, auch dann, wenn das Resultat der gesamten Bedingung bereits sicher feststeht:
Im oben angegebenen Code werden im ersten Beispiel immer beide Funktionen ausgewertet, im zweiten Beispiel jedoch die Funktion b
nur dann, wenn bereits a
zu True
evaluierte. Wenn die Reihenfolge der Auswertung egal ist, sollte die schnellere Funktion außen ausgewertet werden und die langsamere geschachtelt im If
. Sind beide Funktionen ungefähr gleich schnell, sollte diejenige, die öfter zu False
evaluiert, außen ausgewertet werden. Diese Technik funktioniert allerdings nur dann, wenn nicht beide Funktionen ausgewertet werden müssen; das ist beispielsweise der Fall, wenn in jeder der Funktionen persistente Änderungen durchgeführt werden.
Select Case
und If
Wenn geprüft werden soll, ob eine Variable einen von mehreren Werten besitzt, sollte Select Case
anstelle von If
verwendet werden, wobei beachtet werden sollte, daß die am häufigsten eintretenden Fälle bei Select Case
am weitesten oben plaziert werden:
Aus obenstehendem Code wird dann:
Vereinfachung boolescher Rückgabewerte
Der folgende Ausschnitt kann auch ohne If
-Block geschrieben werden:
Vereinfachter Code (man kann die Klammern beim rechten Ausdruck auch weglassen):
Funktionsversionen mit und ohne $
Einige Funktionen in Visual Basic, z. B. Mid
, liegen in zwei Versionen vor: Mid
und Mid$
. Der Unterschied besteht darin, daß Mid
einen Wert vom Typ Variant
retourniert, Mid$
hingegen eine Zeichenfolge. Die Wahl der richtigen Version kann enorme Laufzeitverbesserungen nach sich ziehen. Einige der Funktionen besitzen zudem Byte
-Pendanten, beispielsweise MidB
. Diese Funktionen sind noch etwas schneller.
Collection
versus Array
Oft wird gefragt, ob die Verwendung von Collection
-Objekten Arrays vorzuziehen ist oder umgekehrt. Dazu gibt es keine eindeutige Antwort, sondern es muß je nach Fall entschieden werden. Allgemein kann man folgende Dinge feststellen, die für die Verwendung der Klasse Collection
sprechen:
- Iteration über die Elemente in Reihenfolge des Hinzufügens
- Dynamische Datenmenge
- Zugriff über Index und Schlüssel
- Löschen von Elementen „aus der Mitte“
- Oftmaliges Löschen und Einfügen
- Selten Zugriff über Index
Für ein Array spricht hingegen Folgendes:
- Iteration in Indexreihenfolge
- Oftmaliges Ändern von Werten
- Selten Hinzufügen bzw. Zwischen-Elemente-Einfügen
- Seltenes Löschen
- Arrays benötigen weniger Speicherplatz als
Collection
-Objekte
Im Normalfall wird man eher ein Array verwenden, die Klasse Collection
sollte verwendet werden, wenn die speziellen Eigenschaften wie z. B. das Ansprechen eines Wertes über einen Schlüssel, benötigt werden.
Vielerorts hört man, daß die Verwendung von Collection
-Objekten aus Gründen der Performanz nicht empfehlenswert ist. Der Grund dafür liegt im falschen Einsatz der Klasse Collection
. Will man nämlich alle Elemente einer Collection
durchlaufen, sollte dies nicht mit einer For…To
-Schleife gemacht werden, die die Elemente nach ihren Indizes durchgeht, sondern über eine For…Each
-Schleife.
Der Grund dafür ist, daß Visual Basic die Daten der Collection
für den Indexbasierten Zugriff als lineare Liste speichert, wobei man immer nur vom Anfang der Liste zum nächsten Element gelangen kann. Jedes Listenelement besitzt einen Zeiger auf das nachfolgende Element, die Indizes sind nur virtuell vorhanden, indem, wenn z. B. das 10. Element in der Liste abgefragt wird, einfach 10 Mal vom ersten Element in der Liste weg immer zum Nachfolger des Elements in der Liste weitergeht.
Führt man das 100 Mal durch, läuft Visual Basic beim ersten Mal (für das Element mit Index 1) direkt zum ersten Element, beim zweiten Element wird wieder am Anfang der Liste (erstes Element) begonnen, dann zum zweiten Element weitergegangen, dabei ein Zähler inkrementiert und geprüft, ob er bereits gleich dem gesuchten Index ist. Anschließend wird dieses Element zurückgegeben. Dasselbe wird dann für alle weiteren Elemente durchgeführt, sodaß insgesamt zum Durchlaufen einer Liste mit Elementen insgesamt die Summe von bis Schritte erforderlich sind. Verwendet man nun eine For…Each
-Schleife, wird nicht anhand der Indizes durchmustert, sondern direkt innerhalb der Liste nur zum nächsten Element (anhand des Zeigers) weitergegangen:
Image- versus PictureBox-Steuerelement
Eine häufig gestellte Frage ist, was der Unterschied zwischen dem PictureBox- und Image-Steuerelement ist und wann man sich für welches der beiden Steuerelemente entscheiden soll. Das Image-Steuerelement benötigt weniger Speicher, allerdings stellt es weniger Funktionen zur Verfügung. Eine PictureBox muß verwendet werden, wenn mit Zeichenbefehlen in das Bild gezeichnet werden soll, ein Image-Steuerelement hingegen dann, wenn nur eine Grafik angezeigt werden soll. Interessant ist die Möglichkeit des Image-Steuerelements, die darin enthaltene Grafik zu strecken.
Grafikoperationen
Müssen viele Grafikoperationen hintereinander durchgeführt werden, ist es in einigen Fällen ratsam, auf die entsprechenden API-Funktionen zurückzugreifen. Will man jedoch mit Visual-Basic-eigenen Funktionen arbeiten, dann sollte man einige Hinweise beachten. Um ein Rechteck zu zeichnen, sollte die Line
-Anweisung mit der Option B
verwendet werden; das Zeichnen von vier einzelnen Linien ist viel aufwendiger, fehleranfälliger und auch langsamer. Um Punkte zu zeichnen, sollte die Line
-Anweisung anstelle von PSet
verwendet werden, da sie schneller ist.
Grafiken richtig verwenden
Visual Basic unterstützt mehrere Bildformate, die angezeigt werden können. Intern kennt Windows aber nur das Bitmap-Format. Wird eine Grafik geladen, muß sie zuerst evtl. dekomprimiert und anschließend in dieses Format transformiert werden. Das ist zeitaufwendig. Wenn es schnell gehen soll, sollten daher direkt Bitmaps anstelle von GIF- oder JPEG-Grafiken benutzt werden. Der Nachteil davon ist, daß die Dateien sehr groß sind. Muß man Speicherplatz sparen und ist dadurch auf komprimierte Formate angewiesen, sollte man nicht bei jeder Verwendung die Grafik neu laden, sondern sie zuerst in eine PictureBox einfügen und dann in alle Bereiche hineinkopieren, in denen man sie braucht.
Bewegen von Steuerelementen und Formularen
Um Steuerelemente oder Formulare zu verschieben, sollte deren Move
-Methode verwendet werden. Das getrennte Setzen von Left
und Top
ist etwas langsamer.
Zugriff auf Eigenschaften von Steuerelementen
Benötigt man oft die Werte der Eigenschaften von Steuerelementen, ohne diese inzwischen zu verändern, dann sollte man diese in eine Variable zwischenspeichern, da das Ermitteln des Eigenschaftswertes länger dauert, als wenn man direkt den Wert aus einer Variablen verwendet.
Steuerelementfelder
Steuerelementfelder belegen weniger Speicher als einzelne Steuerelemente. Daher sollte man zumindest für Beschriftungen und statische Elemente der Benutzerschnittstelle Steuerelemente indizieren. Natürlich ist dies auch sinnvoll, wenn man auf Steuerelemente über den Index zugreifen will.
Die ClipControls
-Eigenschaft
Diese Eigenschaft, z. B. bei Formularen zu finden, sollte immer auf den Wert False
festgelegt sein. Die Eigenschaft gibt an, ob bei Aktualisierungen der Anzeige auch der Bereich unter den Steuerelementen aktualisiert werden soll. Meist besteht dazu aber kein Grund und es kann dadurch ein störendes Flackern verhindert werden, das ab und zu in Zusammenhang mit dieser Eigenschaft auftritt.
Anwendungsstart
Das Starten von Anwendungen sollte so schnell wie möglich ablaufen, im Normalfall sollte es nicht mehr als zwei Sekunden auf einem etwas älteren Rechner beanspruchen. Oft kann das aber nicht eingehalten werden und man muß auf Methoden zurückgreifen, die beim Benutzer den Eindruck erwecken, daß der Ladevorgang schneller vor sich geht.
Eine Möglichkeit dazu wäre, ein Willkommensformular anzuzeigen, auf dem Logo und Informationen zum Programm zu lesen sind. Bei besonders langen Ladevorgängen könnte hier auch der Status des Ladevorgangs integriert werden, entweder durch Angabe des gerade laufenden Operation oder über eine Fortschrittsanzeige. Bei sehr langen Ladezeiten (z. B., wenn eine Verbindung in das Internet oder zu einer Datenbank aufgebaut werden muß), könnte man dem Benutzer über eine kleine Animation, z. B. ein sich drehendes Rad, eine Rückmeldung geben, ob die Anwendung aktiv und nicht hängen geblieben ist. Eine andere Möglichkeit besteht darin, in der Form_Load
-Prozedur vorzeitig ein Me.Show
durchzuführen, um das Formular anzuzeigen, bevor der Ladevorgang durchgeführt wurde. Hier können jedoch Probleme auftreten, wenn der Benutzer Steuerelemente betätigt, die noch nicht initialisiert sind.
Die beiden genannten Maßnahmen ändern nichts an der tatsächlichen Zeit, es kann dadurch der Ladevorgang sogar noch langsamer ablaufen, doch es wird dem Benutzer suggeriert, daß die Anwendung bereits geladen ist oder daß ein Arbeitsprozeß durchgeführt wird – die wahrgenommene Geschwindigkeit steigt. Im Folgenden werden Methoden zur Beschleunigung angegeben, die dazu dienlich sind, die wirkliche Startzeit zu verringern. Formulare sollten nicht beim Start geladen werden, d. h., man soll nicht alle Formulare und Module gleich beim Anwendungsstart in den Speicher laden. Die Formulare können dann zwar schnell angezeigt werden, okkupieren aber schon von Anfang an Speicher, der sonst überhaupt nicht benötigt worden wäre.
Arbeitsspeicherbedarf
Beim Entwurf eines Programms sollte man darauf achten, daß nicht sinnlos Speicher belegt wird. Wenn nicht alle Programme, die aktiv sind, in den Arbeitsspeicher passen, werden Teile davon auf den Externspeicher, d. h., die Festplatte, ausgelagert. Diesen Vorgang nennt man Auslagerung, da dabei Speicherseiten ausgelagert und bei Bedarf wieder in den Hauptspeicher geladen werden. Durch diese Zugriffe kann die Laufzeit eines Programms in Mitleidenschaft gezogen werden. Es gibt eigene Werkzeuge, die Probleme in Bezug auf Arbeitsspeicherbelegung aufzeigen.
Toter Code
Unter totem Code versteht man Quellcode, der keine Funktion hat; z. B. eine Variable, die deklariert, allerdings nie verwendet wird. Dadurch wird Arbeitsspeicher sinnlos belegt bzw. reserviert. Mit speziellen Werkzeugen kann toter Code aufgespürt und entfernt werden, allerdings sollte man dafür sorgen, daß es nicht zu totem Code kommt. Toter Code ist nämlich ein Produkt iterativer Programmentwicklung. Iterative Programmentwicklung verfügt oft nicht über eine Analyse- und Entwurfsphase, sondern es wird ungeplant einfach „drauf los“ programmiert. Häufig sind dabei Änderungen des bisher erstellten Codes erforderlich, wodurch das Entstehen von totem Code erleichtert wird.
Inline-Code verwenden
Inline-Code bedeutet, daß Code einfach untereinander im „Hauptcode“ steht, und nicht in Prozeduren zusammengefaßt wird, die aufgerufen werden. Dadurch wird die Anwendung oft schneller, allerdings belegt sie mehr Speicher, da Code mehrfach vorhanden ist. Daher sollte man in zeitkritischen Anwendungen kleine Teile wie z. B. die Bestimmung eines Minimums von zwei Werten nicht in eine eigene Prozedur packen, sondern an jeder Stelle, an der die Funktion benötigt wird, den Code direkt angeben.
Zeilennummern
Zeilennummern stammen noch aus Vorläufern von Visual Basic. Damals konnten Zeilen anhand ihrer Nummer oder einer Bezeichnung angesprungen werden. Man kann Zeilennummern in Visual Basic 6.0 zwar noch immer verwenden, jedoch benötigen sie relativ viel Speicherplatz.
Module richtig verwenden
Visual Basic lädt Module „bei Bedarf“, d. h., wenn eine Funktion aus einem Modul benötigt wird, wird das gesamte Modul in den Speicher geladen. Aus diesem Grund ist es sinnvoll, mehrere kleine Module anstatt eines großen Moduls zu verwenden, nicht zuletzt deshalb, weil das Editieren des Quellcode dadurch erleichtert und die Übersichtlichkeit des Projekts gewahrt wird. Um die Anwendung schneller anzeigen zu lassen, sollten in der Form_Load
-Prozedur des Startformulars keine Prozeduren aus anderen Modulen benutzt werden.
Debuginformationen
Debuginformationen sollten immer gelöscht und nicht mit der Anwendung ausgeliefert werden. Vor der Ausgabe des Programms sollte immer eine Releasekompilierung erfolgen. Debuginformation erleichtern es, Informationen über den Quellcode einer Anwendung zu erhalten, die der Endbenutzer eigentlich nicht bekommen sollte. Weiters wird unnötiger Arbeitsspeicher durch Debugcode in der Anwendung belegt.
Kompilieren zu P-Code oder Maschinencode?
Sehr rechenintensive Programme sollten zu Maschinencode kompiliert werden. Wenn Kompatibilität mit Debuggern und schnelle Ausführung gefragt ist, stellt Maschinencode die bessere Wahl dar. Jedoch sind Programme mit vielen Operationen auf Zeichenfolgen oder API-Aufrufen immer fast gleich schnell. P-Code wäre dann die bessere Lösung, da Programme dadurch eine geringere Dateigröße aufweisen. Bei Anwendungen, die über das Internet verbreitet werden, sollte wegen der Dateigröße P-Code der Vorzug gegeben werden.
Globale Eigenschaften
Es ist möglich, in einem normalen Modul öffentliche Eigenschaften zu definieren. Diese stellen eine interessante Alternative zu öffentlichen Variablen in Modulen dar, da zugewiesen Eigenschaftswerte auf Validität geprüft werden können. Außerdem können Eigenschaften vor Lesezugriff geschützt werden, was bei Variablen nicht möglich ist.
Daten in Modulen verstecken
Gibt es in einem Modul eine Variable auf Modulebene, die auch nach außen hin gelesen werden können muß, wird man sich meist mit damit behelfen, die Variable als öffentlich zu deklarieren. Damit ist die Variable aber nach außen hin sichtbar und nicht mehr gegen Änderung geschützt. Hier kann mittels einer öffentlichen Prozedur, die die private Variable retourniert oder aber einer schreibgeschützten Eigenschaft Abhilfe geschaffen werden. Im Folgenden wird der Code einer Funktionsprozedur und einer Eigenschaft zur Rückgabe des privaten Mitglieds angegeben: