Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

I have come across the following problem many times in various guises in my programming career. As a simple example, consider the following:

Let's say there is a method (B) that could differ depending on customer requirements. This could be for any number of reasons. Common sense tells you that as things are now, you should write them as a common method and add branching code in the future to handle any future edge cases.

Customer 1: A-->B-->C

Customer 2: A-->B-->C

However, your experience tells you that this particular bit of code is likely to change so it would be easier to have separate methods (B1 & B2) and change the logic for one customer rather than continually add branching code making method (B) non-generic.

Customer 1: A-->B1-->C

Customer 2: A-->B2-->C

Are there any rules of thumb that can be applied in such cases or does every case have to be taken on its merits?

As an aside, I've come across plenty of people who staunchly advocate one approach over the other and I can see the merits of both. But surely there is some middle ground that can be reached based on reasoned arguments?

share|improve this question
1  
If you find yourself in a lot of situations where the start and end of a block of code are the same, but the middle is different, look into higher order functions, where you can pass in the B1 or B2 logic. Increases modularity and (as I see it) maintainability while removing duplication. Unfortunately, higher order functions aren't straightforward in every language. –  KChaloux Mar 11 '13 at 13:50
add comment

5 Answers 5

up vote 4 down vote accepted

In general, business logic tends to be similar by coincidence. On the other hand, program logic (meaning the structure and flow of your application) tends to be similar by convention.

If you think about different types of business entities, they're all going to have a fileTaxReturn method ('tis the season), but there's very little reason for them to use the same method because the way an LLC files its taxes is completely different from the way a C Corp files its taxes. This is business logic.

On the other hand, if you have some sort of behavior (maybe it's an event emitter that gets hooked for logging) that is part of the program logic, it's very likely that that's a convention of the application and can be reused.

There are two maintenance oriented aspects of this question.

  1. Implicit polymorphism - for business logic, often times it makes a lot of sense to prematurely abstract, because otherwise as you add more base cases all of your methods are going to turn into ugly switch statements. By making business cases clear scenarios early on, it makes intent visible and prevents switch statement hell.

  2. Premature abstraction - On the other hand, if you are saying to yourself "Maybe we'll want to log to the database, but sometimes to a flat file, and other times to an RSS feed, and maybe Twitter, or Facebook, or a serial port...", you're doing premature abstraction - creating unnecessary complexity in a system, and outright confusion for the maintenance programmer. Well-encapsulated code is easy to refactor, and will not suffer from a lack of abstraction.

share|improve this answer
    
+1 - "business logic tends to be similar by coincidence" can I get a motivational poster with this slogan on it? –  JeffO Mar 11 '13 at 18:14
    
Excellent. This gets to the heart of the problem. The natural coder's instinct to reduce the code base whilst recognising that there are distinct customer flows that you might want to maintain. –  Robbie Dee Mar 12 '13 at 10:29
add comment

Yes, each case needs to be taken on its own merit, as there is not general rule that says "all branches must be in their own methods".

The branching needs to be done in either case. It's just a matter of deciding if you want to put it in B per your first example, or in A as in your second. Deciding how to do boils down to two things:

  1. Readability: Is the code more readable if you put branching in A vs. B?
  2. Maintainability: Is the code easier to maintain if you put branching in A vs. B?

More often than not, these two goes hand in hand. Code that is easy to understand tend to be easier to change. Start with the simplest possible case (all code in one place) and then extract stuff out until you reach a point where the code is short and sweet, but still compact enough to make out the details.

When (if) you need to add B3 and B4, you have a candidate for introducing something a bit more flexible, like creating a common superclass and what not. But not before that.

share|improve this answer
    
Actually, I have seen too often the case where people write duplicate code for "A->B1->C" and "A->B2->C", because they insist that this is simpler / more readable, but they forget how the maintainability decreases by any duplicate code. –  Doc Brown Mar 11 '13 at 12:36
    
You raise an interesting point. How the code base expands would drive further design decisions. If customers 3 & 4 wanted the same sort of bespoke code as customers 1 & 2, you would probably extract the common code out and then have the bespoke stuff in separate methods. Whereas if all customers started asking for very specific code, it would make sense for method B to be on a per customer basis. –  Robbie Dee Mar 11 '13 at 12:51
    
Yes @RobbieDee, now you're getting it :-) YAGNI in a nutshell. –  Martin Wickman Mar 11 '13 at 12:54
add comment

Are there any rules of thumb that can be applied in such cases or does every case have to be taken on its merits?

There are at least one or two dozen different kind of solutions for that kind of problem, and to choose the "best" one (whatever that means) depends on the details of the specific situation. That does not mean there are "no rules of thumb" - there are a lot, but I guess more than it would make sense trying to list them here in one posting.

Said that, here are some things which could influence your decision:

  • what "triggers" the branching? Do you need a compile time or a run-time switch?
  • how big are the differences between B1 and B2? Does one have to duplicate code in B1 and B2? Are you going to duplicate code by doing "A -> B1 -> C" and "A -> B2 -> C" (which would be a bad thing)?
  • is B getting "too big" or "too complex" (and why doesn't B just branch between B1 and B2)?
  • do you use OOP and are A, B and C parts of a class where applying "template method pattern" may be the right thing to do?
  • do you have a programming language at hand where instead of calling B1 or B2, you could inject a callback to either B1 or B2 into the code which has to call either B1 or B2?
  • must the decision "B1 vs. B2" be made at just one place in code, or are there many places where the result of that decision is evaluated (and the switch between B1 and B2 has to be done again)?
share|improve this answer
add comment

B is behavior. So have an interface for it and introduce new implementations as needed. Equip different object instances with different implementations when instantiating them. This way you stick to the Open Closed Principle, because you just need to implement an interface and configure its usage. The owner of such an interface instance won't be affected by introductions of new interface implementations.

This way you are also separating the branching, which is a configuration issue, and the actual execution. The code will get a lot more readable, because there won't be any branches during the execution of the business logic.

share|improve this answer
    
There are occasions where I've defined interfaces thinking each implementation would be different for each customer but in reality, the majority of the code turned out to be the same. In this case, it should really have been an abstract class with virtual methods. But, you're perfectly correct for the simple example I outlined - these should be defined as interfaces. –  Robbie Dee Mar 12 '13 at 10:23
add comment

KISS & YAGNI

...simple & intuitive systems are usually way easier to evolve/adapt than complex systems that try to cover all possible "potential evolutions" upfront.

If the differences are minor, go for a common method/object/whatever.

If the differences are big, implement them in distinct method/object/whatever.

My 2 cents

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.