Das Kinderzimmer im Code:
Polymorphismen in der Programmierung

Was sind Überschreibungen, Überladungen und Polyfills – und wozu benötigt man diese überhaupt?

Wer (mindestens) ein kleines Kind zu Hause hat, kennt sicher das folgende Phänomen: An einem Tag steht die Spielküche noch in der Ecke hinten links, aber schon am Folgetag hat der Nachwuchs das gesamte Zimmer umgeräumt, so dass nicht nur die Spielküche in eine andere Ecke bugsiert wurde, sondern nahezu jedes nur irgendwie bewegliche Möbelstück seine Position verändert hat.

Auch wenn mir wohl jeder zusatimmen wird, dass sich das Erscheinungsbild des Zimmers eindeutig verändert hat, ist es immer noch als Kinderzimmer zu erkennen. Damit unterliegt Das Zimmer einem Effekt, den man in der Programmierung Polymorphismus nennt.

Zugegeben, dieser Vergleich hinkt ein Wenig (wie wohl die meisten Vergleiche), denn eigentlich müsste ergänzt werden „während Sie das Zimmer am ersten Tag noch durch die Tür betreten haben, mussten Sie am Folgetag durch das Fenster hereinklettern“ oder „obwohl mithilfe einer Trockenbauwand das Zimmer um einige Quadratmeter verkleinert wurde, hat die Spielküche noch hineingepasst“, doch das Grundkonzept sollte klar sein: Polymorphismen treten in der objektorientierten Programmierung immer dort auf wo zur Laufzeit ähnliche Funktionen unter leicht varrierenden Bedingungen benötigt werden oder Methoden an Laufzeitbedingungen angepasst werden müssen. Dazu gehören z.B.:

  • Unterschiedliche Interpreter oder Laufzeitumgebungen, in denen der gleiche Code (z.B. ein Skript) ausgeführt werden soll – z.B. unterschiedliche Webbrowser, in denen ein-und-dieselbe HTML-Website gerendert werden soll
  • Aufruf von Methoden mit Übergabe unterschiedlicher Argumente, deren vorherige Umwandlung zu aufwändig oder schlicht nicht möglich wären
  • Methoden, die zur Laufzeit unterschiedliche Werte(-Typen) zurückgeben müssen, die evtl. auch nicht ohne Mehraufwände ineinander umgewandelt werden können
  • Vererbung von Klassen, bei denen z.B. eine Tochterklasse eine in der Elternklasse definierte Operation mit geänderten Parametern aufrufen muss

Polymorphismen können mithilfe einer Vielzahl verschiedener Techniken erreicht werden. Gerade bei der Softwareentwicklung mittels .net ist wohl die Überladung eine der am häufigsten eingesetzten Technik.

//version 1 
public int GetSum(int val1, int val2)
{ 
    return val1 + val2; 
} 

//version 2 
public int GetSum(int[] values)
{ 
    int result = 0; 
    
    foreach(int val in values)
    { 
        result += val; 
    } 
    
    return result; 
} 

//version 3 
public string GetSum(string val1, string val2)
{ 
    int result = int.Parse(val1) + int.Parse(val2); 
    return result.ToString(); 
}

Wie in diesem Beispiel, besitzen sich überladende Methoden zwar den gleichen Namen, unterscheiden sich aber in den Argumenten und/ oder Rückgabetypen.

Das Überschreiben ist eine Methode, die gern im Zusammenhang mit Klassenvererbung eingesetzt wird. Dabei haben die Methoden in der Eltern- und auch der Kinderklasse den gleichen Namen, jedoch wird die Methode in der abgeleiteten Klasse aufgerufen – anstatt die in der Elternklasse. Voraussetzung dafür ist es (in C#), die Methode in der Elternklasse als virtual zu definieren und die Methode in der Kinderklasse mit override zu dekorieren.

//die Eltern-Klasse 
public class ParentClass
{ 
    public virtual void DoSomething()
    { 
        MessageBox.Show(DateTime.Now.ToString()); 
    } 
} 

//die Kind-Klasse 
public class ChildClass : ParentClass
{ 
    public override void DoSomething()
    { 
        MessageBox.Show("Hello World!"); 
    }
}

Eine besondere Form des Polymorphismus hat sich in JavaScript herausgebildet: Häufig müssen Eigenheiten der verschiedenen Browser bei der Erstellung von Methoden berücksichtigt werden. Häufig unterstützen einzelne Browser Variablen oder Methoden, die ein anderer gar nicht kennt und umgekehrt. Ein Beispiel dafür, ist document.documentMode in Microsofts Internet Explorer:

if (document.documentMode == 7) { 
     doSomethingIn.IE7_only; 
} else { 
     doSomething.everwhereElse; 
}

Eine Prüfung auf eine dieser Eigenschaften kann in der Praxis dafür verwendet werden um einen bestimmten Browser(-Typ) zur Laufzeit zu identifizieren und spezifische Operationen auszuführen.

Mithilfe sogenannter Polyfills ist es möglich in JavaScript Methoden für einen Browser zur Verfügung zu stellen, die dieser nativ gar nicht zur Verfügung stellt – ggf. auch um Code kompatibel zu älteren Browser-Versionen zu machen. So wird die string.startsWith()-Methode erst ab Version 12 nativ durch den Internet Explorer unterstützt, mithilfe eines Polyfills ist es aber möglich ältere Systeme damit „nachzurüsten“. Damit agieren Polyfills ähnlich den Extension-Methoden in z.B. VB.net oder C#.

//add the 'string.startsWith()' function (polyfill) (e.g. for the Internet Explorer) 
if (!String.prototype.startsWith) { 
    String.prototype.startsWith = function(searchString, position) { 
        position = position || 0; 
        return this.indexOf(searchString, position) === position; 
    }; 
}

Neben der Vererbung von Klassen selbst, gehören Polymorphismen zu den im Rahmen von Clean Code-Initiativen am stärksten diskutierten und kritisierten Themen. Als Hauptargumente gegen derartige Techniken wird häufig eine erhöhe Komplexität bzw. Unübersichtlichkeit des Quellcodes aufgeführt. Ich gebe diesen Kritikern recht, dass zumindest Überladungen und Überschreibungen zu Verwirrung und Code-Wiederholungen führen können, jedoch bin ich mir auch nicht ganz sicher wie wartbar ein Quellcode wird, in dem ich z.B. ständig vermeidbare Typen-Konversionen durchführen muss oder während der Laufzeit nicht in der Lage bin flexibel auf Abweichungen reagieren zu können.

Sofern notwendig, würde ich lieber für einige Tage durch das Fenster in das Kinderzimmer klettern, als die Wand zu durchbrechen um eine zweite Tür einzubauen, die u.U. die Statik des Gesamtgebäudes gefährdet.

Schlangenbeschwörer:
Externe Skripte in .net-Anwendungen

Wie lassen sich externe Skripte in eine .net-Anwendung integrieren?

Nicht immer ist es möglich oder sinnvoll Anwendungen „aus einem Guss“ bzw. in einer Programmiersprache zu entwickeln oder zu betreiben. Unabhängig vom Konzept der Modularisierung, sollte, meiner Meinung nach, auch immer der Ansatz der „Best Available Technology (BAT)“ (im Deutschen wohl am besten zu übersetzen mit „Stand der Technik“) berücksichtigt werden. Viel zu häufig wird – gerade bei Konzeptionierung neuer Anwendungen – die Wahl der verwendeten Sprachen, Frameworks und Techniken darauf basierend getroffen, welche Kenntnisse der jeweilige Dienstleister anbieten kann und nicht welche Auswahl für die Lösung einer bestimmten Fragestellung am sinnvollsten wäre. Auch kann die Kombination relativ junger Technologien mit bereits etablierten Frameworks eine Herausforderung darstellen. Dazu ein konkretes Beispiel:

Eine bereits existierende, auf .net basierende Anwendung soll um Features aus dem Bereich Deep Learning erweitert werden. Neben den .net-Entwicklern, arbeiten an diesem Projekt natürlich auch Data Scientists mit, die u.a. für die Entwicklung der künstlichen neuronalen Netzwerke zuständig sind – dies aber natürlich in Python tun. Die Aufgabe besteht nun darin diese beiden Technologien zu kombinieren. Was also tun?

Option 1: Warum (überhaupt) Python?

Mit ML.Net steht ein Framework aus dem Hause Microsoft zur Verfügung, mit dessen Hilfe sich verhältnismäßig einfach machine learning-Lösungen in C# erstellen lassen. Die Daten-Wissenschaftler würden also (versuchen) ihre Konzepte in einer Sprache zu realisieren, die eine Integration unnötig macht und ggf. im Rahmen eines kompilierten Assemblies recht einfach in die bestehende Anwendung integriert werden kann.

Unabhängig davon ob sich die betroffene Fachgruppe darüber freuen würde bei der Wahl der zu verwendenden Tools eingeschränkt zu werden, gibt es einige Gründe warum sich Python und damit verbundene Frameworks im Bereich machine learning durchgesetzt haben. Mit dieser Lösung würde man sich ggf. einen Sonderfall ins Haus holen, bei dem man nur mit deutlich erhöhtem Aufwand auf dem aktuellen Stand der Technik bleiben und somit gegen den BAT-Grundsatz verstoßen.

Option 2: Python in .net interpretieren

Es gibt eine Reihe recht guter Projekte, wie z.B. Python.net oder IronPython, die Bibliotheken bereitstellen um Python-Skripe in .net-Anwendungen auszuführen und zu interpretieren. Mithilfe verschiedener Injection-Szenarien ist es sogar möglich Variablen und Funktionen bereitzustellen, die üblicherweise nicht Bestandteil der (Core-)Python-Umgebung sind.

Leider kommen diese Interpreter gerade bei wissenschaftlichen Bibliotheken an ihre Grenzen. Um z.B. aufwändige mathematische Operationen oder (wissenschaftliche) Berechnungen durchführen zu können, stellen Frameworks wie Anaconda zusätzliche Funktionen und Bibliotheken bereit, die bei den genannten Interpretern nicht zur Verfügung stehen. Somit stellt dies nur eine engschränkt nutzbare Lösung dar.

Option 3: Die Python-Skripte als externe Prozesse ausführen

In einem Artikel in der dotnetpro vom März 2019 wird – neben einem Beispiel für die Verwendung von IronPython – genau dieses Szenario beschrieben, mit dem Lösungsansatz das Python-Skript mithilfe der .net-eigenen Process-Klasse aufzurufen, die Ausgabe zu überwachen und in der Core-Anwendung abzuwarten bis das Skript vollständig durchlaufen wurde bis die entstandenen Daten weiterverarbeitet werden.

Natürlich funktioniert dies nicht nur für Python-Skripte, sondern für alle Arten externer Skripte und Anwendungen – und das ganz ohne z.B. tiefgreifende Anpassungen an der Original-Anwendung durchführen zu müssen. Möchte man jedoch nicht jedes mal aufs Neue

  • die Process-Parameter, inkl. ggf. notwendiger für das externe Skript oder die Anwendung notwendige Kommandozeilenparameter, konfigurieren
  • den Ablauf überwachen und auf auftretende Output- oder Fehler-Events reagieren

empfiehlt sich die Kapselung in eine dafür optimierte Klasse.

Mit der ManagedProcess-Klasse stellt das aktuelle Release (1.4.1) des BYTES.NET-Frameworks genau eine derartige Kapselung bereit.

Private Async Sub RunScript()

    'create the process class instance
    Dim process As ManagedProcess = New ManagedProcess

    With process
        .Executable = "C:\Windows\System32\cmd.exe"
        .Arguments = {"/c " & """" & "%InstallationDir%..\..\..\..\samples\Scripts\LongRunningSampleScript.cmd" & """" & " TestValue"}
        .ShowUI = Me.ShowUI
    End With

    'handle the events
    AddHandler _process.OutputReceived, Sub(data As LogEntry)
                                            _log.Add(data)
                                        End Sub

    AddHandler _process.Exited, Sub(code As Integer)
                                    MsgBox("Process finished with: " & code)
                                End Sub

    Await _process.RunAsync()

End Function

Mithilfe weniger Zeilen Code ist es z.B. möglich ein Skript (mithilfe eines beliebigen externen Interpreters) auszuführen und sowohl auf Rückgaben zu reagieren als auch auf den Abschluss des Runs zu warten. Zudem bietet die Klasse die Vorteile, dass

  • mit RunAsync bereits eine Methode bereitgestellt wird, die die Nutzeroberfläche während (eines beliebig langen) Durchlaufs nicht blockiert
  • ein einheitliches Event (OutputReceived) zur Verfügung steht, dass die Verarbeitung der Rückgaben ermöglicht; Fehler können mithilfe des InformationLevel des zurückgegebenen LogEntry-Objekts identifiziert werden
  • relative Pfade – z.B. mithilfe der generischen %InstallationDir%-Variable – für den Interpreter-Pfad unterstützt werden, so dass auch Interpreter wie z.B. Anaconda in einer portable-Version (z.B. als Bestandteil eines Installationspakets) genutzt werden können

Ein detailliertes Beispiel für den Einsatz mit Windows-Batch (*.cmd) und Python (*.py) ist im zugehörigen Repository auf Github verfügbar.

Nachgefragt. Nicht-blockende Dialoge in WPF

Ein nicht-blockender Dialog, in .net, basierend auf dem MVVM-Designmuster. Geht das? Ja, klar!

Genau genommen, ist dies ein Post aus dem letzten Jahr – jedoch aktualisiert im Rahmen des Release meines BYTES.NET-Projekts. Der Quellcode der Beispielanwendung ist im zugehörigen Github-Repository zu finden und dient als Basis für diesen Artikel:

Jeder Entwickler kommt irgendwann an den Punkt, an dem er den Anwender nach weiteren Angaben oder Eingaben fragen muss. Sofern es sich nicht um eine „Single Page“- oder „inline“- Anwendung handelt, gehört es zum üblichen Vorgehen, einen Dialog zu öffnen, auf die Eingabe (und deren Bestätigung) zu warten und die Informationen anschließend (weiter) zu verarbeiten.

In einer Windows Forms-Anwendung gibt es den recht komfortablen OpenFileDialog um z.B. eine Datei zu öffnen. In Windows Presentation Foundation (WPF) gibt es diesen und ähnliche Dialoge standardmäßig nicht. Der übliche Tipp für WPF und den soeben genannten Anwendungsfall lautet daher häufig „bitte auf das System.Windows.Forms-Assembly referenzieren und dann den OpenFileDialog verwenden“.

Wie geht man aber damit um, wenn man eine Eingabe benötigt, für die es keinen vorgefertigten Dialog gibt oder wenn die Standarddialoge nicht ausreichen?

Entsprechend des MVVM-Designmusters, besteht ein Dialog aus

  • Einem Window, das als View verwendet wird und in dem die notwendigen Controls per XAML definiert werden.
  • Die Eigenschaften der Controls werden an die entsprechenden Eigenschaften eines View Models gebunden.
  • Anschließend wird der Dialog über Window.Show() anzeigen.

So weit so gut, jedoch liegt auch hier – wie allzu häufig – die Herausforderung im Detail. Was ist zum Beispiel…

  • … wenn der Dialog bzw. das Warten auf die Eingabe die übrigen Anwendungsfunktionen nicht blockieren darf (weil beispielsweise asynchrone Prozesse im Hintergrund ausgeführt werden)?
  • … wenn man häufig verschiedene Dialoge benötigt und die Codebase möglichst schlank halten möchte (z.B. auch vor dem Hintergrund des Bugfixings und der Wartbarkeit)?

Gleichzeitig ist es (aus meiner Sicht) deutlich eleganter – ähnlich wie im OpenFileDialog eine ShowDialog()-Methode über das View Model aufzurufen, anstatt eine neue Instanz der Window-Klasse zu erstellen und diese dann aufzurufen.

Die Lösung dafür bietet eine eigene DialogViewModel-Basisklasse, die von BYTES.NET.WPF.MVVM.ViewModel abgeleitet wird und somit auch INotifyPropertyChanged implementiert. Diese Klasse stellt eine Methode namens ShowDialog() bereit, in der gleich drei Dinge passieren:

  • Zum Ersten wird das zugehörige View (= Window) über die Methode ShowView() angezeigt.
  • Es wird darauf gewartet, dass der Anwender den View wieder schließt – entweder in Form einer Eingabebestätigung oder eines Abbruchs
  • Nach dem Schließen des View wird ein Boolean zurückgegeben, der in der aufrufenden Klasse dann ausgewertet werden kann:
Public Function ShowDialog() As Boolean

    'show the view
    ShowView()

    'wait for user input
    _myBlock = New EventWaitHandle(False, EventResetMode.ManualReset)
    WaitForEvent(_myBlock, New TimeSpan(24, 0, 0))

    'remove the wait handle
    _myBlock.Dispose()

    'return the dialog closing indicator
    Return Me.DialogResult

End Function

Ein Beispiel für eine daraus abgeleitete View Model-Klasse ist BYTES.NET.Sample.ViewModels.DialogVM auf Github. Durch die beiden überschreibbaren Methoden ShowView() und CloseView() ist es möglich nicht nur Windows als Views zu verwenden, sondern ggf. auch eigene GUI-Elemente wie z.B. Benutzersteuerelemente. Zudem lässt sich bei Bedarf noch während des Schließens der Wert von DialogResult manipulieren.

Kommen wir nun zum Verhindern des Blockens der Anwendung, beim Warten auf die Eingabebestätigung, WaitForEvent(). Nachdem ich einige (mehr oder minder erfolgreiche) Lösungsansätze probiert habe, bin ich in diesem Artikel auf eine sehr elegante Lösung zu dieser Problematik gestoßen: Die Grundidee beruht darauf einen zusätzlichen Thread zu verwenden um asynchron auf ein Ereignis zu warten – wodurch der Hauptthread (und mit ihm auch die Oberfläche) nicht blockiert wird. Zeitlich achgelagerte Operationen werden erst dann ausgeführt, wenn ShowDialog() eine Antwort zurückgegeben hat.

Private Function WaitForEvent(eventHandle As EventWaitHandle, Optional timeout As TimeSpan = Nothing) As Boolean

    Dim didWait As Boolean = False
    Dim frame = New DispatcherFrame()
    Dim start As New ParameterizedThreadStart(Sub()

                                                didWait = eventHandle.WaitOne(timeout)
                                                frame.[Continue] = False

                                              End Sub)

    Dim thread = New Thread(start)
    thread.Start()
    Dispatcher.PushFrame(frame)
    Return didWait

End Function

Schön und gut, was passiert nun aber, wenn der Anwender – statt auf z.B. einen Button zu drücken – das Dialogfenster über das kleine Kreuzchen rechts oben im Header schließt? Das Fenster wird zwar geschlossen, aber der „Warte“-Thread wird in der Regel nicht automatisch beendet und die Anwendung wird nicht sauber geschlossen.

Um auch dieses Problem zu lösen, gibt es einen recht einfachen Trick, den ich wiederum in diesem Artikel gefunden habe: Mithilfe eines Behaviors wird ein delegated Command (in meinem Fall ViewModelRelayCommand) an das ClosedEvent des Window gebunden, so dass – wie bei jedem anderen Command auch – die Close()-Methode (mit einem Negativwert für das DialogResult) aufgerufen werden kann. Als Teil eines XAML-definierten Window sieht das Ganze dann so aus:

<Window x:Class="Views.MVVM.DialogView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BYTES.NET.SAMPLE.Views.MVVM"
        
        xmlns:Behaviors="clr-namespace:BYTES.NET.WPF.Behaviors;assembly=BYTES.NET"
        Behaviors:WindowClosingBehavior.Closed="{Binding Path=Commands[CancelCmd]}"
        
        mc:Ignorable="d"
        Title="{Binding Title, Mode=OneWay}" Height="300" Width="500" ResizeMode="NoResize">

    ...

</Window>

Schnell mal eben gebastelt:
Rapid Prototyping in der Softwareentwicklung

(Software-)Prototypen bieten eine gute Möglichkeit Ideen und Lösungsansätze schnell und effizient zu testen. Aber was sollte man dabei zu beachten?

Im Rahmen der (Weiter-)Entwicklung einer Software ist es nichts Neues oder Ungewöhnliches Prototypen einer Anwendung oder der – ggf. zu verbessernden – Teilfunktion(en) zu erstellen. Während in der eher traditionellen Softwareentwicklung häufig sogenannte Minimum Viable Product(s) (MVP) entstehen, kommt dem Rapid Prototyping in der agilen Softwareentwicklung – häufig im Rahmen des sogenannten Design Thinking – eine besondere Bedeutung zu:

Im Unterschied zu einem MVP, das häufig gegen Ende einer Konzept- oder Anforderungsanalysephase und mit teils doch recht hohen Entwicklungsaufwänden ensteht, soll ein Rapid Prototyp mit möglichst geringem Entwicklungsaufwand und auch bereits möglichst frühzeitig erstellt werden. Häufig werden für einzelne Ideen dedizierte Prototypen gebaut oder spezielle Lösungsansätze werden mithilfe von Prototypen validiert – um das so erhaltene Feedback frühestmöglich bei der Projektplanung zu berücksichtigen.

Häufig werden die Prototypen gemeinsam mit Mockups oder Wireframes verwendet um die Gesamtarchitektur einer Lösung zu visualisieren. Im Unterschied dazu, sollen Protypen es Teammitgliedern oder ausgewählten Testnutzern gestatten typische Anwendungsfälle auszuprobieren oder eingesetzte Technologien (z.B. auf Performance) konkret zu bewerten.

Was ist jedoch beim Einsatz von Rapid Prototyping zusätzlich zu beachten?

Keep it simple!

Wie schon erwähnt, soll ein Prototyp meist einen ganz bestimmten Ansatz oder eine Idee abbilden. Schnell passiert es jedoch, dass man sich z.B. aufgrund der verwendeten Technologien – oder aus dem Wunsch heraus einmal investierte Zeit sinnvoll weiterverwenden zu können – im Detail verliert oder unnötig komplizierte Architekturen implementiert. Wenn es die (Gesamt-)Anwendungsarchitektur zulässt modulare Feature-Prototypen zu erstellen, optimal. Falls nicht, bleiben Sie fokussiert und konzentrieren Sie sich auf die eigentliche Grundidee! Was ist der minimale Funktionsumfang der nötig ist um den aktuellen Ansatz testen zu können?

Es sollte darauf geachtet werden, dass die Frage-/ Aufgabenstellung realistisch ist bzw. so weit heruntergebrochen werden kann, dass sie mit einem einfachen Prototypen abgebildet werden kann. Während einfache Datenkonvertierungen und Statusübergänge u.U. noch abbildbar sind, macht es wenig Sinn zu versuchen komplette Workflows in einem einzelnen Prototypen abzubilden.

Low-Code?

Ein Artikel zum Thema Rapid Prototyping aus dem letzten Jahr legt u.a. einen der Schwerpunkte auf Low-Code-Plattformen. Es wird beschrieben, dass die Einsatzmöglichkeit von Low-Coding-Systemen u.a. von Ansatz, Funktionsumfang, Zielgruppe und der Bereitschaft der Mitarbeiter abhängt, sich mit der Erstellung von Applikationen zu beschäftigen.

Grundsätzlich möchte ich dieser Aussage nicht wiederprechen, finde jedoch, dass man an dieser Stelle zwischen „Prototypen zur Gestaltung von (Geschäfts-)Prozessen“ und „echten Anwendungsprototypen“ unterscheiden sollte: Ich glaube durchaus, dass man Prototypen z.B. für die Modellierung von Prozessen innerhalb integrierter Geschäftsanwendungen per Low-Code-Verfahren erstellen kann. Für „native“ Software hängt dies stark von den verwendeten Technologien ab – und führt fast immer dazu, dass zumindest im geringfügigen Umfang Programmierung notwendig ist.

Häufig kann man sich das Leben durch den gezielten und bewussten Einsatz von (Software-)Artefakten, Bibliotheken und Paket-Managern erleichtern. Im .net-Umfeld ist Nuget der wohl populärste Paket-Manager. Der Vorteil: Einmal für regelmäßig auftretende Anforderungen erstellte und standardisierte Bibliotheken können gepackt, über ein zentrales Repository verteilt und in zukünftigen Anwendungen wiederverwendet werden. Nicht wirklich Low-Code, ermöglicht es aber eine starke Vereinfachung und Beschleunigung beim Erstellen von Anwendungsprototypen.

Eine Bibliothek mit häufig in meinen Projekten eingesetzten Basis-Klassen und -Implementierungen sind zu finden bei Nuget mit Beispielcodes gehostet auf Github.

Team und Tools

Es liegt in der Natur der Idee, dass sich in einem agilen und interaktiven Arbeitsprozess (Software-)Versionen und Spezifikationen schnell und regelmäßig ändern. Damit ist es nicht nur wichtig, dass alle Team-Mitglieder Zugang zu den für sie relevanten Informationen erhalten, sondern auch regelmäßig auf den aktuellen Stand gebracht werden. In diesem Zusammenhang ist es meist vorteilhaft eine Person zu benennen, die sich gezielt um die Vorbereitung und Strukturierung der Informationen sowie ggf. die Leitung von Sitzungen und Besprechungen kümmert.

Für externe Anwendungen, Bibliothekten und Tools sollte sichergestellt sein, dass alle Team-Mitglieder – soweit notwendig – ausreichende Kenntnisse besitzen. Falls dies (z.B. aufgrund von Kosten, Zeitplänen oder Qualifikation) nicht möglich ist, macht es häufig auch Sinn externe Spezialisten hinzuzuiehen.

Zeit- und Projektmanagement

Aus technologischer Sicht, erhält man mithilfe des Rapid Prototyping-Ansatzes schnell eine erste Testversion. Man sollte jedoch nicht nur darauf achten sich vorab die notwendige Zeit zu nehmen um konkrete und klare Anforderungen zu entwickeln, sondern:

  • Den Testnutzern – sofern vorhanden – ausreichend Zeit einräumen sich auch mit dem Prototypen auseinanderzusetzen
  • Feedback, z.B. durch Interviews, einholt und strukturiert
  • Regelmäßig eine (Neu-)Bewertung durchführt.

Ein Artikel aus dem Jahr 2018 stellt zudem eine schöne Liste typischer Fallstricke im Rahmen eines Prototyp-basierten Projekts dar.