Archiv der Kategorie: Deutsch

Artikel auf Deutsch

Wie erstellt man am besten Objektkopien?

Image courtesy of "Stuart Miles" / FreeDigitalPhotos.net

Image courtesy of „Stuart Miles“ / FreeDigitalPhotos.net

In einem Projekt benötigen wir nicht veränderliche Objekt-Instanzen, die aber als Kopien in eine zweite Struktur eingefügt werden sollen, wobei an einigen Properties teilweise kleine Änderungen vorgenommen werden müssen.

Die Klasse sieht etwa wie folgt aus:

public class MyClass
{
  public IContainer Container { get; private set; }
  public int ImutableValue1 { get; private set; }
  public string ImutableValue2 { get; private set; }
  public int MutableValue3 { get; private set; }
  public string MutableValue4 { get; private set; }

  public MyClass(IContainer container, int imutableValue1 = 1, string imutableValue2 = "a", int mutableValue3 = 2, string mutableValue4 = "b")
  {
    Container = container;
    ImutableValue1 = imutableValue1;
    ImutableValue2 = imutableValue2;
    MutableValue3 = mutableValue3;
    MutableValue4 = mutableValue4;
    if(container != null)
    {
      container.Register(this);
    }
  }
}

Um in die neue Struktur hinzugefügt zu werden muss der container gesetzt werden. Die anderen Werte können gesetzt werden, ansonsten wird der Default-Wert übernommen.

Für das Erstellen der Kopie gab es am Anfang folgenden Code, der im Client angesiedelt war:

public MyFunction(IContainer source)
{
  // Do some things

  foreach(var myClass in source.MyClassList)
  {
    var newClass = new MyClass(newContainer, myClass.ImutableValue1, myClass.ImutableValue2, myClass.MutableValue3, myClass.MutableValue4);
  }

  // Do some other things
}

Dieser Code beinhaltet folgende Probleme:

  • Der Client hat bzw. benötigt zu viel Wissen über die Klasse.
  • Wenn ein Property ändert oder ein neues hinzukommt müssen alle Stellen, an welchen dieser Kopier-Code vorhanden ist, angepasst werden.
  • Die Gefahr ist gross, dass bei einem neuen Property einige Stellen vergessen werden und dann in der Kopie immer der Default-Wert (und nicht der Instanz-Wert) eingetragen wird. Je nach Tests und Test-Abdeckung kann dies erst sehr spät, sprich erst im regulären Betrieb, auffallen.

Auf der Suche nach einer Lösung haben wir verschiedene Ansätze diskutiert.

Copy Constructor

Der Copy Constructor ist besonders aus der C++-Welt bekannt. Dort ist immer mindestens ein (impliziter) Copy Constructor vorhanden.

Für das oben gezeigte Beispiel würde der Copy Constructor etwa wie folgend aussehen:

public class MyClass
{
  // ...

  public MyClass(MyClass source, IContainer newContainer, int? mutableValue3 = null, string mutableValue4 = null)
  {
    Container = newContainer;
    ImutableValue1 = source.ImutableValue1;
    ImutableValue2 = source.ImutableValue2;
    MutableValue3 = mutableValue3 != null ? mutableValue3 : source.MutableValue3;
    MutableValue4 = mutableValue4 != null ? mutableValue4 : source.MutableValue4;
    if(Container != null)
    {
      Container.Register(this);
    }
  }
}

Der Client-Code würde dann so aussehen:

public MyFunction(IContainer source)
{
  // Do some things

  foreach(var myClass in source.MyClassList)
  {
    var newClass = new MyClass(myClass, newContainer);
  }

  // Do some other things
}

Clone()-Funktion

Im C#-Umfeld ist die Clone-Funktion verbreiteter als der Kopierkonstruktor, besonders durch das Interface IClonable des .NET-Frameworks. Da wir aber keine 1:1-Kopie der Objekt-Instanz erstellen wollen können wir das Interface hier nicht einsetzen.

Die Implementation unserer Clone-Funktion könnte etwa so aussehen:

public class MyClass
{
  // ...

  public MyClass Clone(IContainer newContainer, int? mutableValue3 = null, string mutableValue4 = null)
  {
    var newClass = new MyClass(
                                newContainer,
                                ImutableValue1,
                                ImutableValue2,
                                mutableValue3 != null ? mutableValue3 : MutableValue3,
                                mutableValue4 != null ? mutableValue4 : MutableValue4
                              );
    return newClass;
  }
}

Mit der Clone()-Funktion würde sich dann der Client-Code wie folgt präsentieren:

public MyFunction(IContainer source)
{
  // Do some things

  foreach(var myClass in source.MyClassList)
  {
    var newClass = myClass.Clone(newContainer);
  }

  // Do some other things
}

Factory

Die dritte Variante besteht aus einer Factory, die sich um die Erzeugung der MyClass-Instanzen kümmert. Der Konstruktor der Klasse MyClass sollte dann auf internal gesetzt werden um die Erzeugung über die Factory zu erzwingen.

public class MyClassFactory
{
  public static MyClass CreateMyClass(IContainer container, int imutableValue1 = 1, string imutableValue2 = "a", int mutableValue3 = 2, string mutableValue4 = "b")
  {
    var myClass = new MyClass(container, imutableValue1, imutableValue2, mutableValue3,  mutableValue4);
    return myClass;
  }
 
  public static MyClass CopyMyClass(MyClass source, IContainer newContainer, int? mutableValue3 = null, string mutableValue4 = null)
  {
    var newClass = new MyClass(
                                newContainer,
                                source.ImutableValue1,
                                source.ImutableValue2,
                                mutableValue3 != null ? mutableValue3 : source.MutableValue3,
                                mutableValue4 != null ? mutableValue4 : source.MutableValue4
                              );
    return newClass;
  }
}

public class MyClass
{
  // ...

  internal MyClass(IContainer container, int imutableValue1 = 1, string imutableValue2 = "a", int mutableValue3 = 2, string mutableValue4 = "b")
  {
    // ...
  }
}

Für den Einsatz der Factory wäre folgender Client-Code nötig:

public MyFunction(IContainer source)
{
  // Do some things

  foreach(var myClass in source.MyClassList)
  {
    var newClass = MyClassFactory.CopyMyClass(myClass, newContainer);
  }

  // Do some other things
}

Vergleich

Der Kopierkonstruktor und die Clone-Funktion haben beide den Nachteil, dass der Benutzer der Klasse die Varianten schnell übersehen kann. Dadurch würde trotzdem Code wie im anfänglichen Client-Code-Beispiel geschrieben. Durch das Verschieben der Erzeugungs-Funktionen in eine Factory sind die Varianten der Erzeugung schnell ersichtlich und der Benutzer kann so die passende Variante wählen.

Aus diesem Grund haben wir dann auch die Variante mit der Factory gewählt.

Reflektion KW 47

Image courtesy of "marcolm" / FreeDigitalPhotos.net

Image courtesy of „marcolm“ / FreeDigitalPhotos.net

In der vergangenen Woche war ich auch direkt beim Performance-Problem eingespannt. Bei der Behebung konnten wir den Speicherverbaruch des Caches, der zur Verbesserung der Performance beigetragen hat, verkleinern ohne die Performance wieder zu verschlechtern. Auch das Management konnte sich entscheiden; es wird einen Zwischen-Release mit einigen zusätzlichen kleinen Features geben.

Wir werden nun die Verbesserungen nun in sauberen Code umsetzen und mitsamt den zusätzlichen neuen Features eines Zwischen-Release erzeugen. Dies wird dann eine intensive Testphase zur Folge haben um den Release plangemäss ausliefern zu können.

Reflektion KW 46

Image courtesy of "marcolm" / FreeDigitalPhotos.net

Image courtesy of „marcolm“ / FreeDigitalPhotos.net

Auch die vergangene Woche war geprägt vom Performance-Problem. Während die Analyse und die Behebung als Prototyp gut voran kam und gute Resultate lieferte tat sich das Management schwer mit der Entscheidung, ob nun ein schneller Bugfix oder ein Zwischen-Release mit einigen zusätzlichen kleinen Features etwas später gemacht werden soll.

Währenddessen konnte ich einige kleine Tasks, die ‚vor sich hin dümpelten‘, in dieser Woche abschliessen, so dass ich nächste Woche bei der Performance-Optimierung mithelfen kann.

Reflektion KW 45

Image courtesy of "marcolm" / FreeDigitalPhotos.net

Image courtesy of „marcolm“ / FreeDigitalPhotos.net

Die vergangene Woche war auch durch die „Altlast“ des vorherigen Releases (die Performance-Probleme bei grossen Mengengerüsten, siehe Reflektion KW 44) geprägt. Zwei Team-Mitglieder beschäftigten sich mit der Analyse der Probleme und der Erarbeitung möglicher Lösungen. Da das Management schnelle Antworten verlangte nahmen sie dementsprechend auch nicht am Review und der Retrospektive teil, gaben aber für die Retrospektive ihren Input vorgängig dem Scrum-Master mit.

Das Review wurde auf zwei Teile mit unterschiedlichen Themen aufgeteilt. Die Stakeholder wurden entsprechend eingeladen und, wie in der Retrospektive der Woche 42 beschrieben, über die zu erwartenden Ergebnisse vorab informiert. Dadurch konnten die Diskussionen in der nötigen Tiefe abgehalten werden ohne dass die Hälfte der Stakeholder nicht an dem Thema interessiert gewesen wäre.

Im Rahmen des einen Review kam zum Vorschein, dass nicht nur die Dokumentation angepasst werden sollte sondern auch die daraus folgenden nötigen Anpassungen kommuniziert werden sollten. Es stellt sich hier die Frage, ob es sich dabei um eine Hol- oder eine Bring-Schuld handelt. Aus meiner Sicht sollten, wenn bekannt ist dass das andere Team die Funktionalität zur Zeit implementiert, Änderungen dem anderen Team auch mitgeteilt werden und nicht darauf gehofft werden, dass schon nachgefragt wird.

Reflektion KW 44

Image courtesy of "marcolm" / FreeDigitalPhotos.net

Image courtesy of „marcolm“ / FreeDigitalPhotos.net

Zum vierten Mal reflektiere ich die vergangene Arbeitswoche.

Diese Woche schwebte eine „Altlast“ des vorherigen Releases wie ein Damoklesschwert über dem Team: Die Performance-Probleme bei grossen Mengengerüsten waren bekannt und wurden auch immer offen kommuniziert. Da nun aber ein Auftrag mit einem grossen Mengengerüst ansteht wurde das Management nervös und dieses Problem muss nun sofort angegangen werden. Zwei bis drei Team-Mitglieder werden sich nun diesem Problem widmen während der Rest am neuen Release weiterarbeitet.

Während des „normalen“ Tagesgeschäftes zeigte sich wieder einmal, dass der Wert einer Dokumentation sinkt, wenn sie nicht aktuell gehalten wird. Besonders wenn es sich um die Definition eines Protokolls handelt, da normalerweise für die Kommunikation zwei Parteien nötig sind. Wenn nun Fehler oder Unklarheiten in der Dokumentation beim Implementieren auftauchen, diese aber nicht korrigiert werden, muss auf der Gegenseite dieselbe Lernkurve nochmals durchlaufen werden.

Reflektion KW 43

Image courtesy of "marcolm" / FreeDigitalPhotos.net

Image courtesy of „marcolm“ / FreeDigitalPhotos.net

Wie schon die beiden Wochen zuvor reflektiere ich die vergangene Arbeitswoche.

Diese Woche waren zuerst Ergänzungsarbeiten für den vorherigen Release zu leisten. Da sich die Komponenten der vorgegebenen und erwarteten Architektur entsprachen und auch beim anzusprechenden Gerät keine Überraschungen auftraten konnte ich diese Arbeiten schneller als geplant erledigen.

Dadurch konnte ich früher als erwartet wieder an den neuen Funktionalitäten arbeiten. Bei der Umsetzung machten wir auch wieder eine eintägige intensive und fruchtbare Pair-Programming-Session.

Reflektion KW 42

Image courtesy of "marcolm" / FreeDigitalPhotos.net

Image courtesy of „marcolm“ / FreeDigitalPhotos.net

Nachdem ich letzte Woche mit der ersten Reflektion der Arbeitswoche gestartet habe folgt nun die zweite Reflektion.

Da meine Arbeitswoche verkürzt war (einen Tag frei genommen) und diese Woche ein Sprint fertig wurde und ein neuer startete ist die Reflektion geprägt von der Retrospektive des Sprints.

Kommunikation zwischen Projekt-Teams
Als ein Problempunkt hat sich die Kommunikation zwischen den zwei Projekt-Teams herausgestellt. Ein Grund für die schwierige Kommunikation dürfte beim Auslassen des Kickoff-Meetings liegen. Dies wird nun so bald als möglich nachgeholt.

Planung des Reviews
Beim Review des zu Ende gegangenen Sprints konnten keine Änderungen an der Software gezeigt werden, da hauptsächlich technische Aspekte behandelt wurden. Dadurch war es für nicht-technische Stakeholder schwierig, Feedback geben zu können.
Beim Planning des neuen Sprints wurde diese Problematik berücksichtigt und die Stakeholder in der Einladung zum Review bereits über die geplanten Resultate vor-informiert.

Reflektion KW 41

Image courtesy of "marcolm" / FreeDigitalPhotos.net

Image courtesy of „marcolm“ / FreeDigitalPhotos.net

Ich habe mir zu Ziel gesetzt, jede Woche hier im Blog kurz zu reflektieren. Diese Woche starte ich und versuche, dies zumindest bis Ende Jahr durchzuziehen.

Pair Programming
Diese Woche habe ich an zwei Tagen mit zwei verschiedenen Team-Mitgliedern im Pair Programming an zwei Tasks gearbeitet. Es waren zwei intensive Tage, die aber zu guten Ergebnissen in kurzer Zeit geführt haben.

Halbwertszeit des Wissens
Obwohl ich selbst über das Problem mit den Namespaces in XML gebloggt hatte bin ich gut zwei Jahre später wieder in dieselbe Falle getappt. Aber wenigstens ist mir schnell eingefallen, dass ich dieses Problem schon einmal hatte.

Binäre Werte in C#

Image courtesy of "Stuart Miles" / FreeDigitalPhotos.net

Image courtesy of „Stuart Miles“ / FreeDigitalPhotos.net

Da ich mich wieder einmal in die Tiefen eines Protokolls herab wagen musste war ich auch mit Bitmasken konfrontiert. Da merkte ich, dass ich nicht mehr wusste, wie binäre Werte in C# anzugeben sind. Also kurz Dr. Google gefragt und die Antwort bei StackOverflow gefunden: Geht nicht!

Mit der neuen Compiler-Platform „Roslyn“ (C# 6) waren „binary literals“ geplant (Das Feature war für C# im Status „planned“, für VB.NET schon „done“), sind nun aber nicht mehr für den nächsten Release vorgesehen.

Ein paar Vorschläge, wie man sich behelfen kann sind bei StackOverflow auch noch zu finden:

Ein anwendbarer Weg, wenn nicht zu viele Werte nötig sind, ist der Vorschlag von Markus Johnsson:

Man kann die Werte als Konstanten definieren, die mit b… beginnen:

const int b001 = 1;
const int b010 = 2;
const int b011 = 3;
// etc ...
Debug.Assert((b001 | b010) == b011);

Eine andere Variante (von Marc Gravell) ist, mit Strings zu arbeiten und diese zu Parsen:

int i = Convert.ToInt32("01101101", 2);

Sahuagin vertritt in seiner Antwort die Meinung, dass man gefälligst mit hexadezimalen Werten rechnen lernen soll…

Aber der Vorschlag von Dmitry Tashkinov dürfte mehr Ärger verursachen als helfen, wenn er angewendet wird:

long bitMask = 1011001;
// And later
int bit5 = BitField.GetBit(bitMask, 5);
// Or
bool flag5 = BitField.GetFlag(bitMask, 5);`

// Helper class
public static class BitField
{
    public static int GetBit(int bitField, int index)
    {
        return (bitField / (int)Math.Pow(10, index)) % 10;
    }

    public static bool GetFlag(int bitField, int index)
    {
        return GetBit(bitField, index) == 1;
    }
}

Entsprechend auch der Kommentar dazu: Using int thousand to mean 1-0-0-0 is just asking for trouble.

Piwik 2.5.0

Logo Piwik 2

Logo Piwik 2

Beim Release 2.5.0 von Piwik wurde der Fokus auf das API gelegt. Die Änderungen sind nun im neu eingeführten Changelog ersichtlich.

Die insgesamt 87 geschlossenen Tickets umfassen unter anderem folgende Änderungen:

  • Es werden nun weitere Suchmaschinen als Referer erkannt
  • Verbesserungen bei der Anzeige der erkannten Browser
  • Verbesserung beim Schutz der Privatsphäre der Besucher
  • Code wurde aufgeräumt und unbenutzter Code entfernt