A single responsibility, please

| 0 Comments
Having got the CMessageProcessor under test in the last posting. I added a few easy tests for the object and then I came to another hard to add test. The reason that it was hard to add was that the object is doing a little too much.

The easy to add tests were for the object's Initialise() and Shutdown() functions; though whilst adding the tests I made a note to see if I could do away with the functions in the near future. I personally abhor objects with two-stage construction (ctor and then init method), but I can understand why I wrote it this way as the object does things that have thread affinity and the object itself is created before the thread that it has affinity with is...

The complicated test was for the Process() function. This function is the 'main()' of the business logic processing of the server. It looked like this:

void CMessageProcessor::ProcessMessage(
   CAsyncSocket *pSocket,
   IBuffer *pBuffer)
{
   // lots of business logic dealing with message processing omitted...

if (pBuffer) { pSocket->Write(pBuffer); } else { pSocket->Shutdown(); } }

The function gets given a socket that represents the connection that it's processing a message for and a buffer which contains a complete, distinct, ISO8583 message. It does whatever it needs to do and, if it needs to, creates a response message in the same buffer and then sends it out via the socket. In very rare situations, such as being given a message that it doesn't understand, it simply closes down the socket connection.

Unfortunately the ProcessMessage() function is violating the Single Responsibility Principle (see also Agile Software Development (2nd edition) by Robert Martin). The CMessageProcessor class should, probably, just process messages and produce responses. By adding in the additional responsibility of sending those responses back to the client we introduce additional complexity into the class and make it harder to test or reuse. The worker thread class that calls methods on the message processor already deals mostly with sockets and buffers and so the work that the message processor does that relates to sockets is more its responsibility. By moving that code out to the calling class the CMessageProcessor class becomes simpler and more focused and the calling class becomes completely responsible for all things socket related.

The code is easily adjusted to something like this:<.p>

bool CMessageProcessor::ProcessMessage(
   IBuffer *pBuffer)
{
   // lots of business logic dealing with message processing omitted...

return ok; }

The calling code is then adjusted to send the response or shut down the connection depending on the result returned by the message processing function. Our message processor is now devoted to one thing, processing messages, rather than two, processing and sending messages. This has made it easier to test the ProcessMessage() function as we now don't have to pass a socket, or a mocked up socket, into the function. We simply pass in a message and we get a message back in the same buffer. Of course we could have simply changed the concrete socket to an instance of IAsyncSocket and passed in a mock socket to use in testing, but doing so wouldn't have improved the design...

Our first test for this function now looks a bit like this:

void CMessageProcessorTest::TestProcessMessageX()
{
   CMockErrorMessageLog errorLog;

   CMockDatabaseConnectionFactory connectionFactory;

   CMessageProcessor processor(errorLog, connectionFactory);

   processor.Initialise();

   connectionFactory.CheckResult(_T("|CreateConnection|"));

   CMock0200Message message;

   THROW_ON_FAILURE(true == processor.ProcessMessage(&message));

   processor.Shutdown();

   connectionFactory.CheckResult(_T("|DestroyConnection|"));

   errorLog.CheckNoResults();
}

You can ignore the CheckResult() stuff, it's just simple C++ mock objects using text logging; effectively declaring the expected function calls on the mocks.

The CMock0200Message needs to implement the IBuffer interface and allow me to build ISO8583 0200 messages in ways that will allow me to test the message processor. The problem is, IBuffer is a big interface and we only actually use a small number of the functions inside our message processor.

IBuffer came about whilst some earlier refactoring of the socket server framework. Unfortunately when the interface was originally created it was created to allow for multiple different types of buffer to be used interchangeably; which it does. The thing is, it too is doing too much; the interface has two distinct uses. The two responsibilities of this class are 1) memory buffer management and 2) socket transmission bookkeeping data management. The split between the multiple responsibilities can be clearly seen when you look at a client of the interface that only uses one half of the interface. Sure the interface has many more clients than the CMessageProcessor class but looking at them shows that they too either use one side or the other, with a few using mostly one side and a method or two from the other...

Perhaps it's because I have my SRP goggles on, but it looks to me that the interface should be split into two; one to manage the memory buffer and one to manage the socket transmission bookkeeping stuff. For now, for ease of performing this transformation I'll simply pull all of the memory buffer stuff out into a new interface and derive the old one from it. ProcessMessage() can now take an instance of the new interface and CMock0200Message becomes much easier to implement.

After implementing a bare bones version of the mock 0200 message class and getting the above test to fail I realise that it's time to change my focus a little. The ProcessMessage() test fails because the existing business logic that remains from the server shell that we started with knows how to spot an 0200 message and deblock it into a CMessage0200 object for further processing. Looks like I should continue to develop the mock message whilst using it to test the real message; given our ISO8583 library it's trivial to put together message classes from specs and that was one of the first thing I did when I started to look at the spec, now I can write some tests for that work and by doing so validate the mock message that I'll use to test the message processor...

Sticking to the Single Responsibility Principle means that code is more cohesive; it only does one thing! This makes understanding and testing of the code easier because you only have one concept to understand or test at a time.

Leave a comment