Kolumne

Hitchhiker’s Guide to Docs as Code: The Beautiful Code

Ralf D. Müller, Gernot Starke

Ein halbes Dutzend Folgen dieser Kolumne, bevor wir endlich zum Thema Sourcecode kommen: Wir möchten in Architekturdokumentation an manchen Stellen Code integrieren, etwa zur Beschreibung wichtiger Schnittstellen. AsciiDoc bietet hierfür schicke Features. Wie immer finden Sie den Quellcode dieser Kolumne online hier.

Unsere Beispiele übersetzen wir mit Gradle, und wieder haben wir einen Default Task für Asciidoctor konfiguriert:

> cd folge-7

> gradle

Der AsciiDoc-Prozessor erzeugt den HTML-Output im Verzeichnis build/asciidoc/html5/, auch das kennen Sie ja schon.

Code in der (Architektur-)Doku?

Auch wenn Architekturdokumentation für Überblick im Großen sorgen soll, benötigen wir an manchen Stellen doch detaillierte Infos auf Ebene von Quellcode. Daher möchten wir an einigen Stellen Code(fragmente) in die Architekturdokumentation integrieren, beispielsweise für folgende Anwendungsfälle: Schnittstellen, Erläuterung technischer Konzepte und spezielle Lösungsansätze für (wiederkehrende) Aufgaben mit Architekturrelevanz. Schauen wir uns an, wie das mit AsciiDoctor grundsätzlich funktioniert.

Sourcecode anzeigen

Bevor wir mit echtem Sourcecode loslegen, müssen wir AsciiDoc mitteilen, welche Bibliothek wir für Syntax-Highlighting verwenden möchten – es werden z. B. CodeRay, highlight.js, Pygments und diverse andere Highlighter unterstützt. Fügen Sie im Header Ihres AsciiDoc-Dokuments folgende Deklaration ein:

= HHGDAC: Sourcecode integrieren
:source-highlighter: coderay

Die konkrete Wahl des Highlighters hängt hauptsächlich von den benötigten Ausgabeformaten und den in Ihrem Projekt verwendeten Sprachen ab.

So, jetzt sind wir bereit für Real Stuff: In AsciiDoc zeichnen Sie Sourcecode durch die Angabe von aus. Der nachfolgende Block wird dann als Codeblock formatiert (die —- markieren Anfang und Ende eines Blocks), das Ergebnis finden Sie in Abbildung 1.

----
# This program prints Hello, world!
print('Hello, world!')
----

Abb. 1: So wird Code gerendert

Abb. 1: So wird Code gerendert

Die Sprache festlegen

Um das Syntax-Highlighting von AsciiDoctor zu verbessern, können Sie die Programmiersprache als zweiten Parameter in der -Deklaration angeben. AsciiDoc (beziehungsweise die jeweiligen Source Highlighter) können sich dann (noch) besser auf die jeweilige Syntax einstellen. In Listing 1 finden Sie das passende Beispiel, ausnahmsweise mal für die Sprache Go. Unserer Erfahrung nach sind die gängigen Highlighter relativ gut darin, die Sprache selbst zu erkennen, in vielen Fällen können Sie sich die explizite Deklaration also sparen. Probieren Sie es einfach für Ihre Listings aus.

.Go gefällig?

----
package main
import "fmt"
func main() {
    fmt.Println("hello world")
}
----

Jetzt haben Sie Sourcecode in Ihrer Dokumentation, aber oftmals möchten Sie bei der Erklärung Bezug auf bestimmte Stellen dieses Codes nehmen, was am einfachsten ganz klassisch über Zeilennummern in diesen Listings geht. Dazu nehmen Sie einfach nur den Parameter linenums in die -Deklaration auf. Ein Beispiel zeigt Listing 2, das fertig gerenderte Ergebnis finden Sie in Abbildung 2. Leider funktionieren die Zeilennummern nicht in diversen Preview-Funktionen bei AsciiDoc-Editoren, u. a. auch beim Atom-Editor und GitHub-Preview. Das Plug-in für IntelliJ IDEA bildet hier eine positive Ausnahme.

.Oder JavaScript?

--
function documentTitle(theTitle) {
  theTitle  = theTitle || "Untitled Document";
}
--
Abb. 2: Listing mit Zeilennummern

Abb. 2: Listing mit Zeilennummern

Neben diesem klassischen Ansatz bietet Asciidoctor allerdings noch die (elegante) Möglichkeit der sogenannten Callouts. Das sind visuelle Hervorhebungen, die beim Rendering eingebracht werden. In Abbildung 3 finden Sie einen solchen Callout. Nach typischer AsciiDoc-Manier können Sie solche Hervorhebungen ganz einfach vornehmen, indem Sie in spitzen Klammern am Ende einer Zeile im jeweiligen Sourcecode eine Ziffer setzen: <1>.

Abb. 3: Sourcecode mit Callout

Abb. 3: Sourcecode mit Callout

Im Regelfall haben Sie natürlich Listings, in denen mehrere Zeilen interessant sind. Ein Beispiel aus der Fantasy-Welt zeigt Listing 3. Darin haben wir drei Callouts sowie die zugehörigen Erklärungen untergebracht – in AsciiDoc folgen diese unmittelbar hinter der abschließenden Blockbegrenzung —-.

[[listing-ref-example]]
.Code mit Erklärungen

--
import org.harryp.Voldemort; // <1>
import org.harryp.Harry; // <2>

public class Stakeholder {

    public String wandName; // <3>

    // ...
}
--
<1> diesen Namen besser vermeiden
<2> der Knabe mit der Brille
<3> Zauberstäbe tragen Namen

Wie sie in Listing 3 auch sehen können, unterstützt es AsciiDoc, die Callouts in Kommentare zu setzen, damit der Code weiterhin korrekt ist. Die Kommentarzeichen werden beim Rendern des Blocks jedoch weggelassen (Abb. 4). Das ist wichtig, wenn Sie den Sourcode nicht in der Dokumentation duplizieren wollen.

Abb. 4: Komplexeres Callout-Beispiel

Abb. 4: Komplexeres Callout-Beispiel

Code aus dem Repository

Sourcecode mit Copy und Paste in die Dokumentation zu übernehmen, verletzt das DRY-(Don’t-repeat-yourself-)Prinzip. Das sollten Sie daher tunlichst vermeiden. Wie aber soll der Code in die Doku kommen, wenn wir ihn dort nicht hineinkopieren sollen? Sie haben es sicherlich erwartet, AsciiDoc bietet auch für dieses Problem eine elegante und produktive Lösung: Sie inkludieren Code einfach aus Ihrem Repository (genaugenommen aus einer Working Copy, auf die der jeweilige Build Zugriff hat). Im Grunde funktioniert das genauso wie das bereits vorgestellte Konzept der modularisierten Dokumentation: Wir verwenden den schon bekannten include-Befehl. Zusätzlich schlagen wir vor, den Pfad zum Code-Repository als eine Variable zu definieren.

----
include::{main_sourcepath}/org/aim42/CheckResults.groovy[] 
----

Über die Variable {main_sourcepath} wird ein include von einem beliebigen Dateipfad ermöglicht. Für unser Beispiel haben wir {main_sourcepath} zu Beginn der AsciiDoc-Datei definiert:

= HHGDAC: Sourcecode integrieren
:source-highlighter: coderay
:coderay-linenums-mode: inline
:main_sourcepath: ../main/

Das Ergebnis sehen Sie in Abbildung 5.

Abb. 5: Direkt eingebundener Code

Abb. 5: Direkt eingebundener Code

Pfadvariablen und Verzeichnisbäume

Die Pfade für den include-Befehl des AsciiDoc-Prozessors werden im Gegensatz zum image-Befehl relativ zum Dokument aufgelöst. Für die Strukturierung einer Dokumentation ist das praktisch, für das Einbinden von Code nicht. Je nachdem, wo das AsciiDoc-Dokument liegt, müsste ein anderer Pfad gesetzt werden, weshalb es sich für größere Dokumentationen anbietet, die Variable main_sourcepath im build.gradle als absoluten Pfad zu setzen (Abb. 6).

Abb. 6: Direkt eingebundener Code

Abb. 6: Direkt eingebundener Code

Abbildung 6 verdeutlicht dies nochmal: Erstens ist unser Root-Verzeichnis im Buildfile build.gradle definiert, und zweitens zeigt die Pfadvariable main_sourcepath. Zum Rendern dieses Verzeichnisbaums benutzen wir eine AsciiDoc-Erweiterung namens MonoTree.

Abb. 7: Direkt eingebundener Code

Abb. 7: Direkt eingebundener Code

Ein Listing referenzieren

Sie können natürlich auch bestimmte Listings Ihrer Dokumentation referenzieren. Das schreiben Sie in AsciiDoc als normale Referenz in doppelten spitzen Klammern: <<listing-ref-example>>. Asciidoctor ersetzt dies dann gegen eine Referenz der entsprechenden Überschrift als Text.

Bestimmte Stellen aus Code inkludieren

Oftmals genügt es, bestimmte (kurze) Teile aus Sourcefiles zu inkludieren. Auch dafür hat AsciiDoc eine elegante Antwort gefunden: Nachfolgend sehen Sie einen Extrakt der Datei aus Listing 3, diesmal beschränkt auf zwei Methoden:
include::{main_sourcepath}/org/aim42/CheckResults.groovy[tags=CheckResultsInterface]

Wie Sie in Abbildung 5 sehen, haben wir dafür die betreffenden Methoden mit den Kommentaren // tag::CheckResultsInterface[] und // end::CheckResultsInterface[] markiert. Im ersten Moment ist dies gewöhnungsbedürftig. Bei der Verwendung ist es aber praktisch, da Sie im Code dadurch immer direkt erkennen können, welche Zeilen in der Dokumentation referenziert sind.

JavaDoc mit AsciiDoc

Wie wäre es, wenn Sie JavaDoc-Kommentare endlich nicht mehr als HTML schreiben müssten? Auch hierfür können Sie ab sofort AsciiDoc einsetzen. Der Ansatz basiert auf dem Asciidoclet, das bei GitHub unter der Asciidoctor-Organisation entwickelt wird. Um diese AsciiDoc-Erweiterung zu nutzen, müssen wir unser Buildfile deutlich erweitern (Listing 4).

configurations {
  asciidoctorDoclet
}
dependencies {
  asciidoctorDoclet "org.asciidoctor:asciidoclet:1.5.4"
}
configurations {
  asciidoclet
}
javadoc {
  options.with {
    docletpath = configurations.asciidoctorDoclet.files.asType(List)
    doclet = 'org.asciidoctor.Asciidoclet'

    addStringOption '-base-dir', projectDir.toString()

    def attributes = [projectName: project.name,
                      projectVersion: project.version]

    addStringOption '-attribute', attributes*.toString().join(',')

    overview = 'src/docs/overviewJavadoc.adoc'

    version = true
    author = true
  }
}

Ruft man gradle javadoc auf, wird wie gewohnt die Dokumentation der Klassen erzeugt, wobei die einzelnen Elemente jetzt aber in AsciiDoc, nicht mehr in HTML, beschrieben werden. Die javadocDokumentation und die Architekturdokumentation können durch gegenseitige Verlinkung kombiniert werden. Dazu wird in der javadoc-Konfiguration ein Übersichtsdokument angegeben. Dieses Übersichtsdokument verlinkt dann auf Ihre Architekturdokumentation und umgekehrt. Theoretisch könnte hier auch direkt die Architekturdokumentation referenziert werden. Aufgrund der verschieden konfigurierten AsciiDoc-Prozessoren raten wir davon jedoch ab.

Schnittstellen dokumentieren

Schnittstellen sind für eine Softwaredokumentation natürlich ein ganz wichtiges Thema. Verwenden Sie REST-basierte Schnittstellen mit Swagger in Ihrem Projekt, haben wir zum Abschluss noch eine coole Option. Mit Swagger2Markup – einem Open-Source-Projekt von Robert Winkler – wandeln Sie Ihre Swagger-Definitionen ganz einfach in AsciiDoc um und ergänzen so über Ihren Build die Architekturdokumentation.

Fazit

Sourcecode in AsciiDoc-Manier integrieren stellt sicher, dass Doku und Code zueinander passen (zumindest an den betroffenen Stellen). Mit dem passendem Build-Prozess können wir sicherstellen, dass der inkludierte Code seine Testfälle bestanden hat – das ist gegenüber dem herkömmlichen Copy and Paste eine signifikante Verbesserung. Wenn Sie das alles selbst ausprobieren möchten, finden Sie wie gewohnt unsere Beispiele im Repository. In der nächsten Folge stellen wir Ihnen einige Werkzeuge vor, mit denen Sie Docs as Code effektiv und effizient erstellen und pflegen können. Bis dahin wünschen wir happy Docu Coding!

Geschrieben von
Ralf D. Müller
Ralf D. Müller
Ralf D. Müller arbeitet als Architekt und Entwickler. Er erlebt täglich die Notwendigkeit effektiver Dokumentation. Außerdem ist er erklärter AsciiDoc-Fan und Committer bei arc42 sowie Gründer des docToolchain-Projekts.
Gernot Starke
Gernot Starke
    Informatikstudium an der RWTH Aachen, Dissertation über Software-Engineering an der J. Kepler Universität Linz. Langjährige Tätigkeit bei mehreren Software- und Beratungsunternehmen als Softwareentwickler, -architekt und technischer Projektleiter. 1996 Mitgründer und technischer Direktor des „Object Reality Center“, einer Kooperation mit Sun Microsystems. Dort Entwickler und technischer Leiter des ersten offizielle Java-Projekts von Sun in Deutschland. Seit 2011 Fellow der innoQ GmbH.  
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.