Individualisierte Steuerelemente mit Xamarin.Forms

Xamarin.Forms ist eine Technologie, um Benutzeroberflächen zu beschreiben, sodass sie auf mehreren Plattformen (Android, iOS, Windows) mit derselben Logik aber mit plattformspezifischem Äußeren dargestellt werden. Dazu bildet Xamarin.Forms die Schnittmenge der Steuerelemente ab, die auf allen Plattformen vorhanden sind. In der Praxis bestehen Anwendungen aber nicht nur aus solchen Steuerelementen, sondern sie sollen an individuelle Bedürfnisse angepasst werden, um eine individuelle User Experience (UX) zu gewährleisten. Angefangen mit einfachem Styling über Templates bis hin zu Eigenentwicklungen von Steuerelementen bieten sich im Rahmen von Xamarin.Forms hierfür verschiedene Lösungsansätze.

So stellt sich die Frage, wann ein einfaches Styling reicht und wann ein Template oder wann der aufwändige Weg zur Erstellung eines eigenen Steuerelementes gegangen werden muss? In diesem Artikel sollen die technischen Möglichkeiten aufgezeigt und Hinweise zur Auswahl der richtigen Möglichkeit auf Basis meiner Erfahrungen gegeben werden.

Der Weg, wie Xamarin Controls abbildet

Auf allen Plattformen sind native UI-Controls implementiert, die für diese Plattformen typisch sind. Xamarin adaptiert diese und bildet sie als managed Pendants ab, sodass sie von CLR-kompatiblem Code aus verwendet werden können. Da sie sich zwischen den Plattformen in ihrer Bestimmung und Funktionsweise decken, hat Xamarin nochmals eine weitere Schicht über diese Adaptionen gelegt, um sie einheitlich verwenden zu können. Somit war die Grundidee von Xamarin.Forms geboren.

Doch wie genau macht Xamarin.Forms das?

Für jedes Steuerelement gibt es je eine CLR-Klasse, die auf die native Implementierung verweist. Zudem gibt es je Steuerelementgruppe für ein Steuerelement, das auf allen Plattformen verfügbar ist (z. B. alle Textblöcke), nochmals eine Xamarin.Forms-Klasse, um die gemeinsamen Eigenschaften dieser Gruppe abzubilden. Dieser Sachverhalt wird von der nachfolgenden Abbildung veranschaulicht. Zudem dient je Plattform ein sogenannter Renderer dazu, die Adaption zwischen Xamarin.Forms-Klasse und dem Xamarin-Adapter herzustellen.

xamarin controls

 

Individuelle Gestaltung von Benutzeroberflächen

Zunächst lassen sich im Rahmen von Xamarin.Forms Anwendungen über die gemeinsam abgebildeten Eigenschaften beeinflussen und somit gestalten. So hat jedes Label üblicherweise eine Text-Eigenschaft, um den auszugebenden Text anzugeben, aber auch Eigenschaften, um Schriftart, -größe und -stil anzugeben und vieles mehr. Dabei wird ausschließlich das Xamarin.Forms-Steuerelement verwendet, da es plattformunabhängig den nativen Steuerelementen der jeweiligen Plattformen mitteilt, welche Aktionen durchzuführen sind. Für das Label-Steuerelement z. B. ist diese Beziehung mit einem Auszug der Eigenschaften in der nachfolgenden Abbildung dargestellt.

properties

 

Sollen jedoch Anpassungen vorgenommen werden, die nur eine bestimmte Plattform betreffen oder die (noch) nicht von Xamarin.Forms ermöglicht werden, müssen diese Anpassungen durch Erweiterung des plattformspezifischen Renderes vorgenommen werden (siehe folgende Abbildung). Der Renderer stellt dabei eine Art Vermittler zwischen der Xamarin.Forms Steuerelement-Repräsentation und dem nativen Steuerelement dar und kann diese folglich bidirektional beeinflussen. Somit sorgt er letztlich dafür, dass die vom Xamarin.Forms-Steuerelement abgebildeten Charakteristika auf die Charakteristika des nativen Steuerelements verweisen. Auf diese Weise können alle vorhandenen Eigenschaften und Funktionen eines nativen Steuerelements beeinflusst und genutzt werden und Änderungen dieser zur Laufzeit vermittelt werden. Dabei kann auf diese Weise die Xamarin.Forms-Repräsentation des Steuerelements erweitert werden, indem Eigenschaften, Ereignisse und Aktionen des nativen Steuerelements an diese erweiterte Repräsentation weitervermittelt werden. Somit lässt sich eine – auf die Möglichkeiten der nativen Steuerelemente beschränkte – Erweiterung und Individualisierung vornehmen, die zudem sehr effizient ist, da die verwendbaren Möglichkeiten bereits nativ umgesetzt sind. Wenn es nötig ist, kann sogar das gesamte native Steuerelement gegen ein anderes ausgetauscht werden.

renderer

Neben dem Beeinflussen von Eigenschaften und dem Erweitern von Steuerelementen bieten manche Xamarin.Forms-Steuerelemente die Möglichkeit, Inhalte anzuzeigen. Dabei werden sogenannte Cells eingesetzt, die zum einen auf Basis nativer Steuerelemente vorgefertigt sind, und zum anderen aber auch individuell gestaltet werden können. Insbesondere die ViewCell bietet die Möglichkeit, unter Nutzung von Xamarin.Forms-Steuerelementen eine eigene Gestaltung dieser Inhalte vorzunehmen. Im Folgenden findet sich ein einfaches Beispiel hierzu, wobei in einer ViewCell, die als DataTemplate verwendet werden soll, zwei Textausgaben unterschiedlicher Formatierungen untereinander dargestellt werden sollen:

   1:  <DataTemplate>
   2:      <ViewCell>
   3:          <ViewCell.View>
   4:              <StackLayout Orientation="Horizontal">
   5:                  <Label Text="{Binding Name}" />
   6:                  <Label Text="{Binding Details}" FontSize="Micro" />
   7:              </StackLayout>
   8:          </ViewCell.View>
   9:      </ViewCell>
  10:  </DataTemplate>

So ist eine individuelle Gestaltung möglich, und theoretisch könnten auf diese Weise beliebig komplexe Benutzeroberflächen erzeugt werden. Jedoch ist man hier auf die in Xamarin.Forms abgebildeten Möglichkeiten beschränkt und muss auf die Performanz der Anwendung achten, da solche Templates aufgrund der Adaptionskette eher als schwergewichtig zu betrachten sind. Ferner ist die Abstraktionsmöglichkeit bei direkter Verwendung von DataTemplates beschränkt.

Erstellen von eigenen Steuerelementen

Wenn die zuvor aufgezeigte Flexibilität aufgrund von Abstraktionsmöglichkeiten oder Performanz nicht ausreichend ist, besteht zu guter Letzt die Möglichkeit, ein eigenes Steuerelement umzusetzen. Dafür gibt es zwei Wege: Zum einen kann ein solches Steuerelement als Ableitung eines Xamarin.Forms-Steuerelements umgesetzt werden. Zum anderen kann ein solches Steuerelement plattformspezifisch je Plattform umgesetzt werden.

Setzt man ein Steuerelement als Ableitung eines Xamarin.Forms-Steuerelements um, bietet sich am ehesten ein Layout an. Ein solches Steuerelement stellt sich wie ein erweitertes Template dar, welches wiederum mit Xamarin.Forms-Steuerelementen aufgebaut werden kann. Jedoch können erweiterte/dynamische Verhaltensweisen umgesetzt werden, und ferner gemeinsame übergreifende Eigenschaften und Funktionen definiert werden.

Ein triviales Beispiel dafür ist ein ItemsControl, wie man es aus WPF kennt. In ähnlicher Weise ist ein solches Steuerelement bereits in Form des ListViews vorhanden. Da dies jedoch die Selektion zur Zielsetzung hat und nativ abgebildet wird, ist eine Modifikation, dass es wie ein ItemsControl funktioniert, aufwändig oder ggf. sogar nicht praktikabel.

Im Wesentlichen definiert sich ein solches ItemsControl als StackLayout, welches das Binden einer ItemsSource ermöglicht und somit automatisch mit deren Items befüllt wird. Diese Items werden in einem festen Template oder in itemspezifischen Templates, welche über einen TemplateSelector ausgewählt werden, dargestellt. Somit wird das StackLayout mit aus dem jeweiligen Template erzeugten Steuerelementen befüllt. Dieser Zusammenhang wird auch noch mal in der Abbildung veranschaulicht. Eine Beispielimplementierung für ein solches ItemsControl findet sich hier: [.zip – 292KB]

Itemscontrol

Eine solche Umsetzung bietet den Charme, dass sie nur einmal für alle Plattformen implementiert werden muss. Allerdings birgt dieses Vorgehen leider auch Probleme: In Hinsicht auf die Performanz weist das Beispiel einen Engpass bei der dynamischen Befüllung auf. Der dynamische Anteil ist deshalb nur sehr beschränkt – in diesem Fall bis zu 20 Elementen – verwendbar. In nativen Controls dieser Art sind dafür in der Regel zur Performanz-Optimierung ein Control-Lifecycle und Virtualisierung implementiert. Dies müsste hier nachimplementiert werden, da das zu Grunde liegende Steuerelement dies nicht unterstützt. Somit entsteht auch hier wieder ein Aufwand, um die entstandenen Performanzeinbußen z. B. bei Aktualisierungen, auszugleichen.

Ferner stehen für den Inhalt nur nach Xamarin.Forms adaptierte Steuerelemente zur Verfügung, was die Flexibilität dieser Art und Weise, Controls zu erstellen, einschränkt. Und auch dabei ist zu bedenken, dass jedes von Xamarin.Forms erzeugte Steuerelement die vollständige Adaptionskette abbildet und zur Laufzeit im Speicher hält, und somit die Performanz negativ beeinflusst.

Um also ein Steuerelement umzusetzen, welches stets die optimale Plattformintegration und dazu noch eine optimale Performanz bietet, muss je Plattform ein natives Steuerelement umgesetzt werden (Beispiel für iOS), welches dann in Xamarin.Forms adaptiert (Beispiel für Android) werden muss. Dabei muss auf gemeinsame Eigenschaften und Verhaltensweisen über die verschiedenen Plattformen geachtet werden. Somit ist dies der aufwändigste Weg, ein eigenes Steuerelement umzusetzen, jedoch ist es auch der Weg mit den meisten Möglichkeiten und einem optimalen Ergebnis.

Fazit

Zuvor wurden verschiedene Möglichkeiten von einfach und schnell bis komplex und aufwändig aufgezeigt, wie mit Xamarin.Forms die UI einer Anwendung angepasst werden kann. Dabei ist klar geworden, dass jede Möglichkeit unterschiedliche Freiheitsgrade bietet aber auch ihre Grenzen hat. Um diese zu überwinden, gibt es jedoch immer eine Möglichkeit, die gewünschten Anforderungen umzusetzen:

Wenn Xamarin.Forms-Steuerelemente zu begrenzt sind, ist dem mit Renderern beizukommen. Wenn Templates zu einfach sind, können plattformunabhängige Steuerelemente umgesetzt werden. Wenn diese wiederum zu unperformant sind, können die Steuerelemente nativ umgesetzt werden.

Letztlich stellt sich in der Praxis natürlich die Frage, in welchem Umfang solche Lösungsansätze in Zeit und Budget umsetzbar sind. Meines Erachtens ist der Aufwand bis hin zu plattformunabhängigen Steuerelementen zu vertreten, da diese der erweiterte Arm einer View und schnell umsetzbar sind. Ist jedoch ein optimiertes Steuerelement erforderlich, würde ich raten, zu entsprechenden Bibliotheken von Anbietern wie Syncfusion, Telerik, ComponentOne, Infragistics oder Anderen zu greifen, sofern die Möglichkeit besteht.

Store Deployment von Xamarin Apps

Von der SDX wurden bereits mehrere mobile Business-Anwendungen für die Plattformen Windows, iOS und Android sowohl für Tablets als auch für Phones entwickelt. Bisher fanden ausschließlich Anwendungen auf Basis der Windows Plattform den Weg in den Store. (z.B. SDX Worktime Pro) Dies wird sich nun ändern, da wir während unseres letzten Kundenprojektes eine Anwendung mit Xamarin für iOS u. Android entwickelt haben, die auch im Store zu finden ist. In diesem Artikel möchte ich über die von uns gesammelten Erfahrungen beim Store Deployment mit Xamarin berichten und auf die dabei aufgetauchten Probleme und Fallstricke hinweisen sowie unsere Lösungen dazu aufzeigen.

Die Entwicklung der Anwendung hat in einem Team stattgefunden, dessen Erfahrungsschatz hauptsächlich auf der .NET-Entwicklung beruht. Dank Xamarin musste dabei Visual Studio als Entwicklungsumgebung nicht verlassen werden, obwohl für die Plattformen iOS und Android entwickelt wurde. Allenfalls das einmalige Setup der Debug-Umgebung für die iOS-Anwendung, erforderte eine kurzzeitige Arbeit des Entwicklers mit einem Mac. Nachdem die Entwicklungsphase abgeschlossen war, musste die Anwendung getestet werden und sollte anschließend in die Stores deployed werden. Xamarin selbst bietet hierzu bereits je einen Guide für die beiden Stores an, wobei jeweils das Store Deployment Schritt für Schritt für iOS und auch für Android beschrieben wird. Doch war das schon alles? Musste lediglich eine „Checkliste“ abgearbeitet werden, und man hat die Anwendungen im Store? Leider fingen die Probleme mit dem Abarbeiten dieses Guides erst an.

Fallstricke beim Deployment der iOS-Anwendung

Während der Entwicklung der Anwendung wurde deren Build zum Debuggen allein von Visual Studio aus gesteuert. Jedoch besteht von Visual Studio aus nicht die Möglichkeit, ein Paket für den Store zu erstellen. Folglich muss dieses im Xamarin Studio auf dem Mac erstellt werden. Auffällig war dabei insbesondere, dass die Auswahl bei den Build-Einstellungen zwischen Visual Studio und Xamarin Studio leicht abweichend ist. In Xamarin Studio stehen beispielsweise exakt die installierten SDKs und verfügbaren Compiler-Optionen (z. B. auch ARM64 und ARM7s) zur Auswahl, während in Visual Studio nur eine pauschale Auswahl (z. B. bei den Compiler-Optionen nur ARMv6 und ARMv7) zum Debuggen, oder gar keine Auswahl – abgesehen von dem Wert „Standard“ – zur Verfügung steht. Auch weicht die Anordnung und Verteilung der Optionen voneinander ab, sodass diese in Xamarin Studio erneut zu suchen und zu prüfen sind. Dies lässt sich auch im nachfolgenden Screenshot nachvollziehen:

settings

Nach erfolgreicher Erstellung des Pakets in der Release-Konfiguration ist zusätzlich die Verwendung von Xcode erforderlich, um das Publishing in den Apple App Store durchzuführen.

Eine weitere Besonderheit der iOS App stellt die Information Property List (Datei “Info.plist”) dar. Die Info.plist ist eine Zusammenfassung von Eigenschaften der Anwendung, die in deren Paket beim Übersetzen integriert und auch zum Übersetzen verwendet sowie vom Store ausgelesen und ausgegeben werden. Die meisten dieser Eigenschaften lassen sich in der UI für die Projekteigenschaften über den Reiter iOS Application bearbeiten. Manche dieser Eigenschaften müssen jedoch von Hand in die Info.plist eingefügt werden; so etwa die Sprache, die im App Store angezeigt werden soll.

   1: <key>CFBundleLocalizations</key>

   2: <array>

   3:     <string>de</string>

   4: </array>

   5:  

   6: <key>CFBundleDevelopmentRegions</key>

   7: <string>German</string>

Fallstricke beim Deployment der Android-Anwendung

Das Deployment der Android-Anwendung in den Google Play Store hingegen hat sich zunächst einfach gestaltet. Später hat sich noch herausgestellt, dass der vom Xamarin Visual Studio Plugin verwendete Algorithmus zur Paketsignierung nicht von jedem Android-Gerät unterstützt wird. Einige Geräte gaben bei der Installation lediglich die Fehlermeldung „Package file was not signed correctly.“ zurück. Um dies zu beheben, musste das Paket unter Nutzung eines anderen Algorithmus signiert werden: In einem herkömmlichen Android-Projekt wird dazu die build.xml angepasst. In Xamarin ist dies jedoch nicht möglich. Hier muss das Paket als Release erstellt sowie von Hand mit den entsprechenden Tools aus dem jeweiligen JDK und Android SDK signiert werden. Dazu finden sich nachfolgend die erforderlichen Kommandos an unserem Beispiel mit aktuellen Tool-Versionen:

"Y:PathToourJavajdk1.8.0_31injarsigner.exe" -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore %KeyStoreFileName%.keystore %AppPackageFileName%.apk %KeyAlias%

"Y:PathToourandroid-sdkuild-tools21.1.2zipalign.exe" -v 4 %AppPackageFileName%.apk %AppPackageFileName%.signed.apk

Abgesehen davon ist beim Android Deployment die Möglichkeit zu erwähnen, im Google Play Store spezifische Geräte zu deaktivieren, die von der Anwendung nicht unterstützt werden. Dies ist insbesondere deshalb relevant, weil es derzeit allein über 2102 Android Tablets gibt, die unterstützt werden müssen. Soll jedoch ein Formfaktor wie insbesondere z. B. von kleinen Geräten (Phablets) nicht unterstützt werden, können diese Geräte hier explizit deaktiviert werden. Möchte man diese dennoch unterstützen, bietet sich die Xamarin Test Cloud an, diese zuvor zu testen.

Fazit

Insgesamt hat sich das Store Deployment mit Xamarin problemlos hinsichtlich des Aufwands gestaltet. Selbst die Dauer des Freigabeprozesses hat sich mit ein paar Stunden bei Android und ca. zwei Wochen bei Apple relativ kurz gestaltet. Folglich ist die Anwendung nun sowohl im Apple App Store als auch im Google Play Store zu finden.

Erste Eindrücke: WinRT Apps mit Xamarin.Forms

Xamarin.Forms stellt eine Abstraktionsebene vor den Xamarin CLI-Portierungen von nativen Controls dar. Bisher unterstützt Xamarin.Forms auf diese Weise allerdings nur die hauseigenen CLI-Portierungen für iOS und Android sowie auch Windows Phone. Somit können Anwendungen für iPad, iPhone, Android Tablets und Android Phones sowie das Windows Phone entwickelt werden, nicht jedoch für WinRT und somit Windows Tablets. Um dafür Abhilfe zu schaffen, gab es in der Community bereits Anstrengungen, mit Xamarin.Forms auch WinRT adressieren zu können. Doch hat nun auch Xamarin den Support für WinRT angekündigt, und bereits eine Vorschau-Version ausgeliefert. Diese ist zwar noch sehr rudimentär, dennoch möchte ich mit diesem Artikel unsere ersten Schritte und ersten Eindrücke damit schildern.

Xamarin.Forms in einem WinRT-Projekt

Im Rahmen eines kleinen Tests hatte ich es mir zum Ziel gesetzt, eine bestehende Xamarin.Forms-Anwendung auf WinRT zu portieren. Das Visual Studio Plugin von Xamarin bietet dafür jedoch noch keine Vorlagen. So stellte sich die Frage, was dafür notwendig ist?

Um eine Windows Store Tablet Anwendung mit Xamarin.Forms zu entwickeln, ist das Vorgehen beim Projekt Setup analog zum Vorgehen beim manuellen Erstellen eines Xamarin.Forms-Projekts für eine andere Plattform: In diesem Fall wird ein Projekt für eine Windows Store Anwendung erstellt und anschließend werden die Xamarin.Forms-Komponenten via NuGet referenziert. Diese können über die Kommandozeile installiert werden:

Install-Package Xamarin.Forms.Windows –Pre

Natürlich können die Komponenten auch über die NuGet-Benutzeroberfläche als Pre-Release bezogen werden. Letztlich sollten neben den Referenzen auf die typischen WinRT-Komponenten auch die Referenzen auf die Xamarin.Forms-Komponenten für WinRT vorliegen:

nuget

Anschließend muss die Kontrolle für die Darstellung der Benutzeroberfläche an Xamarin.Forms übergeben werden. Was in einem iOS Projekt das AppDelegate oder in einem Android die MainActivity ist, ist im WinRT Projekt die App.xaml. Diese unterscheidet sich jedoch nur in einer Zeile von der Standard App.xaml, die beim Erzeugen eines nativen Windows Store Projekts bereitgestellt wird. Denn es ist hier ebenso, wie beim Start einer Xamarin.Forms-Anwendung auf den anderen Plattformen, dass Xamarin.Forms initialisiert werden muss. Dies geschieht im OnLaunched-Handler vor dem Setzen des Fensterinhalts:

   1: protected override void OnLaunched(LaunchActivatedEventArgs e)

   2: {

   3:     ...

   4:     Xamarin.Forms.Forms.Init(e);

   5:  

   6:     // Place the frame in the current Window

   7:     Window.Current.Content = rootFrame;

   8:     ...

   9: }

Das Hauptfenster der Anwendung ist auch kein Standard-WinRT-Fenster sondern ein Xamarin.Forms-spezifisches Fenster. Hierbei handelt es sich um eine WindowsPage aus dem Namespace Xamarin.Forms.Platform.WinRT. Deshalb muss folglich dessen Xaml angepasst werden:

   1: <forms:WindowsPage

   2:     x:Class="SDX.Forms.WinRT.MainPage"

   3:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   4:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   5:     xmlns:forms="using:Xamarin.Forms.Platform.WinRT"

   6:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

   7:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

   8:     mc:Ignorable="d"/>

Im Code-Behind dieses Fensters wird dann die eigentliche Xamarin.Forms-App geladen:

   1: this.LoadApplication(new Forms.App());

Fortan kann die Anwendung mit Xamarin.Forms entwickelt werden, und nur plattformspezifische Anpassungen müssen ggf. noch im WinRT-Projekt vorgenommen werden.

Was bietet Xamarin.Forms für WinRT?

Ziel von Xamarin.Forms für WinRT ist es ebenso, wie für die anderen Plattformen, gemeinsame Funktionen auf gleiche Weise zur Verfügung zu stellen. Abgesehen davon, dass diese noch nicht vollständig adaptiert sind, stellt WinRT Xamarin hier vor die Herausforderung, dass es für bestimmte, insbesondere auf Phones verfügbare Steuerelemente keine Pendants gibt. Xamarin hat sich hier entschlossen, dafür entsprechende Pendants nachzubauen. Ein Beispiel dafür ist die Navigation Page. Diese stellt sich nachgebaut und aufgegliedert wie folgt dar:

Navpage_kasten

Dem Bild ist zu entnehmen, dass hierbei die von den Store-Richtlinien vorgegebene Aufteilung nachempfunden wurde. Wie sich dabei jedoch die individuelle Anpassbarkeit gestaltet, lässt sich jetzt noch nicht abschätzen. Jedoch insbesondere der linke Rahmenbereich wirkt statisch und könnte beim horizontalen Scrollen Probleme bereiten, da er nicht mehr überschrieben werden kann und das scrollende Steuerelement erst im Inhaltsbereich beginnt. Auch zeigt dieses Beispiel das horizontale Paradigma von Windows Store Apps auf, welches aber zum Beispiel dem oft eher vertikalen Paradigma auf dem iPad widerspricht. Deshalb stellt sich in Hinsicht darauf schon jetzt die Frage, wie viel Code gemeinsam in Xamarin.Forms abgebildet werden kann und wie viele Plattform-Weichen im Xaml-Code, oder sogar zusätzliche Views zum Ausgleichen dieser unterschiedlichen Prinzipien, erforderlich sind, wenn darauf Wert gelegt wird.

Des Weiteren stehen auch manche für Windows Store Apps übliche Steuerelemente – wie GridView oder ItemsControl – nicht zur Verfügung. Wie Xamarin hiermit in Zukunft umgehen wird, wird sich zeigen: Ob es dafür einen Nachbau für die anderen Plattformen geben wird, oder ob dieses Steuerelement ausgespart wird, da es für die anderen Plattformen ohnehin eher untypisch ist.

Dennoch wird sich der Windows Store-Entwickler Fragen stellen, wie: Was ist mit der App Bar oder der Navigation Bar?

Fazit

Im Rahmen des Tests war mit Xamarin.Forms eine extrem schnelle Portierung (weniger als 2 Stunden!) einer bestehenden Xamarin.Forms App möglich. Zwar war die Stabilität bei der Verwendung noch nicht optimal, da hin und wieder das für Windows Store App-Entwickler wohlbekannte typische E_FAIL insbesondere bei der Verwendung von Xamarin nachgebauter Steuerelemente auftauchte, doch war sie für eine Alpha-Version sehr akzeptabel. Auch war die App noch nicht schön anzusehen, aber dafür im vollen Funktionsumfang lauffähig. Dennoch ist der aktuelle Stand noch nicht für den Produktiveinsatz gedacht.

Die Frage, die sich nun umso mehr stellt, ist eigentlich eher fachlicher Natur: Will man eine Windows Store App, die den Interaktionsparadigmen einer z. B. iOS App folgt? Oder eine iOS App, die wie eine Windows Store App aufgebaut ist? Letztlich ist es eine Frage, wie viel Aufwand man bereit ist in die Plattformspezifika zu stecken und wie viel z. B. für eine interne App erforderlich ist. Natürlich sollte man nicht vernachlässigen, dass gerade im Rahmen einer BYOD Strategie im Unternehmen von den Anwendern doch Plattformspezifika erwartet werden, da sie diese gewohnt sind. Doch wie viel Aufwandserleichterung bringt Xamarin.Forms dann? Zumindest hat man eine gemeinsame Codebasis, die nur einmal gewartet werden muss.

Abseits davon ist auch das Lizenzmodell noch unklar. Xamarin.WinPhone erforderte eine Xamarin.iOS- oder Xamarin.Android-Lizenz. Derzeit ist das für WinRT auch noch so. Dennoch bleibt auch hier abzuwarten, ob Xamarin eventuell für die Windows-Plattform eine zusätzliche Lizenz einführen wird.

Weiterführendes

Für diejenigen, die mehr darüber erfahren wollen, lohnt sich ein Blick auf die Xamarin-Webseite, oder in ein Beispielprojekt von Scott Hanselman.

async/await und Model-View-ViewModel (MVVM)

Mit dem .NET-Framework 4.5 wurden neue Framework-Komponenten sowie bestehende, die in Vorgängerversionen synchron waren, asynchron mithilfe der Task Parallel Library (TPL) abgebildet. Dies hat (insbesondere im Rahmen von WinRT) zur Folge, dass für viele Aufgaben asynchrone Aufrufe verwendet werden müssen. Somit hat der Entwickler keine Alternative, als Aufgaben asynchron mit async/await umzusetzen. Wenn diese Aufgaben mit synchronen Methoden realisiert werden würden, riskiert der Entwickler Deadlocks oder das Blockieren der UI.

Das async/await-Konzept stellt allerdings nur einen Sprachaufsatz von C# auf die Common Language Specification (CLS) dar. Die Schlüsselwörter async und await werden also nicht etwa in IL-Code übersetzt, sondern zusammen mit ihrem Code-Kontext zur Übersetzungszeit vom Compiler als ein Zustandsautomat in IL-Code abgebildet. Die zugrunde liegenden Sprachbestandteile der CLS sind aber nach wie vor für die synchrone Programmierung konzipiert. So können etwa Konstruktoren und Eigenschaften nicht asynchron implementiert werden. Dies hat weitreichende Auswirkungen auf bestehende Konzepte wie MVVM, welches Gegenstand dieses Beitrags ist.

Die Eigenschaften eines ViewModels sind die Grundlage für Data Bindings, welche für einen synchronen Programmfluss konzipiert sind. Mit dem Aufkommen von async/await müssen diese jedoch auch häufiger asynchron ermittelte Informationen zurückliefern. Folglich steht der Entwickler vor der Herausforderung, asynchron bezogene Informationen synchron im ViewModel zur Verfügung zu stellen. Da das synchrone Abwarten asynchroner Aufgaben nicht in Frage kommt, ist dafür eine andere Strategie erforderlich. Um diese zu spezifizieren ist folgende Frage zu stellen:

Wann wird welche Information benötigt?

Erforderliche Informationen

Es gibt Informationen, die für die Funktion eines ViewModels unerlässlich und somit zwingend erforderlich sind. Solche erforderlichen Informationen, die synchron bezogen werden können, sollten nach wie vor im Konstruktor ermittelt werden. Zwingend erforderliche Informationen, die asynchron bezogen werden müssen, sollten hingegen über eine Factory-Methode ermittelt und erzwungen werden. Eine Factory-Methode ist die einzige Umsetzungsmöglichkeit dafür, da sie zum Erzeugen einer Instanz selbst asynchron sein kann, wodurch die nötigen Informationen asynchron bezogen werden können. An einem Beispiel könnte das wie folgt aussehen:

public class ViewModel {
    // Privater Konstruktor um Factory-Methode zur Erzeugung zu erzwingen
    private ViewModel() { }

    // Eine Eigenschaft, die asynchron bezogene Informationen enthalten soll
    public string RequiredAsyncProperty { get; set; }

    // Die Factorymethode, die eine Instanz erstellt und
// für die Befüllung der Eigenschaft sorgt
public static async Task<ViewModel> CreateAsync() { var viewModel = new ViewModel { RequiredAsyncProperty = await AsyncDataSource.GetSomeDataAsync() }; return viewModel; } } // Initialisierung vom View oder an anderer Stelle im UI-Kontext ViewModel.CreateAsync().ContinueWith( t => { this.DataContext = t.Result; }, // Da CreateAsync evtl. den Kontext wechselt, soll hier sichergestellt werden, dass
// der UI-Kontext zum Zuweisen des DataContext verwendet wird.
TaskScheduler.FromCurrentSynchronizationContext());

Nachträglich geladene/optionale Informationen

Andererseits sollen aber auch Informationen nachträglich geladen werden, die nicht zwingend erforderlich sind bzw. für den Benutzer nicht essentiell sind, die oft verändert werden oder nur in einem spezifischen Zustand verwendet werden. Solche Informationen können direkt auf Anfrage einer Eigenschaft bezogen werden:

// Das Feld für den asynchron zurückgelieferten Wert
private string onDemandPropertyValue;

// Das Feld für den asynchronen Vorgang
private Task<string> onDemandPropertyValueTask;
public string OnDemandProperty { get { // Doublechecking um zu vermeiden, dass eine unnötige Memorybarrier den Thread
// aufhält.
if (this.onDemandPropertyValueTask == null) { lock (this.lockingObject) { if (this.onDemandPropertyValueTask == null) { this.onDemandPropertyValueTask = AsyncDataSource.GetSomeDataAsync(); this.onDemandPropertyValueTask.ContinueWith(t => { lock (this.lockingObject) { // Falls der Task verändert wurde, ist diese Fortsetzung nicht
// mehr aktuell und das gelieferte Ergebnis wäre veraltet,
// insbesondere falls dieser Aufruf sogar länger gedauert hat
// als der nachfolgende.
if (this.onDemandPropertyValueTask != t) return; // Nach Abschluss des Vorgangs wir der Wert bei Erfolg gesetzt
// und die Wertänderung mitgeteilt.
if (t.IsCompleted && !t.IsFaulted) { this.onDemandPropertyValue = t.Result; this.NotifyPropertyChanged(() => this.OnDemandProperty); } } }, TaskScheduler.FromCurrentSynchronizationContext()); } } } // Das eigentliche Zurückgeben des Wertes, welcher solange er nicht bezogen wurde,
// dem default des Typen entspricht.
return this.onDemandPropertyValue; } }

Da nach der Anfrage eine Referenz auf den Task beibehalten bleibt, können Abhängigkeiten zu anderen asynchron bezogenen Informationen mithilfe dieser abgebildet werden. Allerdings kann auf diese Weise die Reihenfolge, in der Informationen bezogen werden, nur schwer oder gar nicht beeinflusst werden. Folglich nimmt die Komplexität schnell zu (und die Lesbarkeit ab), wenn Abhängigkeiten zwischen mehreren Eigenschaften abgebildet werden sollen. Auch asynchrone Vorgänge in Convertern lassen sich auf diese Art und Weise nicht abbilden. Praktisch haben sich deshalb eine Kapselung des Aufrufes und/oder ein explizites sequentielles Ermitteln mehrerer voneinander abhängiger Werte als nützlich erwiesen.

Kapselung und Reihenfolge

Ein Konzept, das sich in Projekten der SDX als nützlich erwiesen hat, ist der von uns entwickelte AsyncData-Typ. Dabei werden der Ergebniswert und der Zustand einer asynchronen Aktion passiv in eine Struktur gekapselt. Der Wert einer so gekapselten Eigenschaft wird von einem sequentiellen Aufruf zum Beziehen des jeweiligen Wertes schrittweise aktualisiert, indem die komplette Struktur je nach Zustand der Aufgabe ausgetauscht wird. Dazu folgendes Beispiel:

// Eine asynchron befüllte Eigenschaft, von der 'DependantAsyncProperty' abhängig ist
public AsyncData<string> AsyncLoadedProperty {
    get { return this.asyncLoadedProperty; }
    set {
        this.asyncLoadedProperty = value;
        this.OnPropertyChanged("AsyncLoadedProperty");
        this.OnPropertyChanged("DependantAsyncProperty");
    }
}

// Eine weitere asynchron befüllte Eigenschaft, von der 'DependantAsyncProperty' abhängig
// ist
public AsyncData<string> OtherAsyncLoadedProperty { /* ... äquivalent ... */ } // Die Initialisierungs- bzw. Aktualisierungsmethode, die die Werte in Reihenfolge neu
// bezieht und Fehler behandelt und mitteilt
public async Task InitializeAsyncDataAsync() { // AsyncLoadedProperty hat hier bereits einen Wert oder ist im IsLoading-Zustand try { this.AsyncLoadedProperty = await AsyncDataSource.GetSomeDataAsync(); // AsyncLoadedProperty hat hier einen Wert und ist im IsSucceeded-Zustand } catch (Exception) { this.AsyncLoadedProperty = new AsyncData<string>(null, true); // AsyncLoadedProperty hat hier null als Wert und ist im IsFailed-Zustand } try { this.OtherAsyncLoadedProperty = await AsyncDataSource.GetSomeDataAsync(); } catch (Exception) { this.OtherAsyncLoadedProperty = new AsyncData<string>(null, true); } } // Eine von AsyncLoadedProperty und OtherAsyncLoadedProperty abhängige Eigenschaft. public AsyncData<string> DependantAsyncProperty { get { var firstValue = this.AsyncLoadedProperty; var secondValue = this.OtherAsyncLoadedProperty; // Ist eine von beiden Eigenschaften ladend, ist auch diese Eigenschaft ladend if (firstValue.IsLoading || secondValue.IsLoading) { return new AsyncData<string>(); } // Ist eine von beiden Eigenschaften fehlgeschlagen, ist auch diese Eigenschaft
/ fehlgeschlagen
if (firstValue.IsFailed || secondValue.IsFailed) { return new AsyncData<string>(null, true); } // Ansonsten wird der von beiden Eigenschaften abhängige Wert bestimmt return string.Format("{0} combined with {1}",
firstValue.Value, secondValue.Value); } }

In der Methode InitializeAsyncDataAsync() werden optionale Informationen in einer spezifischen Reihenfolge ermittelt, Fehler behandelt und ebenso der spezifischen Eigenschaft zugeordnet. Zudem wird hierbei aufgezeigt, wie der Wert einer davon abhängigen optionalen Eigenschaft asynchron ermittelt und signalisiert werden kann. Selbigen Sachverhalt mit dem zuvor aufgezeigten Konzept würde einige Felder und Codezeilen mehr erfordern. Ein Problem stellt hierbei allerdings dar, dass entweder alle Werte aktualisiert werden müssen, sobald es für einen Wert erforderlich ist, oder eine separate Methode benötigt wird. Dafür kann hier allerdings der Wert während der Aktualisierung erhalten bleiben. Für den Fall, dass Logik abgebildet werden soll, sind asynchrone Eigenschaften allerdings in beiden vorangestellten Fällen mit dem Umstand verbunden, dass die Asynchronität berücksichtigt werden muss. Folglich entsteht dadurch nach wie vor Komplexität.

Asynchrone Aufrufe in Convertern

Auch lassen sich mit dem AsyncData-Konzept keine asynchronen Converter abbilden. Hierfür ist ein aktiver Container erforderlich, der über den Zustand eines Tasks Auskunft gibt. Im Nito Projekt findet sich dazu ein interessanter Ansatz: der TaskCompletionNotifier (dort NotifyTaskCompletion genannt). Der TaskCompletionNotifier implementiert INotifyPropertyChanged und teilt der UI mit, wenn ein Task abgeschlossen wurde, woraufhin jene das Ergebnis des Tasks weiternutzen kann. Dies wird im nachfolgenden Beispiel anhand eines Converters, der asynchrone Aufrufe verwendet, und anhand eines Bindings unter Nutzung einer Instanz dieses Converters veranschaulicht:

// Beispiel für einen Converter der einen asynchronen Aufruf durchführt.
public class TaskCompletionNotifierConverter : IValueConverter {
    public object Convert(object value, Type targetType, object parameter, 
CultureInfo culture) { // Kapseln des asynchronen Aufrufs in einen Notifier return new TaskCompletionNotifier<string>(
AsyncDataSource.GetSomeDataAsync(value as string)); } /* ... */ }
<!-- Ein Binding dessen Converter den Wert asynchron verändert. -->
<TextBlock Text="{Binding Result}" DataContext="{Binding AValueToConvertAsync, 
Converter={StaticResource TaskCompletionNotifierConverter}}"
/>

Dieser Container kann auch zum Beziehen von Informationen außerhalb von Convertern eingesetzt werden. Aufgrund der Kapselung sind allerdings Abhängigkeiten schwer abbildbar. Ferner ist auf diese Weise auch die Fehlerbehandlung eher umständlich und explizit vorzunehmen. Ich empfehle daher, diesen Container vor allem in Convertern einzusetzen. Dabei ist allerdings zu bedenken, dass es sich schwierig gestaltet, in diesen Converter Abhängigkeiten oder Instanzen von Services zu injizieren.

Fazit

Es gibt verschiedene Strategien, um auch im Rahmen synchroner MVVM-Bindings asynchrone Aufrufe zu verwenden, ohne auf synchrones (blockierendes) Warten zurückgreifen zu müssen. Mit diesen kann letztlich eine technisch saubere Umsetzung gewährleistet werden.

Jede dieser Strategien stellt einen Lösungsansatz für eine spezifische Anforderung (OnDemand, Converter, Abhängigkeiten) dar. Somit sind diese Strategien zwar Konkurrent zueinander, aber nicht vollständig untereinander austauschbar. Solche oder ähnlich geartete Ansätze bilden letztlich das Fundament einer MVVM-Anwendung mit async/await.

Ein Beispielprojekt, in welchem die zuvor beschriebenen Ansätze konsolidiert sind, findet sich hier.

Umdenken mit async/await

Mit dem .NET-Framework in der Version 4.5 und C# 5 hat Microsoft C# auf Sprachebene um asynchrone Programmierparadigmen erweitert. Ziel dessen ist es asynchrone Programmierung für den Entwickler ähnlich der synchronen Programmierung zu gestalten und dabei Threads durch die Nutzung der Task Parallel Library (TPL) so optimal wie möglich auszunutzen. Sowohl hier im Flurfunk der SDX AG als auch auf anderen Internetseiten finden sich bereits zahlreiche Artikel, Tutorials und Whitepaper, die sich mit den Grundlagen beschäftigen. Was sollten aber Entwickler, außer dem Methoden-Modifikator “async”, dem Operator “await” und dem Pattern noch wissen, um qualitativ hochwertigen und fehlerfreien Code zu produzieren?

Viele .NET-Entwickler sind bisher gewohnt, dass sie einen Thread erzeugen und starten bzw. einen Threadpool oder ferner auch TPL-Funktionen nutzen, um eine parallelisierbare Aufgabe durchzuführen. Das Abschließen jener Aufgaben wird letztlich (wenn erforderlich) von einem anderen Thread/Kontext abgewartet. Zudem kommen Synchronisationsmechanismen wie das lock-Schlüsselwort zum Einsatz. Bei der Umstellung auf async/await muss jedoch ein Umdenken stattfinden, da die altbekannten blockierenden Mechanismen mit den neuen nichtblockierenden Mechanismen nicht kompatibel sind. Ursache dafür ist bei async/await die von der TPL hergestellte Threadtransparenz durch die Nutzung von Kontexten in Form von Threadpools, welche von dem in die Sprache integrierten async/await-Pattern wiederverwendet werden.

Wenngleich im Umgang mit async/await von Tasks die Rede ist, sollte der Entwickler dennoch wissen, in welchem Thread der geschriebene Code ausgeführt wird. Dies wird bereits an einem sehr einfachen Beispiel deutlich. Man nehme eine einfache WPF-Anwendung mit einem Button, der eine asynchrone Aktion durchführen soll. In den Augen eines async/await-Neulings, der alte und neue Konzepte mischt, da er das Konzept von async/await noch nicht verinnerlicht hat, könnte folgender Code valide aussehen:

private async void HandleButtonClick(object sender, RoutedEventArgs e) {
    this.DoSomeWorkAsync().Wait();
}

private async Task DoSomeWorkAsync() {
    await Task.Delay(1000);
}

Tatsächlich handelt es sich hierbei um einen Deadlock. Der Klick auf den Button wird im UI-Thread durchgeführt, woraufhin der durch den Aufruf der Methode DoSomeWorkAsync erzeugte Task im selben Kontext und somit auch vom UI-Thread ausgeführt wird. Da dieser Thread nach dem await im Task aber freigegeben wird, wird anschließend das blockierende Warten auf den erzeugten Task durchführt. Im Kontext des Tasks ist aber nur dieser Thread verfügbar, weshalb weder der Task noch das Warten auf ihn beendet werden kann. Mehr zum Threadwechsel mit async/await lässt sich erfahren, wenn man sich mit der generierten StateMachine auseinandersetzt, deren einfaches Prinzip verstanden werden sollte, um fehlerfreien Code zu schreiben.

Auflösen ließe sich dieser Deadlock durch nichtblockierendes Warten auf DoSomeWorkAsync mithilfe des await-Operators:

private async void HandleButtonClick(object sender, RoutedEventArgs e) {
    await this.DoSomeWorkAsync();
}

Eine andere Möglichkeit besteht in der Konfiguration des abgewarteten Delay-Tasks. Durch ConfigureAwait(false) wird nach Abwarten des Tasks der Kontext gewechselt, wodurch ein anderer Thread zum Beenden des Aufrufs verwendet wird:

private async Task DoSomeWorkAsync() {
    await Task.Delay(1000).ConfigureAwait(false);
}

Für die Vermischung blockierender und nichtblockierender Mechanismen lassen sich weitere beliebig komplexe Beispielproblemfälle aufzeigen. Ziel soll jedoch sein, solche Vermischungen zu vermeiden. Es ist deshalb zu empfehlen durchgehend nichtblockierende Mechanismen zu verwenden und die Anwendung durchgehend unter Nutzung von async/await umzusetzen. Im Anschluss findet sich dazu eine kleine Tabelle, für vergleichbar oder äquivalent einsetzbare nichtblockierende Mechanismen zu blockierenden Mechanismen:

Blockierende Mechanismen

Nichtblockierende Mechanismen

task.Wait(), task.Result

await

task.WaitAny()

task.WhenAny()

task.WaitAll()

task.WhenAll()

Thread.Sleep()

await Task.Delay()

lock

await SemaphoreSlim.WaitAsync()

Sofern konsequent nichtblockierende Mechanismen durch alle Schichten einer Anwendung verwendet werden, steht fehlerfreiem Code bezüglich der Asynchronität nichts mehr im Wege.

Weiterführende Artikel zum Thema async/await:

TFS Code-Reviews in Scrum-Teams

Die SDX entwickelt derzeit mit einem Scrum-Team an einer intern genutzten Software. Als ALM-Tool wird dabei der Team Foundation Server 2012 (TFS 2012) eingesetzt, da er viele projektunterstützende Hilfsmittel bietet und sich direkt in die IDE integriert. Eine der vom Team genutzten Neuerungen im TFS 2012 sind die IDE integrierten Codereviews, deren Adaption im Scrum-Team ich in diesem Blogbeitrag darlegen möchte.

TFS 2012 Codereviews bieten die Möglichkeit auf Changeset- oder Shelveset-Basis vorgenommene Änderungen zu prüfen und gesamt, zeilen- oder blockweise zu kommentieren. Ein Codereview kann für ein Change- oder Shelveset angefragt werden, oder direkt aus der aktuellen Arbeit („My Work“) erzeugt werden. Ziel ist es, Problemen und Fehlern frühzeitig entgegenzuwirken und die Codequalität zu erhöhen. Da Codereviews allgemein dem agilen Vorgehen (wie Scrum) nicht fremd sind, wurde im Team beschlossen, die TFS 2012 Codereviews in die „Definition of Done“ aufzunehmen. Das bedeutet, dass ein Product Backlog Item (PBI) erst als Done (Abgeschlossen) deklariert werden darf, wenn für alle mit diesem assoziierten Änderungen ein Codereview mit positivem Ergebnis durchgeführt wurde. Bei der Adaption dieses Vorgehens ist das Team jedoch auf verschiedene Hürden gestoßen.

Codeabdeckung von Reviews

Aus der derzeitigen Implementierung der IDE-gestützten Codereviews ergibt sich ein Problem mit der Forderung nach kontinuierlicher Integration: Code soll während der Entwicklung stets in den Gesamtquelltext integriert werden und lauffähig sein. Durch die im Verlauf der Arbeit vorgenommenen Änderungen können daher für ein Workitem mehrere Changesets entstehen. Da ein TFS Codereview derzeit aber lediglich auf ein einziges Shelveset oder Changeset bezogen werden kann, müssen ggf. mehrere Reviews angefordert werden, um ein komplettes Workitem abzudecken.

Durch erneute Überarbeitungen im Rahmen desselben Workitems können Changesets teilweise oder vollständig hinfällig sein. Folglich ist das zuvor angedeutete Vorgehen in diesem Fall nur unter hohem Aufwand anwendbar, da nicht alle Changesets vollständig betrachtet werden müssten. Meiner Meinung nach besteht das Problem also darin, dass im TFS derzeit leider keine Möglichkeit vorliegt, Changesets miteinander zu kombinieren. Eine Kombination von Changesets wäre jedoch erforderlich, damit deren zusammengefasste Änderungen mit einem Review geprüft werden können. Dabei wäre allerdings zu berücksichtigen, dass auch Überarbeitungen im Rahmen anderer Workitems, die Changesets des zu betrachtenden Workitems teilweise oder vollständig hinfällig machen können. Ein Review das ausschließlich ein Workitem umfasst, wäre somit nicht möglich. Voraussetzung dafür ist, dass die Workitems unabhängig voneinander sind. Alternativ muss der Kontext des Reviews erweitert werden, wodurch sich wiederum dessen Aufwand erhöht.

Vor dem geschilderten Hintergrund können TFS Codereviews leider nur sehr punktuell verwendet werden. Und so muss, um einen größeren Kontext (über mehrere Changesets) mit einem Review zu prüfen, nach wie vor eine Durchführung ohne die TFS Codereviews als technisches Hilfsmittel stattfinden. Im Scrum-Team der SDX wird dennoch versucht die umfassende Prüfung eines Workitems nach dem zuvor genannten Verfahren zu ermöglichen. Dazu werden Workitems möglichst unabhängig voneinander gehalten, damit es nicht zu Konflikten kommt. Zudem werden Aufgaben möglichst feinkörnig aufgeteilt, um Workitems klein zu halten, damit für diese nicht zu viele Changesets entstehen.

Laufzeit eines Reviews

Auch aus der Laufzeit eines Reviews ergibt sich ein Konflikt mit der Praktik der kontinuierlichen Integration. Sobald ein Review angefragt wird, vergeht Zeit bis zu dessen Abschluss. Das daraus entstehende Problem möchte ich anhand eines Beispiels veranschaulichen: Wird etwa ein Review auf Basis eines Shelvesets angefragt, kann es bereits im Zeitraum vor seiner Bearbeitung hinfällig werden. Dies ist dann der Fall, wenn vor der Bearbeitung des Reviews Änderungen vorgenommen wurden, durch die das Shelveset inkompatibel geworden ist. Aber auch während oder nach der Bearbeitung eines Reviews können im Rahmen anderer Aufgaben vorgenommene Änderungen das Ergebnis des Reviews ungültig machen. Dies wird insbesondere im Fall eines Shelvesets deutlich, wenn dessen Integration nach einem Review aufgrund von Inkompatibilität nicht mehr möglich ist. Unabhängig davon ist bei Änderungen die Gültigkeit des Reviews zu hinterfragen, weil Änderungen den Kontext des im Review betrachteten Codes verändern können.

Um solche überflüssigen Reviews zu vermeiden, muss sich das Team untereinander abstimmen und klären, ob und wo während des Reviews weiterhin am Code gearbeitet werden darf. Macht es Sinn, weiterhin am gleichen Workitem zu arbeiten, bevor das Review abgeschlossen ist? Wie wird damit umgegangen, wenn im Rahmen eines anderen Workitems (u.U. von einem anderen Entwickler) an derselben Codestelle gearbeitet wird? Zwar wird mit einem Changeset immer nur ein spezifischer Codestand betrachtet, und auch Shelvesets können ggf. automatisch wieder integriert werden, doch sind die Auswirkungen von Änderungen auf das Ergebnis eines Reviews nach wie vor nicht absehbar. Deshalb empfehle ich, Änderungen während eines Reviews auszuschließen.

Im Scrum-Team der SDX wurde diesem Problem wiederum dadurch entgegen gewirkt, dass Workitems möglichst unabhängig voneinander sind. Ferner wurde ein gemeinsames Verständnis dafür geschaffen, dass Änderungen während eines Reviews zu vermeiden sind. Außerdem werden bevorzugt Reviews auf Changesets angefragt, sofern das Risiko eine direkte Integration zulässt, damit Inkompatibilität eines Shelvesets vermieden wird. Einen anderen wichtigen Punkt zur Vermeidung ungültiger Reviews stellt aber auch deren zeitnahe Bearbeitung dar. Folglich gelten Reviews als hochpriorisiert und sollten möglichst am selben Tag bearbeitet werden. Der TFS 2012 bietet dafür unterstützend E-Mail-Notifikationen für den/die Reviewer, dass eine neue Review-Anfrage vorliegt. Diese kommen im Scrum-Team der SDX zum Einsatz, und tragen auch dazu bei, dass es nicht zu unnötigen Verzögerungen kommt.

Fazit

TFS Codereviews bedürfen in Kombination mit kontinuierlicher Integration organisatorischer Maßnahmen, um effizient zu sein. Dazu wäre insbesondere die Funktion „Codereview für ein Workitem“ wünschenswert. Diese könnte ferner auch auf ein PBI oder den Gesamtquelltext übertragen werden. Alles in allem werden die Reviews vom Team aber als sehr praktisches Hilfsmittel wahrgenommen und nach wie vor eingesetzt.

Abseits von der Team-Organisation und dem technischen Hilfsmittel wurde dabei festgestellt, dass häufige Reviews einen nicht zu unterschätzenden zusätzlichen Aufwand darstellen. Darum wurde im Team entschieden, nicht alle Änderungen zu prüfen, sondern nur kritische oder nichttriviale Änderungen nach eigenem Ermessen explizit prüfen zu lassen. Dies ermöglicht zudem die gemeinsame Durchführung eines Reviews, bei der der Entwickler und der Prüfer des Codes den Code gemeinsam betrachten und sich gegenseitig direktes Feedback geben können. Die TFS Codereviews können dabei unter anderem zum temporären Festhalten besprochenen Änderungsbedarfs verwendet werden.