Samstag, 7. November 2009

Wie komme ich zu der Ehre?

Gerade vom .Net Open Space zurück starte ich meinen E-Mail Client und sehe eine Mail von einem Patrick Smacchia. Ich hatte bis jetzt noch nie von ihm gehört. In der E-Mail fragt er mich ob ich mir NDependend anschauen kann und eventuell mal hier darüber berichten könnte. Ich war erstmal etwas skeptisch und verunsichert. Warum werde gerade ich gefragt? Naja nun habe ich hier NDependend zum ausgiebigen Testen liegen. Es scheint ein sehr interessantes Tool zu sein, leider hatte ich bisher noch nicht die Zeit mir sämtliche Features anzuschauen. Ich werde mich aber in Kürze weiter damit beschäftigen und ausgibig berichten, was das Tool genau kann und in wie fern es brauchbar ist.

Sonntag, 18. Oktober 2009

.Net Open Space 2009

Puh. Ich bin gerade erst von der .Net Open Space zurück. Es gab wieder viel zu lernen. Für mich selbst waren die interessanten Themen Clean Code, Teambildung, Entwicklungsprozesse und Architektur. Im letzten Jahr ging es mehr um TDD und BDD.

Zu Beginn gab es von Stefan Lieser eine kleine Einführung in die Open Space Philosophie. Danach ging es direkt in die Themenfindung für die einzelnen Sessions. Nach Wahl der Sessions besuchte man den entsprechenden Raum.



Dies war die Session "Architektur technologieunabhängig?" in der meine persönliche Erkenntnis war, dass ich mich kontinuierlich weiterbilden muss. Vor allem auch zielungerichtet um mal einen Blick über den Tellerrand zu bekommen.


Zwischen den Sessions gab es immer genügend Zeit sich mit den anderen Teilnehmern auszutauschen. Hier gab es Gespräche über konkrete Applikationen, Tools, Entwicklungsmethodiken und Entwicklungsprozesse. Eigentlich alles, was irgend wie mit der Sofwareentwicklung zu tun hat.


Eine der für mich interessantesten Sessions war die KattaPotter Dojo Session. Hier sollte von sechs Teilnehmern eine Übungsaufgabe gemeinschaftlich gelöst werden.


Es war schon interessant zu sehen, wie kompliziert manche im ersten Ansatz versuchen an eine Aufgabe heranzugehen. Dabei wurde die eigentlich Problematik völlig ausser acht gelassen. Warscheinlich werde ich die Katta in Kürze selbst machen und von meinen Erfahrungen berichten. Denn ich befürchte, ich hätte es an einigen Stellen genau so gemacht.

Alles in allem war die diesjährige .NET Open Space super. Ich habe viel gelernt und werde auch nächstes Jahr wieder in Leipzig sein. Jetzt muss ich erst mal ein wenig Schlaf nachholen ;-)

Freitag, 2. Oktober 2009

Update zu "Bug in NHibernate 2.1 die Dritte"

Mein Patch zu dem Bug über den ich in diesem Post berichtete wurde mittlerweile doch aktzeptiert.

Update zu "Bug in NHibernate 2.1 die Zweite"

Wie in einem meiner vorherigen Posts zu lesen war, gab es Probleme mit One-To-Many und access="none"

Richard Brown hat mich darauf hingewiesen, dass das Setzen des Bags im Parent auf inverse="true" das Problem behebt.

Aber seltsam ist das Verhalten doch trozdem. Ich frage mich, ob das so gedacht war.

Samstag, 5. September 2009

NHibernate 2.1 short codereview

Um erst einmal eins vorweg zu sagen, ich mag NHibernate wirklich sehr. Ich will dieses Tool nicht schlecht machen oder so, auch wenn ich in letzter Zeit viel über Bugs oder komische Codestellen berichte. Es ist ein sehr gutes Tool und wenn mir die Qualität nicht passt habe ich selbst die Möglichkeit etwas dran zu ändern.

Da ich in letzter Zeit vermehrt in den Sourcecode von NHibernate reingeschaut habe, sind doch ein paar Stellen aufgefallen die ich einfach grottig fand. Eine von diesen werde ich kurz erläutern.

In der Klasse Configuration gibt es die Methode BuildSessionFactory(). Diese sieht momentan so aus:

public ISessionFactory BuildSessionFactory()
{

    ConfigureProxyFactoryFactory();
    SecondPassCompile();
    Validate();
    Environment.VerifyProperties(properties);
    Settings settings = BuildSettings();

    // Ok, don't need schemas anymore, so free them
    Schemas = null;

    return new SessionFactoryImpl(this, mapping, settings, GetInitializedEventListeners());
}

 Die rot markierte Zeile ist das Interessante an der Sache. Wenn ich so in den Qullcode schaue, würde ich ja denken hier werden Properties überprüft. Wenn wir uns nun mal die Methode anschauen stellen wir fest ...

/// <summary>
/// Issue warnings to user when any obsolete property names are used.
/// </summary>
/// <param name="props"></param>
/// <returns></returns>
public static void VerifyProperties(IDictionary<string, string> props) {}

sie macht garnichts!
Sehr cool finde ich den Kommentar.

Donnerstag, 3. September 2009

.NET Open Space 2009

Bald ist es wieder soweit. Zum zweiten mal findet in Leipzig am 17. und 18. Oktober die .NET Open Space statt. Dort wird es wieder spannende Diskussionen rund um die Softwareentwicklung, hauptsächlich im Bereich .NET geben. Mittlerweile ist die maximale Teilnehmeranzahl fast erreicht.

Ich freue mich auf jeden Fall darauf. Wir werden uns dort sehen.

Mittwoch, 2. September 2009

Bug in NHibernate 2.1 die Dritte

Da dachte ich, ich hätte den Fehler in der Example-Klasse, wie hier schon beschrieben, behoben. Allerdings fand ich dann in der nhusers-Group diesen Beitrag.

Joe Smith beschreibt zuerst den Fehler, den ich schon mit einem Patch behoben hatte. Doch dann stellte sich heraus, dass es ein weiteres Problem für Oracle Datenbanken im Zusammenhang mit der LikeExpression zu geben scheint.

Oracle macht einen unterschied, ob man "like 'UPPER%'" oder "like 'lower%' schreibt. Bei mir tritt das Problem nicht auf, da ich momentan entweder mit MySQL oder MS-SQL arbeite und dort like case insensitive ist.

Nach ca. 30 Minuten suchen im NHibernate Quelltext habe ich das Problem gefunden. Wenn die LikeExpression auf ignoreCase geschaltet wird, wird nur für die geprüfte Spalte lower() aufgerufen jedoch nicht für den übergebenen Parameter. Ich habe für Joe auf die schnelle einen Patch fertig gemacht indem lower() auch für den Parameter aufgerufen wird. Diesen habe ich auch an NHibernate-JIRA geschickt, mit der Anmerkung, dass das gleiche Problem auch in der InsensitiveLikeExpression besteht (warum gibt es eigentlich zwei unabhängige Klassen und nicht eine, die auf die Andere aufbaut?).

Eigentlich dachte ich, damit sei die Sache erst mal erledigt aber Fabio Maulo ist wohl nicht der Ansicht, dass dieses Verhalten ein Bug sei. Schließlich sei der Entwickler seiner Meinung nach selbst für den Parameter verantwortlich und nicht NHibernate.

Das macht für mich allerdings überhaupt keinen Sinn.
  1. Wenn ich NHibernate mitteile, dass es case insensitive arbeiten soll, dann ist mir doch egal wie der Parameter aussieht, es soll einfach case insensitive arbeiten.
  2. Das Verhalten vor NH2.1 scheint genau so gewesen zu sein, wie von Joe (und mir) eigentlich erwartet. Von daher soll es sich doch auch mit NH2.1 weiterhin so verhalten.
  3. Wenn ich mich selbst um den Parameter kümmern muss, müsste ich auch wissen darüber haben, wie NHibernate arbeitet. Ich weiß erstmal nicht, dass lower() angewendet wird. Könnte ja auch sein das jemand gerne upper() verwendet. Ich weiß es nicht. Dies ist ein reines Implementierungsdetail und als Benutzer sollte es mir vollkommen egal sein können.
Bis jetzt kam leider keine weitere Antwort zu dem Thema. Allerdings ist mir in diesem Zusammenhang ein weiterer möglicher Bug aufgefallen. Aber dazu später mehr.

Sonntag, 30. August 2009

Bug in NHibernate 2.1 die Zweite

Da habe ich mich auf das neue Access-Feature none gefreut, das ja schon mit NH 2.0 und dem nop Wert eingeführt wurde, und jetzt funktioniert das für diesen Fall nicht. Ich habe folgende Classen:
public class Parent{
public long Id{get;set;}
public string Name{get;set;}
private IList<Child> children;
}

<class name="Parent">
<id name="Id">
<generator class="native" />
</ id>
<property name ="Name" />
<bag name ="children" access="field">
<key column="ParentId">
<one-to-many class="Child" />
</ bag>
</ class>

public class Child{
public long Id{get;set;}
public string Text{get;set}
public long ParentId{get;set}
}

<class name="Child" >
<id name="Id" >
<generator class="native" />
</ id>
<property name="Text" />
<property name="ParentId" />
</ class>
Es ist Absicht, dass ein Child nur die ParentId kennt und nicht den Parent direkt. Es ist ebenfalls Absicht, dass children ein private field ist ohne irgend einen Zugriff, da ich diese Variable zu NH 1.2 Zeiten ausschlisslich für Query-Zwecke brauchte. Es gab nun folgende Criteria:

using(var session = sessionFactory.OpenSession){
using(var transaction = session.BeginTransaction){
session.CreateCriteria<Parent>()
.Add(Restrictions.IsNotEmpty("children"))
.List<Parent>();
transaction.Commit();
}
}
Diese Query funktioniert wunderbar. Was mich stört, ist die unnüze Variable children. Durch das neue none Feature, was eigentlich genau dafür gemacht wurde, dachte ich diese Variable elemenieren zu können. Also löschte ich nun children aus der Parent-Klasse und änderte das Parent-Mapping:
<class name="Parent">
<id name="Id">
<generator class="native" />
</ id>
<property name ="Name" />
<bag name ="children" access="none">
<key column="ParentId">
<one-to-many class="Child" />
</ bag>
</ class>
Wenn ich die Query erneut ausführe, passiert jetzt etwas seltsames. Aus irgend einem Grund versucht NHibernate nun die ParentId im Child-Objekt auf NULL zu setzen. Mir ist schleierhaft wie das zu stande kommt. Auch cascade expliziet auf none zu schalten macht keinen Unterschied.

Ich haben den Fehler im NHibernate JIRA gemeldet und zähneknirschend das private field children wieder in die Parent-Klasse eingebunden.

Montag, 24. August 2009

Clean Code Developer (CCD)

Diesesmal will ich etwas über die CCD Initiative berichten. Im Grunde geht es dabei um Techniken, mit denen man seine Software besser machen kann. Dabei sind diese Techniken auf unterschiedliche Grade aufgeteilt.

Mit jedem Rang arbeitet man daran bestimmte Prinzipien und Praktiken während der Softwareentwicklung anzuwenden. Die ersten Ränge sind so aufgebaut, dass man die Praktiken für sich alleine erarbeiten kann. Dazu zählen einfache Prinzipien wie DRY (Don't repead your self) und KISS (Keep it simple and stupid) Bei späteren Rängen sollte das gesamte Team und letztendlich auch das Management beteiligt sein, da hier zum Teil auch Infrastruktur geschaffen werden muss. Als Beispiel sei Continuous Integration und Iterative Entwicklung genannt.

Ich persönlich arbeite gerade am gelben Grad und bin froh darüber, dass ich viele der Praktiken schon kenne und anwende. Somit kann das, was ich bisher gemacht habe, nicht komplett falsch gewesen sein.

Also ... keep the clean code!!!

Samstag, 22. August 2009

Bug in NHibernate 2.1 die Erste

Vor kurzem habe ich für eine Software ein Upgrade von NHibernate 1.2 nach NHibernate 2.1 durch geführt. Ich meinte "Ach das ist kein Problem, wir müssen nur ein paar Namespaces ändern".

Leider enthielt NH 2.1 nicht nur neue Features und Bugfixes, sondern auch tolle neue Bugs. So lieferten auf einmal Suchfunktionen der Software keine Ergebnisse mehr, die eine QBE verwendeten.

Also habe ich mir den Quelltext von NHibernate runtergeladen und selbst nach dem Fehler gesucht. Anscheinend wurde in der Example Klasse vergessen beim "EnableLike" den MatchMode zu übergeben. Ich habe den Bugfix an NHibernate JIRA geschickt, der in Version 2.1.1 und 3.x enthalten sein wird.

Beim schauen in den Quelltext sind mir ein paar Sachen aufgefallen wo es mich doch etwas gruselt. Aber dazu später mehr.

Sonntag, 8. März 2009

Behaviour-Driven Development (BDD) and Syntactic Shugar

Da ich gerade mein Abalone Projekt neu aufsetze, will ich das Ganze dieses mal test-driven entwickeln. Dabei will ich, wenn es um das Verhalten von Controlern und diverser Objekte geht auf den BDD-Style setzen. Bei diesem Style geht es darum, dass Tests weniger wie Tests aussehen, sondern eher wie die Spezifikation des Systems. So beschreibt ein BDD-Test eine Aktion und was als Ergebnis erwartet wird.

Man hätte dann etwas in folgender Form:

When the application was told to register a new user
-a new user will be created
-a register email will be send
-a success message will be shown

Dahinter würden sich nun entsprechende Methodenaufrufe verbergen die verifizieren ob die Anforderungen eingehalten werden. Das hat den Vorteil, dass man noch nach einer langen Zeit genau weiss, wie sich das Objekt verhalten sollte. Der andere Vorteil ist, dass man dem der die Anforderungen stellt (z. B. der Kunde oder Chef), die Tests als Protokoll zeigen kann.

Ich komme jetzt gerade auf das Thema, weil ich dabei bin Anforderungen für mein Eingangsportal zu sammeln. Jetzt wollte ich anfangen den ersten BDD-Test für mein Projekt zu schreiben. Da ist mir aufgefallen, dass ich noch keine BDD-Lib habe um eine schöne Lesbare Syntax hin zu bekommen. Vor allem für das Setup und Asserts von gemockten Objekten habe ich mir überlegt wie kann man eine schöne lesbare Syntax hinbekommen. In Anlehnung an schon bekannte Frameworks habe ich mir für das Setup Folgende Syntax überlegt und in einem standard Unit-Test spezifiziert:

[Test]
public void when_a_mockobject_was_told_to_return_a_value_it_will_return_it() {
var mockRepository = new MockRepository();
var testobject = mockRepository.Stub<ITestDependency>();

When.The(testobject).was_told_to(x => x.DoSomething(4)).Then.Return("success");

mockRepository.ReplayAll();
string result = testobject.DoSomething(4);
result.will_be_equal_to("success");
mockRepository.VerifyAll();
}

public interface ITestDependency
{
string DoSomething(int value);
}

Also ich finde, dass die Zeile
'When.The(testobject).was_told_to(x => x.DoSomething(4)).Then.Return("success");'
schon sehr fluffig aussieht. Was man nicht alles für Spielereien machen kann :-)

Sonntag, 1. März 2009

Refactoring die Zweite

Durch meinen guten Kollegen Stefan Lieser bin ich auf diese Seite gestoßen. Dort geht es darum, eine Enumeration nicht als enum zu verwenden, sondern eine Klasse zu bauen mit statischen Enumerations-Objekten. Die Klasse lässt sich verwenden wie eine normale Enumeration hat aber den Vorteil, dass man noch zusätzliche Attribute hinterlegen kann. Ich fand die Idee sehr interessant und habe mir überlegt, ob ich das für mich irgend wie einsetzen kann. Ursprünglich hatte ich für die Bewegungsrichtungen der Kugeln eine Enumeration mit allen möglichen Richtungen.

   1: public enum Direction {    
   2:     UpperLeft,    
   3:     UpperRight,    
   4:     Left,    
   5:     Right,    
   6:     LowerLeft,    
   7:     LowerRight    
   8: }  

Das Problem daran ist, dass ich zu jeder Richtung wissen muss, ob die x/y Koordinate hoch oder runter gezählt werden muss oder ob sie gleich bleibt. Desweiteren muss ich zu einer Richtung die Gegenrichtung kennen.

Der erste Ansatz war eine Methode der den inkrementellen Wert für die Richtung über ein switch-case zurück gibt und ein Dictionary, dass als Schlüssel eine Direction hat und als Value die entsprechende Gegenrichtung.

Viel schöner finde ich es, dass über die Enumeration-Klasse zu machen.

Hier die neue Implementierung der Direction:


   1: public class Direction : Enumeration {    
   2:     private readonly Func<Direction> inverseProjection;    
   3:     public int Xincrement { get; private set; }    
   4:     public int Yincrement { get; private set; }    
   5:      
   6:     public Direction InverseDirection { get { return inverseProjection(); } }    
   7:      
   8:     public Direction(string displayName, int xincrement, int yincrement, Func<Direction> inverseProjection)    
   9:         : base(displayName) {    
  10:         this.inverseProjection = inverseProjection;    
  11:         Xincrement = xincrement;    
  12:         Yincrement = yincrement;    
  13:     }    
  14:      
  15:     public static readonly Direction UpperLeft = new Direction("UpperLeft",-1,-1, () => LowerRight);    
  16:     public static readonly Direction UpperRight = new Direction("UpperRight",0,-1, () => LowerLeft);    
  17:     public static readonly Direction Left = new Direction("Left", -1,0, () => Right);    
  18:     public static readonly Direction Right = new Direction("Right", 1, 0, () => Left);    
  19:     public static readonly Direction LowerLeft = new Direction("LowerLeft", 0, 1, () => UpperRight);    
  20:     public static readonly Direction LowerRight = new Direction("LowerRight", 1, 1, () => UpperLeft);    
  21: }


Wie man sehen kann, wurde der Klasse X und Y Inkrement hinzugefügt. Die Erweiterung ist nicht sehr aufregend, da ich einfach im Konstruktor die entsprechende Konstante übergebe.

Viel interessanter ist die InverseDirection. Im ersten Versuch habe ich einfach im Konstruktor die entsprechende Gegenrichtung übergeben. Leider legt sich das Programm direkt auf die Nase, da zum Zeitpunkt der Zuweisung die Gegenrichtung noch NULL ist. Deshalb wird hier die Lambda Expression verwendet.

Was man noch refactoren könnte ist, dass im Konstruktor keine nackten Zahlen mehr übergeben werden. Das Problem ist, dass nach mehreren Monaten keiner mehr weiß was “-1” bedeutet.

Das schöne ist, dass ich meine switch-case Anweisung und das Dictionary nun wegwerfen kann und der Code wesentlich sauberer aussieht. Im Gegensatz zu


   1: private Ball GetNextBall(Direction dir, int posx, int posy) {    
   2:     int xinc, yinc;    
   3:     GetIncrement(dir, out xinc, out yinc);    
   4:     if (IsOutOfField(posx + xinc, posy + yinc)) {    
   5:         return null;    
   6:     }    
   7:     return GetBall(posx + xinc, posy + xinc);    
   8: }  
finde ich

   1: private Ball GetNextBall(Direction dir, int posx, int posy) {    
   2:     if (IsOutOfField(posx + dir.Xincrement, posy + dir.Yincrement)) {    
   3:         return null;    
   4:     }    
   5:     return GetBall(posx + dir.Xincrement, posy + dir.Yincrement);    
   6: }
wesentlich besser. Wobei GetIncrement ungefähr so aussah:

   1: private void GetIncrement(Direction dir, out int xinc, out int yinc){    
   2:     switch(dir){    
   3:         case Direction.UpperLeft:    
   4:             xinc = -1;    
   5:             yinc = -1;    
   6:             break;    
   7: ...


Sonntag, 8. Februar 2009

Refactoring die Erste

Wenn man nach einiger Zeit mal wieder in alten Code reinschaut fällt einem immer wieder auf, dass mal wieder ein Refactoring nötig ist. Generell ist es das Ziel, dass Programm-Code auch nach langer Zeit immer noch lesbar ist und man versteht, was dort passieren soll. Als Beispiel will ich die IsInRow() Methode aus meinem letzten Post verwenden. Wenn ich mir diese for-Schleife anschaue gruselt es mich.

Hier noch einmal die Methode vom letzten mal:

public bool IsInRow(List<Ball> balls) {
if (balls.Count <= 1) {
return true;
}

int minX = balls.Min(ball => ball.PosX);
int minY = balls.Min(ball => ball.PosY);

int dx = balls.Max(ball => ball.PosX) - minX;
int dy = balls.Max(ball => ball.PosY) - minY;

int xinc = Convert.ToInt32(dx > 0);
int yinc = Convert.ToInt32(dy > 0);

for (int i = 0, x = 0, y = 0; (x <= dx && y <= dy) ||
i < balls.Count; x += xinc, y += yinc, i++) {
if (balls.Find(ball => ball.PosX == (x + minX) &&
ball.PosY == (y + minY)) == null) {
return false;
}
}
return true;
}
Sofort ist mir aufgefallen, dass die erste Bedingung der for-Schleife überflüssig ist. Generell kann ich ruhig jede Kugel prüfen und damit reicht die zweite Bedingung aus.
...
for (int i = 0, x = 0, y = 0; i < balls.Count;
x += xinc, y += yinc, i++) {
...
}
Als nächstes kann ich die Suche nach einer Kugel an der jeweiligen Position durch eine Methode austauschen.
...
for (int i = 0, x = 0, y = 0; i < balls.Count;
x += xinc, y += yinc, i++) {
if (ThereIsNoBall(balls, minX + x, minY + y)) {
return false;
}
}
...

private bool ThereIsNoBall(List<Ball> balls) {
return balls.Find(ball => ball.PosX == x && ball.PosY == y) == null;
}
Da ich den Test so oft durchführen muss, wie Kugeln in der Liste sind kann ich die Iteration auch über eine foreach-Schleife machen. So brauche ich selbst nicht mehr auf das Ende der Liste prüfen.

...
int x = 0;
int y = 0;
foreach (Ball ball in balls) {
if (ThereIsNoBall(balls, x + minX, y + minY)) {
return false;
}
x += xinc;
y += yinc;
}
...

Im nächsten Schritt werde ich zwei Dinge verändern. Zum einen werden die Variablen dx und dy nur an einer stelle verwendet. Also kann ich mir diese Variable auch komplett sparen. Als nächtes werde ich noch die index-Variablen x und y entfernen, da ich auch direkt minX und minY hochzählen kann. Sinnvollerweise benenne ich minX und minY gleich in posX und posY um.
...
int posX = balls.Min(ball => ball.PosX);
int posY = balls.Min(ball => ball.PosY);

int xinc = Convert.ToInt32((balls.Max(ball => ball.PosX) - posX) > 0);
int yinc = Convert.ToInt32((balls.Max(ball => ball.PosY) - posY) > 0);

foreach (Ball ball in balls) {
if (ThereIsNoBall(balls, posX, posY)) {
return false;
}
posX += xinc;
posY += yinc;
}
...
Da jetzt die Zeile um x-/y-inc zu ermitteln etwas zu kryptisch aussieht, werde ich das noch in eine Methode auslagern. Ausserdem passiert im wesentlichen in beiden Zeilen das Gleiche, was ebenfalls für eine neue Methode sprechen würde.
...
int xinc = GetStepSize(balls, posX, ball => ball.PosX);
int yinc = GetStepSize(balls, posY, ball => ball.PosY);
...

private int GetStepSize(IEnumerable<Ball> balls, int pos,
Func<Ball, bool> positionProjection) {
return Convert.ToInt32((balls.Max(positionProjection) - pos) > 0);
}
Als letztes werde ich noch überall das List gegen IEnumerable austauschen. Das hat den Grund, dass ich an keiner Stelle der Methode meine List verändern will. Mit einer Liste könnte ich das machen. Mit einem IEnumerable nicht. Dafür ist es nötig balls.Count gegen die Extension balls.Count() und balls.Find() gegen balls.FirstOrDefault() auszutauschen. Die fertige Methode sieht dann so aus.

public bool IsInRow(IEnumerable<Ball> balls) {
if (balls.Count() <= 1) {
return true;
}

int posX = balls.Min(ball => ball.PosX);
int posY = balls.Min(ball => ball.PosY);

int xinc = GetStepSize(balls, posX, ball => ball.PosX);
int yinc = GetStepSize(balls, posY, ball => ball.PosY);

foreach (Ball ball in balls) {
if (ThereIsNoBall(balls, posX, posY)) {
return false;
}
posX += xinc;
posY += yinc;
}
return true;
}

private int GetStepSize(IEnumerable<ball> balls, int pos,
Func<Ball, bool> positionProjection) {
return Convert.ToInt32((balls.Max(positionProjection) - pos) > 0);
}

private bool ThereIsNoBall(IEnumerable<Ball> balls, int x, int y) {
return balls.FirstOrDefault(ball => ball.PosX == x && ball.PosY == y) == null;
}

Ansich finde ich, dass der Code so wensentlich besser zu lesen ist als vorher. Was man vielleicht noch machen könnte wäre, die x/y Werte zu einem Point zusammen zu fassen. Ich denke aber, dass es fürs Erste reichen sollte.

Wie gut, dass ich vorher Tests geschrieben habe und nun überprüfen kann, ob mein Refactoring irgend welche Auswirkungen auf das Verhalten hat :-)

Mittwoch, 4. Februar 2009

Das Spiel Abalone und die Algorithmen dahinter

In meinem Projekt will ich ja das Spiel Abalone als Onlineversion implementieren. Falls jemand das Spiel oder die Regeln nicht kennt, so findet er hier alle notwendigen Erklärungen.

Als ich mir Abalone als Spiel aussuchte dachte ich mir "Hmm die Regeln sind ja recht einfach. Das sollte nicht sonderlich schwer sein umzusetzen". Als ich mit der Implementierung der Spielmechanik begonnen hatte, hat sich meine Annahme als Irrtum herausgestellt. Es fängt mit so einfachen Dingen an wie "Liegen die gewählten Kugeln in einer Reihe?" und hört bei "Wo liegen die Kugeln nach dem Verschieben?" auf.

Als Beispiel erläutere ich, wie ich erkenne, ob Kugeln in einer Reihe liegen. Generell benötige ich diese Funktionalität beim Selektieren der einzelnen Kugeln und beim Verschieben einer Reihe.
Ich betrachte in der Methode immer eine Menge von Kugeln. Da ich mein Spielfeld als zweidimensionales Array darstelle haben meine Kugeln eine X und eine Y Koordinate.

Als erste Variante habe ich einfach versucht über die Kugeln zu iterieren um zu schauen, ob die nachfolgende Kugel in Reihe zu der vorherigen Kugel liegt. Der Versuch schlug schon nach erstem Probieren Fehl. Erstens weiß ich nicht in welcher Richtung die Kugeln liegen und zweitens können die Kugeln völlig unsortiert sein. Es könnte also passieren, dass ich drei Kugeln in Reihe habe die durchnummeriert sind aber in der Iteration die Reihenfolge 1:3:2 haben. Somit würde diese Variante spätestens bei der zweiten Kugel in der Liste davon ausgehen, dass es keine Reihe ist.

Ich benötige also eine Möglichkeit die wahrscheinliche Richtung meiner Kugeln zu bestimmen, so dass ich später nur noch die Position in die Richtung verschieben und schauen muss, ob dort eine Kugel gibt.

Hier also der Algorithmus:
  1. Bilde die Differenz zwischen der Maximalen und der Minimalen X und Y Koordinate die die zu Prüfenden Kugeln haben.
  2. Ist die Differenz größer 0 so muss später bei der Iteration die X / Y Koordinate hochgezählt werden.
  3. Iteriere so über das Spielfeld, dass bei der Kugel mit geringstem X, Y Wert begonnen wird
  4. Suche in der Liste nach Kugeln die diese Koordinate haben
  5. Inkrementiere die X / Y Koordinate je nach Ergebnis in Schritt 2.
  6. Wiederhole den Vorgang bis entweder eine Kugel nicht gefunden wird (bedeutet dass die Kugeln nicht in Reihe liegen) oder alle Kugeln eine der erwarteten Koordinaten haben
Hier mal die Methode:
public bool IsInRow(List<Ball> balls) {
if (balls.Count <= 1) {
return true;
}

//finde kugel mit kleinstem x und kleinstem y
int minX = balls.Min(ball => ball.PosX);
int minY = balls.Min(ball => ball.PosY);

int dx = balls.Max(ball => ball.PosX) - minX;
int dy = balls.Max(ball => ball.PosY) - minY;

int xinc = Convert.ToInt32(dx > 0);
int yinc = Convert.ToInt32(dy > 0);

for (int i = 0, x = 0, y = 0; (x <= dx && y <= dy) ||
i < balls.Count; x += xinc, y += yinc, i++) {

if (balls.Find(ball => ball.PosX == (x + minX) &&
ball.PosY == (y + minY)) == null) {

return false;
}
}
return true;
}

Irgend wie müsste ich das Ganze nochmal Refactorn. Die for-Schleife ist mir noch eine Nummer zu kryptisch. Als ich nach etwa einem 3/4 Jahr nochmal draufgeschaut habe dachte ich mir "Huch was hast du denn da verbrochen" :-)

PS: Hat jemand eine Ahnung, wie man hier Quelltexte vernünftig darstellen kann?

Samstag, 10. Januar 2009

Mein Aktuelles Webprojekt

Hallo zusammen,

wie im letzten Post erwähnt, werde ich hier Sachen zu meinen Projekten vorstellen. Bei meinem aktuellen Projekt geht es allgemein um Spiele die man über das Web gegeneinander spielt. Mir ging es darum, dass man möglichst keine zusätzliche Software wie Silverlight oder Flash benötigt. Eignen würden sich hierfür Rundenbasierte spiele. Um erst einmal mit einem möglichst einfachen Spiel zu beginnen werde ich versuchen das gute alte Abalone umzusetzen. Tatsächlich habe ich schon eine Version die Theoretisch funktioniert. Nur läuft diese viel zu langsam, ist so gut wie ungetestet was automatisierte Tests angeht und die Struktur gefällt mir nicht.

Eines der Hauptprobleme das bei einem rundenbasierten Spiel entsteht ist, dass ich vom Server keine Benachrichtigung an den Client schicken kann. Die einzige mir bekannte Möglichkeit besteht darin, den Status beim Server immer wieder abzufragen.

Das gesamte Projekt wird in .NET entwickelt. Für das Userinterface werde ich vermutlich das ASP.NET MVC Framework verwenden.

Ich denke das ich in den nächsten Wochen ein paar Sachen vorstellen kann.

Freitag, 2. Januar 2009

25C3

Willkommen auf meinem ersten Blogpost.

In diesem Blog will ich über laufende Projekte und allgemein über Softwareentwicklung schreiben. Bin mal gespannt was da in den nächsten Monaten zusammen kommt.

Da ich auf dem 25C3 letztes Jahr war, will ich dies als Anlass für meinen ersten Post nehmen. Es war eine sehr interessante Veranstaltung. Es gab viel zu lernen über diverse Sicherheitslücken und Rechtliches.

Im Zusammenhang mit der Online-Durchsuchung und Durchsuchungen allgemein fand ich es sehr beunruhigend, dass nach Aussage von Ulf Buermeyer, Richter am Landgericht Berlin, ein Richter im Schnitt nur 2 Minuten Zeit hat zu entscheiden, ob er eine Durchsuchung genemigen soll oder nicht. Wird die Genehmigung nicht erteilt, muss er dies sogar noch begründen. Andersherum wäre es mir lieber, da die richterliche Genehmigung den Bürger eigentlich ja schützen soll.

Was ich leider etwas negativ an dem 25C3 anmerken muss ist, dass Politik ein sehr stark vertretenes Thema war. Auch wenn Themen wie Datenschutz und Online-Durchsuchung sehr wichtig sind, hätte ich mir etwas mehr zu Entwicklung und Hacking gewünscht.

Das Highlight zum Ende des Kongresses war für mich der Hack einer NPD Seite wie auf Heise.de zu lesen ist. Wer kommt eigentlich immer auf solche Ideen?