Last Thursday, I’ve attended an open forum organised by ALT.NET France user group, about the Software Craftsmanship Community and the “Fier d’être développeur” (“Proud to be dev”) groups, in order to compare their intents and try to explain why they exist and what they try to achieve.
One of the questions that was raised during the evening was the following : Is the “Software Craftsmanship” movement elitist or is it a way to democratize good practices ? After a great deal of discussion, we agreed on the fact that it can be seen the both ways. Let’s face it : we meet in the evening to talk about programming… We’re part of the “10 percent alpha-geeks”. And yes, there is some kind of elitism about it.
On one hand, to someone that comes for the first time, all the practices, languages and tools we talk about might seem too complex, and frighten him (or her). On the other hand, realizing that there are so many things that we don’t know is the first step to getting better, isn’t it ? To my mind, this whole question of “raising the bar” instead of letting the average level go low, is not specific to programming but is true for any kind of teaching/learning topic. What makes it so important in programming is that we can’t stop learning during our career, if we want to stay up-to-date…
But if our goal is to “raise the bar”, how do we do it on a day-to-day basis ? If we want to show our colleagues some tricks, so that they ask for more, not that they run away…
Let’s illustrate that with an example from real life. A couple days ago I wanted extract from several data series the dates that were common to all these series. This might seem quite simple, but there are numerous ways to do that, and I find it a good case study of how to balance readability and (perceived) complexity…
<YAK-SHAVING>My Visual Studio 2012 RC has expired… I’m switching to MonoDevelop in order to write my sample code…</YAK-SHAVING>
Let’s consider several collections of points, where a point is simply a date and a decimal value :
public class Point { public DateTime Date { get; set; } public decimal Value { get; set; } }
And now imagine that I have a collection of series of points, represented as such :
IEnumerable<IEnumerable<Point>> series
Now the point is to get the dates that are common to all these series. With a straight implementation, this could go this way :
[Test()] public void TestStraightLoops() { IEnumerable<IEnumerable<Point>> series = DataSeries.GetSeries(2013); List<DateTime> commonDates = new List<DateTime> (); bool first = true; foreach (var serie in series) { if(first) { foreach(var point in serie) { commonDates.Add(point.Date); } } else { List<DateTime> datesToRemove = new List<DateTime>(); foreach(var commonDate in commonDates) { bool found = false; foreach(var point in serie) { if(commonDate == point.Date) { found = true; break; } } if(!found) { datesToRemove.Add(commonDate); } } foreach(var dateToRemove in datesToRemove) { commonDates.Remove(dateToRemove); } } first = false; } Assert.AreEqual(76, commonDates.Count); }
OK, this works. How long does it take to read it and understand it ? What if, instead, I chose to use my basic knowledge of LINQ ? I’ll skip a few steps, but what if came up with this :
[Test()] public void TestForEachIntersect () { IEnumerable<IEnumerable<Point>> series = DataSeries.GetSeries(2013); IEnumerable<DateTime> commonDates = null; foreach (var serie in series) { var dates = serie.Select(p => p.Date); commonDates = (commonDates == null) ? dates : commonDates.Intersect(dates); } Assert.AreEqual(76, commonDates.Count()); }
This is obviously shorter. Is it more or less difficult to read and maintain ? Should I stop here ? What if wrote the following, using Aggregate to do the same thing as above, but inside an extension method ?
public static IEnumerable<TResult> IntersectMany<T, TResult> (this IEnumerable<IEnumerable<T>> source, Func<T, TResult> selector) { return source.Aggregate( (IEnumerable<TResult>)null, (a, s) => a == null ? s.Select(selector) : a.Intersect(s.Select(selector))); }
Obviously this method is less readable… But the consumer code is now the following :
[Test()] public void TestIntersectMany() { IEnumerable<IEnumerable<Point>> series = DataSeries.GetSeries(2013); var commonDates = series .IntersectMany(p => p.Date); Assert.AreEqual(76, commonDates.Count()); }
And… I kind of like the expressiveness of it. So where must we stop ? To my mind, we must stop when it can be maintained. Aren’t we pragmatic programmers, after all ?
I think that Software Craftsmanship is very well summed-up in Sandro Mancuso’s book title : “Software Craftsmanship : Professionalism, Pragmatism, Pride”. Those words are in the right order. Pride is part of it, but it come only third.