Das ewige Leid der Programmierer

Eigentlich ist ja alles sooo einfach: Man bekommt eine Aufgabe und die soll man softwaretechnisch umsetzen. Also sucht man sich ein paar Wege und fängt an zu coden. Irgendwann stellt man dann fest, dass es hier nicht weitergeht. Also geht man zurück auf Anfang und durchdenkt das gesamte Konzept nochmal. Dann ist man wieder fertig und zeigt das Ganze dem Auftraggeber und prompt kommen wieder weitere Anmerkungen ("Ja und wenn das Programm mal abschmiert, was passiert dann?"). Also googelt man nach Fehlerbehandlungsmethoden für VB.NET und implementiert diese. Das tolle dabei ist: Man muss eigentlich nur noch eine Methode schreiben (außer an sehr schwierigen Stellen) und falls nun irgendwo was schief gehen sollte (beispielsweise sagt Windows: "Dein COM-Objekt mag ich nicht, deshalb kill ich es mal!"), wird das Programm beendet. Wenn es nicht gerade die Funktion für den Mailversand war, die der Auslöser war, wird noch eine Fehlermeldung verschickt!

Ach und außerdem soll noch was im Konzept geändert werden, was wir eigentlich ja auch so besprochen haben. Dummerweise habe ich es aber dann wieder anders verstanden und es lässt sich auch nicht wegkürzen. Naja, also ändert man eben wieder seinen Code und hofft, dass das nächste Mal alles in Ordnung ist. Solche Momente sind ein bisschen enttäuschend, aber lieber solche Momente (also Fehlersuche vor dem produktiven Einsatz), als wenn hinterher 5 Kollegen rummeckern (äh, das geht nicht....).

Ach und btw: VB.NET ist auf der einen Seite toll, man muss nicht viel machen. Aber auf der anderen Seite sind Bedingungen sehr krass dargestellt. In C oder von mir auch C# schreibst du eben einen 2 Zeiler:

If x = 0;
doing = "nix";

In VB.NET:

If x = 0
doing = "nix"
End If

Man hat zwar keine ; aber braucht manchmal ewig viele Zeilen. In solchen Momenten verflucht man dann doch mal VB. Aber gut, da ich sowieso alles auf Konsolenebene programmiere, ist mir das auch egal. Hauptsache das COM-Objekt arbeitet für mich und macht was ich will. :)

Warum ich das schreibe? Einfach so. Weil ich endlich mal das Programm live einsetzen möchte und nicht immer warten... ;) Aber heute haben wir alles besprochen und nun müsste es gehen.

Von der buero+ COM-Programmierung

Oftmals blocke ich alles Neue, mir Unbekannte ab. Erstens kenne ich es nicht und zweitens kann ich den Arbeitsaufwand immer schlecht einschätzen. Doch im Zuge einer Erweiterung des Lagers musste nun doch die immer wieder aufgeschobene COM-Programmierung in Angriff genommen werden. Der Angst davor war im Endeffekt aber dann doch ziemlich unbegründet, sondern eher aus Unwissenheit aufgebauscht worden. Schon im Februar im Rahmen der automatischen PDF-Auswertung-und-in-Datenbank-Arbeit war die COM-Programmierung ein sehr großer Knackpunkt. Nun habe ich die Dokumentation aber doch noch einmal genauer angelesen und festgestellt, dass auch ein paar brauchbare Delphi und VB Beispiele dabei sind. Damit war dann der grundsätzliche Aufbau schnell geklärt und mir war auch klar, dass ich wegen mangelnder Kenntnis der Objektorientierung zum damaligen Zeitpunkt nicht durchgestiegen bin. Damals hatte ich von einem Programmierer ein Beispiel zur Implementierung der Datenbankänderung in C# bekommen. Als Gegenzug gabs für den netten Menschen eine Packung Gummibärle.
Da ich C# nie gelernt habe, habe ich den Code anfangs auch nicht verstanden. Aber es hat funktioniert. Dank Debugging des C# Programms wurden mir dann auch die Aufrufe etwas klarer und die Doku machte auf einmal wieder mehr Sinn. Vor allem die Try... Catch... Finally... Anweisungen haben mich ein wenig gewundert. Doch das macht nun natürlich auch Sinn, denn nur so kann ich elegant auf Exceptions (also Ausnahmefehler, wenn beispielsweise die Datenbank belegt ist) reagieren.
Und nach ein bisschen ausprobieren und abschauen von den ganzen anderen Funktionen und Beispielcodes ging das meiste recht flott von der Hand. So habe ich zum Beispiel entdeckt (bzw. wurde darauf hingewiesen), dass ein Lagerbuchungsassistent existiert. Somit wird schon mal Arbeit erspart.
Und wenn ich mal nichts mehr direkt wusste und manche Fehlermeldung komisch erscheint gibt es ja immer noch Google. Beispielsweise eine Umwandlung von DBNull nach Double ist nicht möglich. Also muss noch eine Abfrage rein um zu prüfen ob das Ergebnis DBNull ist. Ich konnte es zwar nicht nachvollziehen, da in der WaWi es richtig angezeigt wird, aber naja... :)
Nach anfänglichen Bedenken, ob die 9 Wochen Arbeitszeit reichen würden, stand das komplette Grundgerüst innerhalb 3 Tagen zusammen. Es hat sogar einwandfrei funktioniert, doch die Fehlerbehandlungsroutinen fehlten noch und es war ziemlich chaotisch. Nun ist es ein bisschen geordneter und vor allem übersichtlich nach Funktionen sortiert. (Aktuell sind es mit Kommentaren und teilweise Leerzeilen ca. 400 Zeilen Code!) Als dass dann fertig war kam noch die zweite Anforderung, nämlich eine Excelliste mit den lieferfähigen Artikeln erstellen zu lassen. In der WaWi ist dazu eine Funktion implementiert, welche leider keine genauen Rückschlüsse auf die verwendeten Datenbanken zulässt. Doch ein Blick in die Variablenliste hat dann auch wieder manches Licht ins Dunkel gebracht. Die kannte ich wenigstens schon, da man diese auch öfters mal für den Druckdesigner braucht.
Je mehr ich mich damit beschäftige, desto mehr Respekt bekomme ich vor diesem WaWi. Gleichzeitig wird mir aber auch immer klarer, warum andauernd so viele EAccess Violations auftreten. Das System ist einfach ziemlich mächtig von den Funktionen und Möglichkeiten her, was aber leider auch immer wieder neue Fehlerquellen eröffnet. Doch diese sind ja meistens gut abgefangen.

"Wenn du meinst dein System sei idiotensicher, dann erfinden die User einen neuen Idioten."

Hat irgendjemand mal gesagt und leider ist damit auch ziemlich viel wahres dran.
Hoffentlich finden wir bis zum September alle Fehlerquellen und die passenden Lösungen.

Ein Gutes hat das Ganze auch: Ich habe wieder ein Ziel und damit wieder neue Lust aufs Programmieren bekommen. Ist zwar nur Konsolenprogrammierung, aber das reicht ja auch. Und wenn man es bedenkt ist COM-Programmierung auch relativ angenehm. Man greift auf fertige Funktionen zurück und muss diese eigentlich nur noch nutzen. Vielleicht noch ein wenig darauf achten, dass diese auch effizient ablaufen. Die COM-Schnittstelle ist nämlich schon recht langsam.
So im Nachhinein bin ich fast schon froh, dass mein PDF-Umwandlungsprogramm nicht zum Einsatz kam. Denn die vielen externen Programmaufrufe kann ich nun doch um einiges eleganter lösen. Vor allem macht es Sinn alles in einer Sprache zu schreiben. Das erleichtert auch das Warten des Codes. :)

Kommentare im Quellcode

Gehörst du auch zu den Leuten, die meinen, dass man auch ohne Kommentare im Quellcode gut programmieren kann? Eigentlich hast du damit recht. Das Compilieren geht vielleicht auch ein paar Sekunden schneller, wenn er die Kommentare nicht mitlesen muss.

Doch es ist sehr wichtig, wenn man gewisse Stellen einfach auskommentiert. Mir geht es zum Beispiel so, dass ich jetzt einen Code, denn ich vor 3 Monaten mal reingehackt habe kaum mehr lesen kann. Das macht die Sache nicht besonders einfach.

Also fangt an und nehmt euch die Zeit und kommentiert auch mal Sachen aus. In den üblichen Sprachen wie PHP, C, C#, VB und Co. wird einfach ein // genommen und schon ist die Zeile auskommentiert.

Oder eben mal /* */ um bestimmte Funktionen auszuschalten.

Damit wird vielleicht der Code etwas länger, aber es hilft ungemein und erleichtert die Lesbarkeit des Codes auch später noch.

Wollte ich nur mal so anmerken, da ich gerade wieder alten Code von jemand anderem durchschaue um die COM-Programmierung vielleicht besser zu kapieren. Gar nicht so einfach. :)

Regexp in VB - ein kleines How To

Für mein letztes Projekt habe ich auch immer wieder Reguläre Ausdrücke gebraucht, da sonst das Auslesen bestimmter Werte unmöglich gewesen wäre.
Wer wissen will, was Reguläre Ausdrücke sind findet im Wikipedia eine gute Anlaufstelle.
Da es leider auch sehr viele unterschiedliche Implementierungen gibt, gehe ich mal auf die VB.NET Variante ein. Diese dürfte auch in allen anderen .NET Sprachen funktionieren, aber garantieren tue ich es nicht.

Übrigens eine gute Einstiegshilfe für Reguläre Ausdrücke gibts bei txt2re.com

Stell dir vor, du hast ungefähr immer gleiche bzw. ziemlich ähnlich aufgebaute Textzeilen, die sich wiederholen.

000010 02.123.7421.0       BUCHSENKONTAKT   4                                                QMM
       Ihre Artikelnummer: 21076
                      200 ST          20,83 EUR                                     /   100      ST                               41,66
       Sonderrabatt 1                 60,00-%                                                                                     25,00-
       Positionsnetto                  8,33 EUR                                     /   100      ST                               16,66
        ST
       Liefertermin (Tag): 25.02.2010

Nun sollst du diese Nummer heraus extrahieren: 02.123.7421.0
Der Aufbau der Ziffern ist immer gleich. Nur vorne kann auch mal ein Buchstabe vorkommen: also Z7.123.1234.0

Der reine Reguläre Ausdrück wäre wie folgt:

((\w{1}\d{1}|[0-9]{2})\.[0-9]{3}\.[0-9]{4}\.[0-9]{1})

1. Ziffernbereiche und Buchstabenbereiche mit Mengenangaben
Trennen wir also mal den Ausdruck auf: "(\w{1}\d{1}|[0-9]{2})\." (natürlich ohne die Gänsefüsschen " gesehen) ist eine Oder Bedingung. Zuerst wird gesucht, ob ein Buchstabe und eine Zahl nacheinander stehen ("\w{1}\d{1}" ) oder eben zwei Ziffern ("[0-9]{2}" ) und davon zwei Stück nacheinander stehen. Der obligatorische Punkt wird durch die Kombination \. dargestellt! Ein einzelner . würde in diesem Fall für ein beliebiges Zeichen stehen und das wäre ungeschickt. Um also nur den . zu finden, muss dieser noch maskiert werden durch ein Backslash \ gefolgt von dem . also eben \.
Eine vollständige Erklärung für solche Metazeichen gibts hier

Die anderen Zeichen wie "[0-9]{3}\." sind dann sicher recht einfach. In den eckigen Klammern [] steht der Ziffernbereich (es könnte auch [025] stehen, dann wären nur die Ziffern 0, 2 und 5 erlaubt) und in den geschweiften Klammern die Anzahl der Ziffern. Zu guter Letzt muss die Ziffernfolge wieder durch ein . beendet werden, bis die nächste Ziffernfolge folgt.

Diese Zeichenkette findet man sicher noch relativ leicht, da sie ziemlich eindeutig ist.

2. Ziffernbereiche (bzw. jede beliebige Bereichsgruppe) und min und max Angaben
Du willst aus dem oben gezeigten Text die Artikelnummer herausextrahieren.
Leider kann diese Artikel aus allen beliebigen Ziffern bestehen und entweder 5 oder 6 Ziffern haben.

Doch eine Regelmäßigkeit gab es: "Ihre Artikelnummer: 21076" Es steht immer der gleiche Text vor der Artikelnummer, nämlich: Ihre Artikelnummer
Warum also nicht ein Regexp schreiben und der die gesamte Zeichenkette (also Text plus Nummer) beinhaltet. Gesagt getan, hier also der Reguläre Ausdruck:

Dim re1 As String = ".*?"           'Non-greedy match on filler
Dim re4 As String = "(Ihre Artikelnummer: [0-9]{5,})"    'Word 3
Dim r2 As Regex = New Regex(re1 + re4, RegexOptions.IgnoreCase Or RegexOptions.Singleline)

So sieht also auch mal eine Beispielsyntax für die Verwendung der Regexp in VB aus. Für die Bedeutungen von IgnoreCase und Singleline gibts hier von Microsoft eine Erläuterung.
IgnoreCase beachtet also die Groß und Kleinschreibung, wenn das Muster gefunden wurde und Singleline gibt dem . die Bedeutung "alle Zeichen".

Der obige Ausdruck sucht nach dem Text "Ihre Artikelnummer: " und einer Nummer mit 5 Ziffern bzw. mehr Ziffern "[0-9]{5,}" Man kann in den geschweiften Klammern nämlich eine Mindestanzahl und eine Maximalanzahl angeben. Die Mindestanzahl steht vor dem Komma und die Maximalanzahl nach dem Komma.

Um also den regulären Ausdruck auch auszuwerten benötigen wir noch weiteren Code.

Dim m2 As Match = r2.Match(txt)     'txt ist eine ausgelesene Zeile
                If (m2.Success) Then                'h-team Nummer
                    Dim word3 As Group = m2.Groups(1)
                    'erste zwanzig Zeichen sprich "Ihre Artikelnummer: " abschneiden
                    Dim hteam As String = word3.ToString.Remove(0, 20)
                    exWS.Range("D" & zählerb - 1).Value = hteam 'Damit wird der Wert in Excel geschrieben. Kann aber auch jeder beliebe andere Befehl verwendet werden.
                End If

Das ist soweit also auch noch klar.

3. Bestimmter Preis in einer bestimmten Zahl finden
Doch nun wollen wir den Preis in der Positionszeile erwischen, das war damals für mich eine ernsthafte Herausforderung und nur dank diesem Forumseintrag habe ich endlich eine Lösung gefunden.

Und er hat es auch ziemlich gut beschrieben, deshalb möchte ich es gerne hier zitieren:

So geht's:

Zunächst testen ob in der Zeile steht: Das Wort  Positionsnetto gefolgt von     einem oder mehreren Zeichen bis die Zeichenfolge      ST kommt und auf diese wieder      ein oder mehrere Zwischenraumzeichen folgen.

Dann muß man noch berücksichtigen, daß der gesuchte Betrag mehrere Tausenderpunkte haben kann , z.B.     12.345.678,90  und immer genau 2 Dezimalstellen hat.

Das Suchmuster muß also so aussehen:

        Dim re7 As String = "(?<=Positionsnetto.+ ST\s+)([0-9]{1,3}.)*[0-9]{1,3},[0-9]{2}"   'word 5

Na alles klar? Zuerst wird also nach der Zeile gesucht, in der "Positionsnetto" vorkommt, dann dürfen ein oder mehere beliebige Zeichen vorkommen ".+" , dann folgt ST, dann wieder ein oder mehrere Zwischenraumzeichen "\s+" und dann kommen die Zahlen. Das ist oben schon ziemlich gut erklärt.

4. Als nächstes war noch die PE herauszufinden, dass ist eigentlich relativ leicht und ist im Prinzip nur eine abgewandelte Regexp von oben. Da ich es auch im Forum schon geschrieben habe, hier die Erklärung dazu:

Erstmal wieder danke, durch deine Erklärungen hat sich mir  dann auch das Suchmuster für die PE (1 oder 100) erschlossen.

Hier das Suchmuster, dass ich zuerst ausprobiert habe.

Dim re8 As String = "(?<=Positionsnetto.+ EUR\s+/\s+) [0-9]{1,3}" 'word 7

Das hat aber dann nicht genau hingehauen, da damit zwar

Positionsnetto                  8,33 EUR                                     /   100      ST                               16,66

und

Positionsnetto                 71,66 EUR /                                   100    ST                              71,66

abgedeckt waren.

Aber solche Zeilen wie diese hier:

Positionsnetto                  41,09 EUR / 100 ST                                                            82,18

wurden verworfen.

Also habe ich nun folgendes Suchmuster angewendet:

Dim re8 As String = "(?<=Positionsnetto.+ EUR.+) [0-9]{1,3}" 'word 7

Sprich in der Zeile muss das Wort Positionsnetto gefolgt von einem oder mehreren Zeichen bis die Zeichenfolge EUR kommt und auf diese wieder  ein oder mehreren Zeichen folgen und dann eine Zahl mit 1 oder 3 Ziffern (also entweder 1 oder 100).

Nur so als Hilfestellung, falls jemand mal ähnliche Probleme hat.

5. Und zu guter letzt wäre natürlich auch noch der Liefertermin interessant.

Dieser ist eigentlich ziemlich logisch aufgebaut. "Liefertermin (Tag): 25.02.2010"
Doch leider kommt es auch vor, dass "Liefertermin unbestätigt" drin steht.
Aber auch hier gab es wieder Hilfe von diesem Peter_Punkt:

So geht's:

Bei dieser Aufgabenstellung treten gleich mehrere Probleme auf:

Umlaute im Suchmuster (z.B. ä) sind nicht erlaubt.

Klammer () haben eine besondere Bedeutung. Wenn man sie als normale Zeichen behandelt wissen will, dann muß man \ voranstellen.

Außerdem muß man die erzielten Treffer nachbehandeln und Teile (z.B. "(Tag): ") entfernen.

Code-Beispiel:

Dim re6 As String = "(?<=Liefertermin )(\(Tag\): \d{2}\.\d{2}\.\d{4})|(unbest){1}"
            Dim r2 As Regex = New Regex(re6, RegexOptions.IgnoreCase Or RegexOptions.Singleline)
            Dim m2 As Match = r2.Match(txt)     ' In txt ist der zu untersuchende String

            If (m2.Success) Then
                Dim word3 As Group = m2.Groups(0)
                Dim treffer As String = word3.ToString
                If treffer = "unbest" Then      ' bis zum ä
                    treffer = "unbestätigt"
                Else
                    treffer = treffer.Replace("(Tag): ", "") ' den Vorspann vor dem Datum entfernen
                End If
                Debug.WriteLine(treffer)
            End If

Man sieht also: Reguläre Ausdrücke sind eine tolle Sache, da man sich damit viel Arbeit ersparen kann. Doch ohne Hilfe in konkreten Problemstellungen ist man ganz schön aufgeschmissen.
Ich hoffe, ich konnte dem einen oder anderen vielleicht eine kleine Hilfestellung sein.
Falls Verbesserungsvorschläge vorliegen. Einfach Kommentar posten und ich werde es korrigieren.