Posts mit dem Label DRY werden angezeigt. Alle Posts anzeigen
Posts mit dem Label DRY werden angezeigt. Alle Posts anzeigen

Freitag, 21. August 2009

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Pecha Kucha "Inkrementelles Design" (Folien und Video)

Auf der JAX '09 im Frühling diesen Jahres habe ich für it-agile in einer Pecha Kucha-Session auf dem Agile Day einen Vortrag "Inkrementelles Design" gehalten. Host war Stefan Roock (Homepage, Twitter, Xing). Das sind die Folien:

View more documents from Bernd Schiffer.

Das alles ist schon ein wenig länger her, aber jetzt hat die Konferenz-Organisation das Video auf JAX TV veröffentlicht. Danke JAX-Team! Hier das Video:


Ich rede zwar viel zu schnell, trotzdem: Viel Spaß damit :-)

Apropos Pecha Kucha I: Auf den XP Days Germany 2009 im November diesen Jahres gibt es gleich 9 Pecha Kucha-Vorträge in drei Blöcken (siehe Programm). Das sind für mich die Perlen dieser Konferenz, also nicht verpassen!

Apropos Pecha Kucha II: Auf der WJAX '09, auch im November diesen Jahres, gibt es wieder eine Pecha Kucha-Session, die ich zusammen mit Martin Heider (Xing, Twitter) auf dem Agile Day halten werde. Thema: "Mein agiler Koffer - Reisetipps mit Pecha Kucha".

Links:

Freitag, 7. August 2009

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Beispiel für Refactoring von Beispiel für Closures

Vor ein paar Wochen hat mein Kollege Stefan Roock (Xing, Twitter) auf seinem Blog von einem "Beispiel für Closure" geschrieben. Wie so oft geht mir beim Lesen (fremden) Codes mein Das-ist-nicht-DRY-Alarm an. So auch bei Stefans Post. Es folgt ein Refactoring.

Stefan will die Laufzeit je Methode für eine Menge von Methoden messen. Die Menge hat er so bechrieben:


Ich konnte in seinem Post die Implementierung von tueA(), tueB() und tueC() nicht finden. Meine Annahme ist, dass sie ein wenig Laufzeit verbrauchen und etwas ausgeben:

Führt man nun die Menge an Methoden aus, so ist das die Ausgabe:

Stefan misst nun die Laufzeit der Methoden, und sein Output sieht (mit meiner Implementierung der Methoden) so aus:

Da ich ein Refactoring beabsichtige, darf sich das Verhalten des Codes, in diesem Falle der Output, nicht ändern.

Aber warum eigentlich Refaktorisieren? Mit Stefans Implementierung als solcher bin ich eigentlich zufrieden. Naja, da kann man noch ein Semikolon weglassen sowie eine Variable duration einführen und im GString hinterm println substituieren. Aber das ist Kleinkram, nebenbei machbar und hier kaum der Rede wert. Stefans Implementierung der Closure:

Nein, es geht mir um den Aufruf von Stefans Lösung (siehe Stefans komplette Lösung). Was muss ich tun, um die Laufzeit einer Menge von Methoden zu messen? Wo ist der Schalter, den ich einschalten muss? Stefan legt diesen Schalter um:

Seht Ihr die Redundanz? Stefan legt nicht einen Schalter um, sondern drei. Drei Mal muss er vor die Methoden timeLogged schreiben, inklusive geschweifte Klammer vor und nach der zu messenden Methode. Das ist nicht DRY.

DRY wäre es, eine Menge von Methoden einzeln zu messen. Das hier schwebt mir vor:

Und das geht. Die Closure-Magie, mit der das geht, hat Ted Naleid (Twitter, LinkedIn) in seinem Blogpost "Groovy closures make unit testing with “soft asserts” simple" ausführlich beschrieben. Hier ist meine Lösung:

Keep on DRYin', Stefan! "Bleib trocken, Stefan!" lag mir auf der Zunge, ging aber hier nun echt nicht... :-)

Links:

Freitag, 4. Januar 2008

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Kata in Groovy ge-DRY-t

Neulich schrieb mir ein it-agile-Kollege...

Von: Sanitz, Sebastian (Blog) (07.12.2007)
====================================
Hallo Bernd,
zur Zeit tue ich mir doch Groovy und bald auch Grails an. Unter anderem probiere ich mich an den CodeKatas vom pragmatischen David um die Sprache richtig kennen zulernen.

Jetzt habe ich da ein paar Fragen. Meine Lösung zum Wetterproblem ist angehängt, sie funktioniert mittlerweile, ist zum Vergleich mit meinen stümperhaften Anfängen "grooviger" bzw. kleiner gemacht. Das Ergebnis sieht so aus:

Tag: 14 Spread:  2 Min: 59 Max: 61
Tag: 15 Spread: 9 Min: 55 Max: 64
Tag: 13 Spread: 11 Min: 59 Max: 70
Tag: 24 Spread: 13 Min: 77 Max: 90
Tag: 12 Spread: 15 Min: 73 Max: 88
Tag: 2 Spread: 16 Min: 63 Max: 79
Tag: 7 Spread: 16 Min: 57 Max: 73
Tag: 28 Spread: 16 Min: 68 Max: 84
Tag: 4 Spread: 18 Min: 59 Max: 77
Tag: 25 Spread: 18 Min: 72 Max: 90
Tag: 27 Spread: 19 Min: 72 Max: 91
Tag: 6 Spread: 20 Min: 61 Max: 81
Tag: 10 Spread: 20 Min: 64 Max: 84
Tag: 16 Spread: 20 Min: 59 Max: 79
Tag: 19 Spread: 20 Min: 61 Max: 81
Tag: 8 Spread: 21 Min: 54 Max: 75
Tag: 3 Spread: 22 Min: 55 Max: 77
Tag: 23 Spread: 22 Min: 68 Max: 90
Tag: 29 Spread: 22 Min: 66 Max: 88
Tag: 5 Spread: 24 Min: 66 Max: 90
Tag: 17 Spread: 24 Min: 57 Max: 81
Tag: 22 Spread: 26 Min: 64 Max: 90
Tag: 20 Spread: 27 Min: 57 Max: 84
Tag: 21 Spread: 27 Min: 59 Max: 86
Tag: 1 Spread: 29 Min: 59 Max: 88
Tag: 18 Spread: 30 Min: 52 Max: 82
Tag: 11 Spread: 32 Min: 59 Max: 91
Tag: 26 Spread: 33 Min: 64 Max: 97
Tag: 30 Spread: 45 Min: 45 Max: 90
Tag: 9 Spread: 54 Min: 32 Max: 86

Hier meine Anfänger-Fragen:
  • Kann ich mit Groovy noch einfacher ein Integer aus dem String machen, als mit Integer.valueOf? (asType wirft natürlich eine Exception...)
  • Gibt es noch eine einfachere Lösung Exemplare von WeatherData mit den Daten zu erzeugen? Meine die Zeile:
    data {{ new WeatherData(day: day, min: min, max: max)
    (Anmerkung: da gehören statt geschweifte spitze Klammern hin, aber damit kommt Blogspot nicht klar :-( )
  • Benutze im Beispiel String.format() für die Ausgabe - gibt es da was groovigeres?
  • Obwohl ich seit über zehn Jahren mit regulären Ausdrücken (früher nur in Perl) hantiere, dauert es sehr lange bis ich eine funktionierende Lösung finde. Hast Du eine schnelle Herangehensweise? Und vielleicht ein anderes Muster für die Aufgabe?
  • Hast Du für die Aufgabe eine ganz andere Lösung?
Viele Grüße - Sebastian

PS
Glaube das liegt an Groovy, man schreibt sehr viel weniger Code und
textet in Emails mehr als sonst ;-)

Von: Schiffer, Bernd (07.12.2007, 23:09 h)
====================================
Hi Sebastian.

> Hier meine Anfänger-Fragen:
> * Kann ich mit Groovy noch einfacher ein Integer aus dem String machen,
> als mit Integer.valueOf? (asType wirft natürlich eine Exception...)
'2'.toInteger()
> * Gibt es noch eine einfachere Lösung Exemplare von WeatherData mit den
> Daten zu erzeugen? Meine die Zeile:
> data {{ new WeatherData(day: day, min: min, max: max)

Nope, das wäre die einfachste mit einem impliziten Konstruktor. Wenn Du einen expliziten Konstruktor benutzt hättest, dann könntest Du über Positional Parameter einfachere WeatherDatas erzeugen:
def data = [day, min, max] as WeatherData
oder
WeatherData data = [day, min, max]
Du brauchst allerdings für Skripte dieser Kürze gar keine Klassen und Objekte, wenn Du so tolle Dinge hast wie literale Maps - später mehr.

> * Benutze im Beispiel String.format() für die Ausgabe - gibt es da was
> groovigeres?

Jein, komme ich gleich zu.

> * Obwohl ich seit über zehn Jahren mit regulären Ausdrücken (früher nur
> in Perl) hantiere, dauert es sehr lange bis ich eine funktionierende
> Lösung finde. Hast Du eine schnelle Herangehensweise? Und vielleicht ein
> anderes Muster für die Aufgabe?

Mein Muster ist ähnlich wie Deines.
Deins:
~/\s+(\d+)\D+(\d+)\D+(\d+).*/
Meins:
def number = /\W+(\d+)/
/(?m)^${number * 3}.*/
Der Unterschied: Deins ist nicht DRY. Du wiederholst zweimal \D+(\d+). Du kannst statt Deines \d+ und Deines \s+ auch ein \W+ nehmen. Dann sind sogar dreimal \D+(\d+). Groovys Slashed Strings sind GStrings, d.h. Du kannst sie substituieren per $-Schreibweise. So kannst Du die Duplikation entfernen.

Mein (?m)^ am Anfang hat mit der Art zu tun, wie ich die Daten einlese.
Deins:
def pattern = ~/\s+(\d+)\D+(\d+)\D+(\d+).*/
new File("weather_orginal.dat").each{ line ->
matcher = pattern.matcher(line);
if (matcher.matches()) {
def day = matcher.group(1)
int max = Integer.valueOf(matcher.group(2))
int min = Integer.valueOf(matcher.group(3))
...
}}
Meins:
def number = /\W+(\d+)/
new File('weather.dat')
.text
.eachMatch(/(?m)^${number * 3}.*/){
all, tag, max, min ->
...
}
(Anmerkung: Wenn man das nicht im Blog schreibt, dann ist das ein Zweizeiler.) Dein Code kompiliert übrigens nicht mit Groovy-1.5.1 (da gibt's kein file.each mehr). Abgesehen davon: Was wir eigentlich gerne hätten wäre ein file.eachLineMatch auf File, aber da es das nicht gibt, nutze ich halt die lange Version file.text.eachMatch (was um ein Zeichen länger ist ;-) ). Und dann habe ich auch die Möglichkeit, sprechender die Gruppen zu benennen mit all, tag, max, min.

Der Grund für (?m)^: ?m ist der MULTILINE-Mode (siehe JavaDoc zu Pattern). Nichts Neues, wenn man JavaRegExp kennt. Über diesen Mode wird ^ zum Beginn einer Zeile, nicht mehr zum Beginn des Strings.

Reguläre Ausdrücke ertüftel ich mir testgetrieben. Sofern es "technische" Probleme gibt, weiche ich auf die GroovyConsole aus. Mit diesem Snippet bekommst Du ganz brauchbare Aussagen darüber, welche Group was gemacht hat:
def regexp = ...
def text = ...
text.eachMatch(regexp){ println it }
> * Hast Du für die Aufgabe eine ganz andere Lösung?

Nein - drei :-)

Schau Dir diese Version an:
def number = /\W+(\d+)/
def eintraege = []
new File('weather.dat')
.text
.eachMatch(/(?m)^${number * 3}.*/){
all, tag, max, min ->
def spread = max.toInteger() - min.toInteger()
eintraege << [tag, spread, min, max] // #1
}
eintraege
.sort{ it[1] } // #3
.each{
println String.format('Tag:%3s Spread:%3s Min:%3s Max:%3s',
*(it*.toString())) // #2
}
Bei #1 erzeuge ich Einträge als Liste von Listen. In den inneren Listen sind die Werte in der Reihenfolge, wie sie von String.format gebraucht werden. Das komische Konstrukt bei #2 - *(it*.toString())) - ruft erst auf allen Werten toString() auf (ich habe ja spread als int gespeichert) um dann das Ergebnis nochmal zu spreaden (*. ist der Dot-Spread-Operator und macht aus einer Liste [a, b] eine kommaseparierte Wertefolge a, b, die man etwa in anderen Listen oder zwecks Parameterübergabe benutzen kann; hat jetzt überhaupt nichts mit dem Spread aus der Kata zu tun!), damit die Parameter für String.format() erzeugt werden. Ich kann auch schreiben it*.toString().toArray() - ist aber länger.

Diese Lösung hatte ich zuerst gebaut. Und dann fiel mir auf, dass die nicht DRY ist: Wenn Du die Reihenfolge der Spalten ändern willst, also z.B. Spread vor Tag oder so, dann musst Du an zwei völlig unterschiedlichen Stellen schrauben, nämlich an der Liste bei #1 und beim beschreibenden String bei #2. Du mußt sogar noch bei #3 etwas ändern, wenn sich Spread nicht mehr an 2. Stelle befindet. Und Du hast das Padding auf 3 gesetzt - an vier Stellen. Fazit dieser Variante: Sehr fragil, unflexibel, redundant - naja, eben nicht DRY.

Dann bin ich auf das hier gekommen:
def number = /\W+(\d+)/
def eintraege = []
new File('weather.dat')
.text
.eachMatch(/(?m)^${number * 3}.*/){
all, tag, max, min ->
def spread = max.toInteger() - min.toInteger()
eintraege << [tag:tag, spread:spread, min:min, max:max] //#1
}
eintraege
.sort{ it.spread } // #3
.each{
println it.collect{ // #2
def label = format(it.key)
def wert = it.value.toString().padLeft(3)
"$label:$wert "
}.join()
}

def format(label) {
label[0].toUpperCase() + label[1..-1]
}
Bei #1 übergebe ich jetzt keine Liste mehr, sondern eine Map (da wird bereits eine LinkedHashMap erzeugt, damit die Einträge in der entsprechenden Reihenfolge bleiben). Da habe ich zwar immer noch Redundanz zwischen Key und Value, aber dazu später mehr. Durch die Map kann ich bei #3 jetzt explizit auf den Key verweisen. Ändere ich die Reihenfolge bei #1 ist davon #3 nicht betroffen. Zu #2: Das ist jetzt die groovyfizierte Version von String.format(). Statt %3s ist der Ausdruck it.value.toString().padLeft(3) gekommen - finde ich jetzt nicht so schön, wo man doch mit %3s so eine nette DSL hat. Besser finde ich da schon format(label), denn damit kann ich mir das Label aus dem Key der Map erzeugen (und wenn Du alles lowercase haben möchtest, dann reicht auch def label = it.key - aber Du hast ja mit der Formatierung angefangen ;-) ).

DRY ist das Ding aber jetzt: Wenn ich an #1 die Reihenfolge ändere, dann zieht #2 schön mit und nichts geht in die Brüche.

Frage mich, ob DRY und String.format() geht. Geht. Hier:
eintraege
.sort{ it.spread }
.each{ // #2
def format = it.keySet().collect{
format(it) + ':%3s'
}.join(' ')
def values = it.values()*.toString()
println String.format(format, *values)
}
Hier ist jetzt nur #2 zu sehen, der Rest blieb gleich. Dachte, dass wäre simpler, als die Variante zuvor, aber wenn ich mir das jetzt so anschaue, dann "gewinnt" mein zweiter Vorschlag. Beide sind DRY, aber die zweite Variante ist IMHO verständlicher als die erste. Ist jetzt aber nur mein Bauchgefühl.

> PS
> Glaube das liegt an Groovy, man schreibt sehr viel weniger Code und
> textet in Emails mehr als sonst ;-)

Echt? Ist mir gar nicht aufgefallen ;-) Mist, Kaffee alle, muß ins Bett!

Viele Grüße,
Bernd

====================================

Die Codesnippets gibt's hier als Nicht-DRY, DRY, und String.format-DRY.