Tuesday, 14 July 2009

Collapsing foreach Loops using LINQ

Much of the business logic we have traditionally written has been buried in a heap of nested foreach loops and if statements. All this additional cermoney obfiscates the meaning of the code making finding what a method does more difficult.

One the key strength of LINQ, as I see it, is in its ability to put your application's business logic center stage. By seperating your query from your command within each method, the clarity of those methods is greatly increased.

Time for a example I think. Take the following example of traditional business logic:

public void FulfillOrders()
{
foreach(Customer customers in Customers)
{
if(customer.CanPlaceOrders())
{
foreach(Order order in customer.Orders)
{
if(!order.IsShipped()
{
foreach(OrderLine line in order.Lines)
{
if(line.IsInStock())
{
customer.Ship(order, line);
}
else
{
customer.BackOrder(order, line);
}
}
}
}
}
}
}

OK, I admit, not a great bit of DDD - but I'll live with that; its not the point of this blog. However this code, is fairly difficult to read and its going to quickly become unmaintainable as more business logic is heaped upon it. LINQ allows us to collapse down those foreach loops and if statements into a query thereby achieving command query seperation within the method.

public void FulfillOrders()
{
var allOrders = from customer in Customers
from order in cutomer.Orders
from line in order.Lines
where customer.CanPlaceOrders()
&& !order.IsShipped
select new {Customer = customer,
Order = order,
Line = line};

var shippable = from item in allOrders
where item.IsInStock()
select item;

var outOfStock = from item in allOrders
where !item.IsInStock()
select item;

foreach(var item in shippable)
{
item.Customer.Ship(item.Order, item.Line);
}

foreach(var item in outOfStock)
{
item.Customer.BackOrder(item.Order, item.Line);
}
}

Note, how in the allOrders query I am stacking up the "from" statements with no joins. This tells LINQ to do a cross join. But wouldn't this mean that every customer is joined to every order and every order line regardless of whether the customer owned the orders. Well, no; in the object model the Orders belong to the Customer and the OrderLines belong to the Orders. In this situation LINQ is smart enough to figure out which constraints should apply.

In this new "LINQed-up" version we have clearly defined the queries (at the top) from the commands (at the bottom). This leads to clarity in code readability which in-turn leads to an increase in maintainability. Getting to grips with the syntax of LINQ can take some time, but the increase in code maintainability is well worth the effort.

Thursday, 14 May 2009

Redundant Layers

There been an interesting difference of opinion on the blogs of Ayende and Greg Young regarding the usage of the Repository pattern. Whilst some great points were made on both sides, I tend to agree with Ayende's view that advances in tooling can render patterns, that we have become accustomed to using, redundant. In this case he argues that NHibernate has left little reason to implement Repository any more.

We segregate our applications into separate layers to help us manage the complexity of differing concerns. But we should accept that as new layers are added to an application, overall complexity of that application increases. If I was able to succinctly express my entire application in a single text file, without recourse to layers, I would. That is not (yet) possible so I use a layered approach.

As advances are made in tooling we need to constantly re-evaluate our use of layers and patterns to see it they have been superseded by technological advances. Take MVC as an example; had I been a Smalltalk programmer fifteen years ago I would have needed to create a controller for every widget. As tooling has advanced this has become unnecessary and we now have a different kind of MVC.

All too often I see applications where the architecture has been treated like a check list of layers. DTOs - check, domain - check, web services - check, repository - check; you get the idea. For me there has to be a very good reason for creating a new layer in my application. Do I really need that abstraction? If not, I don't feel compelled to include it. 90% of the applications I work with use a domain model. But that doesn't mean I always include that layer. A simple CRUD application with a few validation rules probably only requires a DAL and few DTOs and a presentation layer, for example.

We need to constantly question ourselves and the decisions we make about architecture, and should only implement layers where we feel there is a need; not because that's the way we've always done so.

Wednesday, 22 April 2009

Unity: Accessing the Creating Container

Here's something I learned today about Microsoft's Unity.

Often when an object is created through a container, you want to be able to access that container so that you can perform further resolutions. In the past my solution to this was to create a static class that held onto a single global instance of the container, essentially a Service Locator. The Service Locator could then be used by any class to access the same container.

I have never been 100% happy with this because, as we all know, static global data is a *bad* thing which introduces dependencies which are hard to test. Today I discovered that when a container resolves a class which has a parameter of type IUnityContainer, the container will pass itself to that parameter. This means that the container can be passed down the chain of constructors without resorting to any global static nonsense.

Here is my test:


[Test]
public void ContainerPassesItselfToObjectsItCreates()
{
var container = new UnityContainer();
container.RegisterType<ClassCreatedByIoC>();

var objectCreatedByIoC =
container.Resolve<ClassCreatedByIoC>();
Assert.That(objectCreatedByIoC.Container,
Is.SameAs(container));
}

public class ClassCreatedByIoC
{
public ClassCreatedByIoC(IUnityContainer container)
{
Container = container;
}

public IUnityContainer Container { get; private set; }
}



I assume this works with injection methods and properties as well, but I haven't tested it.

Thursday, 19 February 2009

Edward Woodward

Yesterday, I create a class called TheEqualizer. This has made me way more happy than should be the case.

Now I am worried that I more of geek than I thought I was.

Sunday, 15 February 2009

Scrum is Only a Recipe

I like Scrum. It offers a great template for getting started with agile project management. But the important word in the previous sentence is template. I see too many teams worrying if they are doing scrum properly. That's not the point, doing Scrum properly is not the point. The point is trying to make a process that suits your current situation that is as agile as possible. Scrum is just a useful first stepping stone in getting there.

The first time you cooked a recipe from a cookery book, you would follow it blindly. What else could you do? When you eat the food it may be too bland, so you next time you cook it you add a little salt; or it may be dry, so you adjust the cooking time. You use your previous experience to improve the outcome.

Agile is like that: execute, reflect, improve, repeat. Sprint planning not working for you? Find another way to do it. Users cannot go three weeks without changing the scope? Shorten the iteration length. Don't feel constrained by the scrum process, remember it's only a template, change the bits that aren't working for you.

Don't put up with crap food, improve your recipe.

Thursday, 29 January 2009

The x => Factor

I may be swimming against the tide here, but I don't understand why it has become an idiom of C# to use x as the parameter name in lambda expressions, as in:
var matching = list.ForEach(x => x.Version > 1)
You wouldn't expect to see
foreach(var x in list)
{
if(x.Version > 1)
{
yield return x;
}
}
I suspect it has a lot to with conciseness. Whilst I highly value conciseness, I value readability over conciseness.

Sunday, 25 January 2009

News Feeds Changed to Feedburner

I have changed the news feeds for this blog to use FeedBurner. You may want to update your links.