2012-03-19
QCon London 2012 Retrospective/Post-mortem
This was my second visit to QCon London. My impression of the conference, based on these two visits, is that it tries to focus more on the bigger issues such as architecture and management, and less on coding details (although present). An example would be that a dedicated .net track wasn't present, despite the platform's popularity. øredev, which I've visited basically every year, does the opposite and focuses more on hands-on coding, but let this be about QCon.
I didn't really learn anything new at the conference, but was rather strengthened in what I've come to hold valuable regarding sustainable, high quality software development over the years (due to experience, theory, or both). I had the opportunity to chat with some of the speakers, since QCon is organized with social events to facilitate that, and was even further convinced by that.
Architecture
One big trend was splitting up larger applications and web sites in "systems of systems" and software architecture on both micro and macro levels. The latter being between smaller systems, hence more stable and important, while the inner workings of the smaller systems is of much less importance.
"What's simpler than one thing? Two!" meaning that small systems are much easier to modify than large.
Michael Brunton-Spall gave a talk named Architecting for Failure, where he described how The Guardian had re-architectured their whole web site from a huge monolith, into a lot of smaller systems. The only thing they had standardized on, was the JVM. Not only are they more robust and flexible nowadays, but innovation has increased a lot due to the change. Now they code, release, and test small bits in isolation.
So why is it then, that we're stuck with large monolithic applications, that are hard to modify? The main objectives change over time. First, we value things like ease of development, homogeneity, cohesion (one big sln-file to rule them all) and simplicity. Over time we come to value things like heterogeneity, decoupling, modularity and autonomy.
In one talk it was said that there's always a lot of management reasons to not change the architecture from the former to the latter, despite it being a good idea. I guess it's easier to identify the risks, than the opportunities that comes thereof.
Software Engineering
Since I'm a big fan of GOOS, and working in a need-driven fashion, it was great to listen to Steve Freeman et.al., describing a team delivering high quality software every second week for seven consequitive years. How? Using most practices from XP and making sure every feature has executable acceptance tests (or specification by example, as it's commonly called these days due to Gojko Adzic's book) and time isn't more important than getting the requirements right. Last, but not the least, the team made sure that new hires not only had high technical qualifications, but also shared the same mindset in order to fit in the culture.
Since they pair-program 100% of the code, turnover hasn't affected the speed of the team. Conclusion of XP and NDD according to them: cheap and reliable.
I think Rob Bowley summarized it great, with the quote "It’s the code, stupid". He went on with details on the subject, but this one stands out: "clean, maintainable, well tested code [is] more important than anything else you do". One can read his notes from the talk for more info.
Testing
Context is king. Great results seem to come from teams doing SBE and TDD. However, if you're a part of the tiny fraction of the software industry creating services like social networks and games, and the like, where the service is free or very cheap, and the impact of bugs isn't that big; you can let the end users test your software for you (logs, feedback links, forums, etc.) in order to produce the software faster and cheaper.
This also addresses the biggest problem these services have; what's the right product? When working with SBE in a traditional setting, the pre-requisit is, that you via specification workshops, domain experts, or something else can capture pretty much exactly what you're supposed to develop. If this simply isn't possible, you have to let your user-base guide you.
Management
In his talk Agile Adoption is Fool's Gold (and other stories from the coal face), Rob Bowley said what we've known for years: SCRUM == training wheels, while you're better off with Kanban, when you're mature enough (you can't go there directly).
DevOps? Yes! Dan North told a great story about how they had managed developers and operations personal to work together, and how beneficial that had been. So much low hanging fruit!
Jodi Moran took it one step further, and said "operations is dead" which was clarified with "When all engineers operate the system, the constraints and requirements of the operational environment will always be taken into account", and two other statements.
If you want to change anything, telling people what to do will most likely fail. Make them want to go where you want them to. How? Prove that it makes sense. Pair program. Action! "A little less conversation a little more action"
Zach Holman's talk How GitHub Works was very inspirational, despite that I've read all about it before. Zach is a very professional coder and speaker, who backs his story with real success. He basically tells the story of a programmer's heaven. No meetings. At. All. No managers (they distract). No estimates and deadlines. No time keeping. At. All. ("Hours are BS!", "Creating code is a crative endeavor!", "Embrace flexibility!", "Working long hours isn't a badge of honor!" and "Marathons drains you mentally == shit code!") Family-friendly. Everything is optimized for happiness :-) We want the best work, and get it when people are happy. (BTW, check out the RSAnimate about why that's a great idea.) 8000 tests in 250 seconds (slow test considered regression). Pair program scary code, and with new hires. Only hire really good people. The list goes on... and it feels good to be an enterprise customer of GitHub.
Everybody talking on the subject of management emphasized the importance of hiring only the best people. I wholeheartedly agree, but I've never hired someone with money from my own pocket, so I haven't really put my money where my mouth is on this one... If the theory and evidence on this subject shows great results, why isn't it done more often? I've even had a boss, who said "we don't need good developers here"... If you're interested in the subject, Why Zuckerberg Is (Almost) Right About Great Talent is a good article IMHO.
In another talk, Dan North talked about how every decision is a trade-off, and concluded that "there are no best-practices". Context is king.
London by Night
A big <3 to my colleagues, with whom I spent some really fun London nights with!
2011-08-22
Goodbye _, Hello R# Color Identifiers
I've also obeyed the larger part of the classic IDesign Coding Standard for who knows how long.
Therein, rule #67 states: "Do not use the this reference unless invoking another constructor from within a constructor." Usually, you'll use the same name for your fields and constructor arguments, why a this reference is needed if the fields aren't prefixed.
While re-reading the great book Clean Code authored by Uncle Bob today, I came across the following statement on page 24: "You […] don't need to prefix member variables […] anymore. […] you should be using an editing environment that highlights or colorizes members to make them distinct.".
The argument appealed to me, but how should I follow that advise? As a C# programmer, you're more or less forced to use Visual Studio. I knew, despite using ReSharper (R#) since its first version, that I've never come across field/member highlightning in the Fonts and Colors section.
After some googling, I finally found what I was looking for in R#; you need to enable Color Identifiers. Once I'd done that, the "ReSharper Field Identifier" (and more) show up among the display items in the aforementioned Fonts and Colors section.
Goodbye _, hello color identifiers!
Now I need to write a refactoring regex that refactors old, prefixed code…
UPDATE 2011-08-24: I've now refactored two C# solutions where I got rid of the prefixes by using VS' find and replace with regex and capture groups like so:
find what: private {.*} _{.*}
replace with: private \1 \2
and similar expressions.
2011-06-28
2011-05-02
Project Euler: 1 Down. 335 To Go!
I set up a Mercurial account at Google Code, and solved the first, very basic, problem tonight.
It was good fun, and now that I have the Visual Studio solution set up, I hope to solve more of these in my spare time.
Spoiler alert!
The C# implementation can be found here: http://code.google.com/p/project-euler-csharp/source/browse/ProjectEuler/Problem001_Find_the_sum_of_all_the_multiples_of_3_or_5_below_1000.cs
And the tests can be found here: http://code.google.com/p/project-euler-csharp/source/browse/ProjectEulerTests/Problem001_Find_the_sum_of_all_the_multiples_of_3_or_5_below_1000_Tests.cs
2011-03-16
Er tidens digitale teknologier en af årsagene til den øgede fascisme?
Kresten ser med rædsel at det sproglige niveau er på nedtur bland de studerende ved CBS og andre universiteter, hvor han underviser og har været censor de seneste ti år. Dette på trods af at han mener at de samtidig er mere flittige og fokuserede end tidligere generationer.
Personligt mangler jeg selvfølgelig Kresten's empiriske grundlag, men jeg har samme erfaringer (dog hovedsagelig i en svensk kontekst). Disse kommer fra at lytte til P3, unge i fjernsynet og i byen, snakke med min elleve år yngre søster og hendes venner, etcetera.
Lige præcis som Kresten, frygter jeg at denne nedtur leder til flere fordomme og at der bliver flere synes, og færre af logisk underbyggede argumenter. "Færre ord, mindre råderum, flere fordomme. Dét er, som bekendt, fascisme." Det er med andre ord en utrolig alvorlig sag, som samfundet må bekæmpe! Hvis man er i tvivl om sprogets indflydelse på tanken, kan man bare læse Morten Things anmeldelse af Victor Klemperers Det Tredje Riges sprog.
Kresten anerkender dannelsekrisen i familien og skolen, men ønsker derefter at udpege "tidens digitale teknologier" som en stor grund til den gældende nedtur mht. sprogbrug og logisk argumentation. Af alle ydelser man kan tilgå i mobilnettet/på internettet, fokuserer Kresten kun på SMS, Facebook og Twitter. Disse er jo alle lige designede for hurtige, korte beskeder. Facebook er jo desuden et stort socialt netværk hvor idéen er at man skal hygge sig, dele feriefotos, musikvideoer, og tilsvarende. Dette er selvfølgelig ikke arenaen for dyb, sokratisk argumentation eller diskussion! Kresten, hvad mener du egentlig? (Man kan jo for søren også finde Sprogpolitiet på Facebook!)
Hvad med blogrevolutionen hvor jeg selv læser mange blogs med meget god logisk opbygning og argumentation (især tekniske, men også politiske)? ... og hvad med alle fora på nettet hvor du ikke kommer ret langt ved kun at synes ting uden argumenter? Jeg mener at den digitale revolution vi er midt i, giver os mange flere muligheder for god, klassisk debat og udveksling af ideer, end nogensinde før. Lige denne post er jo et eksempel på det.
I stedet for at gå i denne faldgrube, synes jeg at Kresten skal adlyde opfordringen fra Lars Friis Farsøe, en af Krestens tidligere studenter, når han siger at Kresten burde "vende blikket indad" og fokusere på sin egen rolle i egenskab af, blandt andet, lektor på et universitet med den slags store problemer.
Jeg vil slutte med at citere Tanja Juul Christiansen som både blogger med klogskab og undrer sig over hvem der egentlig har aben: "At skyde skylden på digitale teknologier som sådan er en uholdbar generalisering, der fordrejer sagen, og forringer debatten om, hvordan vi kan gøre noget ved problemet."
2011-01-28
Conway's Game of Life Code Kata #2
It would be a really fun exercise to visualize the generations with WPF, Silverlight or something else. I'd also like to finish the attempts made at the retreat to implement this solution with JavaScript. As one of my pairing partners pointed out, visualization could be done by manipulating the DOM of an ordinary web page.
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using NUnit.Framework; using SharpTestsEx; namespace ConwaysGameOfLifeKatas.Generations.UnitTests { [Description("en.wikipedia.org/wiki/Conway's_Game_of_Life")] public class GenerationsUnitTests { [Test] public void new_generation_should_kill_alive_cell_with_fewer_than_two_live_neighbours() { var generations = new Generations(new Cell(1, 1), new Cell(0, 1)); generations.First().Contains(new Cell(1, 1)).Should().Be.False(); } [Test] public void new_generation_should_keep_alive_cell_with_two_live_neighbours_alive() { var generations = new Generations(new Cell(1, 1), new Cell(0, 1), new Cell(2, 1)); generations.First().Contains(new Cell(1, 1)).Should().Be.True(); } [Test] public void new_generation_should_keep_alive_cell_with_three_alive_neighbours_alive() { var generations = new Generations(new Cell(1, 1), new Cell(0, 1), new Cell(2, 1), new Cell(0, 0)); generations.First().Contains(new Cell(1, 1)).Should().Be.True(); } [Test] public void new_generation_should_kill_alive_cell_with_more_than_three_alive_neighbours() { var generations = new Generations(new Cell(0, 0), new Cell(1, 0), new Cell(2, 0), new Cell(0, 1), new Cell(1, 1)); generations.First().Contains(new Cell(1, 1)).Should().Be.False(); } [Test] public void new_generation_should_revive_dead_cell_with_three_alive_neighbours() { var generation = new Generations(new Cell(0, 0), new Cell(1, 0), new Cell(2, 0)); generation.First().Contains(new Cell(1, 1)).Should().Be.True(); } [TestCase(0,0,0,false)] [TestCase(1,0,0,true)] [TestCase(2,0,0,false)] [TestCase(0,1,0,false)] [TestCase(1,1,0,true)] [TestCase(2,1,0,false)] [TestCase(0,2,0,false)] [TestCase(1,2,0,true)] [TestCase(2,2,0,false)] [TestCase(0,0,1,false)] [TestCase(1,0,1,false)] [TestCase(2,0,1,false)] [TestCase(0,1,1,true)] [TestCase(1,1,1,true)] [TestCase(2,1,1,true)] [TestCase(0,2,1,false)] [TestCase(1,2,1,false)] [TestCase(2,2,1,false)] public void blinker_oscillator_should_oscillate_according_to_wikipedia(int x, int y, int generationIndex, bool isAlive) { var generations = new Generations(new Cell(0, 1), new Cell(1, 1), new Cell(2, 1)); generations[generationIndex].Contains(new Cell(x, y)).Should().Be.EqualTo(isAlive); } } public class Generations : IEnumerable<Generation> { private readonly Generation _seedGeneration; public Generations(params Cell[] seed) { _seedGeneration= new Generation(seed); } public IEnumerator<Generation> GetEnumerator() { return new GenerationEnumerator(_seedGeneration); } public Generation this[int index] { get { return this.ElementAt(index); } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public class GenerationEnumerator : IEnumerator<Generation> { public GenerationEnumerator(Generation generation) { Current = generation; } public void Dispose() {} public bool MoveNext() { Current = Current.Tick(); return true; } public void Reset() {} public Generation Current { get; private set; } object IEnumerator.Current { get { return Current; } } } } public class Generation { private readonly ISet<Cell> _aliveCells; public Generation(params Cell[] aliveCellsSeed) { _aliveCells = new HashSet<Cell>(aliveCellsSeed); } private IEnumerable<Cell> KeepAlives { get { return _aliveCells .Where(c => GetNumberOfAliveNeighboursOf(c) == 2 || GetNumberOfAliveNeighboursOf(c) == 3); } } private IEnumerable<Cell> Revives { get { return _aliveCells .SelectMany(GetDeadNeighboursOf) .Where(c => GetNumberOfAliveNeighboursOf(c) == 3); } } public Generation Tick() { return new Generation(KeepAlives.Union(Revives).ToArray()); } private IEnumerable<Cell> GetDeadNeighboursOf(Cell cell) { return GetNeighboursOf(cell).Where(c => !Contains(c)); } private static IEnumerable<Cell> GetNeighboursOf(Cell cell) { return Enumerable.Range(-1, 3) .SelectMany(x => Enumerable.Range(-1, 3) .Select(y => new Cell(cell.X + x, cell.Y + y))) .Except(cell); } private int GetNumberOfAliveNeighboursOf(Cell cell) { return GetNeighboursOf(cell).Count(Contains); } public bool Contains(Cell cell) { return _aliveCells.Contains(cell); } } public struct Cell : IEquatable<Cell> { private readonly int _x; private readonly int _y; public Cell(int x, int y) { _x = x; _y = y; } public int Y { get { return _y; } } public int X { get { return _x; } } public bool Equals(Cell other) { return other._x == _x && other._y == _y; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (obj.GetType() != typeof (Cell)) return false; return Equals((Cell) obj); } public override int GetHashCode() { unchecked { return (_x*397) ^ _y; } } } public static class Extensions { public static IEnumerable<T> Except<T>(this IEnumerable<T> @this, T element) { return @this.Except(new[] {element}); } } }
2011-01-27
Conway's Game of Life Code Kata
At my first attempt; I focused on the cell and tried to use a state machine. It didn't take too long before it felt cumbersome, so I stopped (although the state machine tests passed ;-)).
Then I tried to mimic "the real world" (oh, what a fallacy...) again, but this time I implemented a Grid<Coordinate> with an internal List<List<Coordinate>>. This was also a mistake... Imagine keeping a reference for all empty cells as well as making the grid infinite.
Third time's the charm, right? Why not just let a logical Grid instance keep a set of live cells? And why not remove all state changes, hence making both the Coordinate and the Grid types immutable? That way the Grid is asked to create a new immutable Grid for each generation.
By using these ideas, I quickly arrived at the solution below which I really like. It'll be very interesting to see what other solutions we'll come up with at the retreat.
using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; using SharpTestsEx; namespace ConwaysGameOfLifeKatas.GridAsListOfAliveCells.UnitTests { public class GridAsListOfAliveCellsTests { [Test] public void new_generation_should_kill_alive_cell_with_fewer_than_two_live_neighbours() { var grid = new Grid(new Coordinate(1, 1), new Coordinate(0, 1)); grid = grid.CreateNextGeneration(); grid.IsAlive(new Coordinate(1, 1)).Should().Be.False(); } [Test] public void new_generation_should_keep_alive_cell_with_two_live_neighbours_alive() { var grid = new Grid(new Coordinate(1, 1), new Coordinate(0, 1), new Coordinate(2, 1)); grid = grid.CreateNextGeneration(); grid.IsAlive(new Coordinate(1, 1)).Should().Be.True(); } [Test] public void new_generation_should_keep_alive_cell_with_three_alive_neighbours_alive() { var grid = new Grid(new Coordinate(1, 1), new Coordinate(0, 1), new Coordinate(2, 1), new Coordinate(0, 0)); grid = grid.CreateNextGeneration(); grid.IsAlive(new Coordinate(1, 1)).Should().Be.True(); } [Test] public void new_generation_should_kill_alive_cell_with_more_than_three_alive_neighbours() { var grid = new Grid(new Coordinate(0, 0), new Coordinate(1, 0), new Coordinate(2, 0), new Coordinate(0, 1), new Coordinate(1, 1)); grid = grid.CreateNextGeneration(); grid.IsAlive(new Coordinate(1, 1)).Should().Be.False(); } [Test] public void new_generation_should_revive_dead_cell_with_three_alive_neighbours() { var grid = new Grid(new Coordinate(0, 0), new Coordinate(1, 0), new Coordinate(2, 0)); grid = grid.CreateNextGeneration(); grid.IsAlive(new Coordinate(1, 1)).Should().Be.True(); } [Test] public void blinker_oscillator_should_oscillate_according_to_wikipedia() { var grid = new Grid(new Coordinate(0, 1), new Coordinate(1, 1), new Coordinate(2, 1)); grid = grid.CreateNextGeneration(); grid.IsAlive(new Coordinate(0, 0)).Should().Be.False(); grid.IsAlive(new Coordinate(1, 0)).Should().Be.True(); grid.IsAlive(new Coordinate(2, 0)).Should().Be.False(); grid.IsAlive(new Coordinate(0, 1)).Should().Be.False(); grid.IsAlive(new Coordinate(1, 1)).Should().Be.True(); grid.IsAlive(new Coordinate(2, 1)).Should().Be.False(); grid.IsAlive(new Coordinate(0, 2)).Should().Be.False(); grid.IsAlive(new Coordinate(1, 2)).Should().Be.True(); grid.IsAlive(new Coordinate(2, 2)).Should().Be.False(); } } public class Grid { private readonly ISet<Coordinate> _aliveCoordinates; public Grid(params Coordinate[] aliveCoordinatesSeed) { _aliveCoordinates = new HashSet<Coordinate>(aliveCoordinatesSeed); } public Grid CreateNextGeneration() { var keepAliveCoordinates = _aliveCoordinates .Where(c => GetNumberOfAliveNeighboursOf(c) == 2 || GetNumberOfAliveNeighboursOf(c) == 3); var reviveCoordinates = _aliveCoordinates .SelectMany(GetDeadNeighboursOf) .Where(c => GetNumberOfAliveNeighboursOf(c) == 3); return new Grid(keepAliveCoordinates.Union(reviveCoordinates).ToArray()); } private IEnumerable<Coordinate> GetDeadNeighboursOf(Coordinate coordinate) { return GetNeighboursOf(coordinate).Where(c => !IsAlive(c)); } private static IEnumerable<Coordinate> GetNeighboursOf(Coordinate coordinate) { return Enumerable.Range(-1, 3).SelectMany( x => Enumerable.Range(-1, 3).Select(y => new Coordinate(coordinate.X + x, coordinate.Y + y))) .Except(new []{coordinate}); } private int GetNumberOfAliveNeighboursOf(Coordinate coordinate) { return GetNeighboursOf(coordinate).Count(IsAlive); } public bool IsAlive(Coordinate coordinate) { return _aliveCoordinates.Contains(coordinate); } } public struct Coordinate : IEquatable<Coordinate> { private readonly int _x; private readonly int _y; public Coordinate(int x, int y) { _x = x; _y = y; } public int Y { get { return _y; } } public int X { get { return _x; } } public bool Equals(Coordinate other) { return other._x == _x && other._y == _y; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (obj.GetType() != typeof (Coordinate)) return false; return Equals((Coordinate) obj); } public override int GetHashCode() { unchecked { return (_x*397) ^ _y; } } } }
2010-12-11
Insourcing and responding to change
"Udviklingen er for nylig blevet insourcet igen efter nogle års outsourcing [...] Formålet med insourcingen af udviklingen er at sikre hurtigst mulig reaktion i forhold til både vores danske kædekunder og vores internationale ekspansion."
The ad can be viewed here: http://goo.gl/vFHpm
2010-11-24
The only people who like to hear that their code is bad, is people who's trying to get better
- I can show you how to be better.
- I don't want to be better. I want to keep sucking.
Let them keep sucking! The only people who like to hear that their code is bad, is people who's trying to get better. Those are the only people you wan't to associate with in the first place anyway.
[...] If you don't set yourself apart, you're going to work on mediocre projects. That'll screw you over. It kinda makes you stupid."
- Giles Bowkett in this.life() 1.0.4: Being Mean
2010-05-04
Coders at Work Speaking Words of Wisdom
It's a very interesting book, which I wholeheartedly recommend to anyone who cares about their profession as a software developer.
Below some words of wisdom from three of the interviewees:
Thompson: "My definition of fragile code is, suppose you want to add a feature — good code, there's one place where you add that feature and it fits; fragile code, you've got to touch ten places."
Siebel: "[…] people […] work long hours because we have this idea that we've got to get this product out the door and the way to do it is for everyone to work 80, 100 hours a week."
Thompson: "That generates burnout. […] external deadlines — generate stress."
Siebel: "[…] in terms of getting things done in the short term, does it work?"
Thompson: "Usually you're in a position where such a thing is continual."
Siebel: "Can you estimate how long it's going to take to write a given piece of code?"
Thompson: "[…] if you're doing it for production then usually there are other people involved and coordination — I can't estimate that."
– Ken Thompson, p. 467, 478 & 479
"[…] a design review double checks that the parts that he [the programmer] thought he had right he did have right and potentially give him some insight on the parts that he didn't. […] such an obvious good use of the senior talent doing the review."
– Bernie Cosell, p. 539
"[…] software required so much attention to detail. It filled that much of my brain to the exclusion of other stuff."
"I think it is always going to be true that a person who manages programmers should not expect it to be predictable."
– Donald Knuth, p. 572
2010-03-25
Merge (fka Upsert) Extension Method for IDictionary
[TestFixture] public class IDictionaryExtensionsTests { [Test] public void ShouldAddIfKeyDoesNotExists() { var dictionary = new Dictionary<string, int> { { "nøgle", 100 }, { "key", 200 } }; dictionary.Merge("nyckel", 300).Count.Should().Be.EqualTo(3); } [Test] public void ShouldUpdateIfKeyExists() { var dictionary = new Dictionary<string, int> { { "nøgle", 100 }, { "key", 200 } }; dictionary.Merge("nøgle", 400).Count.Should().Be.EqualTo(2); dictionary["nøgle"].Should().Be.EqualTo(400); } } /// <summary> /// Adds the key/value pair if the key doesn't exist, or updates the key with /// the supplied value if the key exists. /// <remarks> /// The name "Merge" is taken from SQL:2003 (f.k.a. "Upsert") /// <see cref="en.wikipedia.org/wiki/Merge_(SQL)"/> /// </remarks> /// </summary> public static class IDictionaryExtensions { public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> thiz, TKey key, TValue value) { if (thiz.ContainsKey(key)) { thiz.Remove(key); } thiz.Add(key, value); return thiz; } }
2010-02-12
YAK - Yet Another Keyboard Navigator for Chrome
I implemented it as a user/Greasemonkey script, which will pass as an extension in Chrome.
YAK web site: http://yak.nfshost.com/
YAK @ userscripts.org: http://userscripts.org/scripts/show/68609
2010-02-10
Poor Man's Custom Types with C# Using Aliases
What I've just started doing in order to achieve the same level of readability, is using aliases like so:
using Birthday = DateTime;
using PersonId = Int32;
before the class declaration, but after the namespace declaration (so that one doesn't have to fully qualify the type names).
For complex generic types, the readability increases even more IMHO:
using StrangeDictionary = IDictionary<int, KeyValuePair<string, decimal>>;
/Martin
2009-12-01
Oracle Horror Morning
I needed to write a simple function like so:
is_holiday(in_code in varchar2, in_year in integer, in_month in integer, in_day in integer)
that returns a boolean.
First, I wrote a few tests like these:
select is_holiday('xyz', 2006, 8, 1) from dual; -- false
select is_holiday('xyz', 2009, 11, 1) from dual; -- true
When I ran the tests after implementing the function I got the following messages:
SQL Error: ORA-06552: PL/SQL: Statement ignored ORA-06553: PLS-382: expression is of wrong type
I later learned that this is because booleans aren't allowed inside SQL like that. A sort of misguiding and not very helpful error message IMHO.
On my quest of finding a solution, I also tried named params like so:
select is_holiday(in_sm_center_code => 'xyz', in_year => 2006, in_month => 8, in_day => 1) from dual;
That gave me the horror:
ORA-00907: missing right parenthesis
What?!?
Once again, PL/SQL programs differ from the SQL statements like that, where the params can't be named, but must be in positional form.
Why not simply state that in the error dialog box?
Thanks to this blog: oraclequirks.blogspot.com, I got my head around these issues and solved the task at hand.
2009-11-24
More on TDD and BDD, but this time I link to interesting resources from ThoughtWorks
Acceptance Test Driven Development (ATDD)
Acceptance Testing vs. Unit Testing: A Developer’s Perspective
I think these two resources make a good theoretical starting point for the "Why ATDD?" question.
Don't just look at the slides as slide shows, since there's a lot of great stuff in the comments.
2009-11-21
2009-11-02
“Programming as you know it just died”
Just like every .Net class conceptually is a COM-object, Juval argued that with the challenges we as biz app architects/developers face today, every (.Net) class should be a (WCF) service.
The main reason is that basically all complex plumbing such as security, concurrency, logging, fault tolerance, and so forth is given to you for free.
The learning curve is however mammoth, and is best compared with going from procedural to object-oriented programming.
When interpreting all the signs from MS, Intel, and others, Juval means that it is clear that service-orientation is the next paradigm shift that will replace .Net.
The analogy is ATL, which made it it easy to follow the good practice of making your C++ class a COM one. Then .Net came along were this wasn’t framework-based. Now we have WCF making it possible to make every .Net class a service by utilizing a framework.
The financial figures also indicates this being the main focus of MS. Juval claims that the cost of WCF is some 150% of what went into the CLR.
All in all, this was an extremely mind-exercising day that left me with the feeling of seeing the world of programming with a new pair of eyes from now on.
I guess I’ll have to read Juval’s WCF book when the 3rd edition is out… ;-)
UPDATE Feb 11 2010:
1. There's a recent DotNetRocks show with Juval, where he explains all of this.
2. There's now a "rough cut" edition of the book available.
2009-10-21
"I often find that a nice design can come from just being really anal about getting rid of duplicated code"
Hear, hear!
2009-10-15
Same old tale (about code quality and technical dept)
Yesterday, we had yet another great Agile Skåne meeting at Green Lion Inn in Malmö.
One of the members is right now experiencing how much bad code can hurt you.
His new employer went to the lowest bidder without any quality control for many years, and have ended up with a total mess, prohibiting them to add features their competitors offer, and hence can’t improve their market share.
How come this serious mistake is made over and over again? After all it’s common knowledge that quality makes you fast and able to respond to change in the long run. (I know, we’re irrational human beings…)
I don’t know if it’s true, but I want it to be:
“Quality is the best business plan.” – Pixar’s John Lasseter
At least, you can’t blame Pixar for not being successful…
More on the subject:
Fowler recently wrote an interesting piece on technical dept: http://martinfowler.com/bliki/TechnicalDebtQuadrant.html
There’s a book on the subject of not acting according to what’s known to work:
”The Knowing-Doing Gap” (ISBN 1422163520), which I haven’t read.
2009-10-07
Decreased changeability when TDD:ing, and a possible solution
Yesterday, we had a great alt.net øresund meeting at BestBrains in København. Sune held a great presentation about TDD, its pros, and cons. Actually, despite being a TDD fan and user, Sune is very insightful and realizes that TDD isn’t a silver bullet. It could actually decrease your changeability if done wrong for example.
I’ve personally experienced some of the pains Sune described and that we discussed during the open space session that followed. Actually, previously the very same day Martin N Jensen and I tried to start attacking some of them by complementing our TDD efforts with a BDD way of defining higher level requirements in the form of stories and scenarios.
During the bicycle/train ride home, i thought some more about these issues. Only two days before I had to fix a lot of tests that utilized mocking/stubbing when an API changed that didn’t affect the system behavior in any way. That got me thinking – what the frak am I doing?!? No business value in that activity…
My not-IRL-tested idea goes something like this:
You have two sets of unit tests.
- Stable BDD tests that drives the SUT (System Under Test) by only looking at the highest level of state (i.e. the database), performs an action, and then verifies the resulting state/output on the highest possible level. These tests corresponds one-to-one with the requirements if using stories as requirements
- Instable “classic” TDD tests that drives the design of the inner workings (classes and their interactions, individual methods, etc.) of the SUT
My idea definitely requires UFD (UpFront Design) more according to the lean way of developing software as opposed to some agile start-immediately approaches.
If you start with a set of fairly stable stories, and implement them as BDD tests in one way or another (we’re using StoryQ at the moment), you consider these tests as stable meaning it should require some thinking before changing them.
The much more fragile TDD tests are considered something that could be thrown away (likely in parallel with writing new ones), or rewritten if still needed, when the inner workings are refactored (e.g. when replacing Castle Active Record with Frog.Net Sune, or in my case when removing specialized repositories in favor of one generic the other day).
I feel that if I have my stories implemented as executable acceptance tests, then I would really get
- true confidence when undertaking big refactorings
- tests that could actually be read and understood by peers, and possibly some other stakeholders
- regression test suite that verifies the SUT’s behavior, i.e. that the stakeholders get what’s agreed upon
As a potential benefit I also get extremely readable status reports of every story on e.g. the build server every time an ok commit is made. This is a huge benefit IMHO since it allows you to remove the possible need for a complex task tracking system but still gives your PM (and all others) complete real-time status, allowing you to work with a whiteboard and post-its as a kanban board.
2009-10-02
Programmatically configure Log4net with two rolling file appenders
public static class LogBootstrapper { public static void Bootstrap() { RootLogger.AddAppender(CreateRollingFileAppender(Level.All)); RootLogger.AddAppender(CreateRollingFileAppender(Level.Info)); RootLogger.Repository.Configured = true; } private static Logger RootLogger { get { return ((Hierarchy)LogManager.GetRepository()).Root; } } private static RollingFileAppender CreateRollingFileAppender(Level level) { var usingFileName = string.Format("logs\\MyProject_{0}-{1}-{2}_{3}.log", DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day, level.Name); var layout = new PatternLayout("[%level] %message%newline"); var rollingFileAppender = new RollingFileAppender { Layout = layout, AppendToFile = true, RollingStyle = RollingFileAppender.RollingMode.Date, File = usingFileName, ImmediateFlush = true, Threshold = level }; rollingFileAppender.ActivateOptions(); return rollingFileAppender; } }
2009-10-01
Pragmatic Thinking and Learning: Refactor Your Wetware
2009-09-24
Great Lean/Kanban Post
http://raibledesigns.com/rd/entry/lean_teams_doing_more_with
Trying out Sharp Tests Ex with NUnit 2.5
/*before*/ Assert.That(logHandlersCount, Is.EqualTo(1));
/*after*/ logHandlersCount.Should().Be.EqualTo(1);
Even better readability :-)
More on Sharp Tests Ex here: http://sharptestex.codeplex.com/Wiki/View.aspx?title=SyntaxMainPage
2009-09-23
2009-09-18
A NUnit Custom Constraint together with a String Extension Method
[Test]
public void WhenMyStringPropertyIsEmptyThenMyEntityMustBeInvalid()
{
var myEntity = new Entity { MyStringProperty = string.Empty };
Assert.That(myEntity, "MyStringProperty".IsIncludedInBrokenRules());
}
IsIncludedInBrokenRules() is an extension method that returns an instance of a class that inherits Constraint.
2009-09-08
SQLite - works on my machine ;-)
Recently I started using SQLite in-memory for unit testing NHibernate persistence logic.
When committing, my dear colleague with an older x86 machine got problems...
I reproduced the problem on my machine by setting the build target to x86 in VS08.
Just change to the 32-bit version of SQLite did the trick.
2009-09-02
Linq Group By
[Test]
public void CanAggregateIdAndValue()
{
var idValuePairs = new List<IdValuePair>
{
new IdValuePair(1, 1),
new IdValuePair(1, 2),
new IdValuePair(1, 3),
new IdValuePair(2, 1),
new IdValuePair(2, 2),
new IdValuePair(3, 1),
new IdValuePair(4, 1),
new IdValuePair(4, 2)
};
var expectedAggregatedIdValuePairs = new List<IdValuePair>
{
new IdValuePair(1, 6),
new IdValuePair(2, 3),
new IdValuePair(3, 1),
new IdValuePair(4, 3)
};
var aggregatedIdValuePairs = Aggregate(idValuePairs);
Assert.IsTrue(expectedAggregatedIdValuePairs.SequenceEqual(aggregatedIdValuePairs));
}
private static IEnumerable<IdValuePair> Aggregate(IEnumerable<IdValuePair> idValuePairs)
{
// imperative OO
/*var map = new Dictionary<int, int>();
foreach (var idValuePair in idValuePairs)
{
if (map.Keys.Contains(idValuePair.Key))
{
map[idValuePair.Key] += idValuePair.Value;
}
else
{
map[idValuePair.Key] = idValuePair.Value;
}
}
foreach (var aggregatedIdValuePair in map)
{
yield return aggregatedIdValuePair;
}*/
// declarative linq
return (from ivp in idValuePairs
group ivp by ivp.Key into aggregateGroup
select new IdValuePair(aggregateGroup.Key, aggregateGroup.Sum(x => x.Value)));
}
2009-08-20
Send to [blog] from GReader - had to try this
Since our last big launch, we've been thinking about ways to help our users better share, discover, and consume content in Reader. Today, I'm happy to announce several new features that we hope will further improve the way you use Reader.
Send to...
We've made it easier to share posts you like to Blogger, Twitter, Facebook, and more, with our new 'Send to' feature. (Incidentally, Blogger is celebrating its tenth birthday this month, and we're hoping our friends there will like this little birthday present.)
Just head over to the settings page, and enable the services you want to use. If your favorite service isn't listed (and you're feeling extra geeky), you can create your own 'Send to' link with a URL template.
To share an item on one of your sites, simply click the 'Send to' button and choose your service. If you're into keyboard shortcuts, 'shift-t' will do the same.
Feeds from people you follow
When we added following, we tried to make it easier to find and follow people who share similar interests. Now we've gone even further, and made it possible for you to subscribe directly to the blogs, photos, or Twitter updates that anyone you're following has included on their Google profile.
To quickly subscribe to these sites, click the 'From people you follow' tab on the 'Browse for stuff' page.
More control for mark all as read
We know people can be overwhelmed by too many unread items, and sometimes only want to see recent posts. The 'Mark all as read' button now has a menu that lets you choose to only mark items as read if they're older than your specified time frame. A tip of the hat to Nick Bradbury who pioneered this 'panic button' feature.
Finally, a few small tweaks in this release:
- When you expand an item in comment view, you now get the full set of actions, enabling you to share, like, and star items without leaving comment view.
- We added a 'Feeds' start-page option for the iPhone/Android/Pre mobile interface, so you can see a list of your subscriptions when you sign in.
- There is now an option to show notes when embedding your shared items on other pages as clips.
As always, if you have feedback, please head over to our help group, Twitter, or Get Satisfaction.
"2009-07-29
string.RemoveRegex(pattern)
[Test]
public void ShouldRemoveRegex()
{
const string stringWithListNumbers = "This is a #9: string with #10: list numbers.";
Assert.AreEqual("This is a string with list numbers.", stringWithListNumbers.RemoveRegex("#[0-9]*:"));
}
public static string RemoveRegex(this string arg, string pattern)
{
var tagRegex = new Regex(pattern);
return tagRegex.Replace(arg, string.Empty);
}
string.Remove(string toReplace)
/// <summary>
/// Alias for 'ReplaceWithEmpty'
/// </summary>
public static string Remove(this string arg, string toReplace)
{
return arg.ReplaceWithEmpty(toReplace);
}
2009-07-21
string.ReplaceWithEmpty(string toReplace)
public void ShouldReplaceWithEmpty()
{
const string arg = "This is a string";
Assert.AreEqual("This is a", arg.ReplaceWithEmpty(" string"));
}
public static string ReplaceWithEmpty(this string arg, string toReplace)
{
return arg.Replace(toReplace, string.Empty);
}
2009-06-30
Alt.Net Oresund Meeting at ITU 25th of June
I definitely learned a few new tricks that I'm eager to put to practice.
These include
- BDD style constraints (improved readability)
- Theories (specifications that run several combinations of input)
- Generic test fixtures (run the same test for chosen implementations of an interface)
From 2001 to sometime 2005, I however used NUnit, and with the new additions and a new employer, maybe we'll see each other again soon.
I haven't fully grasped the theory concept, but will look into it. This seems like a good place to start: Theories in Practice: Easy-to-Write Specifications that Catch Bugs
A new employer? After having three really good offers to consider, I finally made up my mind this weekend. I will start working with the very same Martin N Jensen, and the other guys at BankInvest this autumn. I'm very excited, and eager to produce great financial software with great people.
2009-06-16
Fired
[...] It means that the job isn't right for you.
I have been fired five times, and each time my career took a step forward. [...]"
- Paul Arden in It's Not How Good You Are, It's How Good You Want To Be
"Fired? It's the Best Thing That Can Happen To You.
[...] You hated your situation anyway.
You must begin again.
It's a wonderful opportunity for you.
Literally, they let you go."
- Paul Arden in Whatever You Think, Think the Opposite.
More about these books from a far more famous programmer than me can be read here: http://www.codinghorror.com/blog/archives/001177.html
2009-06-11
What's a "renaissance developer"?
At the JAOO conference 2006, the same thing was called "helstøbt udvikler".