Archiv für den Monat April 2014

Ready-to-Use C# Enumeration Extension Methods

Bei dem unten stehenden Code handelt es sich um fertige Enum Extension Methods in C#. Damit lassen sich unter anderem

  • die Enum Description
  • der Enum Comment
  • das Enum Value
  • alle Enum Values und
  • gesetzte Enum Flags

auslesen. Der Code ist bereits ein wenig in die Jahre gekommen und einige Methoden habe ich lediglich noch aus Kompatiblitätsgründen drin. Die Tests sind mit Machine.Specifications 0.6.2 geschrieben.

Kopiert euch einfach, was ihr benötigt und passt es nach Belieben an. Über Likes oder Comments, wenn euch der Code Arbeit gespart hat, wäre ich dankbar.

Über gute Alternativen zu Enums könnt ihr hier etwas lesen.

 

Implementierung:

public static class EnumerationExtensions

{

    public static bool ByteIsSet<TEnum>(this Enum bytes, TEnum enumValue)

    {

        if (!Attribute.IsDefined(typeof (TEnum), typeof (FlagsAttribute)) ||

            bytes.GetType() != enumValue.GetType() ||

            bytes.GetType() != typeof (TEnum))

        {

            return false;

        }

 

        return ((Convert.ToInt32(bytes) & Convert.ToInt32(enumValue)) == Convert.ToInt32(enumValue));

    }

 

    public static IList<EnumEntry> GetEnumerationEntries(this Type enumeration)

    {

        var values = Enum.GetValues(enumeration);

 

        var result = new List<EnumEntry>();

 

        foreach (var value in values)

        {

            // ReSharper disable ExpressionIsAlwaysNull

            var desc = (value as Enum).GetEnumDescription();

            var comment = (value as Enum).GetEnumComment();

            // ReSharper restore ExpressionIsAlwaysNull

 

            var name = Enum.GetName(enumeration, value);

            var id = (int)value;

 

            result.Add(new EnumEntry

                       {

                           Name = name,

                           Id = id,

                           DisplayName = desc,

                           Comment = comment

                       });

        }

 

        return result;

    }

 

    public static string GetEnumDescriptionIfExists(this Enum enumeration)

    {

        return GetEnumDescription(enumeration, true);

    }

 

    public static string GetEnumDescription(this Enum enumeration)

    {

        return GetEnumDescription(enumeration, false);

    }

 

    private static string GetEnumDescription(Enum enumeration, bool nullIfNotExists)

    {

        var fi = enumeration.GetType().GetField(enumeration.ToString());

 

        if (fi == null)

        {

            return nullIfNotExists ? null : string.Empty;

        }

 

 

        var attribute = (DescriptionAttribute[])fi.GetCustomAttributes(typeof (DescriptionAttribute), false);

 

        if (attribute.Length > 0)

        {

            return attribute[0].Description;

        }

 

 

        return nullIfNotExists ? null : enumeration.ToString();

    }

 

    public static string GetEnumCommentIfExists(this Enum enumeration)

    {

        return GetEnumComment(enumeration, true);

    }

 

    public static string GetEnumComment(this Enum enumeration)

    {

        return GetEnumComment(enumeration, false);

    }

 

    private static string GetEnumComment(Enum enumeration, bool nullIfNotExists)

    {

        var fi = enumeration.GetType().GetField(enumeration.ToString());

 

        if (fi == null)

        {

            return nullIfNotExists ? null : string.Empty;

        }

 

        var attr = (EnumCommentAttribute[])(fi.GetCustomAttributes(typeof (EnumCommentAttribute), false));

 

        if (attr.Length > 0)

        {

            return attr[0].ResourceComment;

        }

 

        var result = GetEnumDescription(enumeration);

        return nullIfNotExists ? null : result;

    }

 

    public static bool HasValue(this Enum enumeration)

    {

        if (enumeration == null)

        {

            return false;

        }

 

        var type = enumeration.GetType();

        // ReSharper disable once CheckForReferenceEqualityInstead.1

        if (type.Equals(null))

        {

            return false;

        }

 

        var fi = enumeration.GetType().GetField(enumeration.ToString());

        if (fi.Equals(null))

        {

            return false;

        }

 

        return true;

    }

}

 

Contract:

public class EnumEntry

{

    public int Id { get; set; }

    public string Name { get; set; }

    public string DisplayName { get; set; }

    public string Comment { get; set; }

}

Tests:

internal class EnumerationExtensionsSpecs{

    internal class EnumBase

    {

        protected enum DummyEnum

        {

            [Description("Desc1")]

            WithDescription = 1,

 

            WithoutDescription = 2

        }

 

        protected enum DummyEnum2

        {

            [EnumComment("Comment1")]

            WithComment = 1,

 

            WithoutComment = 2

        }

 

        protected enum DummyEnum3

        {

            WithValue = 1,

 

            WithoutValue

        }

 

        protected enum DummyEnum4

        {

            [Description("DisplayName1")]

            [EnumComment("Comment1")]

            Value1 = 1,

 

            [Description("DisplayName2")]

            [EnumComment("Comment2")]

            Value2 = 2

        }

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class Wenn_eine_Enum_2_Werte_besitzt_und_diese_aufgelistet_werden_sollen : EnumBase

    {

        private static IList<EnumEntry> _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = (typeof (DummyEnum4)).GetEnumerationEntries(); };

 

        private It dann_ergeben_sich_daraus_2_Listenwerte = () => _result.Count.ShouldEqual(2);

 

        private It dann_werden_alle_Daten_von_Wert1_auf_den_Listeneintrag1_gemappt = () => (

                                                                                               _result.First().Id == 1

                                                                                               && _result.First().Name == "Value1"

                                                                                               && _result.First().DisplayName == "DisplayName1"

                                                                                               && _result.First().Comment == "Comment1"

                                                                                           )

                                                                                               .ShouldBeTrue();

 

        private It dann_werden_alle_Daten_von_Wert2_auf_den_Listeneintrag2_gemappt = () => (

                                                                                               _result.Last().Id == 2

                                                                                               && _result.Last().Name == "Value2"

                                                                                               && _result.Last().DisplayName == "DisplayName2"

                                                                                               && _result.Last().Comment == "Comment2"

                                                                                           )

                                                                                               .ShouldBeTrue();

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_decorated_with_a_description_and_null_if_emtpy_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum.WithDescription.GetEnumDescriptionIfExists(); };

 

        private It should_resolve_the_description_text = () => _result.ShouldEqual("Desc1");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_not_decorated_with_a_description_and_null_if_emtpy_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum.WithoutDescription.GetEnumDescriptionIfExists(); };

 

        private It should_resolve_null = () => _result.ShouldBeNull();

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_decorated_with_a_description_and_name_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum.WithDescription.GetEnumDescription(); };

 

        private It should_resolve_the_description_text = () => _result.ShouldEqual("Desc1");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_not_decorated_with_a_description_and_name_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum.WithoutDescription.GetEnumDescription(); };

 

        private It should_return_the_name_of_the_enum_value = () => _result.ShouldEqual("WithoutDescription");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_decorated_with_a_comment_and_name_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum2.WithComment.GetEnumComment(); };

 

        private It should_return_the_name_of_the_enum_value = () => _result.ShouldEqual("Comment1");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_not_decorated_with_a_comment_and_name_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum2.WithoutComment.GetEnumComment(); };

 

        private It should_return_the_name_of_the_enum_value = () => _result.ShouldEqual("WithoutComment");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_decorated_with_a_comment_and_null_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum2.WithComment.GetEnumCommentIfExists(); };

 

        private It should_resolve_the_description_text = () => _result.ShouldEqual("Comment1");

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_is_not_decorated_with_a_description_and_null_if_empty_is_requested : EnumBase

    {

        private static string _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum2.WithoutComment.GetEnumCommentIfExists(); };

 

        private It should_return_the_name_of_the_enum_value = () => _result.ShouldBeNull();

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_has_a_value : EnumBase

    {

        private static bool _result;

 

        private Establish context = () => { };

 

        private Because of = () => { _result = DummyEnum3.WithValue.HasValue(); };

 

        private It should_return_true = () => _result.ShouldBeTrue();

    }

 

    [Subject(typeof (EnumerationExtensions))]

    internal class When_an_Enum_has_no_value : EnumBase

    {

        private static bool _result;

 

        private Establish context = () => { };

 

        private Because of = () =>

                             {

                                 DummyEnum3? d3 = null;

                                 _result = d3.HasValue();

                             };

 

        private It should_return_false = () => _result.ShouldBeFalse();

    }

 

    internal class When_Enum_is_not_decorated_with_FlagsAttribute

    {

        private static TestEnum_MissingFlagAttribute MyEnum;

 

        private Because of = () => { MyEnum = TestEnum_MissingFlagAttribute.Val2 | TestEnum_MissingFlagAttribute.Val3; };

 

        private It should_return_False1 = () => MyEnum.ByteIsSet(TestEnum_MissingFlagAttribute.Val1).ShouldBeFalse();

 

        private It should_return_False2 = () => MyEnum.ByteIsSet(TestEnum_MissingFlagAttribute.Val2).ShouldBeFalse();

 

        private It should_return_False3 = () => MyEnum.ByteIsSet(TestEnum_MissingFlagAttribute.Val3).ShouldBeFalse();

 

        private enum TestEnum_MissingFlagAttribute

        {

            Val1,

            Val2,

            Val3

        }

    }

 

    internal class When_Enum_is_compared_with_wrong_Enum_type

    {

        private static TestEnum1 MyEnum1;

 

        private Because of = () => { MyEnum1 = TestEnum1.Val2 | TestEnum1.Val3; };

 

        private It should_return_False1 = () => MyEnum1.ByteIsSet(TestEnum2.Val1).ShouldBeFalse();

 

        private It should_return_False2 = () => MyEnum1.ByteIsSet(TestEnum2.Val2).ShouldBeFalse();

 

        private It should_return_False3 = () => MyEnum1.ByteIsSet(TestEnum2.Val3).ShouldBeFalse();

 

        [Flags]

        private enum TestEnum1

        {

            Val1 = 0x01,

            Val2 = 0x02,

            Val3 = 0x04

        }

 

        [Flags]

        private enum TestEnum2

        {

            Val1 = 0x01,

            Val2 = 0x02,

            Val3 = 0x04

        }

    }

 

    internal class When_Enum_contains_enum_value

    {

        private static TestEnum MyEnum;

 

        private Because of = () => { MyEnum = TestEnum.Val2 | TestEnum.Val3; };

 

        private It should_return_False_for_val1 = () => MyEnum.ByteIsSet(TestEnum.Val1).ShouldBeFalse();

 

        private It should_return_True_for_val2 = () => MyEnum.ByteIsSet(TestEnum.Val2).ShouldBeTrue();

 

        private It should_return_True_for_val3 = () => MyEnum.ByteIsSet(TestEnum.Val3).ShouldBeTrue();

 

        [Flags]

        private enum TestEnum

        {

            Val1 = 0x01,

            Val2 = 0x02,

            Val3 = 0x04

        }

    }

}

Wie besetze ich die Rolle des Product Owners

Prinzipiell ist es nie leicht die Rollen in Scrum adäquat zu besetzen. Der Product Owner (PO) ist dabei keine Ausnahme. An dieser Stelle kann ich deshalb keine generellen Empfehlung aussprechen, einfach weil dies meines Erachtens nicht möglich ist. Stattdessen beschreibe ich den Weg, den wir in der Firma heco mit Erfolg gegangen sind.

Einer unserer Administratoren in der IT sprach mich darauf an, dass er mit seinen Aufgaben zum Teil unterfordert ist und sich zum Teil auch noch mehr einbringen wolle. Beim Reflektieren darüber stachen für mich 5 wesentliche Eigenschaften hervor:

  • In seiner aktuellen Aufgabe hatte unser Admin sehr engen und guten Kontakt zu allen Mitarbeitern, die das Produkt (hauseigenes ERP-System) einsetzen
  • Sein Verständnis für Geschäftsprozesse war stark ausgeprägt, was auch seine akademische Karriere (Wirtschaftsgymnasium, DHBW Studium) belegte
  • Seine IT-Kenntnisse machten die Kommunikation mit dem Entwickler Team einfacher
  • Er betreute bereits das Produkt auf technisch administrativer Ebene
  • Er war motiviert und wollte das Produkt aktiv mitgestalten

Klar war aber, dass ihm das fachliche Know How und deshalb auch die konkrete Produktvision fehlen würde. Deshalb waren die ersten zwei Schritte ihn direkt zu der Person zu setzen, die das Produkt bis ins kleinste Detail kannte: Unseren Geschäftsführer. Um den Einlernungsprozess zu beschleunigen, war die erste Aufgabe eine vollständige Dokumentation über alle Schnittstellen zu schreiben – sowohl fachlich (das WAS) als auch technisch (das WIE).

In der anschließenden Retrospektive zeigte sich, dass damit der Lernprozess nicht schnell genug voran kam. Deshalb übertrugen wir ihm die Aufgabe alle User Stories fortan selbst zu erfassen. Wir wichen also von den Empfehlungen der Standardliteratur ab, dass die Entwickler die User Stories schreiben sollen. Für mich eine der besten Entscheidungen überhaupt. Immer wenn er nicht in der Lage war die User Story zu erfassen, egal ob zu formulieren oder gemäß dem Business Value zu priorisieren, musste er mit unserem GF darüber sprechen. Zur Gänze hab ich ihn immer zusammen mit den Fachexperten die zugehörigen Informationen im Unternehmens-Wiki festhalten lassen, also z.B. die erarbeitete Domänen-Sprache (vgl. Ubiquitous Language) oder die Bedienungsanleitung. Konsequenterweise war er auch an allen Meetings anwesend. Dass Informieren der Mitarbeiter in Form von Trainings, Schulungen oder E-Mails vervollständigte die Botschaft: Er ist der PO, an den sich die Belegschaft wenden muss.

Die ersten 4 Monate agierte er als sogenannte Proxy Product Owner, der dem eigentlichen PO (in unserem Falle dem Geschäftsführer) versuchte möglichst viel Arbeit abzunehmen, v.a. wenn dieser nicht die nötige Zeit für die Ausübung seiner Rolle hatte. Der Anlernphase folgte dann das Gespräch mit unserem GF, in welchem er seine PO Position übergab. Das bedeutete in erster Linie: Benötigt das Entwickler Team sofort eine Antwort auf eine Frage, dann hat der neue PO die entsprechende Kompetenz, eine etwaig endgültige Entscheidung zu treffen. Diesen Aspekt möchte ich ganz klar hervorheben, weil er nicht unterschätzt werden darf! Zuvor hatten wir bereits eine 100%ige Verfügbarkeit des PO für das komplette Scrum Team erreicht. Diese beiden Eigenschaften zusammen lieferten einen unglaublichen Impuls für die Entwicklung.

Ein Beispiel hierfür: In der Vergangenheit trauten sich die Kollegen aus den Niederlassungen nicht direkt an unseren Geschäftsführer mit Verbesserungsvorschlägen oder Fehlermeldungen heran. Oftmals wählten sie einen Umweg über mich, jedoch verwies ich immer auf den GF als Product Owner, da ich nicht priorisieren konnte bzw. wollte. Das Problem war mir bereits früher aufgefallen, doch fand ich nie eine zufriedenstellende Lösung. Inzwischen ist eine äußert produktive Feedback Kultur entstanden, bei der die Belegschaft die Umsetzung ihrer Wünsche logischerweise sehr positiv aufnimmt.

Selbstverständlich machte unser PO parallel zur Produktschulung regelmäßig Scrum Schulungen (Inhouse), las Bücher (Empfehlung hier), ging zu Scrum Stammtischen oder machte Webinare. Alternativ kann auch ein externer Berater herangezogen werden, mit dem die ersten Schritte gemeinsam gegangen werden.

 

Mich würde interessieren, wer unter den Lesern mit der unternehmenseigenen Besetzung des PO zufrieden ist. Vor allem in Hinblick auf Verfügbarkeit fürs Team!

Wo findet sich der Quality Manager in Scrum wieder?

Kürzlich sprach ich mit einer Bewerberin, die während ihres Studiums erste Erfahrungen mit Scrum in einem größeren Betrieb gemacht hat. Mit Interesse verfolgte ich ihre Antwort auf die Frage, wie die konkrete Implementierung aussah. Dabei fielen die Bezeichnungen Product Manager und Quality Manager.

Das ist insofern interessant, weil Scrum die beiden Rollen gar nicht kennt. Während der klassische Product Manager in den 3 Rollen Product Owner, Scrum Master und Developer Team aufgeht, sieht das beim Quality Manager anders aus. Wie schon beim Lean Management steckt der Gedanke hinter Scrum, dass die Qualität nicht zum Schluss durch eine Person geprüft werden, sondern dass Qualität im kompletten Entwicklungsprozess von jedem Beteiligten als eigener Anspruch etabliert werden soll. Im Falle eines Autos würde das bedeuten, dass nicht erst das fertige Auto am Ende der Fertigungsstraße auf Qualität geprüft wird. Stattdessen soll kontinuierlich vom Start bis zum Ende an jeder einzelnen Fertigungsstelle der Bearbeiter über die Standards Bescheid wissen und sie kontrollieren. Sollte währenddessen das Produkt dem Anspruch nicht genügen, würde die Fertigungsstraße pausiert und darüber beraten werden, wie es mit dem Fahrzeug weitergehen soll. Kurz gesagt: Wenn das Kind in den Brunnen gefallen ist, muss nicht mehr über die Absicherung des Brunnens gesprochen werden.

Besagter Gedanke wird in Scrum zum Einen über die Definition of Done etabliert. Eine User Story darf erst als erledigt gekennzeichnet werden, wenn der Definition entsprochen wurde. Darin stehen unter anderem Spezifikationen für die automatisierte Testabdeckung, sowie etwaige manuell durchzuführende Tests. Im Übrigen finden sich dort auch Aussagen zur Sicherheit, weil das Absichern einer Anwendung im Nachhinein genauso wenig zielführend ist wie im Falle der Produktqualität.

Darüber hinaus gehört zu einem erfolgreichen Scrum Prozess auch ein Build Server, der automatisiert alle Arten von Tests durchführt und bei jedem Check-In im Versionskontrollsystem (auch das VKS ist Teil der Qualitätssicherung) alle Produktteile integriert.

Das bedeutet, dass die Aufgaben des Quality Managers in Scrum an das Entwickler Team übergehen. Das Team soll schließlich eigenverantwortlich arbeiten und gerade hier zeigt sich, ob sich der Product Owner das richtige Team für seine Vision herausgesucht hat. Oder aber es zeigt sich, dass dem Team gar nicht erlaubt wird so zu arbeiten, wie es das für richtig hält. Hier ist dann der Scrum Master gefragt. Beispielsweise habe ich von Teams gehört, denen der Build Server und das Versionskontrollsystem vorgeschrieben wurde, obwohl die Entwickler klar kommunizierten, dass die Software nicht den Ansprüchen genügt.

%d Bloggern gefällt das: