Vermeidung von Dokumentationskosten mit Python
Einleitung
Dieser Artikel zeigt, wie ich Python, COM, DocBook, OpenJade und Word kombiniert habe, um ein Dokumentationswerkzeug für BEACON, eine visuelle Programmierumgebung, zu erstellen. Dieses Dokumentationswerkzeug wurde für Code-Reviews in der Softwareentwicklungsmethodik in meinem Unternehmen verwendet und führte zu erheblichen Kosteneinsparungen (über 1 Mio. USD).
Bevor ich dieses Projekt begann, hatte ich keine Erfahrung mit SGML, XML oder anderen Markup-Sprachen für Dokumente. Erst als ich kurz davor stand, das Konzept von Markup-Sprachen neu zu erfinden, indem ich direkte PythonCOM-zu-Word-Schnittstellen aus Mark Hammonds Buch Python Programming on Win32 anpasste, entschied ich, dass es aus Design- und Wartungsgesichtspunkten einen besseren Weg geben musste.
Eine Websuche lieferte mir einen Crashkurs über Markup-Sprachen wie XML, HTML und SGML. Meine Suche lieferte auch Einblicke in DocBook SGML, einen beliebten offenen Standard, und OpenJade, ein Open-Source-Paket, das DocBook SGML in Word Rich Text übersetzen kann.
Diese Anordnung war nicht perfekt, aber ich erkannte schnell, dass ich sie nutzen konnte, um Entwicklungs- und Wartungskosten um ein Jahr zu reduzieren und schneller auf neue Aufgaben zu reagieren.
Der Kern-Datenfluss
Meine Hauptaufgabe bestand darin, beliebige Daten, die aus verschiedenen Quellen in meiner Organisation extrahiert wurden, in ansprechend aussehende Microsoft Word 97-Berichte zu übersetzen.
Ich entschied, dass dies am besten durch eine Kernpipeline von Anwendungen gehandhabt werden konnte, die mit gemeinsamen Datenkonventionen zusammenarbeiteten. Diese Pipeline würde von einer Python-Generatoranwendung gesteuert werden, die eine Reihe von Front-End-Übersetzern, einen Content-Inserter und einen Post-Processing-Formatter antreibt, um die Berichte zu generieren. Python war kürzlich als Nachfolgesprache für automatisierte Testskripte in unserer Abteilung ausgewählt worden. Ich erkannte das Potenzial von Python über den Testbereich hinaus und erhielt die Erlaubnis meines Managers, es für diese Aufgabe zu verwenden.
Die Front-End-Übersetzer in dieser Anwendung ernten Inhalte (Bilder, Tabellen, Absätze) aus verschiedenen Datenquellen und legen sie in einem Wörterbuch ab. Der Content-Inserter erstellt ein Word-Dokument und fügt Werte aus dem Wörterbuch ein. Der Post-Processing-Formatter nimmt das Ergebnis und modifiziert es gemäß der neuesten Corporate Word Format Style-Vorlage.
Dieser Fluss wurde entwickelt, um Änderungen an den Anforderungen der verschiedenen Teams innerhalb unserer Abteilung und an unternehmensweiten Standards Rechnung zu tragen. So wurde beispielsweise das Layout von Berichten durch die Generatoranwendung in einer Layout-Klasse bestimmt, die durch andere Klassen ersetzt werden konnte, um neue Berichtsarten zu unterstützen.
Der erste Front-End-Übersetzer, den ich erstellen musste, sollte Bilder, Tabellen und Daten aus rekursiven Property-Listen aufnehmen, die von einem visuellen Programmierwerkzeug für die Luft- und Raumfahrtindustrie namens BEACON erstellt wurden. Von diesem Übersetzer konnte ich eine riesige Menge an Beispieldaten für das Testen des Word Content-Inserters erhalten.
Probleme mit dem Inserter
Die erste Version des Content-Inserters basierte auf Prinzipien, die in Python Programming on Win32 demonstriert wurden, welches eine detaillierte Beschreibung enthält, wie Python Word-Dokumente mit dem Word 97 COM-Objektmodell erstellen und manipulieren kann. Diese Implementierung fügte den Inhalt in Word-Dokumente ein, indem Word direkt über seine COM-Schnittstelle gesteuert wurde.
Leider war die COM-Schnittstelle zu langsam, um die riesige Anzahl von Tabellenzellen zu bewältigen, die aus dem BEACON-Quellcode extrahiert wurden.
Schlimmer waren die Wiederverwendungsprobleme mit den Klassen, die ich für die verschiedenen stilistischen Anforderungen auf den verschiedenen Ebenen von Abschnitten, Überschriften, Absätzen und Phrasen schrieb.
Um diese Probleme zu lösen, erwog ich, ASCII-Textdateien zu schreiben, um Ränder, Schriftart, Überschriftenebene und Einfügepunkte für Standardformulare anzugeben. Aber das Erfinden meines eigenen Standards für textbasierte Typografie wäre zeitaufwändig und kostspielig sowohl in der Entwicklung als auch in der Wartung gewesen.
Was ich wirklich brauchte, war eine Python-API, um schnell gut typografierte Texte in Word 97 für eine begrenzte Anzahl von Dokumenten zu generieren. Aber im Jahr 2001 gab es nichts Derartiges. Meine einzige Alternative war, einen offenen Standard für Typografie zu finden, der nach der Erstellung von Dokumenten in das Word 97-Format übersetzt werden konnte.
Einkauf eines Standards
Um eine Lösung zu finden, verbrachte ich einige Zeit mit der Untersuchung verfügbarer Open-Source-Typografie-Lösungen. Die beiden beliebtesten sind TeX und DocBook, die beide durch Open-Source-Implementierungen in C unterstützt werden.
DocBook wurde gewählt, weil es klarer definierte und dokumentierte Produktionsregeln für typografische Elemente hatte. DocBook, The Definitive Guide ist ein echter Schatz, wenn es um die detaillierte Erklärung dieser Regeln geht.
DocBook bietet eine Reihe von Document Type Definition (.DTD) und Document Stylesheet (.DSL) Dateien, die in DSSSL, der Document Style Semantics and Specification Language, geschrieben sind. DocBook wird in zwei Varianten geliefert, eine für SGML und die andere für XML-Dokumentenkodierung. Sowohl SGML als auch XML haben ähnliche verschachtelte Tag-Strukturen und die gleiche logische Struktur für DTDs und DSLs. Die XML-Regeln waren jedoch im Jahr 2001 noch in der Entwicklung, daher entschied ich mich für den zuverlässigeren und ausgereifteren SGML-Regelsatz.
DocBook bietet auch die Möglichkeit, lokale Dokumenttypdefinitionen und Stylesheets zu schreiben, genanntLocal.DTDundLocal.DSLjeweils. Diese ermöglichen die Einführung zusätzlicher Dokumentelemente und deren Darstellung zusätzlich zu denen, die von DocBook bereitgestellt werden.
Beispielsweise wünschten sich einige Teams in meinem Unternehmen Funktionen im endgültigen Word-Dokument, die einer beliebigen Word-Feldcode-Unterstützung in SGML gleichkamen.
Um dies zu unterstützen, schrieb ich ein Paar lokaler DocBook-Stylesheets und Definitionsdateien(Local.DSL, Local.DTD)um die Sequenzen in RTF zu erzeugen, die den gewünschten Feldcodes entsprechen. RTF erfordert nicht-escaped Zeichen{, }, und\\, also modifizierte ich OpenJade, um Unicodes abzubilden0xFFFD, 0xFFFE, und0xFFFFim Stylesheet auf diese nicht-escaped Zeichen.
Ich fand auch einen archivierten Beitrag auf der docbook-apps Mailingliste, der äußerst hilfreich war, um die Inhalte von DocBook-Download-Komponenten zu einer funktionierenden Hierarchie zusammenzufügen.
Ein Beispiel für eine Python-API für DocBook
Zur Unterstützung der Entwicklung des notwendigen Content-Inserters mit DocBook benötigte ich eine Python-API, mit der SGML-formatierte Dokumente schnell generiert werden konnten. Das gewählte Design für diese API bietet abstrakte Klassen für jeden Dokumentelementtyp in einem DocBook Python-Modul. Diese abstrakten Klassen können in Code vererbt werden, der spezifische Dokumentstrukturen definiert, und beliebig verschachtelt werden, sodass jede einem anderen Level oder Teil der Struktur des Ausgabedokuments entspricht.
Als Beispiel nehmen wir an, wir möchten die folgende Tabelle als Teil eines Word-Dokuments generieren
Name Typ statex Integer statey Long
Der SGML-Text für diese Tabelle wird in Bezug aufLocal.DSLundLocal.DTDwie folgt geschrieben
<!DOCTYPE informaltable SYSTEM "C:\Local.dtd"> <informaltable frame='all'> <tgroup cols='2' colsep='1' rowsep='1' align='center'> <colspec colname='Name' colwidth='75' align='left'></colspec> <colspec colname='Type' colwidth='64' align='center'></colspec> <thead> <row> <entry><emphasis role='bold'>Name</emphasis></entry> <entry><emphasis role='bold'>Type</emphasis></entry> </row> </thead> <tbody> <row> <entry><phrase role='xe' condition='italic'>statex</phrase></entry> <entry>Integer</entry> </row> <row> <entry><phrase role='xe' condition='italic'>statey</phrase></entry> <entry>Long</entry> </row> </tbody> </tgroup> </informaltable>
Hier ist das Python-Listing, das das obige SGML durch Vererbung vom DocBook-Klassenbaum generiert
from DocBook import DocBook
class ItalicIndexPhrase (DocBook.Rules.Phrase):
"italic indexible text phrase"
TITLE = DocBook.Rules.Phrase
def __init__ (self, text):
DocBook.Rules.Phrase.__init__ (self, 'xe', 'italic')
self.data = [ text ]
class NameCell (DocBook.Rules.Entry):
"table row cell describing name of identifier (italic and indexible text!)"
TITLE = DocBook.Rules.Entry
def __init__ (self, text):
DocBook.Rules.Entry.__init__ (self)
self.data = [ ItalicIndexPhrase (text) ]
class StorageCell (DocBook.Rules.Entry):
"table row cell describing storage type of identifier (ordinary text)"
TITLE = DocBook.Rules.Entry
def __init__ (self, text):
DocBook.Rules.Entry.__init__ (self)
self.data = text
class TRow (DocBook.Rules.Row):
"each row in application's informal table body"
TITLE = DocBook.Rules.Row
def __init__ (self, binding):
(identifier, storage) = binding
DocBook.Rules.Row.__init__ (self, [ NameCell (identifier),
StorageCell (storage)
])
class TBody (DocBook.Rules.TBody):
"application's informal table body"
TITLE = DocBook.Rules.TBody
def __init__ (self, items):
DocBook.Rules.TBody.__init__ (self, map (TRow, items))
class TGroup (DocBook.Rules.TGroup):
"application's informal table group"
COLSPECS = [ DocBook.Rules.ColSpec ('Name', 75, 'left'),
DocBook.Rules.ColSpec ('Type', 64, 'center')
]
SHAPE = [ '2', '1', '1', 'center' ]
TBODY = TBody
class InformalTable (DocBook.Rules.InformalTable):
"application's informal table"
TGROUP = TGroup
class Example (DocBook):
'example application of DocBook formatting class'
SECTION = str (InformalTable)
def __call__ (self):
self.data = [ InformalTable ()(self.data) ]
return DocBook.__call__ (self)
if __name__ == '__main__':
print Example ([('statex', 'Integer'), ('statey', 'Long')]) ()
Die OpenJade-Schnittstelle
OpenJade ist ein Open-Source-Produkt, das einen Weg bietet, von SGML-kodierten Dokumenten zu Microsoft Rich Text Format (RTF) zu gelangen. Es liest die DocBook DSSSL-Stylesheets und die lokalen DSSSL-Stylesheets des Benutzers, falls vorhanden. DSSSL wird auf dem SGML-Quelltext des Benutzers ausgeführt, um ein finales Dokument zu schreiben, das in den Textverarbeitungsprozessor des Benutzers geladen wird.
Für dieses Projekt wollten wir automatisch Dateien generieren, die von Microsoft Word gelesen werden konnten, daher wurde OpenJade so eingestellt, dass es Microsoft Word Rich Text-Dateien ausgibt. OpenJade funktioniert als Befehlszeilenanwendung und ist daher mit demPopen4Python-Standardbibliotheksaufruf einfach aus Python-Code zu steuern.
Nachbearbeitung mit Word-Automatisierung mit PythonCOM
Die von OpenJade erstellten Microsoft Rich Text Format-Dateien sind im Gesamterscheinungsbild sehr ansprechend. Sie entsprachen jedoch nicht vielen der unternehmensweiten Standards für formatierte Word-Dokumentationsdateien.
Ein lokales DSSSL-Stylesheet (Local.DSL) wurde geschrieben, um mehrere der standardmäßigen DocBook DSSSL-Einstellungen zu überschreiben und sie an die Unternehmensstandards anzupassen. Dies löste jedoch nicht die Notwendigkeit, standardisierte Word-Stil-Bezeichner in den Dokumenten zu setzen.
Um dieses Problem zu lösen, war ein Reformatter als letzte Stufe der Dokumentpipeline erforderlich. Er greift auf Word als COM-Objekt zu, um die Stil-Bezeichner auf Tabellen-, Bild-, Überschriften- und Section-Ebene im Objektmodell des generierten RTF-Dokuments zu durchlaufen. Während der Traversierung benennt er die Stil-Bezeichner um, damit sie mit denen übereinstimmen, die in einer Microsoft Word Document Template (.DOT)-Datei bereitgestellt werden, die von unserer lokalen Reprografie-Abteilung als Standard ausgegeben wurde.
Nach dieser Konvertierung speichert der Post-Prozessor das fertige Dokument im Microsoft Word-Dokumentenformat.
Keine der Nachbearbeitungsaufgaben war besonders schwierig. Sobald die COM-Schnittstelle zu einer Win32-Anwendung gut verstanden ist, wird diese Anwendung für einen Python-Entwickler zu einer weiteren Bibliothek.
Return on Investment
Die Annahmen, die zur Herleitung der hier dargestellten Return on Investment (ROI)-Zahlen verwendet wurden, sind konservativ.
Den Großteil des Jahres 2001 verbrachte ich mit der Entwicklung eines Systems, das die Ideen dieses Papiers nutzte, um den Inhalt einer BEACON-visuellen Programmiersprachen-Datei automatisch direkt in ein Word-Dokument zu übersetzen. Im Jahr 2002 nahm ich auch signifikante Überarbeitungen an der Software vor. Mein Gesamtaufwand für Entwicklung, Wartung und Support betrug etwa die Hälfte der Zeit über einen Zeitraum von zwei Jahren.
Zwischen den Jahren 2002 und 2003 hatte meine Abteilung 5 laufende Projekte in verschiedenen Entwicklungsstadien, deren Komplexität von 30 visuellen Programmierdateien bis zu 150 reichte, im Durchschnitt etwa 75.
In jedem dieser Jahre gab es für jedes dieser Projekte mindestens 2 größere, vorgeschriebene Releases, bei denen die wichtigen Inhalte jeder Datei von mindestens 3 Ingenieuren gleichzeitig (Moderator, Autor und Inspektor) peer-reviewed werden mussten.
Jedes dieser Releases erforderte, dass jede visuelle Programmierdatei in ein ausdruckbares Hardcopy-Format gerendert wurde, das alle seine Diagramme enthielt; und eine Querverweistabelle aller Identifikatoren in jedem Diagramm mit Speicherkassen, Bereichen, Anfangswerten, Dokumentation und anderen Feldern.
Die visuelle Programmiersprachen-GUI-Anwendung BEACON verfügt über keinen umfassenden Hardcopy-Generator. Stattdessen benötigte es einen Ingenieur der Einstiegsklasse, der unter moderater Aufsicht den Dateiinhalt mit BEACON unter UNIX über einen UNIX-zu-Win32-X-Terminal-Emulator inspizierte und Text manuell vom X-Terminal in ein geöffnetes Word-Dokument übertrug.
Die am wenigsten komplexen dieser Dateien (ca. 20%) dauerten einen halben Tag. Die meisten Dateien (60%) dauerten im Durchschnitt einen ganzen Tag. Die komplexesten dieser Dateien (ca. 20%) dauerten mindestens 2 Tage.
Dies verschwendete erheblichen Ingenieursaufwand, der besser für die Verbesserung der Qualität der Softwareprodukte meiner Abteilung hätte eingesetzt werden können.
Each project release: 1/5 * 75 * 4 hours = 60 hours
3/5 * 75 * 8 hours = 360 hours
1/5 * 75 * 16 hours = 240 hours
-------------
660 hours
Two major releases per year: * 2 = 1 320 hours
Five projects needing releases: * 5 = 6 600 hours
Two year period (2002-2003) * 2 = 13 200 hours
Total effort avoided: 13 200 hours
Automated releases over 2 year period: 160 hours
My effort (12 * 140 hours per labor month): 1 680 hours
Total investment: 1 840 hours
Net effort avoided, 2002-3: 11 360 hours
Net cost avoided by customers 2002-3 at $100/hour 1 136 000 dollars
Net labor years avoided at 1680 hours/year: 6.76 years
Head count avoided per year: 3.38 people
ROI (Total effort avoided / total invested) 2002-3: 7.17
Aus der obigen Tabelle geht hervor, dass die Automatisierung der Dokumentationserstellung allein für formelle Kunden-Releases meinem Abteilungsbereich geholfen hat, erhebliche manuelle Arbeitskosten zu vermeiden.
Python und DocBook erwiesen sich gemeinsam als eine formidale Kombination zur Beseitigung eines realen Engpasses im Geschäftsprozess.
Schlussfolgerung
Die Entscheidung meiner Abteilung, Python zu adoptieren und mir die Nutzung zusammen mit einem weiteren offenen Standard, DocBook, zu erlauben, hat sich über einen mittel- bis langfristigen Zeitraum durch eine erhebliche Kapitalrendite gerechtfertigt, wenn auch nur in Bezug auf vermiedene Dokumentationskosten.
