Kategorie: Softwareentwicklung

Versionierung 👍 👎

Zu meiner Ergänzung zur Darstellung von IP-Adressen hat mich die Frage erreicht, ob ich vielleicht noch ein paar Sätze dazu schreiben möchte, wie ich es mit der Versionierung konkret halte.

Das möchte ich mit diesem Beitrag gerne machen, jedoch gehe ich da ziemlich traditionell und wenig spektakulär vor, so dass ich keine allzu großen Hoffnungen auf bahnbrechende Erkenntnisse machen möchte. Smiley: winking

Vor der Fertigstellung der initial zu veröffentlichenden Version verwende ich lediglich die internen Versions- bzw. Buildnummern. Zur öffentlichen Versionsnummerierung halte ich mich üblicherweise an folgendes Schema:

  x.y.z
  • x: Hauptversion

    Diese Nummer wird bei einer kompletten Neuentwicklung, bzw. einem Technologiewechsel erhöht. Dies ist z. B. bei einem Wechsel der Programmiersprache der Fall, oder aber wenn strukturelle/inkompatible Änderungen (insbesondere an öffentlichen Schnittstellen) stattfinden. Damit einher geht jedoch neben der – durchaus einschneidenden – Änderung an sich auf jeden Fall auch eine Neuentwicklung wesentlicher Bestandteile des gesamten Projektes.

  • y: Funktionalitätserweiterung

    Diese Nummer wird bei Erweiterungen der Funktionalität einer Anwendung erhöht. Dazu werden einige – im Allgemeinen überschaubare – zu erledigende Aufgaben gesammelt und dann mit dieser Version veröffentlicht. Bei besonders umfangreichen Ergänzungen kann diese Veröffentlichung durchaus auch nur einen einzelnen Aspekt betreffen, andererseits aber eben auch sehr viele kleinere.

  • z: Fehlerbehebung, Optimierung

    Diese Nummer wird bei der Behebung von Fehlern einer Anwendung erhöht. Dazu werden im Allgemeinen einige (insbesondere weniger dramatische) Probleme gesammelt und dann mit dieser Veröffentlichung behoben. Bei kritischen Fehlern, die die Funktionsfähigkeit des gesamten Programmes verhindern, oder aus Sicherheitsgründen schnellstmöglich behoben werden müssen, kann eine solche Version auch nur für einige wenige Änderungen für ein einzelnes Problem veröffentlicht werden.

    Sofern bestehende Funktionalität zwar erwähnenswert optimiert wird, jedoch nicht grundlegend erweitert, erscheint mir diese Stelle ebenfalls angemessen.

Zusätzliche Unterscheidungen oder ergänzende Begriffe verwende ich für gewöhnlich nicht. Zur Versionsverwaltung an sich setze ich auf Git per GitLab. Ein aus meiner Sicht gutes Dokument mit weitestgehend sinnvollen Vorschlägen ist außerdem Semantic Versioning

Dynamische Typisierung in C# 👍 👎

Im Artikel zur impliziten Typisierung hatte ich bereits auf einen späteren Artikel zum Thema dynamische Typisierung verwiesen, was mit diesem Beitrag nun auch erfolgen soll.

Seit .NET 4.0 enthält das Framework die sog. Dynamic Language Runtime, was sich bei C# auf den ersten Blick durch das neue Schlüsselwort dynamic, die Basisklasse DynamicObject und das ExpandoObject mit entsprechender Funktionalität bemerkbar macht.

Die DLR ermöglicht zwar "auch" interessante Aspekte im Hinblick auf die Integration dynamisch typisierter Programmiersprachen wie beispielsweise Python (→ IronPython) oder Ruby (→ IronRuby) in das .NET-Framework, ich möchte in diesem Beitrag aber dennoch bei den Möglichkeiten in C# selbst bleiben. Ein paar Gedanken meinerseits zur Typisierung im Allgemeinen sollen in einem späteren Beitrag folgen.

Wie vielen Lesern dieses Blogs bekannt sein dürfte, handelt es sich bei C# um eine grundsätzlich statisch typisierte Programmiersprache, d. h. die Typen aller Variablen sind bereits zum Zeitpunkt der Kompilierung bekannt, was nicht zuletzt auch ermöglicht, dass euch Visual Studio hilfreiche Hinweise schon während der Entwicklung geben kann.

Durch die DLR wird von diesem Prinzip (glücklicherweise Smiley: winking_grinning) nicht grundsätzlich abgewichen, jedoch stellt sich euch die Möglichkeit, von diesem Vorgehen explizit abzuweichen, indem ihr als Typ dynamic deklariert. Dadurch wird die statische Typprüfung deaktiviert und auf die Laufzeit verschoben. Das bedeutet, dass ihr einer Variable dieses Typs beliebige – also auch gar nicht existente – Eigenschaften zuweisen oder darauf Methoden aufrufen könnt, im Problemfall jedoch zur Laufzeit eine entsprechende Ausnahme erhaltet.

Wir beginnen – wie üblich – mit einem kleinen Code-Beispiel zur Verdeutlichung:
Dynamische Variablen erzeugen
0102
dynamic a = 123;dynamic b = "123";
Nun können wir relativ "normal" damit arbeiten, beispielsweise die Werte in einer MessageBox ausgeben:
Dynamische Variablen verwenden (1)
0102030405060708
MessageBox.Show(a.ToString());  //: "123"MessageBox.Show(b.ToString());  //: "123"/** * C# konvertiert hier nicht in eine mathematische Addition, * da "b" zur Laufzeit als Zeichenkette interpretiert wurde.**/MessageBox.Show(a + b);  //: "123123"
Soweit hätte das natürlich auch ohne dynamic funktioniert, der interessante Aspekt ist jedoch, dass sich beispielsweise auch folgender Code kompilieren lässt – Visual Studio zeigt euch bei Eingabe des Punktes (→ IntelliSense) nur mehr "(dynamischer Ausdruck) Der Vorgang wird zur Laufzeit aufgelöst." an:
Dynamische Variablen verwenden (2)
0102
MessageBox.Show(b.Replace("2", "4"));  //: "143"MessageBox.Show(a.Replace("2", "4"));  // Ausnahme!
Ohne dynamic hätte euch Visual Studio bereits während der Entwicklung angezeigt, dass Replace(…) für den Datentyp int nicht zur Verfügung steht (bzw. erst gar nicht zur Auswahl angeboten) und die Kompilierung mit einem Fehler abgebrochen.

Im Gegensatz zu var kann sich der Typ unserer Variable nun auch während der Laufzeit ändern:
Änderung des Typs zur Laufzeit
01020304050607
MessageBox.Show(a.GetType().ToString());  //: "System.Int32"MessageBox.Show(a.ToString());  //: "123"
a = "test";

MessageBox.Show(a.GetType().ToString()); //: "System.String"MessageBox.Show(a.ToString()); //: "test"
Dies entspricht schon sehr dem Verhalten, wie man es von Sprachen wie PHP kennt und bringt die entsprechenden Vorteile, aber eben auch gravierende Nachteile mit sich. Ob eine Operation zu Problemen führt, erkennt man nunmehr erst zur Laufzeit – und mit etwas Pech auch nicht sofort, da die ungültige Operation möglicherweise nicht sofort zur Verwendung kommt, mit statischer Typisierung aber dennoch bereits zur Entwicklungszeit von Visual Studio darauf hingewiesen worden wäre, dass dies niemals möglich sein wird.

Wenn das nun neben ein bisschen Bequemlichkeit beim Tippen doch eher Probleme einführt, stellt sich natürlich die berechtigte Frage, wozu man so etwas verwenden sollte. Hauptsächlich handelt es sich tatsächlich um ein Mittel, dynamische Sprachen in das .NET-Framework zu integrieren, welche nun einmal auf diesem Konstrukt basieren. Auch die Verwendung entsprechender Programmbibliotheken, um beispielsweise einfach über XML-Knoten zu navigieren (man denke an dieser Stelle auch an das DOM) sind sicherlich eine praktikable Idee. Für die reine Bequemlichkeit ist implizite Typisierung jedoch definitiv besser geeignet.

Ich würde auf jeden Fall dringend davon abraten, unter C# selbst exzessiven und vor allem unüberlegten Einsatz davon zu machen. Natürlich kann man sich dadurch z. B. gewisse Umstände, die Reflektion mit sich bringt, ersparen, dennoch würde ich hier nach wie vor zu einem sauberen, auf Schnittstellen basierten Modell raten. Dadurch, das alles "irgendwie" aufgerufen werden kann, handelt man sich nicht selten schwer auffindbare und auch lösbare Fehler ein. Insbesondere sollte bedacht werden, dass das .NET-Framework beim Aufruf entsprechender Funktionalität auch immer passende Datentypen erwartet; Folgendes ergibt beispielsweise umgehend einen Laufzeitfehler:
Gefahren bei der Interaktion mit dem .NET-Framework
010203
dynamic c = 5;
MessageBox.Show(c); // Ausnahme!
Das Problem ist nämlich, dass Show(…) eine Zeichenkette erwartet; bei b hätte es auch so funktioniert, c muss jedoch zwingend mit ToString() aufgerufen werden. Mit statischer Typprüfung sind das alles Probleme, die man bereits beim Schreiben des Quelltextes erfährt. Mit dynamischer Typprüfung möglicherweise aber erst nach einigen Stunden oder gar noch längerer Laufzeit, wenn die Stelle zum ersten Mal zur Ausführung kommt.

Da wir nun umfassend auf die potentiellen Gefahren eingegangen sind und erste Beispiele betrachtet haben, möchte ich noch einmal kurz auf DynamicObject und ExpandoObject zurückkommen und dazu jeweils ein kleines Anwendungsbeispiel demonstrieren. Wir beginnen mit dem ganz einfachen ExpandoObject und fügen diesem Eigenschaften und Verhalten hinzu:
ExpandoObject definieren und verwenden
01020304050607080910111213141516
dynamic obj = new ExpandoObject();{      // Eigenschaften setzen    obj.FirstName = "Holger";    obj.LastName = "Stehle";
obj.Age = 24;}
// Verhalten definierenobj.GetName = new Func<string>(() => { // der Rückgabetyp könnte auch "dynamic" lauten return obj.LastName + ", " + obj.FirstName;});
// Name und Alterstring person = obj.GetName() + " (" + obj.Age + ")"; //: "Stehle, Holger (24)"
Als nächstes wollen wir noch einen (kleinen) Blick auf das DynamicObject werfen, von welchem wir erben und dabei spezifisches Verhalten für bestimmte Aktionen festlegen können. Konkret möchten wir den Zugriff auf Eigenschaften unabhängig von Groß-/Kleinschreibung ermöglichen:
DynamicObject erben und verwenden
01020304050607080910111213141516171819202122232425262728293031
  // Klasse ableiten und Verhalten anpassenpublic class MyDynamicObject : DynamicObject {    private Dictionary<string,object> data = new Dictionary<string,object>();

// Eigenschaft abrufen public override bool TryGetMember(GetMemberBinder binder, out object value) { return data.TryGetValue(binder.Name.ToLower(), out value); }
// Eigenschaft setzen public override bool TrySetMember(SetMemberBinder binder, object value) { data[binder.Name.ToLower()] = value;
return true; }}
// Klasse verwendendynamic obj = new MyDynamicObject();{ // Eigenschaften setzen obj.FirstName = "Holger"; obj.lastName = "Stehle";}
string firstName = obj.FirstName; //: "Holger"string firstName = obj.firstName; //: "Holger"string lastName = obj.lastName; //: "Stehle"string lastName = obj.LastName; //: "Stehle"
Mein Fazit: Sicherlich wünscht man sich das eine oder andere Mal während der Entwicklung ein wenig mehr Dynamik, aber dafür bezahlt man einen nicht unerheblichen Preis zum Nachteil der Wartbarkeit. Zur Implementierung von Standardaufgaben sollte das unter C# nicht das erste Mittel der Wahl sein, eher eines der letzten – C# bietet hier für gewöhnlich mächtige und vor allem typsichere Alternativen. Ja, das ist durchaus mit etwas mehr Schreibarbeit verbunden.

Wie üblich hält das MSDN weitere Hinweise zur Verwendung parat.

Implizite Typisierung in C# 👍 👎

C# ist und bleibt zwar eine grundsätzlich statisch typisierte Programmiersprache, erlaubt jedoch auch die implizite Typisierung mit Hilfe des var-Schlüsselwortes:
Verwendung
01
var age = 24;  // äquivalent zu "int age = 24"
Der konkrete Typ wird dabei implizit durch den Wert auf der rechten Seite bestimmt. Dies kann den Quelltext besonders bei komplexen Strukturen (z. B. im Hinblick auf generische Programmierung) etwas kompakter gestalten, je nach Gewohnheit aber auch etwas schwerer zu verstehen sein. Es bleibt wie so oft dem Entwickler (bzw. den Richtlinien des Teams) überlassen, wie damit umzugehen ist.

Wichtig zu verstehen ist in jedem Fall, dass var in diesem Fall nur ein syntaktisches Hilfsmittel ist und keine dynamische Typisierung eingeführt wird (dazu in einem späteren Artikel mehr). Das bedeutet insbesondere, dass unserem "age" später nicht etwa eine Zeichenkette zugewiesen werden kann.

Zwingend notwendig ist diese Vorgehensweise jedoch bei anonymen Typen, da der Typ keinen (bzw. lediglich einen internen, dem Compiler bekannten) Typenbezeichner besitzt:
Verwendung bei anonymen Typen
0102030405
var ich = new {    FirstName = "Holger",    LastName = "Stehle",    Age = 24};
Auch im Rahmen von LINQ-Abfrageausdrücken kann dieses Vorgehen beispielsweise bei der Projektion von Daten aus einer Liste mittels Select(…) notwendig sein (aus ähnlichen Gründen wie im Beispiel zuvor).

Optionale und benannte Parameter in C# 👍 👎

Wie viele andere Programmiersprachen (z. B. PHP) unterstützt auch C# seit einiger Zeit optionale Parameter. Die Verwendung ist denkbar einfach:
Optionale Parameter
01020304050607080910
  // Kreisfläche berechnenpublic double GetCircleArea(double radius = 1) {    return (Math.PI * (radius * radius));}

double circleArea = GetCircleArea(5); // Kreisfläche für Radius "5"
double circleArea = GetCircleArea(1); // Kreisfläche für Radius "1"double circleArea = GetCircleArea(); // Kreisfläche für Radius "1"
Der beim letzten Aufruf weggelassene Parameterwert wird dabei implizit durch die Zuweisung des Standardwertes der Definition (radius = 1) gesetzt, um den Flächeninhalt des Einheitskreises zu erhalten. Aus naheliegenden Gründen sind optionale Parameter hinter nicht-optionalen Parametern zu definieren. Ein ähnliches Verhalten lässt sich selbstverständlich auch durch das Überladen der Methode erreichen, was jedoch etwas aufwändiger ist und zumindest in unserem Beispiel nicht ganz so intuitiv erscheint:
Optionale Parameter (per Methodenüberladung)
0102030405060708
  // Kreisfläche berechnenpublic double GetCircleArea(double radius) {    return (Math.PI * (radius * radius));}
public double GetCircleArea() { return this.GetCircleArea(1);}
Der Aufruf kann nun jeweils analog zu unserem vorherigen Beispiel erfolgen.

Darüber hinaus unterstützt C# jedoch auch benannte Parameter. Damit ist es nicht notwendig, die korrekte Position eines Parameters zu verwenden; diese werden stattdessen über ihren Namen angesprochen, was insbesondere im Zusammenhang mit (mehreren) optionalen Parametern sinnvoll sein kann:
Benannte Parameter
0102030405060708091011
  // Flächeninhalt berechnenpublic double GetArea(double height = 1, double width = 1) {    return (height * width);}

double area = GetArea(); // Höhe: 1, Breite: 1double area = GetArea(2); // Höhe: 2, Breite: 1double area = GetArea(3,4); // Höhe: 3, Breite: 4
double area = GetArea(width: 5); // Höhe: 1, Breite: 5 (benannter Parameter)
Es genügt dazu also, ganz einfach den Namen des Parameters, gefolgt von einem ":" (Doppelpunkt) und dem Wert anzugeben. Zu beachten gilt, dass benannte Parameter zwar auf per Position festgelegte Parameter folgen können, der umgekehrte Fall jedoch nicht unterstützt wird.

Index-basierter Zugriff auf Objekte in C# (und PHP) 👍 👎

Manchmal kann es sinnvoll (oder zumindest bequem oder intuitiv) sein, auf ein Objekt im Stile eines Feldes zugreifen zu können. Für das folgende Beispiel zu diesem Thema möchte ich kurz einen Anwendungsfall aus meiner Arbeit beschreiben.

Als ISP müssen wir bei Domains des Öfteren mit sog. "Handles" umgehen. Grob umrissen handelt es sich dabei um einen Kontakt-Datensatz für eine Domain. Darin abgelegt werden beispielsweise der Name, die Anschrift und – je nach TLD – auch noch div. andere Daten.

Im einfachsten Fall verwendet man zur Ablage dieser Informationen also ein assoziatives Feld (in C# z. B. ein generisches Dictionary) als Eigenschaft des Objekts:
"Handle"-Klasse
010203040506
public class Handle {    public Dictionary<string,string> HandleFieldValueList  {        get;        set;    }}
Den Umgang mit derartigen Datenfeldern habe ich zwar schon einmal relativ ausführlich beschrieben, dennoch möchten wir auch hier erst einmal das Objekt mit Daten füllen:
Daten hinzufügen
010203040506070809
Handle ich = new Handle() {      // Feldwerte    HandleFieldValueList = new Dictionary<string,string>() {        {"firstName", "Holger"},        {"lastName", "Stehle"}    }};
// ich.HandleFieldValueList["firstName"] enthält "Holger" etc.
Wie bereits angedeutet, können wir nun wie erwartet auf die entsprechenden Daten zugreifen. In einem solchen Fall – in dem die einzelnen Werte schließlich das Handle an sich ausmachen – wäre es doch aber auch praktisch, diesen Index-basierten Zugriff direkt auf dem Objekt durchführen zu können. Und genau dies ist mit C# ganz einfach möglich. Dazu müssen wir die Klasse wie folgt erweitern:
Indexer ergänzen
01020304050607080910111213141516171819
public class Handle {      // Indexer (-> Feldwerte)    public string this[string fieldName] {        get {            return this.HandleFieldValueList[fieldName];        }        set {            this.HandleFieldValueList[fieldName] = value;        }    }
// Feldwerte public Dictionary<string,string> HandleFieldValueList { get; set; }}
// ich["firstName"] enthält "Holger" etc.
Dies veranschaulicht die Implementierung eines sog. Indexers, welcher sich grundlegend wie eine Eigenschaft verhält. Der hauptsächlich interessante Teil string this[string fieldName] besagt, dass ein Index-basierter Zugriff auf die Instanz (→ this) mit einem Index (→ fieldName) des Typs string einen string liefert und definiert im Stile eines Akzessors, wie genau dies geschieht. Und schon können wir (wie im Code angedeutet) wie erhofft darauf zugreifen.

Indexer können auch überladen werden, so dass z. B. zusätzlich auch ein Zugriff mit numerischem Index ermöglicht werden kann. Zuletzt gilt zu beachten: Es handelt sich hierbei lediglich um eine etwas kompaktere Schreibweise für etwas, was sowieso möglich wäre. Dennoch gibt es – wie ich persönlich finde – interessante Einsatzmöglichkeiten hierfür. Der konkrete Nutzen ist also im Einzelfall abzuwägen.

Um derartiges Verhalten in PHP zu ermöglichen, muss die Klasse die Schnittstelle ArrayAccess implementieren. Ein Beispiel zur Umsetzung findet sich z. B. in der offiziellen Dokumentation.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche