Nothin' but .NET, Sydney 2009: Day 5

The fifth and final day of Nothin’ but .NET kicked off a bit later than normal (once the breakfast chats were over it was closer to 10am, but wound up around 1:45am (I think, my brain was well and truly fried by that point).

Tell, don’t ask

We talked for quite a while on this. The idea is to push responsibilities on to the class that owns the data related to that responsibility. The most common violation of this principle is an object acting on data it asked another object for. For example, instead of having if (game.genre == "Platform") { /* do something */ }), we should push the do something code into the game itself. That way we are telling the game to do something, rather than asking it for information and doing it ourselves. Violating tell don’t ask strips our objects of behaviour and leads to things like anaemic domain models.

Testing

I think I’ve rolled most of the testing stuff from the course into previous posts, but I’ll rehash some of the stuff that came up on day 5 about using tests to drive design.

Again I saw that the context/SetUp of the scenario/test seems to drive most of the design. The way the context is setup determines the responsibilities and API for the SUT’s collaborators and dependencies. By the time they become the SUT and their assertions are being written most of those decisions are made. In that case the only remaining decisions are the design of dependencies’ dependencies. :) It is this relationship that really lets us use the tests to drive the design from the top down.

The "because" block is the API generated from the assertion and the context of the higher level component’s tests. For example, if a test for our FrontController asserts that command.AssertWasCalled(c => c.Run());, then the "because" blocks of our tests around Command implementations become command.Run();. We get a similar result when we stub return values for dependencies while setting up the test context: we need to write scenarios around those calls.

Here’s a pseudo-code example. Say we have a test for an HttpRequestHandler class which asserts that frontController.AssertWasCalled(fc => fc.Process(request)). We can then write tests for the FrontController around that scenario:

When the FrontController is told to process a request:
  It should run the command able to handle this request:
    command.AssertWasCalled(c => c.Run());
  Because:
    sut.Process(request);
  Context:
    request = MockRepository.GenerateStub<Request>();
    command = MockRepository.GenerateStub<Command>();
    commandRegistry = MockRepository.GenerateStub<CommandRegistry>();

    commandRegistry.Stub(c => c.GetCommandFor(request)).Returns(command);
    sut = new FrontController(commandRegistry);

This scenario is telling us that when our FrontController is told to process a request, it needs to Run() a command. This is its sole responsibility. Note how well this responsibility is summarised by the scenario and test name, "When the FrontController is told to process a request, it should run the command able to handle this request". In our context/SetUp method, we’ve made a design decision to have a CommandRegistry responsible for mapping a request to the right command. We’ll then need to drive out the behaviour of what happens when the CommandRegistry.GetCommandForm(Request r) method is called.

I also noticed there a two different styles of TDD: incrementally driving the design vs. incrementally driving the implementation. JP’s “simplest thing that makes sense” approach (see Day 3 wrap up) tends to focus the developer on driving the design, whereas sometimes the “simplest thing that works” can lead me to procedural thinking (first it should do this, test, refactor, then it should do that, test, refactor…). I’m sure this is due to me misusing TDD in this manner, but I’m reasonably confident I’m not the only TDD novice to fall into this trap of driving implementation from the tests instead of design.

Other notes from Day 5

  • With a FrontController architecture Commands are similar to MVC actions.
  • Pipelines (Pipes and Filters pattern) can be a good way to get into event-driven and message-passing architectures.
  • Moving away from layered and onion architectures to component layers that are loosely affiliated. The direction of communication can be guided by the Dependency Inversion Principle and specific requirements (like having Query objects accessible from anywhere via their interface, but consumed only below the service layer).
  • Query object pattern (as used for NHibernate’s Criteria and DetachedCriteria). Related to the Specification pattern.
  • Collaborators for a test can be injected into the SUT, retrieved from another dependency, or retrieved from a Static Gateway (although be careful with that last one).
  • Don’t bother creating interfaces for DTOs.
  • The Service Layer is responsible for unwrapping DTOs packaged by higher layers. DTOs shouldn’t go lower down than that, they are strictly for communication between the Service Layer and higher levels.
  • Had a whirlwind tour of some Domain Driven Design (DDD) concepts.
  • Separating data updates using repositories from queries using Query Object pattern. Don’t just dump create/read/update/delete functions on a repository. Enforce Command Query Separation (CQS).
  • Command and Visitor design patterns are really under-used and under-appreciated.
  • Introducing Pure Fabrications over primitives to help encapsulate behaviour and make writing aggregates and entities easier. An example, instead of using an IDictionary<Product,int> to map products to quantities in a shopping cart, introduce a CartItem fabrication that can be used to track both and encapsulate useful behaviour for the Cart aggregate.
  • Concept of Shu Ha Ri for describing the stages of learning.
  • if and for (loops and conditions) are a tad evil. Getting rid of them makes code nice. :)
  • Problem decomposition is more important than patterns.
  • Always look for the higher level of abstraction. Step back from the details of the problem, and tackle the abstraction instead.

Comments