vendredi 23 octobre 2015

Tests uncover Design Smells

TLDR; Tests, even written after the code, help tremendously in pointing to production code that could

use an improved design. If writing a tests hurts, then it is often the designers fault, not the tests.

Tests, wether written before/during (TDD) or after pushes us to choose a design that will make both tests and code relatively painless. That easiness is of course bounded by the design options we already know of. Many times in life my tests have hurt, without me knowing what to do about it. But then weeks or months later I discover a new way of designing my code that would have made it better. My default assumption whenever I can’t find a way to make my tests painless, is that I have something to learn about software design. I believe this is a state of mind that is very useful if you want to have better code on your project.

Here’s a stripped down typical example of a piece of untested code. I came across this recently and I’ll show how the tests point me to a weakness in the design, and one way to deal with it. First let's have a look at the tests to know what the code does.

$ mocha test/LangBuilder.spec.js 

   ✓ is the language found in the pdf file
   ✓ can be explicitly overridden
   ✓ is french by default
   ✓ is a collection of pages in the pdf file
      every face
   ✓ has the width and height of the first page
   ✓ has a unique id

But its not the whole story, sure it returns a lang, but it also submits another piece of data to a message queue. Admittedly that is a violation of the Single Responsibility Principle, but I’m going to concentrate on a more subtle design smell. So here are the interesting parts of the code, in particularly we'll be interested in the _sendOffSplitJob function.

BTWYou can find a link to the full piece of code at the bottom of this post under "References"

So what could the tests for the sendOff part look like? It’d be something of an ugly stub/mock-hell that is one of the main reasons many people shy away from unit testing. Just for the record I show one of the test, but please don’t spend energy on understanding it. It's just proof of uglyness in case you didn't trust me.

Why is this test difficult to write? Well we deal with a third party library that wasn’t designed for easy mocking and certainly not designed to optimise one specific use case of our application (that’s not saying the library is ill-designed), so from the standpoint of LangBuilder we can simplify the design of the message queue library.

Imagine we had a function: submitJob(splitJobData) that took care of the queue naming retries and eventual send-off. A side-note on this example; javascript has first-class functions so I’m using function-composition by passing a function to the constructor of LangBuilder instead of passing an object as in object-composition. Here's that function

And the refactored LangBuilder

Then we could simply assert that LangBuilder calls it with the correct arguments, like so: 

In the process LangBuilder became easier to use, it doesn't have to bother with configuration related to the library. 

Looking at the submitJob function it became easier to use too, we didn’t just move the complexity of the queue management to a different class, we simplified it's desing with respect to our needs. In it’s original design it was mixing non changing parameters like the queue name and the number of attempts with the changing parameter splitJobData. Now we can pass those parameters at boot time creating a function that takes only the constantly changing parameter. And that simple function can be passed to any function or object that needs it.

Then we can test the submitJob function in integration with a redis server. I always put this kind of test in a different suite for practical reasons, more on that in a follow-up post. The basic idea of such a test is isolate the smallest sensible amount of code into a first-class object or function. Then test that unit extensively with the real thing.

Writing that test was a pain. Acknowledging it as a possible design smell allowed us to improve design as LangBuilder now respects a bit more the Single Responsibility Principle. Both the tests of LangBuilder and jobQueue are easy to write. jobQueue is now a first-class function that is easy to reuse elsewhere in the application.

Listening to tests allows us to spot where the tests and the production code could use an improved design. Of course tests help us spot problems and protect us during refactoring, but they don't find good alternative design. That's up to us developers to find and chose.

lundi 23 mars 2015

Usable Software Design - a federating concept

Design is not just what it looks like and feels like. Design is how it works - Steve Jobs
Alexandru Bolboaca applies ideas from general usability design to code :
Software design is the only type of design that seems to be userless. After all, the end-user has no idea how the software works and doesn’t care. All they do care about is to work ...Software design is not userless. The user is the developer that will have to change the code after you do....

A common goal for the team

This struck me as a very powerful way of framing discussions with software development teams. I get to work with development teams for a few months to help them adopt XP practices that are useful in their context. Typically the hard part for teams is learning TDD, object oriented programming*, SOLID principles, Simple Design etc. In my early days as a XP facilitator I often met resistance and indeed many of my friends in the Software Craftsmanship community still meet this kind of resistance, often to a point where their progress has come to a total standstill. I hope I’ll find energy to explain in detail how _I_ am changing in order for colleagues to find less need to resist my ideas, but for now lets talk about how the concept of "Usable Software Design” can be used to guide change in a team. 

First of all lets see what "Usable Software Design” might mean. Alexandru states three things : Clarity, Consistency and Minimize the unexpected. I decided to try this idea with a new team I just started working with and I reframed the ideas to “We all appreciate workable code and design … it'll have a positive effect on our performance... So what is it that makes the code easily workable?" Here's what I suggested

A team agreement to try

Usable Software Design means to us that

  1. It is written for developers to read
  2. It is easy find where to modify the code
  3. Any modification has a minimal ripple-effect 
  4. It is easy AND fast to validate that we did the right thing
  5. We don't have to do similar modifications in several places

NoDeveloperCanDisagree™! Indeed this time everyone nodded and listened intently. How we get there doesn’t matter to me as long as we get pretty close. And this is extremely important, we’re ONLY talking about desirable outcomes, not how to get there. In particular if we want to keep the federating force we have to welcome ANY SOLUTION that brings those desirable outcomes. 

Now let me dig in to 4. because it has some unexpected consequences. I currently know of only a few ways to get this. In no particular order a) being able to launch and interact with the application in short cycles, b) automated tests at various levels, c) manual testing and shipping shortly after development. Look ma’ it’s not only automated testing that requires isolation from non testable dependencies, a) requires it too, even c) to some extent. Reasoning like this even developers who don’t care much about automated testing want pretty much the same thing as someone like me! It has a federating effect.

5. is going to make us limit duplication, even across process boundaries, to a bearable level. 3 is likely to bring SOLID principles. 4 is likely to favour TDD, and TDD will help 2, 3 and 5. 


As a team we agreed to try this, and because it doesn’t enforce any specific solution as TDD, DDD or SOLID it means we can evaluate any development with pretty much the same yardstick. All that matters to me is that we nail 1-5 because then it’ll be pretty nice to code in that team regardless what tools we use.

And now I'm off to Vienna to train in doing Usable Software Design with a few hardcore Craftsmen!
*Yes even Object Oriented programming is not mastered to the extent that is needed by far, at least in iterative development.