Separation of Concerns

January 24, 2017 — Posted by Ken Pugh

One way to transform code into more maintainable code is to separate concerns into different modules.  There is usually a question of when this should be done and how to do it.  I use testability as one determining factor of how to perform the separation.  Let’s start with an example:

Original Code

Here’s code from some production code that has been de-identified.   The method receives a response from a service and performs logging, throwing an exception, or both.   

class CurrentCode {
    final static String SOME_TERM = "ABC";
    final static String SOME_OTHER_TERM = "DEF";

    void responseMethod(String response) throws ResponseException {
        if (response.startsWith(SOME_TERM)) {
            Log.write("Something happened");
            throw new ResponseException("Really bad error");
        } else if (response.startsWith(SOME_OTHER_TERM)) {
            Log.write("Something else happened");
            throw new ResponseException("Not too bad error");
        }
        // Lot's more else if's here
    }
}

If there was only one response, this method would probably not be considered for separation.  Even with a couple of responses, the method could still remain as it is.  As an exercise, you might come up with the unit tests that check that this module behaves properly.  And then imagine what the unit tests would look like if there were many more conditions.  

Separation

With an increased number of conditions, the method could be transformed.  It might be split into three steps:

  • Parse the string to determine the type of event
  • Determine the response for an event
  • Execute the response

Here’s one way to code these three steps.  The main method calls three other methods (This is often termed programming by intention).  Other people might split the steps differently (as we’ll see in another article)   

    void ResponseGenerate(String response) throws ResponseException {
        Event event = ParseResponse(response);
        EventResponse er = DetermineResponse(event);
        ExecuteResponse(er);
    }

Event has the types of events that can occur.  The event is computed by ParseResponse and passed to DetermineResponse.  

    enum Event {
        NOTHING_HAPPENED, SOMETHING_HAPPENED, SOMETHING_ELSE_HAPPENED
    };

EventResponse is the data object that communicates between DetermineResponse and ExecuteResponse:

class EventResponse {
    String logValue;
    String execeptionValue;
}

Here are the individual pieces of the code:

    final static String SOME_TERM = "ABC";
    final static String SOME_OTHER_TERM = "DEF";
    Event ParseResponse(String response) {
        Event event = Event.NOTHING_HAPPENED;
        if (response.startsWith(SOME_TERM)) {
            event = Event.SOMETHING_HAPPENED;
        } else if (response.startsWith(SOME_OTHER_TERM)) {
            event = Event.SOMETHING_ELSE_HAPPENED;
        }
        // Lot's more else if here
        return event;
    }


    final static String NO_LOGGING = "No logging";
    final static String NO_EXCEPTION = "No exception";

    EventResponse DetermineResponse(Event event) {
        EventResponse er = new EventResponse();
 
       // Could use a map instead of a switch

        switch (event) {
        case SOMETHING_HAPPENED:
            er.logValue = "Something happened";
            er.execeptionValue = "Really bad error";
            break;
        case SOMETHING_ELSE_HAPPENED:
            er.logValue = "Something else happened";
            er.execeptionValue = "Not too bad error";
            break;
        case NOTHING_HAPPENED:
            er.logValue = NO_LOGGING;
            er.execeptionValue = NO_EXCEPTION;
            break;
        default:
            Log.write("Event out of range");
            er.logValue = NO_LOGGING;
            er.execeptionValue = NO_EXCEPTION;
            break;
            // More cases as needed
        }
        return er;
    }

    void ExecuteResponse(EventResponse er) throws ResponseException {
        if (!er.logValue.equals(NO_LOGGING)) {
            Log.write(er.logValue);
        }
        if (!er.execeptionValue.equals(NO_EXCEPTION)) {
            throw new ResponseException(er.execeptionValue);
        }
    }

Now there is more code than in the original version. However, the individual methods are easier to test.   For example, these two methods can be passed various values and the result can be checked.    

Event event = ParseResponse(response);
EventResponse er = DetermineResponse(event);

This method is a little more complicated to test.  You need a logger (real or test double/mock) and to catch exceptions.  However, it does not need to be tested for every event.  It just needs a few tests to make sure it executes properly. 

       ExecuteResponse(er);

The other benefit of the separation is that all events can be specifically defined by name.  In the original code, the events are implicitly defined by having an “if response” statement.

Summary

At some point, code becomes less maintainable unless it is separated into modules that have different concerns.   Using the simplicity of tests as a guide helps in making the separation.  

 

 

Subscribe to our blog Net Objectives Thoughts Blog

Share this:

About the author | Ken Pugh

Ken Pugh was a fellow consultant with Net Objectives (www.netobjectives.com). He helps companies transform into lean-agility through training and coaching. His particular interests are in communication (particularly effectively communicating requirements), delivering business value, and using lean principles to deliver high quality quickly.



        

Blog Authors

Al Shalloway
Business, Operations, Process, Sales, Agile Design and Patterns, Personal Development, Agile, Lean, SAFe, Kanban, Kanban Method, Scrum, Scrumban, XP
Cory Foy
Change Management, Innovation Games, Team Agility, Transitioning to Agile
Guy Beaver
Business and Strategy Development, Executive Management, Management, Operations, DevOps, Planning/Estimation, Change Management, Lean Implementation, Transitioning to Agile, Lean-Agile, Lean, SAFe, Kanban, Scrum
Israel Gat
Business and Strategy Development, DevOps, Lean Implementation, Agile, Lean, Kanban, Scrum
Jim Trott
Business and Strategy Development, Analysis and Design Methods, Change Management, Knowledge Management, Lean Implementation, Team Agility, Transitioning to Agile, Workflow, Technical Writing, Certifications, Coaching, Mentoring, Online Training, Professional Development, Agile, Lean-Agile, SAFe, Kanban
Ken Pugh
Agile Design and Patterns, Software Design, Design Patterns, C++, C#, Java, Technical Writing, TDD, ATDD, Certifications, Coaching, Mentoring, Professional Development, Agile, Lean-Agile, Lean, SAFe, Kanban, Kanban Method, Scrum, Scrumban, XP
Marc Danziger
Business and Strategy Development, Change Management, Team Agility, Online Communities, Promotional Initiatives, Sales and Marketing Collateral
Max Guernsey
Analysis and Design Methods, Planning/Estimation, Database Agility, Design Patterns, TDD, TDD Databases, ATDD, Lean-Agile, Scrum
Scott Bain
Analysis and Design Methods, Agile Design and Patterns, Software Design, Design Patterns, Technical Writing, TDD, Coaching, Mentoring, Online Training, Professional Development, Agile
Steve Thomas
Business and Strategy Development, Change Management, Lean Implementation, Team Agility, Transitioning to Agile
Tom Grant
Business and Strategy Development, Executive Management, Management, DevOps, Analyst, Analysis and Design Methods, Planning/Estimation, Innovation Games, Lean Implementation, Agile, Lean-Agile, Lean, Kanban