Das Problem

Als Informatiklehrer:in verbringt man immer wieder einen Teil der Unterrichtvorbereitung damit, Programmier-Projekte zu erstellen, die im Unterricht als Vorlage für Programme verwendet werden können. In der Regel gehe ich dabei so vor, dass ich das Projekt vollständig implementiere, um einen Eindruck von der Komplexität zu bekommen. (Oft genug passiert es, dass sich eine spannende Idee als schwieriger als gedacht herausstellt.) Bin ich mit der Implementierung zufrieden, dann erstelle ich je nach Projekt, Kurs und Thema verschiedene Vorlagen für die Schüler:innen. Manchmal reicht mir eine Vorlage, mit der die Schüler:innen in die Umsetzung starten können, manchmal werden es aber auch drei oder sogar vier Versionen, die unterschiedliche Hilfestellungen und Vorgaben enthalten. Dies hilft bei der Differenzierung innerhalb der oft sehr heterogenen Informatikkurse.

20220124073155_Path%20Finder

Im Idealfall ist es damit getan, aber leider kommt es häufig vor, dass an den Projekten noch Änderungen vorgenommen werden müssen. Sei es ein Fehler bei der Programmierung, der erst im Unterricht auffällt, sei es die Änderung oder Ergänzung der Aufgabenstellungen oder sei es einfach nur ein Rechtschreibfehler in der Dokumentation. Und selbst wenn das Projekt fehlerfrei ist, möchte ich es häufig im nächsten Schuljahr anpassen, um den neuen Rahmenbedingungen gerecht zu werden.

Änderungen sind aber immer schwierig, da alle Projektversionen einzeln angepasst werden müssen. Dabei passiert es mir immer wieder, dass ich eine Version vergesse oder ich bei der Änderung einer Version noch weitere Fehler einbaue. Dann wird die Fehlersuche umso schwerer, weil ich den Überblick verliere, welche Version welche Änderungen hat.

Die Lösung

jml

Daher habe ich schon vor einiger Zeit ein kleines Python-Skript geschrieben, dass mir bei dieser Arbeit helfen soll. In den vergangenen Jahren hat sich das Tool immer weiter entwickelt und mir unglaublich viel Zeit gespart. Das Skript liest die Dateien eines Projekts ein und durchsucht sie nach Markierungen, die Lösungen und Aufgaben umschließen. Anhand der Kommentare werden dann verschiedene Projektversionen und die Musterlösung erstellt. Änderungen müssen nur einmal in der Basisversion vorgenommen werden und die Projektversionen neu erstellt werden.

In den letzten Tagen habe ich das Skript überarbeitet und als freies Modul unter dem Namen Java Musterlösung (JML) auf pypi.org veröffentlicht.

Auch, wenn das Tool ursprünglich für Java-Projekte entwickelt wurde, lässt es sich mittlerweile für alle textbasierten Projekte einsetzen. Zum Beispiel wäre es auch denkbar, mit jml verschiedene Versionen eines TeX-Arbeitsblatts zu generieren.

Die Grundlagen

Die Grundidee von jml ist, dass in einem Basisprojekt eine lauffähige Musterlösung programmiert werden kann. Bestimmte Bereich werden mit Zeilenkommentaren der Form //ml* und //*ml eingerahmt und dann durch jml aus den Schülerversionen entfernt. Zusätzlich können Aufgaben in Blockkommentaren durch /*aufg* und *aufg*/ eingerahmt werden. Diese Bereiche werden durch jml "auskommentiert" und als Vorlage übernommen.

Ein einfaches Beispiel könnte so aussehen:

/**
 * Im ArrayDojo findest Du einige lose Übungen zu Arrays.
 */
class ArrayDojo {
    public ArrayDojo() {}

    /**
     * Berechnet die Summe der Zahlen im Array.
     */
    public int sum( int[] pArray ) {
        /*aufg*
        // TODO: Implementiere die Methode passend zur Beschreibung oben.
        return 0;
        *aufg*/
        //ml*
        int sum = 0;
        for( int i = 0; i < pArray.length; i++ ) {
            sum += pArray[i];
        }
        return sum;
        //*ml
    }
}

jml erzeugt aus dieser Vorlage zwei Projektversionen des ArrayDojo. Die Musterlösung sähe so aus:

/**
 * Im ArrayDojo findest Du einige lose Übungen zu Arrays.
 */
class ArrayDojo {
    public ArrayDojo() {}

    /**
     * Berechnet die Summe der Zahlen im Array.
     */
    public int sum( int[] pArray ) {
        int sum = 0;
        for( int i = 0; i < pArray.length; i++ ) {
            sum += pArray[i];
        }
        return sum;
    }
}

Die Markierungen für die Musterlösung und die Aufgabe wurden entfernt. Der Inhalt des Aufgaben-Kommentars wurde für die Lösung auch gelöscht. Im Gegensatz dazu würde die Projektversion für die Schüler so aussehen:

/**
 * Im ArrayDojo findest Du einige lose Übungen zu Arrays.
 */
class ArrayDojo {
    public ArrayDojo() {}

    /**
     * Berechnet die Summe der Zahlen im Array.
     */
    public int sum( int[] pArray ) {
        // TODO: Implementiere die Methode passend zur Beschreibung oben.
        return 0;
    }
}

Hier wurde genau umgekehrt der Bereich der Musterlösung entfernt und die Aufgabe steht nun nicht mehr in einem Kommentar. Das Schülerprojekt lässt sich so also auch compilieren und starten.

Die Markierungen für Aufgaben und Lösungen lassen sich individuell konfigurieren und auf die eigenen Bedürfnisse anpassen.

Um nun mehrere Aufgabenversionen zu erstellen, lassen sich die Markierungen für Aufgaben zusätzlich mit einer Versionsnummer versehen. Das Beispiel oben könnte um eine zweite Version ergänzt werden, die eine weitere Hilfestellung enthält:

/**
 * Im ArrayDojo findest Du einige lose Übungen zu Arrays.
 */
class ArrayDojo {
    public ArrayDojo() {}

    /**
     * Berechnet die Summe der Zahlen im Array.
     */
    public int sum( int[] pArray ) {
        /*aufg*
        // TODO: Implementiere die Methode passend zur Beschreibung oben.
        return 0;
        *aufg*/
        /*aufg* 2
        // Hinweis: Nutze eine Zählschleife (for-Schleife), um das Array zu durchlaufen.
        *aufg*/
        //ml*
        int sum = 0;
        for( int i = 0; i < pArray.length; i++ ) {
            sum += pArray[i];
        }
        return sum;
        //*ml
    }
}

jml erzeugt nun zwei Schülerversionen. Die erste entspricht dem Beispiel oben. Die zweite enthält den zusätzlichen Kommentar mit dem Hinweis zur Lösung:

/**
 * Im ArrayDojo findest Du einige lose Übungen zu Arrays.
 */
class ArrayDojo {
    public ArrayDojo() {}

    /**
     * Berechnet die Summe der Zahlen im Array.
     */
    public int sum( int[] pArray ) {
        // TODO: Implementiere die Methode passend zur Beschreibung oben.
        return 0;
        // Hinweis: Nutze eine Zählschleife (for-Schleife), um das Array zu durchlaufen.
    }
}

Wie man sieht, wurde die erste Aufgabe in beide Versionen übernommen, da sie keine Versionsnummer hat. Da es aber eine Aufgabe mit der expliziten Nummer 2 gibt, hat jml angenommen, dass es auch eine Version 1 geben soll. Welche Aufgaben in welcher Version auftauchen lässt sich genau festlegen:

/*aufg* 1
// ich tauche nur in Version 1 auf
*aufg*/

/*aufg* 3
ich tauche nur in Version 3 auf
*aufg*/

/*aufg* >1
ich tauche in Version 2 und 3 auf
*aufg*/

/*aufg* !=2
ich tauche in Versionen 1 und 3 auf
*aufg*/

/*aufg* <=2
ich tauche in Version 1 und 2 auf
*aufg*/

Installation

jml ist ein Python-Modul und lässt sich mit pip installieren. Es wird Python 3.8 oder neuer benötigt.

$ pip install jml

Ob das Skript korrekt installiert wurde, kann durch einen Aufruf des Tools geprüft werden:

$ jml --version
jml, version 0.3.1

Da jml keine externen Abhängigkeiten hat, kann das Skript auch einfach als .py Datei aus dem Git-Repository kopiert und mit python3 ausgeführt werden.

Der Aufruf erfolgt auf der Kommandozeile mit dem Pfad des Basisprojekts als Argument. In der Regel sollte noch ein Ausgabeordner angegeben werden:

jml "Projekte/Basisprojekte/ArrayDojo" --out "Projekte/Versionen"

Nachdem jml seine Arbeit getan hat, sollte folgende Ordnersturktur vorhanden sein (basierend auf dem Beispiel oben):

Projekte/
├── Basisversionen/
│   └── ArrayDojo
└── Versionen/
    ├── ArrayDojo_ML
    ├── ArrayDojo_1
    └── ArrayDojo_2

Fortgeschrittene Nutzung

jml ist durch Argumente und Konfigurationsdateien weitgehend anpassbar und kann für verschiedenste Situationen genutzt werden. Generell arbeitet jml mit Textdateien und lässt sich theoretisch für alle Projekte mit Textdateien nutzen.

!!! Nicht-Textdateien werden von jml einfach in die Zielprojekte kopiert.

Die Optionen können in der Dokumentation des Tools nachgelesen oder direkt in der Kommandozeile abgerufen werden:

$ jml -h
usage: jml [-h] [--version] [-o OUT] [-n NAME] [-nf FORMAT]
           [-mls SUFFIX] [-to TAG] [-tc TAG] [-mlo TAG] [-mlc TAG]
           [-i INCLUDE [INCLUDE ...]] [-e EXCLUDE [EXCLUDE ...]]
           [-v VER [VER ...]] [--project-root PATH]
           [--encoding ENCODING] [--no-clear] [--delete-empty] [-z]
           [--no-ml] [--debug] [--log-level LEVEL] [--dry-run]
           IN

Generiert aus einem Basisprojekt mehrere Projektversionen.

positional arguments:
  IN                    Pfad des Basisprojektes

optional arguments:
  -h, --help            Diesen Hilfetext anzeigen.
  --version             Programmversion anzeigen.
  -o OUT, --outdir OUT  Pfad des Zielordners. Standard ist das
                        Verzeichnis in dem IN liegt.
[...]

In der Dokumentation finden sich auch verschiedene Beispiele zur Anpassung auf andere Projektarten. Praxisbeispiele finden sich in meinem Repository für schulische Projekte und den dazugehörigen (mit jml erstellten) Projektversionen.

Weiterentwicklung und Mitarbeit

jml wird nach Bedarf weiterentwickelt. In den letzten Jahren haben sich meine eigenen Anforderungen immer wieder dafür gesorgt, dass das Skript um neue Funktionen erweitert wurden, wenn sich neue Einsatzgebiete ergeben haben. Beiträge und Feedback zum Tool nehme ich gerne via GitHub Issues entgegen und da das Skript jetzt Open-Source ist, freue ich mich auch über aktive Mitarbeit oder Pull-Requests.