Von der Sinnhaftigkeit funktionierenden Code zu überarbeiten.
Eines der besten Fachbücher, die ich jemals gelesen habe, ist Refactoring: Improving the Design of Existing Code von Martin Fowler, Kent Beck et. al. von 1999. Vereinfacht ausgedrückt bezeichnet Refactoring die Kunst, die interne Struktur eines Programms zu verbessern, ohne sein Verhalten zu ändern. Anlässlich der zweiten Auflage nach 20 Jahren Refactoring: Improving the Design of Existing Code, 2nd Edition, in der sich Martin Fowler stärker auf JavaScript fokussiert, möchte von einem besonders plastischen Beispiel aus unserer Praxis berichten.
Wir werden von Kunden bisweilen beauftragt, die Performance von bestehenden Anwendungen zu verbessern. Häufig sind das Anwendungen, die teilweise seit zehn oder mehr Jahren ihren Dienst versehen. Eine dieser Anwendungen erhielt über Webservices eine XML-Datei, in der wiederum Base64 kodiert ein PDF-Dokument eingebettet ist. Das PDF-Dokument beinhaltet eingescannte Gutachten und Dateigrößen der XML-Datei von 6MB sind eher die Regel als die Ausnahme. Wir wurden unter anderem beauftragt, weil die Verarbeitung einer solchen XML-Datei 12,5 Minuten dauerte.
Wir haben im existierenden Code Profilingmethoden aus unserem LotusScript-Framework eingebaut, um die Verarbeitungsdauer der einzelnen Abschnitte zu messen. Schnell wurde klar, dass allein 9 Minuten für das Umkodieren des Base64-Dateianhangs benötigt wurden. Der Originalcode verwendete dafür einen Algorithmus, der von Julian Robichaux 2002 in einem Blog-Artikel "Encryption Techniques in LotusScript" beschrieben wurde. Julian Robichaux ist einer der besten Entwickler in der Community und sein aktueller Arbeitgeber panagenda kann sich glücklich schätzen. Das Problem liegt also nicht im Algorithmus selber. Die Ursache ist schlicht, dass LotusScript eine interpretierte Programmiersprache ist und somit langsamer als z.B. C-Code ist. Die Verarbeitung von 6MB Textinformationen dauerte einfach seine Zeit.
Unser Ansatzpunkt war in dem Fall also nicht die Verarbeitung in LotusScript. Wir haben uns zu Nutze gemacht, dass Domino & Notes in C geschrieben ist, welches wiederum per LotusScript aufgerufen werden kann. Wobei wir noch nicht einmal die C-API direkt ansprechen mussten. In der LotusScript-Klasse für ein Notes-Dokument existieren Methoden für die MIME-Verarbeitung von E-Mails. Dateianhänge in E-Mails werden ebenfalls Base64 kodiert. Dem entsprechend gibt es auch Methoden für die Umkodierung.
Dieser Testagent diente erst einmal als Proof-of-Concept. In der nicht abgebildeten Methode "DialogSetting" wurde der Pfad der reinen Base64-Testdatei (filepathBase64) und der Ausgabepfad für die PDF-Datei (filepathOutput) abgefragt.
Dim stream As NotesStream
Dim doc As NotesDocument
Dim body As NotesMIMEEntity
Dim doConvertMIME As Boolean
Dim outputStream As NotesStream
Dim agentStart As Variant
Dim agentEnd As Variant
Dim delta As Variant
If DialogSetting() = False Then Exit Sub
agentStart = now
'Auschalten der MIME Konvertierung
doConvertMIME = session.Convertmime
session.ConvertMIME = False
'Öffnen der Datei mit dem Base64 kodiertem Dateianhang
Set stream = session.CreateStream
Call stream.Open(filepathBase64)
'Anlegen eines temporären Notes-Dokumentes
Set doc = currentDb.Createdocument()
'Erzeugen einer MIME Entity
Set body = doc.CreateMIMEEntity
'Den Inhalt der Datei als "Text" an die MIME-Entity übergeben - ENC_BASE64 definiert dabei die Kodierung
Call body.setContentFromText(stream, "", ENC_BASE64)
'Einen Stream für die Ausgabedatei erzeugen
set outputStream = session.Createstream()
'Festlegen, dass es sich um ein binär-Format handelt
Call outputStream.Open(filepathOutput, "binary")
'Gegegebennenfalls den Inhalt löschen
Call outputStream.Truncate()
'Hier passiert die Magie! Den Inhalt der MIME Entity als Bytes ausgeben und in dem Ausgabestream leiten.
'Der Parameter "true" ist für die Dekodierung verantwortlich
Call body.getContentAsBytes(outputStream, true)
'Den Ausgabestream schließen
Call outputStream.close()
'Den Stream für die Eingabe schließen
Call stream.close()
'die MIME Konvertierung wieder einschalten
session.Convertmime = doConvertMIME
agentEnd = now
delta = CDat(agentEnd - agentStart)
MessageBox "Fertig in " & CStr(delta) & " " & Chr$(10) &_
CStr(agentStart) & " - " & CStr(agentEnd), MB_IconInformation, "Base64 Decoding"
Entscheidend für die Verarbeitung von MIME ist das Ausschalten der automatischen Umwandlung. Anderenfalls würde LotusScript automatisch versuchen, die Inhalte in Richtext umzuwandeln.
Mit Hilfe eines temporären Notes-Dokumentes legen wir eine neue MIME-Entity an. Dieser MIME-Entity übergeben wir den Base64-kodierten Text mit setContentFromText
, wobei ENC_BASE64 die Kodierung angibt. Korrespondierend dazu kann mit getContentAsBytes
der umkodierte Inhalt wieder ausgelesen werden.
Der Geschwindigkeitsgewinn basiert darauf, dass oben genannte Methoden im Hintergrund C-Code aufrufen und verwenden. Das Ergebnis ist auf alle Fälle überzeugend.
Zu dem Zeitpunkt als der Originalcode geschrieben wurde, waren die verwendete Klassen NotesStream und die Methoden setContentAsStream und getContentAsStream noch nicht existent. Mit der oben beschriebenen Umstellung und noch einigen anderen Verbesserungen konnten wir die Verarbeitungszeit insgesamt auf 30 Sekunden bringen. Bei der Menge der zu verarbeitenden Dokumente konnten wir die Last auf dem Server massiv reduzieren.
Refactoring ist in dem meisten Fällen eine lohenden Investition. Zugegebenermaßen sind Geschwindigkeitsgewinne in diesen Dimensionen selten. Aber meistens lässt sich etwas herausholen, wenn bestehender Code mit etwas Abstand im Hinblick auf Performance betrachtet wird.