communicare ergo sum

Wie ich schon in dem Artikel über NOS Süd erwähnte, eines der interessantesten Themen, worüber wir in Karlsruhe debattiert haben, ist das Berufsbild des Softwareentwicklers. In einem Punkt waren wir uns alle einig:  der Softwareentwickler muss ein kleines Kommunikations-Genie sein!
Er muss sich in der Lage seines Gesprächspartners versetzen können, um Antworten auf noch nicht gestellten Fragen auszusprechen zu können; er muss seinem Gegenüber diplomatisch den rechten Weg zeigen, vor allem da dieser meistens überhaupt keinen blassen Schimmer von dem hat, was alles in einem Softwareprojekt möglich ist bzw. beachtet werden muss.

In den letzten Tagen habe ich den Eindruck bekommen, dass die Sommerhitze viele dazu gebracht hat, ihr sonst immer vorhandenes Talent im Bereich der Kommunikation zu vergessen. Alle diese Leute sind hochqualifizierte IT-Professionals, die, wenn sie sich zusammentun würden, alles schaffen könnten, was sie sich nur in den Kopf setzen. Ich habe bisher von ihnen wahnsinnig viel gelernt und das wird sich sicher niemals ändern.
Ich hätte allerdings diese Diskussion etwas anders geführt, und zwar nach diesen Regeln:

  1. Niemals in der Hitze des Gefechts antworten: das ist wahrscheinlich das Schwierigste, und in den heutigen digitalen Welt, wo man schnell und von überall antworten kann, ist es fast unmöglich diesem Drang zu wiederstehen.  In der  Twitter- und Blog-Welt verläuft die Kommunikation nicht mehr nur zwischen den einzelnen Beteiligten, sondern vor der “versammelten Mannschaft”. Dies führt unweigerlich dazu, dass jedes Wort schwerwiegender ist und die Folgen eines falsch gewählten Ausdruckes viel gravierender sind.
  2. Niemals jemanden öffentlich Beleidigen: auch wenn jemand einen Fehler gemacht hat, hat es gar keinen Sinn, ihn in eine Situation zu bringen, bei der er mit dem Rücken zur Wand steht. In 90% der Fällen würde aufgrund des Prinzips “Angriff ist die beste Verteidigung” einfach nur zurück-beleidigt werden, wahrscheinlich auf einer noch schlimmeren Art, um seine “Stärke” zu beweisen. Die Leute, die trotzdem objektiv bleiben und ihre eventuelle Schuld eingestehen sind sehr, sehr selten. Und noch seltener die, die sich nach eine Beleidigung öffentlich entschuldigen.
  3. Dem Anderen immer eine Wahl lassen: das Ziel sollte doch immer ein Konsens sein, sonst würde man einfach nur Befehle erteilen und dies ist bekanntlich keine Kommunikation. Also bevor man ein Gespräch beginnt, muss man sich im klaren sein, was er von dem Anderen erwartet und ob das überhaupt fair bzw. machbar ist.
  4. Niemals einen verlorenen Kampf beginnen: wenn es mir klar ist, dass der Gesprächspartner niemals meinem Wunsch entsprechen würde, wozu dann eine Diskussion anfangen? Man muss nicht immer alles durchsetzen, es geht ja hier nicht um Leben und Tod.
  5. Ich entschuldige mich bei den Lesern, die sich jetzt berechtigt fragen, um welche Diskussion es hier geht. Dies ist unwichtig, “vergangen sei vergangen und Zukunft ewig fern … “, es ist viel wichtiger, dass man endlich den gewünschten Konsens findet.
    Diese Community ist noch sehr jung und sehr empfindlich, es würde uns allen schaden, sie teilen zu müssen.
    Ich bin derselbe Meinung wie Mike, unser Ziel ist lernen und lehren mit Freude und Leidenschaft.

Open Space Rules

Am letzten Wochenende war in Karlsruhe der zweite .NET Open Space Süd. Im Herzen der Stadt haben sich um die 50 Entwickler, Architekten, Leute aus anderen IT-Bereiche getroffen, um all die Themen zu besprechen, worüber wir während des Jahres gegrübelt haben oder wofür uns die Zeit einfach gefehlt hat. Wir haben uns über Softwarezellen, Monaden, BDD, F#, Spieleentwicklung, Usability, Scrum und vieles mehr unterhalten.

Ich kann die vielen Eindrücke, die ich mitgenommen habe, nicht mal annähernd aufzählen, dafür muss man selbst teilnehmen. Björn hat es ganz genau zusammengefasst:

To sum it up in 5 words: The event was a blast. I had an unbelievable amount of fun coding, chatting & learning with some of smartest people in the German .NET Community.

Was die Sessions betrifft, und dazu zähle ich auch die unglaublich interessanten Gespräche am Samstag Abend bei einem Glas Wein, möchte ich nur zwei erwähnen. Eine davon behandelte das Thema “DDD” und für mich war dies die dritte Session in den letzten 12 Monaten. Bei der ersten zwei Sessions – in Ulm vor einem Jahr und in Leipzig in Oktober – standen mehr Fragen als Antworten im Raum, für viele war das Thema absolut “exotisch”. Diesmal waren wir alle von der Idee überzeugt, die Frage war nicht mehr ob, sondern nur noch wie wir damit umgehen sollen. Eine unglaubliche Entwicklung, domain-driven design ist in den Köpfen angekommen.

Das zweite Thema hat für ein paar von uns, u.a. Sergey, Albert, Ilker, Kai, Kostja, Daniel, Philipp, Gregor und ich den halben Samstag Abend gefüllt, und war ein Thema, das Ralf bereits in Leipzig angesprochen hat: wie ist das Berufsbild eines Softwareentwicklers und vor allem, wie wird es in 5 Jahren sein? In Leipzig hieß zwar die Frage “Wie machen wir unseren Beruf attraktiver”, in dieser Kneipe in Karlsruhe hatten wir endlich eine Antwort darauf gefunden. Kurz zusammengefasst: die Zeit des “embedded programmer”-s – der Hacker aus dem Keller, der mit seiner Hardware verschmolzen ist – ist vorbei, wir leben in dem Zeitalter des kommunikativen agilen Teams, das sich aus Fachleute aus allen Bereichen zusammensetzt. Kennt ihr noch eine andere Spezies, die den Job wählt bloß um dazu zu lernen, um an der Arbeit Spaß zu haben – und dafür auch noch Geld bekommt? Andere gehen von 8 bis 17 Uhr arbeiten, um Geld zu verdienen. Wir wollen allerdings viel mehr, wir müssen unsere Sucht nach “das neueste Feature ausprobieren, das gerade gelesene Muster anwenden” stillen. Und das ist warum, wir von der Arbeit nach Hause gehen, um dort in Ruhe weiterarbeiten zu können 🙂 Das ist nämlich für Nerds wie wir, keine Arbeit, es ist ein riesen Spaß.

Wir haben sogar einen empirischen Versuch gestartet, die Jungs haben ihren Status bei Facebook in “I know how Facebook works” geändert, um zu sehen, wie viele weibliche Reaktionen sie darauf bekommen 😉 Der Abend hat auch einen anderen Beweis für unseren Nerd-sein geliefert, wie Kai es irgendwann bemerkt hat: wer sitzt noch über eine Stunde, nachdem er gezahlt hat, in einer Kneipe, und trinkt nicht, nur um zu reden und reden und reden? Nur ein Softwarekünstler!

Ich war schon letztes Jahr von Open Space überzeugt, diese Veranstalltung hat dies nochmals bestätigt: Open Space rules. Vielen Dank noch mal an den Organisatoren, ich kann nur wiederholen, was ich schon mal gesagt habe: Chapeau! Wenn ihr ruft, wir kommen 🙂

Kontextabhängige Datenvalidierung

Die Idee stammt von Jimmy Nilsson – Applying Domain-Driven Design and Patterns. Er hat nach einer Möglichkeit gesucht, die immer wiederkehrende Aufgabe, Daten zu validieren, flexibel und kontextabhängig zu gestalten, und zwar so, dass man es nur einmal schreiben muss.

Wie läuft normalerweise so eine Validierung ab? Man will wissen, ob eine Instanz als solche allen Vorschriften entspricht, und wenn nicht, dann welche Felder passen nicht. Jeder, der jemals Webanwendungen geschrieben hat, weiß, wie mühsam und langweilig es ist, jeden Eingabewert auf Gültigkeit zu testen. (Hier geht allerdings nicht unbedingt um Formulare, da kann man ja die Validierung z.B. bei ASP.MVC 2.0 mit Data Annotations durchführen.)

Also zurück zu den Anforderungen: um die verschiedenen Regeln wiederverwendbar zu machen, braucht man diese von einem Interface abzuleiten:

namespace ValidationFramework
{
    public interface IRule
    {
        bool IsValid { get; }
        int IdRule { get; }
        string[] BooleanFieldsThatMustBeTrue { get; }
        string Message { get; }
    }
}

IsValid sagt aus, ob der Regel verletzt wurde. Die IdRule ist dafür da, um diese Regeln einfacher identifizieren zu können. BooleanFieldsThatMustBeTrue kann dafür verwendet werden, um Vorbedingungen zu prüfen. Message braucht wohl keine Erklärung.
Jetzt kann man verschiedene Regeln und eine Basisklasse für gemeinsame Funktionalitäten definieren:

using System.Collections.Generic;
using System.Linq;
using System;

namespace ValidationFramework
{
    public abstract class RuleBase : IRule
    {
        private readonly object m_value;
        private readonly int m_idRule;
        private readonly string[] m_booleanFieldsThatMustBeTrue;
        private readonly object m_holder;

        protected RuleBase(int idRule, string[] fieldsConcerned, string fieldname, object holder)
        {
            m_value = GetPropertyValue(holder, fieldname);
            m_booleanFieldsThatMustBeTrue = fieldsConcerned;
            m_holder = holder;
            m_idRule = idRule;
        }

        private static object GetPropertyValue(object holder, string fieldname)
        {
            return holder.GetType().GetProperty(fieldname).GetValue(holder, null);
        }

        protected object GetValue()
        {
            return m_value;
        }
        protected object GetHolder()
        {
            return m_holder;
        }

        public abstract bool IsValid { get; }
        public int IdRule
        {
            get { return m_idRule; }
        }

        public string[] BooleanFieldsThatMustBeTrue
        {
            get { return m_booleanFieldsThatMustBeTrue; }
        }

        public abstract string Message { get; }

        protected bool BooleanFieldsConcernedAreTrue()
        {
            return
                m_booleanFieldsThatMustBeTrue.Select(a => (bool)m_holder.GetType().GetProperty(a).GetValue(m_holder, null)).
                    Select(b => b).Count() == m_booleanFieldsThatMustBeTrue.Length;
        }
    }

    public class DateIsInRangeRule : RuleBase
    {
        private readonly DateTime m_minDate;
        private readonly DateTime m_maxDate;

        public DateIsInRangeRule(DateTime minDate, DateTime maxDate, int idRule, string fieldName, object holder) : base( idRule, null,fieldName, holder)
        {
            m_minDate = minDate;
            m_maxDate = maxDate;
        }

        public override bool IsValid
        {
            get { 
                var value = (DateTime) GetValue();
                return value >= m_minDate && value <= m_maxDate;
            }
        }

        public override string Message
        {
            get {
                return IsValid
                           ? string.Empty
                           : string.Format("Das Datum ist nicht in gültigen Bereich: {0}-{1}", m_minDate, m_maxDate); }
        }
    }

    public class MaxStringLengthRule : RuleBase
    {
        private readonly int m_maxLength;

        public MaxStringLengthRule(int maxLength, int idRule, string fieldname, object holder) : base(idRule, null, fieldname, holder)
        {
            m_maxLength = maxLength;
        }

        public override bool IsValid
        {
            get { return GetValue().ToString().Length <= m_maxLength; }
        }

        public override string Message
        {
            get {
                return IsValid
                           ? string.Empty
                           : string.Format("Die zugelassene Länge von {0} Zeichen wurde überschritten",m_maxLength); }
        }
    }
}

Jetzt, da die Grundlagen stehen, schauen wir mal, wie man die Validierungsregeln festlegen würde.
Hier ist eine ganz einfache Beispielklasse:

using System;

namespace ValidationFramework
{
    public class Account
    {
        public Account()
        {
            Created = DateTime.Now;
            Address = string.Empty;
        }

        public DateTime Created { set; get; }
        public string Address { get; set; }
        public bool Activated { get; set; }
    }
}

Jetzt definieren wir die Regeln, wonach eine Instanz valide ist oder nicht. Die neue Eigenschaft IsValidated soll diese Information speichern.

    public class Account
    {
        private readonly IList<IRule> m_validationRules = new List<IRule>();

        public Account()
        {
            Created = DateTime.Now;
            Address = string.Empty;
        }

        public DateTime Created { set; get; }
        public string Address { get; set; }
        public bool Activated { get; set; }
        public bool IsValidated { get; set; }

        private void SetupValidationRules()
        {
            m_validationRules.Add(new DateIsInRangeRule(new DateTime(1990,1,1),DateTime.Now,1,"Created",this ));
            m_validationRules.Add(new MaxStringLengthRule(10,2,"Address",this));
        }
    }

Um Regeln auch dynamisch setzen zu können, wird eine Methode AddValidationRule(IRule) definiert

 
    public class Account
    {
...    
        public void AddValidationRule(IRule rule)
        {
            m_validationRules.Add(rule);
        }
...
    }

Nun müssen wir diese Regeln nur noch auswerten. Dafür wird in RuleBase eine statische Methode definiert und in der Beispielklasse die Methode IEnumerable GetBrokenRules()

    public abstract class RuleBase : IRule
    {
...
        public static IEnumerable<IRule> CollectBrokenRules(IList<IRule> rulesToCheck)
        {
            return rulesToCheck.Where(a => !a.IsValid).Select(a => a);
        }
    }
    public class Account
    {
...
        public IEnumerable<IRule> GetBrokenRules()
        {
            SetupValidationRules();
            return RuleBase.CollectBrokenRules(m_validationRules);
        }
...
    }

Um zu beweisen, dass es funktioniert, hier ein Paar Tests:

using System;
using System.Linq;
using NUnit.Framework;

namespace ValidationFramework.Tests
{
    [TestFixture]
    [Category("DateIsInRangeRule")]
    public class If_the_CreationDate_of_the_Account_is_in_range
    {
        [Test]
        public void Then_the_property_Created_is_valid()
        {
            var sut = new Account { Created = DateTime.Now.AddYears(-1) };
            Assert.That(sut.GetBrokenRules().Count(),Is.EqualTo(0));
        }
    }
    [TestFixture]
    [Category("DateIsInRangeRule")]
    public class If_the_CreationDate_of_the_Account_is_to_old
    {
        [Test]
        public void Then_the_property_Created_is_not_valid()
        {
            var sut = new Account { Created = new DateTime(1989,1,1)};
            Assert.That(sut.GetBrokenRules().Count(), Is.EqualTo(1));
            Assert.That(sut.GetBrokenRules().First().IdRule, Is.EqualTo(1));
        }
    }

    [TestFixture]
    [Category("MaxStringLengthRule")]
    public class If_the_Address_of_the_Account_is_to_long
    {
        [Test]
        public void Then_the_property_Address_is_not_valid()
        {
            var sut = new Account { Address = "12345678901"};
            Assert.That(sut.GetBrokenRules().Count(), Is.EqualTo(1));
            Assert.That(sut.GetBrokenRules().First().IdRule,Is.EqualTo(2));
        }
    }
}

Mit den vorhandenen Tests kann man nun refaktorisieren um die Prinzipien der Separation Of Concern einzuhalten. Außerdem ist nun Zeit, auch über die Kontextbezogenheit nachzudenken.
Die Klasse Account wird wahrscheinlich durch irgendeinen ORMapper gefüllt und braucht auf keinem Fall die Verantwortung, die Validierungsregeln zu verwalten. Deshalb kann man das Specification-Pattern von DDD anwenden, und diese Regeln in so eine Spezifikation verschieben:

using System;
using System.Collections.Generic;

namespace ValidationFramework
{
    public interface IValidationSpecification
    {
        IList<IRule> GetValidationRules();
    }

    public class AccountValidationSpecification : IValidationSpecification
    {
        private readonly Account m_objectToValidate;

        public AccountValidationSpecification(object objectToValidate)
        {
            m_objectToValidate = (Account) objectToValidate;
        }

        public IList<IRule> GetValidationRules()
        {
            return new List<IRule>
                       {
                           new DateIsInRangeRule(new DateTime(1990, 1, 1), DateTime.Now, 1, "Created",
                                                 m_objectToValidate),
                           new MaxStringLengthRule(10, 2, "Address", m_objectToValidate)
                       };
        }
    }
}

Die Spezifikationen werden selbstverständlich von einer Factory geliefert und sie werden per Setter Injection gesetzt.

    public  class ValidationSpecificationFactory
    {
        public static IValidationSpecification Create<T>(object objectToValidate)
        {
            if (typeof(T) == typeof(Account))
                return new AccountValidationSpecification(objectToValidate);
            throw new NotSupportedException();
        }
    }
    public class Account
    {
...
        public void SetValidationSpecification(IValidationSpecification specification)
        {
            foreach (IRule rule in specification.GetValidationRules())
                m_validationRules.Add(rule);
        }
...
    }
//Der Aufruf ist dann
var account = new Account();
account.SetValidationSpecification(ValidationSpecificationFactory.Create<Account>(account));

Dadurch ist die Bedingung, Kontextabhängige Validierungsregeln festlegen zu können, erfüllt.

Man kann generische Spezifikationen definieren, die die Regeln für verschiedene Zwecke, z.B. für Persistieren oder auch einfach nur für Akzeptieren definieren:

   
    public interface IValidationSpecification
    {
        IList<IRule> GetValidationRules();
        IList<IRule> GetValidationRulesRegardingPersistence();
    }

Hier der komplette Quellcode zum Herunterladen.