Regions or no regions – this is the question

Die dnc12 ist gerade vorbei und wir könnten uns schon wieder zusammensetzen 🙂
Heute gab es auf Twitter eine kurz angerissene Diskussion, die das Zeug dazu hatte, die Gemüter aufzuheizen: soll man oder soll man nicht #regions nutzen?

 

Nach dem kurzen Tweet-Austausch wurde es klar, dass es viele Entwickler gibt, die Regions gerne nutzen. Ich habe zwar den ganzen Abend nachgedacht, habe allerdings keine Gründe gefunden, sie selbst verwenden zu wollen.

 

Ich meine, warum sollte man Code NICHT sehen wollen?

  • Geht es vielleicht um eine oder mehrere Methoden, die man ausblenden will? Das würde aber entweder bedeuten, dass man
    – die Funktionalität der ganzen Klasse ausblendet, aber dann wozu, man öffnet einfach die Klasse nicht 😉
    – nur ein Teil der Funktionen ausblendet, und dann stellt sich die Frage, warum manche Funktionen viel öfter angeschaut werden als andere? Das hat für mich irgendwie ein CodeSmell
  • Geht es vielleicht um ein Teil einer einzigen Methode, und zwar einer ganz großen, sonst würde man sie nicht teilweise ausblenden wollen? Zusammengeklappt würde man dann eine Art Kommentar sehen, was mich sofort an Martin Fowlers Hinweis bezüglich Kommentare erinnert hat: Kommentare sind ideale Namensgeber. Wenn man im Code einen Kommentar braucht, dann ist das meistens ein Smell für ein Extract Method (genauso wie die Tatsache, dass die Methode wahrscheinlich zu lang ist)

    You have a code fragment that can be grouped together.

    Turn the fragment into a method whose name explains the purpose of the method.

  • Geht es vielleicht um Regionen um Methoden, Events, Fields, Properties, also nach Sichtbarkeit und Rolle? Dafür könnte ich einen einzigen Grund vorstellen, und zwar den, dass man auf Anhieb die öffentliche Methoden und Eigenschaften sehen will. Das wäre allerdings die Aufgabe eines Interfaces, oder? Dazu kommt auch noch meine – persönliche – Vorliebe, Code wie ein Buch zu lesen, von oben nach unten, also von einer öffentlichen Methode weiter in die Details, also zu den privaten Methoden (ganz nach CCD – Single Level of Abstraction (SLA))

    Hilfreich als Analogie ist der Blick auf Artikel in der Tageszeitung: dort steht zu oberst das Allerwichtigste, die Überschrift. Aus ihr sollte in groben Zügen hervorgehen, wovon der Artikel handelt. Im ersten Satz des Artikels wird dies auf einem hohen Abstraktionsniveau beschrieben. Je weiter man im Artikel fortschreitet, desto mehr Details tauchen auf. So können wir auch unseren Code strukturieren. Der Name der Klasse ist die Überschrift. Dann folgen die öffentlichen Methoden auf hohem Abstraktionsniveau. Diese rufen möglicherweise Methoden auf niedrigerem Niveau auf, bis zuletzt die “Bitpfriemelmethoden” übrig bleiben. Durch diese Einteilung kann ich als Leser der Klasse entscheiden, welchen Detaillierungsgrad ich mir ansehen möchte. Interessiert mich nur grob, wie die Klasse arbeitet, brauche ich mir nur die öffentlichen Methoden anzuschauen. In ihnen wird die Funktionalität auf einem hohen Abstraktionsniveau gelöst. Interessieren mich weitere Details, kann ich tiefer einsteigen und mir die privaten Methoden ansehen.

Was meint ihr, übersehe ich da was? (Notiz an mich: bei #nossued das Gespräch fortsetzen!)

Sind Unit Tests wirtschaftlich untragbar?

Immer wieder höre ich die Aussagen “Unit Tests sind schön und gut, wir haben nur keine Zeit dafür.” Oder “Klar, man kann Tests machen, hauptsache, es nimmt nicht zu viel Zeit von der Arbeit weg” ??!!

 

Nur um Missverständnisse zu vermeiden: Tests macht man nicht zum Spaß oder aus Langeweile, die Tests stellen die Essenz, die abstraktester Form der Lösung dar!

 

Ich habe bis heute Schwierigkeiten damit, meinem Gegenüber zu erklären, dass er sich irrt. Ich WEIß es einfach aus Erfahrung, dass dies eine Milchmädchenrechnung ist. Keine ernsthafte Argumente gegen Tests würden bei einer tieferen Überprüfung standhalten. Die Pros übertreffen klar die Kontras. Aber wie soll ich etwas – für mich – Offensichtliches in Worte fassen? Wie soll ich etwas in ein paar Sätzen erklären, was ich in einem andauernden Prozess durch jeden NICHT (oder nicht richtig) geschriebenen Test gelernt habe? Oder durch jeden Aha-Effekt oder durch jeden stressfreien Release (kein Stress entsteht, wo kein Platz für Bugs existiert 😉 )

 

Unit Tests sind für den Open Mind “selbsterklärend”: wenn der Bug in einem ungetesteten Code steckt, dann wird das zu einer “blinden” Fehlersuche führen, die Tage dauern kann und auf jedem Fall Geld und Ruf kostet. Wie lange dauert es, den Fehler in einem getesteten Codebasis zu finden, wo die Eingrenzung innerhalb von Sekunden erfolgt? Wie oft kommt es überhaupt vor, dass dieses Problem entsteht? Für mich schaut die Rechnung so aus:


Zeit_für_fehlersuche = Unproduktive_Zeit;
f(Unproduktive_Zeit) = Verschwendetes_Geld;

0->Zeit_für_fehlersuche(getesteter_Code)----------------------->Zeit_für_fehlersuche(ungetesteter_Code)---.....oo

Ok, ich glaube, ihr kennt jetzt meinen Standpunkt 😉 Aber ich bin ja nicht die ultimative Maßstab dafür, wie man arbeiten sollte. Deshalb habe ich ein paar Artikel und Statistiken von klügeren Leuten zusammengesucht, bitte liest die auch.

 

Diese Infos habe ich bei stackoverflow gefunden:

Realizing quality improvement through test driven development: results and experiences of four industrial teams und hier eine Diskussion darüber.

The study and its results were published in a paper entitled Realizing quality improvement through test driven development: results and experiences of four industrial teams, by Nagappan and research colleagues E. Michael Maximilien of the IBM Almaden Research Center; Thirumalesh Bhat, principal software-development lead at Microsoft; and Laurie Williams of North Carolina State University. What the research team found was that the TDD teams produced code that was 60 to 90 percent better in terms of defect density than non-TDD teams. They also discovered that TDD teams took longer to complete their projects—15 to 35 percent longer.

“Over a development cycle of 12 months, 35 percent is another four months, which is huge,” Nagappan says. “However, the tradeoff is that you reduce post-release maintenance costs significantly, since code quality is so much better. Again, these are decisions that managers have to make—where should they take the hit? But now, they actually have quantified data for making those decisions.”

Es gab auch kleinere Experimente dazu wie z.B. Code Lab – TDD vs. Non-TDD

Over 3 iterations, average time taken to complete the kata without TDD was 28m 40s. Average time with TDD was 25m 27s. Without TDD, on average I made 5.7 passes (delivering into acceptance testing). With TDD, on average I made 1.3 passes (in two attempts, they passed first time, in one it took 2 passes.)

Now, this was a baby experiment, of course. And not exactly laboratory conditions. But I note a couple of interesting things, all the same.

Und weil ein Bild mehr als tausend Worte spricht: Die Kostenverteilung bei getesteten Code schaut ungefähr so aus
Testing Benefits

Tests schreiben ist einfach, der Ertrag ist riesig. Warum soll man also keine Tests schreiben? Sind wir wirklich unfehlbar, schreiben wir immer den perfekten Code? Seien wir mal ehrlich…Ich bin es sicher nicht und ihr auch nicht.

 

Und hier noch die obligatorische Buchempfehlung: The Art Of Unit Testing Das Buch ist wunderbar verständlich geschrieben mit echten Beispielen und guten Argumenten, warum und wie man testen soll.

Templating mit Razor aber ohne MVC

Ich habe unlängst nach einer Möglichkeit gesucht, Seiten schnell und effektiv zu generieren, sowohl für Web als auch für E-Mails als Background-Jobs. Ein Kollege hat mich auf @razorengine aufmerksam gemacht und diese Templating Engine scheint alles zu bieten, was ich gesucht habe.

A templating engine built upon Microsoft’s Razor parsing technology. The RazorEngine allows you to use Razor syntax to build robust templates. Currently we have integrated the vanilla Html + Code support, but we hope to support other markup languages in future.

Die Installation ist so einfach wie möglich:

//mit NuGet:
Install-Package RazorEngine

Man muss danach nur noch die RazorEngine.dll und System.Web.Razor.dll referenzieren und das war’s.

 

Das Generieren von Seiten kann entweder direkt oder mit vorkompilierten Templates erfolgen:

[Test]
public void PageCanBeParsed()
{
   const string template = "Hello @Model.Name! Welcome to Razor!";
   var result = Razor.Parse(template, new {Name = "World"});

   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}
//unterstützt anonyme Klassen
[Test]
public void PageCanBeParsedWithCompiledTemplate()
{
   const string template = "Hello @Model.Name! Welcome to Razor!";
   Razor.CompileWithAnonymous(template, "world");
   var result = Razor.Run(new {Name = "World"}, "world");

   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}
//oder konkrete Typen
[Test]
public void TemplateIsCompiledWithModelType()
{
   const string template = "Hello @Model.Name! Welcome to Razor!";
   var testModel = new TestModel{ Name = "World" };
   Razor.Compile( template, typeof(TestModel), "world2" );
   var result = Razor.Run(testModel, "world2");
   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}

public class TestModel { public string Name; }

Um ehrlich zu sein, ich habe noch keinen Grund gefunden, warum man nicht die vorkompilierte Variante nutzen soll. In diesem Fall wird das Template EIN MAL kompiliert und in Cache abgelegt. Ein Vergleichs- und Stresstest, in dem ich ein halbwegs komplexes Template 500-mal mit Razor.Parse bzw. mit Razor.Compile + Razor.Run aufgerufen habe, hat folgendes geliefert:

   Parse: 03:35.97 min
   Compile+Run: 00:00.63 min

Ich glaube, damit ist alles gesagt. Es sei denn, eine Zeile weniger gilt auch als Argument 🙄

 

RazorEngine unterstüzt fast alles, was Microsoft’s Razor in Views anbietet, wie zum Beispiel Helper-Methoden, Model-Definitionen direkt im Template oder partielle Views:

[Test]
public void EngineSupportsInlineHelper()
{
   const string template = @"@helper MyMethod(string name) {Hello @name}@MyMethod(Model.Name)! Welcome to Razor!";
   var testModel = new TestModel{ Name = "World" };
   var result = Razor.Parse(template, testModel);
   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}

[Test]
public void EngineSupportsInheritsCommand()
{
   const string template = @"@inherits RazorEngine.Templating.TemplateBase
@helper MyMethod(string name) {Hello @name}@MyMethod(Model.Name)! Welcome to Razor!";
   var testModel = new TestModel{ Name = "World" };
   Razor.Compile(template, typeof(TestModel),"testModel");
   var result = Razor.Run(testModel, "testModel");

   Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor!"));
}

[Test]
public void EngineSupportsSubtemplating()
{
    const string masterTemplate = "You are on www.yellow-brick-code.org!";
    Razor.Compile(masterTemplate, typeof(TestModel), "master");

    const string contentView = @"@inherits RazorEngine.Templating.TemplateBase
@helper MyMethod(string name) {Hello @name}@MyMethod(Model.Name)! Welcome to Razor!";
    var testModel = new TestModel{ Name = "World" };

    const string template = contentView + " @Include(\"master\")";

    Razor.Compile(template, typeof(TestModel), "testModelMaster");
    var result = Razor.Run(testModel, "testModelMaster");
    Assert.That(result, Is.EqualTo("Hello World! Welcome to Razor! You are on www.yellow-brick-code.org!"));
}

Und nun sind wir soweit, das Ganze im Web zu testen.

 

Die Engine funktioniert so, dass sie zur Laufzeit aus dem Template eine dll mit einem Zufallsnamen erstellt. Also wenn man die Engine im Web nutzen will, müssen noch ein paar Dinge getan werden:

  1. RazorEngine.Web.dll referenzieren
  2. Ein VirtualPathProvider in Global.asax.cs registrieren
    public class Global : System.Web.HttpApplication
    {
       protected void Application_Start(object sender, EventArgs e)
       {
          HostingEnvironment.RegisterVirtualPathProvider(new RazorVirtualPathProvider());
       }
    ...
    
  3. In Web.Config muss der BuildProvider registriert werden:
    <configuration>
    <configSections>
    <section name="razorEngine" type="RazorEngine.Configuration.RazorEngineConfigurationSection, RazorEngine" requirePermission="false" />
    </configSections>   <razorEngine factory="RazorEngine.Web.WebCompilerServiceFactory, RazorEngine.Web" /><system.web>
    <compilation debug="true" targetFramework="4.0">
    <buildProviders>
    <add extension=".csrzr" type="RazorEngine.Web.CSharp.CSharpRazorBuildProvider, RazorEngine.Web" />
    </buildProviders>
    </compilation>

So aufgerüstet kann man mit den Models und Templates beginnen. Man kann die Templates als html-Dateien speichern. Allerdings wenn man IntelliSense haben möchte, dann muss MVC3 auf den Rechner installiert und die Datei als cshtml gespeichert werden.

 

Das Beispiel hier ist über ein MasterHeader mit dem Anfang der Seite, ein MasterFooter mit dem Ende, beide eingebettet mit " @Include(\"master...\")" in Content. Die ganze Seite bekommt ein Objekt vom Typ Model zum Parsen. Alle Templates werden mit einem ITemplateFinder geladen.

...
ITemplateFinder templateFinder = new TemplateFinder(path);
Razor.Compile(templateFinder.GetTemplate("masterHeader.cshtml"), typeof(Model), "masterHeader");
Razor.Compile(templateFinder.GetTemplate("masterFooter.cshtml"), typeof(Model), "masterFooter");

var model = new Model
{
    PurchaseNo = "011313074142",
    Amount = "270.63",
    Date = "20110121"
};

var template = templateFinder.GetTemplate("Content.cshtml");
Razor.Compile(template, typeof(Model), "content");
var parsedTemplate = Razor.Run(model, "content");
context.Response.ContentType = "text/HTML";
context.Response.Write(parsedTemplate);

Bevor ihr was über Namen oder Verantwortlichkeiten was sagt: das Projekt wurde als Spike erstellt, und als solche hat seine Rolle  perfekt erfüllt 🙂 In Produktion würde ich das Kompilieren von statischen Templates in Application_Start verschieben und das ITemplateFinder sollte auf  jeden Fall Injected werden.

 

Ich muss mich bei den 2 Jungs, die das Projekt entwickeln, sehr bedanken, es war eine super Idee! Schaut es einfach an, die dll kann noch viel mehr.