Showing posts with label behaviour driven development. Show all posts
Showing posts with label behaviour driven development. Show all posts

Tuesday, 18 December 2012

Technical Details as Part of Domain Model

Dear Junior

A classical questions in Domain-Driven Design is whether technical details should be part of the domain model. The usual answer is "no, there should only be business concepts", but I would like to refine that a bit.

One of the drives of Domain Driven Design is obviously to drive development from a design based on the domain - hence the name. So, concepts as "database", "table", "network" and "connection" should typically not be part of the domain and its language.

This kind of technical details does not make sense per se in the domain - they add nothing do the direct understanding. However, databases and network and other technical details might have indirect effects that that we need to understand. The tricky part is to find appropriate abstractions for that understanding.

Let me take "buying a laptop" as an example. Recently I bought a new laptop from the Apple site. This is the "laptop to buy" model, and when doing this I filled out an order form, which is what constitutes the "laptop to buy" domain model.

I know that a laptop is a pretty complex thing, so the "laptop to buy" domain is a really rich one. Some of my hardware freak frieds could talk to lengths about it. As I am not much of a hardware freak myself, I prefer if the "laptop to buy" domain model is as simple as possible - without "technical details" that I am not interested in.

For example, the laptop I buy have a hard-drive, which is made by a manufacturer. I happen to know there are numerous subtle details that differ between hard-drive brands - but none that I care of. It is an uninteresting technical detail, and I do not want to see the choice of hard-drive brand in the domain of "laptop to buy".

Luckily for me, Apple agree, and there is no option for choosing hard-drive brand that makes the order form more complex and harder to understand.

Another technical detail is that the hard-drive have a lot of sectors and blocks, and the number of sectors and blocks differ from hard-drive to hard-drive. This is also something that I do not wish to care about - I just want to stuff my documents, photos, and film clips onto the laptop and it should just work. I do not want this to be part of the "laptop to buy" domain model either.

Unfortunately, Apple disagrees here. They think I need to be aware of that the hard-drive consists of storage cells, and that these actually are limited. Otherwise, I would live in the illusion that I could put things on my laptop indefinitely. That illusion would break down when there suddenly are "no space left on device" - an event which would leave me utterly confused.

In order to make me understand the nature of how it is to work with a hard-drive, I need to be aware of the finite number of sectors and blocks - although it is a "technical detail". But, unfortunately, it is a technical detail I need to be aware of.

Now comes the hunt for the appropriate abstraction to make the blocks and sectors understandable to non-hardware-technical me. "Blocks and sectors" does probably not work very well, but the concept of "size" does. I get that we are not talking about physical size, but some kind of analogy to the size of a suitcase. A small suitcase will become full fast, but you can put more stuff into a larger suitcase before it gets full. The size is still a "technical detail", but it is a detail that cannot be ignored, and it is put in a form I can understand.

Thus Apple has chosen to have options in the order form for how big a hard-drive I want, making it a part of the "laptop to buy" domain model. 

And I can agree with Apple.

When considering whether a concept should be in the domain or not, it is really not interesting whether it is a "technical detail" or not. The interesting discussion is whether this is a concept which the user (or other stakeholder) needs to be aware of. We cannot take away concepts that will make their understanding "break down". And for those technical details, we have to find an appropriate abstraction.

This reminds me of an Einstein quote: "Things should be made as simple as possible, but not simpler" which I think is an excellent way to put Occam's Razor.

So, technical details should not be part of the domain if they are irrelevant to the business. However, it might be that the technical detail is a concept that you need to understand to comprehend how things work. And in that case, it should be part of the domain, even if it stems it origin from the technical side.

Yours

   Dan

Wednesday, 14 March 2012

DRY and False Negatives

Dear Junior

In earlier letters we have talked about the DRY-principle, how looking for duplicated code can help finding violations of DRY, but also about the false positives - the rare locations where unrelated concepts by coincidence happen to be represented by similar code. Using Domain-Driven Design terminology it is where two different concepts are modelled, but where the models happens to be represented the same way.

But the hardest part of DRY is the false negatives.

A "false positive" is when some kind of test raises a signal but does so wrongly. E g a medical lab test might say that you have a disease which you does not have. If you get a false positive you have to do further investigations before you realise that it was just a false alarm. In our case the false positives are where code that was alarmed as duplication, but actually was jus a coincident and no violation of DRY.

A "false negative" on the other hand, is when the test does not raise a signal but does so wrongly. E g a medical lab test says you are healthy, but you actually have the disease. Now, false negatives are a lot harder to handle as the few mistakes (wrong classification by the test) hide within an enormous pile of "perfectly normal cases" (i e the true negatives).

This is the reason why medical tests most often are designed to avoid false negatives even if that design means more false positives. In the medical field this is natural. The cost in money and suffering in a missed AIDS-diagnosis totally overshadows the costs and anxiety of having to run a few extra tests in case the initial test wrongly showed "infected".

Talking about to DRY and code: what are our false negatives? Well, it is those DRY-violations that does not show up as code duplicates. Using DDD terminology we have exactly the same concept being modelled, but it is represented in different places in the model, and the representations differ from each other. Code-wise, we have pieces of code that fulfills the same purpose in the solution, but are written in a different way.

We return to our online sales system as an example to make that clear.

In the customer management there might be a method that validate that zip-codes are five-digit strings. Some programmer wrote the regexp as part of an if-condition and at some later point of time some other programmer extracted it to a metod.

class CustomerManagement {
    private boolean validateZip(String zip) {
        return zip.matches("[1-9][0-9]{4}");
    }
}

Totally unaware of the private method in CustomerManager, some third programmer was working on the shipping logic. This programmer wanted to avoid printing invalid zip codes on packages so he looked for some feasible method in the standard APIs for a minute or two before giving up and whipping together his own helper method. Also, this programmer was not so well versed in regexps so he found another way of validating.

class Shipping {
    public static boolean isValidZipCode(String zipcode) {
        try {
            int i = Integer.parseInt(zipcode);
            return Integer.toString(i).length() == 5;
        } catch (NumberFormatException e) {
            return false;
        }
    }
}

(BTW, these examples are inspired be real code-bases)

This is where we suddenly have a tricky instance of DRY-violation. We express the exact same idea - the criteria for how a valid zip code is formatted. But we do so in completely different way - so different that no code-duplicate detector will find them.

It would be far from tasteful to compare the direct human suffering of failing medical tests with the effects of DRY-violation. Nevertheless, in medical tests the false negatives are far more troublesome than the false positives. And the same holds for violations of DRY. The false positives we can find and discuss, but the false negatives will just silently create implicit couplings between different parts of the code - couplings that are hard to find and might break when the concept change but only a few of the representations are updated. Also, it leads to our code getting bloated.

In summary I dare to make an analogy to medicine. When looking for the DRY-disease, we will get a lot of help from testing for code-duplication. There will be some false positives that we need to keep our eyes open for so that we do not try to cure where there is no illness. However it is the false negatives that the easy test does not find that will cause us the most trouble in the long run.

Yours

Dan

Ps My colleague Anders Helgesson phrased this as "Man ska inte upprepa sig och säga samma sak, om och om igen, flera gånger" wich in translation is "You should not repeat yourself and say the same thing, over and over again, many times" - a wonderful example of a DRY violation where searching for duplicated text gives a false negative.


Wednesday, 7 March 2012

DRY and Duplicated Code

Dear Junior

In my last letter I wrote about how the DRY-principle (Don't Repeat Yourself) talks primarily talks about the number of times an idea is expressed in the code-base, not about text-repetition as such.

If nothing else, the intention of DRY becomes clear when you go back the source that caused the DRY-meme to spread within the programming community: the Pragmatic Programmer by Andrew Hunt and David Thomas, published in 1999.

"Every piece of knowledge must have a single, unambiguous authoritative representation within a system" 

(Needless to say, when reading this book I found its core ideas to be extremely well aligned with my own ideas about programming - and the rest of the book have had profound impact on my view of the craftsmanship aspects of programming)

Now, to put my last letter in perspective it must be pointed out that the absolutely easiest way to find violations of the DRY principle is to search your codebase for duplicated text. It is very likely to find multiple places where the same idea is expressed over an over again using the same technical construct.

If for example you have an order system, there might be an attribute "payment" on the order telling when the order was paid.

class Order {
   Date payment = null;
   Date payment() { return this.payment; }
}

Most probably orders are not allowed to be sent for shipping unless they are paid. So, very probably there will be code somewhere checking this condition.

if(order.payment() != null) // do shipping
else // do not allow shipping

It is also probable that the same check is done in a lot of other places throughout the codebase. What we have ended up with is that the same technical construction "order.payment() != null" is used to express the same idea "order is payed" in a lot of places - a clear violation of DRY. Most probably we will also find a lot of places checking the negation "order.payment == null".

Of course the codebase would benefit from encapsulating the interpretation of a nulled payment-field and putting that encapsulated interpretation in one place - preferably the Order class.

class Order {
   Date payment = null;
   Date payment() { return this.payment; }
   boolean isPaid { return order.payment() != null; }
}

The benefits are several. To start with it is now possible to change the technical representation of an unpaid order without having to run around the code-base. We actually want all those places in the code to change at the same time - because they conceptually related things (actually they are conceptually the same thing).

An even more important benefit is that the API of the Order class becomes more expressive, making the client code more lucent.

if(order.isPaid()) // do shipping
else // do not allow shipping

Obviously, searching for duplicated text is an excellent way of finding candidate violations of DRY. If the code looks exactly the same, then those places will need to change at the same time - i e we have implicit couplings in the codebase that we risk breaking when we change.

There are excellent tools out there to detect duplicated code - some bundled in code-quality tools like Sonar, some available directly in your IDE. Most of these even have support for "fuzzying" where they detect that code is duplicated "apart from different naming of variables" for example.

Using these tools on an "average codebase of the street" will probably yield hundreds of places with duplicated code. Of these candidates, the vast majority of them will be violations of the DRY principle - especially the short code-snippets like "order.payment() == null" which are so easy to "just write" instead of looking for a method that does the job.

However, be aware that within that huge pile of DRY-violations there might also be a few "false positives". Those are the code-places that by accident happen to look the same way, but actually represent different and unrelated ideas. To "reduce the common code" in that case would only cause a irrelevant coupling between unrelated parts of the solution.

In summary, looking for duplicated code is an excellent "screening test" to find loads of candidates where the DRY-principle has been violated. However, the DRY principle is not about duplication of text, but about duplication of ideas. So, the two should be kept apart.


Even if duplicate code most often is a sign of DRY-violation, there are valid reasons to have the same code-text in two different places. 


Jérémie Chassai phrased it pretty well on Twitter: "You should repeat yourself if you say the same thing for two different reasons".

Get those tools, start searching for duplicates, and good DRY-hunting

Yours

Dan

Thursday, 1 March 2012

DRY is about Ideas, not Text

Dear Junior

Ever since the Pragmatic Programmer, the principle of DRY (Don't Repeat Yourself) has been one of the central pieces of advice for good programming. From a private perspective, it has many a time helped me to write code that is better than had I not had that meme in the back of my head. However, I have also seen it misused and (in my understanding) misunderstood several times - sometimes with disastrous result.

What I think is central about DRY is that it refers to ideas. Slightly rephrased DRY can be expressed that "each idea about your software solution should be expressed once and only once in your code". With "idea" I mean stuff like "an order number is a six-digit string not beginning with 0 or 1", or "an order that is cancelled after shipping will still be debited the costs that we cannot recover".

Unfortunately, a common misuse is to let DRY to talk about code as text. Let me make it a little bit more clear though an example. 

Say that we have some kind of order system where order numbers are five-digit numbers. Somewhere we might find this code that checks whether a string is a valid order number or not.

       public static boolean isValidOrderNumber(String orderNumber) {
            return orderNumber.matches("[1-9][0-9]{4}");
        }

The code might be in the Order class, or even inside an OrderNumber value object, and possibly being called by the constructor.

As it is an order system, there will also be a part that talks about shipping, to get the goods to some specified address. The address consists of a lot of things, among those a zip code that in this country is a digit string of length five (initial digit must be non-zero). So, somewhere we can expect to find the code doing this validation.

       public static boolean isValidZipCode(String zip) {
            return zip.matches("[1-9][0-9]{4}");
        }

This code might be in the Address class, or inside the ZipCode value object - possibly called by the constructor.

Now, this is where some DRY-zealots shout "duplication, duplication" because they have pattern-matched the string matching in isValidOrderNumber with the string matching in isValidZipCode and noticed that they consist of exactly the same text (barring a rename of a variable).

I think this is a mistake, and those zealots focus on the wrong thing. 

In the eyes of the zealots, the textual duplication is bad. Instead one method should call the other. This obviously becomes bizarre code if you try it. For example it would create a completely irrelevant coupling between the zip code and the order number - two concepts that are not related at all.

Alternatively, they claim, the common code should be broken out to a separate method called from both places. Now, that might make more sense, but you still introduce a coupling between the un-related concepts order number and zip code: they will now covariant - change one and the other will change.

For example, should this company decide to use letters in their order numbers, then they need to regression test all their use of addresses as well.  

Do this kind of coupling on a massive scale, and you will end up with a system where every change is full of surprises - conceptually totally unrelated concepts start behaving different just because they had some code-snippet in common.

What to do instead?

We should not primarily look at the code as a mass of text. Instead we should look at it as representation of a set of ideas. This is of course the perspective of Domain Driven Design, where we see the code as the encoding of a distilled domain model - a model that captures our selected way of looking at the problem domain.

Now, seen from that perspective the "repeated code text" in the two validation methods is completely unproblematic. It is just two separate concepts that happens to be represented using the same technical mechanism. They have nothing to do with each other - the duplication is a pure coincidence and the two concepts should be able to change totally uncoupled from each other.

The principle of Don't Repeat Yourself is about ideas, it does not apply to text.

Yours

   Dan


Friday, 16 September 2011

Public final and data encapsulation


Dear Junior

We were discussing immutable value objects and using "public final" data-fields for their representation. In that discussion I started off mixing up immutability and encapsulation. Now that we have covered immutability, let us return to encapsulation.

Obviously, declaring a field as public will break data encapsulation. You lose your freedom to change data representation without having to bother the clients.

Let us have a look at a name-class with some actual usage.

public class Name {
    public final String fullname;

    public Name(String fullname) {
        if(!fullname.matches("[a-zA-Z\\ ]+"))
            throw new IllegalArgumentException();
        this.fullname = fullname;
    }

    public String[] names() {
        return fullname.split(" ");
    }
}

The public API of this class consists of three parts: the construction of a name from a string, the attribute “fullname” (accessible through property data field), and the attribute “names (accessible through accessor method).

An example of what client code looks like we can find in the tests.

public class NameTest {

    private final String danbjson = "Dan Bergh Johnsson";

    @Test
    public void shouldHaveFullNameAsAttribute() {
      Assert.assertEquals(danbjson, new Name(danbjson).fullname);
    }

    @Test(expected = IllegalArgumentException.class)
    public void shouldNotAllowWeiredCharsInName() {
      new Name("#€%&/");
    }

    @Test
    public void shouldSplitIntoNamesAtSpaces()  {
      Assert.assertEquals(
        new String[] {"Dan", "Bergh", "Johnsson"},
        new Name(danbjson).names());
    }

}

Imaging that we want to change the internal data representation to using a char-array instead. Doing so will break all the clients as they rely on having that public data-field “fullname”. Thus, it is a bad design. Or?

I would argue that it is still a good design. Using modern tools it is easy to add the encapsulation when needed. Applying "Encapsulate Field" in e g IntelliJ yields.

public class Name {
    private final String fullname;

    public Name(String fullname) {
        if(!fullname.matches("[a-zA-Z\\ ]+"))
            throw new IllegalArgumentException();
        this.fullname = fullname;
    }

    public String[] names() {
        return fullname.split(" ");
    }

    public String fullname() {
        return fullname;
    }
}

And of course the client code has been changed accordingly.

    @Test
    public void shouldHaveFullNameAsAttribute() {
        Assert.assertEquals(danbjson, new Name(danbjson).fullname());
    }


Now, I could have chosen "getFullname" as the method name for the new method. However, I have always found that naming convention a little bit awkward, the property is “full name” and adding a boilerplate “get” does not add any value in my opinion. By the way, JavaBeans is just one naming convention in Java, the naming convention for CORBA predates JavaBeans. In the CORBA convention if you have a property “fullname” of type String, then the way to access it was to call a method “String fullname()” and the way to change the property was to call a method  “void fullname(String)”. So the convention I use is not new to Java at all.

In some languages there is no distinction in syntax between accessing a field and calling a no-arg method. Had the code been written in Eiffel or Scala, the syntax would have been the same and there would be no change to the client code at all.

Now that we have encapsulated the usage via “fullname()” we can change the internal representation to a char array.

public class Name {
    private final char[] chars;

    public Name(String name) {
        if(!name.matches("[a-zA-Z\\ ]+"))
            throw new IllegalArgumentException();
        this.chars = name.toCharArray();
    }

    public String[] names() {
        return new String(chars).split(" ");
    }

    public String fullname() {
        return new String(chars);
    }
}

Just for reference, in Scala the corresponding change would start with this "final public" representation – here represented by the keyword “val”.

class Name(val fullname: String)
{
  if(!fullname.matches("[a-zA-Z\\ ]+")) throw new IllegalArgumentException();

  def names = fullname.split(' ')
}

The change would take us to the slightly more verbose char array representation. Here we have no “val” in external class declaration, but a private val-field hidden inside the class-block instead.

class Name(name: String)
{
  if(!name.matches("[a-zA-Z\\ ]+")) throw new IllegalArgumentException();
  private val chars = name.toCharArray

  def fullname = chars.toString

  def names = fullname.split(' ')
}


In conclusion: As the public final field is accessible by the clients, it is also a part of the API for Name: This breaks data encapsulation. If you want to change data representation, you will need to create an accessor method to which you direct the client.

In Scala or Eiffel this is a no-issue, as the new accessor could transparently have the same name as the old datafield ("fullname") and be accessed with exactly the same syntax ("name.fullname"). However, in Java you have to include an empty pair of parenthesis to call the accessor method - thus the client code must be changed.

Now, I consider this a small issue, as there is excellent refactoring support in modern IDEs that eliminate the change to a fully automated four-click, one-minute exercise. Thus, there is no point in designing for that change up front.

So, even if we *do* break data encapsulation, I would say that it is not much of a problem.

Now, there are other kinds of encapsulations that are more interesting than data encapsulation, but that analysis will have to wait for some other letter.

Yours

   Dan

Monday, 12 September 2011

Public final is also Immutable, Again

Dear Junior

I must apologise as in my last letter I mixed two separate discussions into one: one about immutability, and one about encapsulations. What I was after was immutability, even though encapsulation is also interesting.

To make thinks clear, let us return to the Name class for representing value objects for a name e g "Dan Bergh Johnsson". To focus on immutability, let us simplify it.

public class Name {
    public final String fullname;

    public Name(String fullname) {
        this.fullname = fullname;
    }
}

Now, if I create an object of this class, then no client can mutate the state of that object. This is because

  1. The datafield is final so the client cannot make the reference point to some other String 
  2. Strings are immutable, so the referred object cannot be changed 

Just for reference. I mentioned that Scala has a very elegant way of defining "public immutable datafield properties". The corresponding Scala class would be a one-liner.

class Name(val fullname: String)

Elegant, isn't it? "Name is a class with the attribute value 'fullname' of type String". Scala promotes the use of immutable constructs by making it simple to declare them.

Now, as my friend Tommy Malmström pointed out to me, I should also make my class final. This is a very valid and interesting point, so let me dig into.

The problem in this case is not the objects we construct ourselves, but objects that are sent to us. We should rightfully assume that such objects are also immutable. However, someone could wittingly and deviously create a mutable subclass of Name.

Back in Java land such a subclass could for example overrride toString

package names;

class EvilName extends Name {
    public EvilName(String fullname) {
        super(fullname);
    }

    public String toString() {
        return "Voldemort";
    }

    public static void main(String[] args) {
        Name name = new EvilName("Harry Potter");
        System.out.println(name);
        System.out.println(name.fullname);
    }
}

danbj$ java names.EvilName
Voldemort
Harry Potter

You see how confusing it could be when toString is used to render the name object "Harry Potter".

So, forbidding such overrides by declaring the Name class as final is really a good idea.

On a side note, this is the reason why String is declared final. The String class is for example used to represent class and package information when loading code dynamically over the network - so imagine the consequences had it been possible to make a mutable phoney String.

Well, that was about immutability.

On the issue of encapsulation it can be argued whether "public final String fullname" breaks encapsulation or not - and that discussion also depends on what encapsulation we mean. Any way, that is a separate discussion.

Yours

   Dan

Wednesday, 2 February 2011

Conceptual Abstraction, and Technical

Dear Junior

I think there is an important distinction between solving a technical problem and solving the conceptual problem. And this distinction has a profound effect on what the code will look like. 

Let me take an example. Imagine that you have some class that keeps track of the score during a football game. It might look something like this.

class FootballGame() {
  int home = 0;
  int guest  = 0;
  void homeGoal() { home++; }
  // void guestGoal()
  // String score () , e g "4-3"
  // etc
}

Now you want to add goal logging, that logs each goal to stdout. This might change homeGoal to 

void homeGoal() {
  home ++;
  System.out.println("home goal");
}

Next reasonable change can be that you want the logging to be configurable so that you can log to a file instead of standard out.

This can be done from a technical perspective or a conceptual perspective. Let's have a look at each to see why I prefer doing it the conceptual route.

Technical abstraction

The ambition of technical abstraction is to make yourself independent of the particular implementation you have at the moment. The "how" is here "to log goals to standard out", the "what" is "to log goals somewhere". We want the solution less dependent on the "how".

We refactor homeGoal to use a configurable output stream instead of hard-coding. The code inside FootballGame might turn out something along 

FootballGame(PrintStream out) {
  this.out = out;
}
void homeGoal() {
  home++;
  out.println("home goal");
}

This is a perfectly acceptable solution from a technical perspective. It uses dependency injection, so we can easily configure it with stdout, a stream to a log file or sending the data remote through a socket. For testing we can use a PrintStream backed with a ByteArrayOutputStream or we can mock the PrintString when doing TDD. The unit test for driving that step might look like 

    @Test
    public void shouldLogHomeGoal() {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        FootballGame game = new FootballGame(new PrintStream(out));
        game.homeGoal();
        Assert.assertEquals("home goal", out.toString());
    }

(Arguably we also test some of the functionality of ByteArrayOutputStream - but as that is a standard API class I am pretty comfortable with that.)

Typically technical abstraction can be done by using the standard APIs given by the platform (OutputStream, String, List, File etc).

The weakness of this approach is that the code/solution/architecture gets riddled with technical constructs, and after a while it is hard to remember exactly what purpose that particular OutputStream was for in that particular place.

Conceptual abstraction

The ambition of the conceptual abstraction is also to make yourself independent of the particular implementation. However, we look at the situation a little bit different and thus set of in a slightly different directions. The "how" is here "to log goals to standard out", but the "what" becomes "to log goals".

We refactor homeGoal to use a GoalLogger.

FootballGame(GoalLogger goallogger) {
    this.goallogger = goallogger;
}
void homeGoal() {
    home ++;
    goallogger.log("home goal");
}
interface GoalLogger {
    void log(String msg);
}
This solution does also use dependency injection, it can also be configured to log to standard out, to a log file, or cross network. For TDD we will probably using mocking in the driving testcase - which is really easy as we have an explicit interface.

    @Test
    public void shouldLogHomeGoal() {
        GameLogger gameLogger = Mockito.mock(GameLogger.class);
        FootballGame game = new FootballGame(gameLogger);
        game.homeGoal();
        verify(gameLogger).log("home goal");
    }
The difference is that we will need explicit classes. E g for stdout-logging we will use StdoutGoalLogger.
class StdoutGoalLogger implements GoalLogger {
    void log(String s) { System.out.println(s); }
}
For other purposes we will use a FileGoalLogger or a NetworkGoalLogger.

Obviousness is the Difference

The main difference to me is that it is totally obvious what the intent of the GoalLogger is, as apart from the generic PrintStream in the example of technical abstraction. 

Granted, there are more code in the conceptual abstraction than in the technical abstraction - but I would still prefer the former if it came to maintenance or further development.

But the real difference comes in the next step. We will probably notice that there are two calls that are repeated:
goallogger.log("home goal"); 
goallogger.log("guest goal");
Now that we have the GoalLogger abstraction we can let it evolve.
interface GoalLogger {
    void homeGoal();
    void guestGoal();
}
Note that we have also removed the now obsolete "void log(String msg)", and this is what makes this GoalLogger distinct from some or any general-purpose logger.


Had we done the technical abstraction of working with an PrintStream, it would not be as obvious what could be done next. What I have seen most of the times is some kind of util class emerging containing static "helper methods" like GoalUtil.logHomeGoal(PrintStream), helper methods that are sometimes used, sometimes not. This in turn leads to similar (or duplicated) code in several location and an increase in code line count.

In my experience the small increase in line count for the conceptual abstraction is far better than the "ripple" increase in line count that the technical abstraction cause over time. 

The bottom line. Focusing on the conceptual problem at hand gives a better codebase in the long run than focusing on solving the technical problem does. It might give more code but it need not to take much more effort.

Yours




   Dan