In den vorigen Lektionen haben wir Techniken kennengelernt, um eine Aufgabenstellung in kleinere Bausteine zu zerlegen, etwa in Abschnitte, die wiederholt oder bedingt ausgeführt werden, sowie Teilprogramme, die beim Eintreten bestimmter Ereignisse ausgeführt werden. In dieser Lektion werden wir selbst definierte Anweisungen (Unterprogramme/Methoden) und selbst definierte Ereignisse (Nachrichten/Signale) in Scratch einführen, um komplexere Programme zu strukturieren.
Ein Ziel der Programmstrukturierung ist es, Programme leichter lesbar zu machen und Code-Redundanzen zu vermeiden – also Programmteile, die als Kopie an mehreren Stellen im Programm vorkommen, was nicht nur den Programmumfang vergrößert, sondern auch zu Problemen führt: Soll nachträglich eine Änderung an einer Stelle im Programm vorgenommen werden, muss diese ggf. an mehreren anderen Stellen ebenfalls durchgeführt werden. Das ist zum einen zeitaufwendig, zum anderen potenziell fehleranfällig, da schnell eine Stelle übersehen werden kann.
Um umfangreichere Programme besser nachvollziehbar zu machen, kann es helfen, Programmabschnitte mit Kommentaren zu versehen, also kurzen Anmerkungen, in denen die Bedeutung eines Programmabschnitts zusammengefasst wird. In Scratch lassen sich Kommentare durch einen Rechtsklick in den Skriptbereich in Form von “Notizzetteln” einfügen.
In so gut wie allen moderen Programmiersprachen ist es möglich, Textkommentare zum Quellcode hinzuzufügen.
Komplexere Programm können schnell unübersichtlich werden. Insbesondere kann es vorkommen, dass bestimmte Programmabschnitte zum Lösen derselben Aufgabe an mehreren Stellen vorkommen, was den Programmumfang unnötig vergrößert.
Als anschauliches Beispiel dient hier das Grundgerüst für ein Jump & Run-Spiel in Scratch. Laden Sie das Projekt Jump_and_Run.sb3 hier herunter: Download
In diesem Spiel wird eine Figur gesteuert, die sich mit den Pfeiltasten nach links und rechts bewegen lässt und außerdem springen kann.
Das Springen der Figur kann durch mehrere Ereignisse ausgelöst werden:
Die Aktion “springen” besteht dabei aus zwei Wiederholungen für die Auf- und Abwärtsbewegung, die innerhalb einer bedingten Anweisung stehen (“Befindet sich die Figur momentan auf dem Boden?”). Der entsprechende Programmabschnitt kommt also an drei verschiedenen Stellen im Programm in exakt gleicher Form vor:
Um den Umfang des Programm zu verringern wäre es also hilfreich, die Anweisungen der “springen”-Aktion zu einer neuen Anweisung zusammenzufassen. Das lässt sich in Scratch mit Hilfe selbst definierter Blöcke umsetzen. Ein solcher “eigener” Block definiert ein Unterprogramm, das von anderen Skripten des Objekts mit Hilfe eines speziellen Anweisungsblocks ausgeführt werden kann. Ein Unterprogramm, das zu einem Objekt gehört, wird auch als Methode dieses Objekts bezeichnet.
Ein Unterprogramm muss zunächst definiert werden. Dazu wird in Scratch die Schaltfläche “Neuer Block” in der Kategorie “Meine Blöcke” (rot) ausgewählt und ein eindeutiger Bezeichner für den neuen Block vergeben. Wie bei eigenen Variablen sollte der Bezeichner möglichst aussagekräftig sein (hier zum Beispiel “springe”). Anschließend erscheint ein neuer Anweisungsblock in der Block-Bibliothek unter “Meine Blöcke”.
Außerdem erscheint im Skriptbereich des Objekts ein neuer Definitionsblock, der wie Ereignisblöcke eine “Kopfblockform” hat:
An diesen Block können nun die Anweisungen des Unterprogramms angehängt werden. Der neue Anweisungsblock kann nun in anderen Skripten des Objekts zum Aufruf des Unterprogramms verwendet werden. Wenn dieser Block ausgeführt wird, werden die Anweisungen im Unterprogramm ausgeführt. Das aufrufende Skript pausiert dabei, bis das Unterprogramm zuende ausgeführt wurde und fährt danach erst fort.
Damit lässt sich das Beispielprogramm deutlich vereinfachen: In diesem Fall wird die Anweisungssequenz, welche die Sprung-Aktion darstellt, als Skript an den Definitionsblock angehängt. An den drei Programmstellen, an denen die Sprung-Aktion ausgeführt werden soll, wird nun stattdessen nur der selbst definierte Anweisungsblock “springe” eingefügt:
Im aufrufenden Skript sieht der Aufruf des Unterprogramms nun also genauso aus wie jede andere elementare Anweisung (z. B. “gehe zu Position”, “setze Richtung auf”, “warte”). Das Unterprogramm “verkapselt” dabei die eigentlichen Anweisungen, die beim Aufruf ausgeführt werden. Solange wir wissen, was der Effekt des Anweisungsblock ist (in diesem Fall “führe innerhalb von 1 Sekunde eine Sprungbewegung aus, falls die Figur sich auf dem Boden befindet”), können wir den Block zur Programmierung verwenden ohne genau wissen zu müssen, wie dieser Effekt konkret umgesetzt wird.
Ein Unterprogramm ist also allgemein ein wiederverwendbarer Programmabschnitt, der an anderen Stellen im Programm aufgerufen werden kann, um eine bestimmte Aufgabe zu übernehmen. Wir unterscheiden dabei die Definition des Unterprogramms (“Was macht es?”) vom eigentlichen Aufruf des Unterprogramms (“Mach es!”).
Oft sind Programmabschnitte zum Lösen bestimmter Aufgaben, die an verschiedenen Stellen im Programm vorkommen, nicht exakt identisch, sondern hängen von bestimmten Werten ab. Als Beispiel: Angenommen, die Sprung-Aktion aus dem vorigen Abschnitt soll mit verschiedenen Sprunghöhen durchgeführt werden – beim Drücken der Pfeiltaste oder Maustaste soll die Figur 10 Schritte hoch springen, beim Berühren des Steins dagegen nur 5 Schritte. Das Unterprogramm hängt nun also von einem Parameter ab, in diesem Fall der Sprunghöhe.
Zu diesem Zweck lassen sich parametrisierte Unterprogramme, also Unterprogramme mit Parametern definieren. Beim Aufruf eines parametrisierten Unterprogramms werden – genau wie bei den bisher bekannten parametrisierten Anweisungen in Scratch1 – verschiedene Parameterwerte angegeben, die bei der Ausführung des Unterprogramms berücksichtigt werden können. Dazu müssen beim Erstellen eines eigenen Blocks im Dialog “Neuer Block” entsprechende Eingabefelder für Parameter angelegt werden. Dabei stehen ovale Eingabefelder für Zahlenwerte und Zeichenketten, sowie sechseckige Eingabefelder für Wahrheitswerte zur Verfügung:
In diesem Beispiel wird ein ovales Eingabefeld namens “N” für die Anzahl der Sprungschritte hinzugefügt.2 Im Unterprogramm-Skript wird nun der Wert des Parameters N (statt wie zuvor der feste Wert 10) in der ersten Wiederholung verwendet. Dazu muss der entsprechende Werteblock für diesen Parameter aus dem Definitionsblock in das Eingabefeld der Wiederholung gezogen werden:
Beim Aufruf des Unterprogramms können nun – wie bereits von normalen Anweisungen bekannt – verschiedene Werte im Eingabefeld angegeben werden, die jeweils bei der Ausführung des Unterprogramms als Wert für den Parameter N verwendet werden und für unterschiedlich hohe Sprünge sorgen:
Das Verhalten von Unterprogrammen ist also durch Parameter variierbar.
Parameter sind dabei aus Sicht von Unterprogrammen lokale Variablen, denen beim Aufruf des Unterprogramms Werte zugewiesen werden. Diese Variablen können nur innerhalb des Unterprogramms verwendet werden (daher “lokal”). In Scratch können Sie außerdem – im Gegensatz zu normalen Variablen – während der Ausführung des Unterprogramms nur gelesen, aber nicht überschrieben werden.
Unterprogramme eignen sich also, um Programme übersichtlich zu strukturieren und zusammengehörende Programmteile zusammenzufassen (“Modularisierung” von Programmen). Dadurch kann ein Programm in kleinere Bausteine zerlegt werden, die sich unabhängig voneinander entwickeln lassen und zu komplexeren Programmen zusammensetzen lassen.
Durch Unterprogramme lässt sich Code-Redundanz vermeiden und Programme werden besser wartbar, da nachträgliche Änderungen am Unterprogramm nur in dessen Definition durchgeführt werden müssen. Programme werden außerdem leichter testbar, indem zunächst die Unterprogramme als kleinere Einheiten getestet werden.
Ein entscheidender Vorteil ist auch die Wiederverwendbarkeit: Ein einmal entwickeltes Unterprogramm für eine bestimmte Aufgabe kann immer dann, wenn diese Aufgabe gelöst werden soll, einfach aufgerufen werden, anstatt dass der Inhalt noch einmal neu programmiert werden muss.
So erleichtern Unterprogramme auch die Zusammenarbeit, wenn viele Menschen an einem Projekt arbeiten: Hier kann zuerst überlegt werden, wie sich das Programm am besten in Unterprogramme aufteilen lässt, und anschließend werden die einzelnen Unterprogramme auf mehrere Teams verteilt und parallel entwickelt.
In komplexeren Scratch-Projekten kann es nötig sein, dass ein Skript eines Objekts eine Aktion eines anderen Objekts auslösen soll. Das ist bisher nicht möglich: Wird beispielsweise ein Objekt angeklickt, kann in dem Skript, das auf dieses Ereignis reagiert, nur das Objekt selbst bewegt oder verändert werden, aber nicht ein anderes Objekt. Ebenso können Objekte in Scratch nur ihre eigenen Methoden direkt aufrufen, aber nicht Methoden von anderen Objekte (“fremder Methodenaufruf”).
Um einem Programmierobjekt mitzuteilen, dass es etwas machen soll, werden in der Programmierung Signale verwendet, die in Scratch als Nachrichten bezeichnet werden. Ein solches Signal kann mit einer speziellen Anweisung von einem Objekt ausgesendet werden und von allen Objekten empfangen werden kann (“Broadcasting”). Für Objekte kann über einen bestimmten Ereignisblock angegeben werden, auf welche Signale sie wie reagieren sollen. Nachrichten ermöglichen es also, “eigene” Ereignisse zu definieren, mittels derer verschiedene Figuren und die Bühne miteinander kommunizieren und auf einander reagieren können.
Um eine Nachricht an alle Objekte zu senden, wird die Anweisung “sende Nachricht an alle” aus der Kategorie “Ereignisse” (gelb) verwendet. Die Nachricht selbst kann über die Auswahlliste ▾ ausgewählt werden. Mit der Option “Neue Nachricht” kann eine neue Nachricht erstellt werden, hier beispielsweise eine Nachricht namens “Alarm”:
Wird diese Anweisung in einem Skript ausgeführt, so wird die Nachricht “Alarm” an alle Objekte (auch an den Sender selbst) gesendet und sofort mit der nächsten Anweisung im Skript weitergemacht.
Soll das Skript dagegen warten, bis alle Objekte ihre Reaktion auf die Nachricht zuende ausgeführt haben, bevor es mit seiner nächsten Anweisung fortfährt, wird stattdessen die Anweisung “sende Nachricht an alle und warte” verwendet:
Um Reaktionen auf bestimmte Nachrichten zu programmieren, gibt es einen speziellen Ereignisblock in der Kategorie “Ereignisse”:
Das angehängte Skript wird ausgeführt, sobald das Objekt die angegebene Nachricht empfängt (hier die Nachricht namens “Alarm”). Ein Objekt kann dabei auch unterschiedlich auf verschiedene Nachrichten reagieren, indem Ereignisblöcke mit verschiedenen Nachrichtenbezeichnern im Skriptbereich angelegt werden:
Das obenstehende Beispiel lässt ein Objekt verschwinden, wenn ein anderes Objekt die Nachricht “Alarm” sendet, und wieder erscheinen, wenn ein anderes Objekt die Nachricht “Entwarnung” sendet.
Das folgende Beispiel demonstriert die Kommunikation zwischen mehreren Objekten anhand von Nachrichten. Sie können das Scratch-Projekt Kommunikation.sb3 hier herunterladen: Download
Hier befinden sich drei Figuren auf der Bühne (“Alice”, “Bob” und “Carol”), die Personen in einem Netzwerk darstellen. Beim Anklicken einer Figur sollen jeweils beide oder eine andere Figur reagieren.
Wenn die Figur “Carol” angeklickt wird, sollen (nachdem sie eine Mitteilung angezeigt hat) Aktionen der Figuren “Alice” und “Bob” ausgelöst werden: Beide sollen ebenfalls eine Mitteilung anzeigen.
Dazu wird im Skriptbereich von “Carol” definiert, dass beim Eintreten des Ereignisses “Wenn ich angeklickt werde” eine Nachricht “stellt euch vor” gesendet wird:
Die Reaktion der anderen beiden Figuren auf diese Nachricht wird in deren Skriptbereich mit dem Ereignisblock “wenn ich … empfange” definiert:
Diese Art der Kommunikation wird als “Ein-Weg-Kommunikation” bezeichnet, da das sendende Objekt kein Antwortsignal von den empfangenden Objekten erwartet. Der zeitliche Ablauf der Skriptausführung lässt sich in dem folgenden Balkendiagramm nachvollziehen:
In der “Zwei-Wege-Kommunikation” synchronisieren zwei Objekte eine Aktionssequenz, indem sie Nachrichten hin- und herschicken. In diesem Beispiel soll beim Anklicken der Figur “Alice” eine Aktion der Figur “Carol” ausgelöst werden. Wenn “Carol” ihre Aktion beendet hat, soll wiederum eine weitere Reaktion von “Alice” ausgelöst werden. Dazu werden zwei Nachrichten “prüfe Alices Aufträge” und “bedanke dich, Alice” verwendet. Der zeitliche Ablauf der Skriptausführung ist im folgenden Balkendiagramm dargestellt:
Dazu fügen wir den Figuren “Alice” und “Carol” die folgenden Skripte zur Ereignisbehandlung hinzu:
Da die Figur “Bob” keine Ereignisblöcke für diese beiden Nachrichten besitzt, reagiert sie auf diese nicht.
Analog können wir auch den Dialog zwischen “Bob” und “Carol” umsetzen (siehe Beispielvideo), indem wir weitere Nachrichten dafür verwenden (z. B. “prüfe Bobs Aufträge”, “bedanke dich, Bob”).
In Fällen wie diesen lässt sich die Zwei-Wege-Kommunikation auch vereinfachen, indem die Anweisung “sende Nachricht und warte” verwendet wird:
Hier pausiert das Skript des sendenden Objekts (hier “Alice”) automatisch, bis alle Objekte, die einen Ereignisblock für diese Nachricht besitzen (hier “Carol”), ihre entsprechende Reaktion zuende ausgeführt haben. In der Reaktion von “Carol” muss nun also keine Antwortnachricht mehr versendet werden:
Der zeitliche Ablauf ist hier im Resultat genauso wie beim vorigen Beispiel – der Unterschied besteht darin, dass hier nur ein Skript von “Alice” ausgeführt wird (das zwischenzeitlich pausiert), statt wie im vorigen Beispiel je ein Skript für die 1. und 3. Aktion:
Nachrichten (auch Signale) werden in der Programmierung zur ereignisgesteuerte Kommunikation zwischen Programmobjekten verwendet.
Nachrichten (“Signale”) eignen sich gut, um komplexere zeitliche Programmabläufe zu koordinieren, in denen Aktionen von Objekten durch Aktionen anderer Objekte ausgelöst werden, beispielsweise in einer Animationssequenz. Die zeitliche Abstimmung verschiedener Programmabläufe aufeinander wird als Synchronisation bezeichnet.
Mit Hilfe von Nachrichten lassen sich Objekte durch Skripte anderer Objekte steuern, beispielsweise lässt sich so eine Figur durch das Anklicken von Schaltflächen bewegen.
Nachrichten lassen sich allgemein verwenden, um selbst definierte Ereignisse eines Objekts an andere Objekte zu melden, beispielsweise “Wenn anderes Objekt angeklickt wird”, “Wenn anderes Objekt den Rand berührt” oder “Wenn anderes Objekt verschwindet/erscheint”.
In den meisten Programmiersprachen, in denen mit Objekten gearbeitet wird, können Objekte nicht nur ihre eigenen Methoden aufrufen, sondern auch Methoden anderer Objekte (fremde Methoden). In Scratch können Nachrichten verwendet werden, um solche Fremdmethodenaufrufe umzusetzen, indem einem anderen Objekt mittels einer Nachricht mitgeteilt wird, dass es eine eigene Methode aufrufen soll.
Dazu muss für jede Methode, die durch andere Objekte aufgerufen werden kann, eine eigene Nachricht verwendet werden. Um nicht den Überblick zu verlieren, bietet es sich an, diese Nachrichten nach einem bestimmten Schema zu benennen, beispielsweise “Objekt.Methode”. Das folgende Beispiel ergänzt das Objekt “Bär” aus dem Beispiel zu Unterprogramme um das entsprechende Ereignis:
Die Methode “springe” dieses Objekts kann nun durch andere Objekte mit der Anweisung “sende Bär.springe an alle” aufgerufen werden (bzw. “sende … und warte”, wenn das aufrufende Skript erst nach dem Fremdmethodenaufruf fortfahren soll):
zur Erinnerung: siehe Lektion 1 – Einstieg in Scratch, Abschnitt Parameter und Werte ↩︎
Neben Eingabefeldern für Parameter können in Scratch auch weitere Textteile zum Namen des neuen Blocks hinzugefügt werden, um ihn in natürlicher Sprache lesbarer zu machen oder für die programmierenden Menschen hilfreiche Informationen zu den Parametern zu ergänzen (z. B. Einheiten). In diesem Beispiel heißt der Block “springe (N) Schritte”, wobei (N) ein Eingabefeld für einen Parameter darstellt, der im Unterprogramm-Skript “N” heißt. ↩︎