42

I have a colleague sitting next to me who designed an interface like this:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}

The problem is, right now, we are not using this "end" parameter anywhere in our code, it's just there because we might have to use it some time in the future.

We are trying to convince him it's a bad idea to put parameters into interfaces that are of no use right now, but he keeps on insisting that a lot of work will have to be done if we implement the use of "end" date some time later and have to adapt all the code then.

Now, my question is, are there any sources that are handling a topic like this of "respected" coding gurus that we can link him to?

17
  • 29
    "Prediction is very difficult, especially about the future." Jul 11, 2014 at 9:50
  • 10
    Part of the problem is that it's not the best interface to begin with. Getting Foos and filtering them are two separate issues. That interface is forcing you to filter the list in one particular way; a much more general approach is to pass in a function that decides if the foo should be included. However, this is only elegant if you have access to Java 8.
    – Doval
    Jul 11, 2014 at 11:26
  • 5
    To counter is point. Simply make that method accepting custom type whos for now only contain what you need and could eventually add the end parameter to that object and even default it in order to not break code
    – Rémi
    Jul 11, 2014 at 13:13
  • 3
    With Java 8 default methods there is no reason to add unnecessary arguments. A method can be added later with a default implementation that simple calls the defined with with, say, null. Implementing classes can then override as needed. Jul 11, 2014 at 13:45
  • 1
    @Doval I don't know about Java, but in .NET you'll sometimes see things like to avoid exposing the quirks of IQueryable (can only take certain expressions) to code outside the DAL Jul 11, 2014 at 16:24

9 Answers 9

62

Invite him to learn about YAGNI. The Rationale part of Wikipedia page may be particularly interesting here:

According to those who advocate the YAGNI approach, the temptation to write code that is not necessary at the moment, but might be in the future, has the following disadvantages:

  • The time spent is taken from adding, testing or improving the necessary functionality.
  • The new features must be debugged, documented, and supported.
  • Any new feature imposes constraints on what can be done in the future, so an unnecessary feature may preclude needed features from being added in the future.
  • Until the feature is actually needed, it is difficult to fully define what it should do and to test it. If the new feature is not properly defined and tested, it may not work correctly, even if it eventually is needed.
  • It leads to code bloat; the software becomes larger and more complicated.
  • Unless there are specifications and some kind of revision control, the feature may not be known to programmers who could make use of it.
  • Adding the new feature may suggest other new features. If these new features are implemented as well, this could result in a snowball effect towards feature creep.

Other possible arguments:

  • “80% of the lifetime cost of a piece of software goes to maintenance”. Writing code just in time reduces the cost of the maintenance: one has to maintain less code, and can focus on the code actually needed.

  • Source code is written once, but read dozens of times. An additional argument, not used anywhere, would lead to time wasted understanding why is there an argument which is not needed. Given that this is an interface with several possible implementations makes things only more difficult.

  • Source code is expected to be self-documenting. The actual signature is misleading, since a reader would think that end affects either the result or the execution of the method.

  • Persons writing concrete implementations of this interface may not understand that the last argument shouldn't be used, which would lead to different approaches:

    1. I don't need end, so I'll simply ignore its value,

    2. I don't need end, so I'll throw an exception if it is not null,

    3. I don't need end, but will try to somehow use it,

    4. I'll write lots of code which might be used later when end will be needed.

But be aware that your colleague may be right.

All previous points are based on the fact that refactoring is easy, so adding an argument later won't require much effort. But this is an interface, and as an interface, it may be used by several teams contributing to other parts of your product. This means that changing an interface could be particularly painful, in which case, YAGNI doesn't really apply here.

The answer by h.j.k. gives a good solution: adding a method to an already used interface is not particularly hard, but in some cases, it has a substantial cost too:

  • Some frameworks don't support overloads. For example and if I remember well (correct me if I'm wrong), .NET's WCF doesn't support overloads.

  • If the interface has many concrete implementations, adding a method to the interface would require going through all the implementations and adding the method there too.

2
  • 4
    I think it's also important to consider how likely this feature is to appear in the future. If you know for certain it will be added but not right now because of whatever reason then I'd say that's a good argument to keep in mind since it's not just a "in case" feature. Jul 11, 2014 at 17:26
  • 3
    @JeroenVannevel: certainly, but that's very speculative. From my experience, most features I believed would certainly be implemented were either cancelled or delayed forever. This includes features which were originally highlighted as very important by the stakeholders. Jul 11, 2014 at 18:17
26

but he keeps on insisting that a lot of work will have to be done if we implement the use of "end" date some time later and have to adapt all the code then.

(some time later)

public class EventGetter implements IEventGetter {

    private static final Date IMPLIED_END_DATE_ASOF_20140711 = new Date(Long.MAX_VALUE); // ???

    @Override
    public List<FooType> getFooList(String fooName, Date start) throws Exception {
        return getFooList(fooName, start, IMPLIED_END_DATE_ASOF_20140711);
    }

    @Override
    public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception {
        // Final implementation goes here
    }
}

That's all you need, method overloading. The additional method in the future can be introduced transparently without affecting calls to the existing method.

2
  • 6
    Except that your change means it's no longer an interface, and won't be possible to use in cases where an object derives from an object already and implements the interface. It's a trivial problem if all classes that implement the interface are in the same project (chase compile errors). If it's a library used by multiple projects, the addition of the field causes confusion and work for other people at sporadic times over the next x months / years.
    – Kieveli
    Jul 11, 2014 at 14:38
  • 1
    If OP's team (save the colleague) really believe the end parameter is unnecessary now and has gone on to use the end-less method throughout the project(s), then updating the interface is the least of their worries because it becomes a necessity to update the implementation classes. My answer is targeting specifically about the "adapt all the code" part, because it sounds like the colleague was thinking along the lines of having to update the callers of the original method throughout the project(s) to introduce a third default/dummy parameter.
    – h.j.k.
    Jul 13, 2014 at 3:56
18

[Are there] "respected" coding gurus that we can link him to [to persuade him]?

Appeals to authority are not particularly convincing; better to present an argument that is valid no matter who said it.

Here's a reductio ad absurdum that should persuade or demonstrate that your co-worker is stuck on being "right" regardless of sensibility:


What you really need is

getFooList(String fooName, Date start, Date end, Date middle, 
           Date one_third, JulianDate start, JulianDate end,
           KlingonDate start, KlingonDate end)

You never know when you'll have to internationalize for Klingon, so you better take care of it now because it will require a lot of work to retrofit and Klingons are not known for their patience.

5
  • 22
    You never know when you'll have to internationalize for Klingon. That's why you should just pass a Calendar, and let the client code decide if he wants to send a GregorianCalendar or a KlingonCalendar(I bet some already did one). Duh. :-p
    – SJuan76
    Jul 11, 2014 at 9:19
  • 3
    What about StarDate sdStart and StarDate sdEnd? We can't forget about the Federation after all...
    – WernerCD
    Jul 11, 2014 at 14:32
  • All those are obviously either subclasses of or convertible to Date!
    – Darkhogg
    Jul 12, 2014 at 13:29
  • If only it was that simple.
    – msw
    Jul 12, 2014 at 15:54
  • @msw, I'm not sure he was asking for an "appeal to authority" when he asked for links to "respected coding gurus". As you say, he needs "an argument that is valid no matter who said it". "Guru"-type people tend to know a lot of those. Also you forgot HebrewDate start and HebrewDate end.
    – trysis
    Jul 14, 2014 at 0:01
12

From a software engineering perspective, I believe the proper solution for this kind of problems is in the builder pattern. This is definitely a link from 'guru' authors for your colleague http://en.wikipedia.org/wiki/Builder_pattern.

In the builder pattern, user creates an object that contains the parameters. This parameter container will then be passed into the method. This will take care of any extension and overloading of parameters that your colleague would need in the future while making the whole thing very stable when changes need to be made.

Your example will become:

public interface IEventGetter {
    public List<FooType> getFooList(ISearchFooRequest req) {
        throws Exception;
    ....
    }
}

public interface ISearchFooRequest {
        public String getName();
        public Date getStart();
        public Date getEnd();
        public int getOffset();
        ...
    }
}

public class SearchFooRequest implements ISearchFooRequest {

    public static SearchFooRequest buildDefaultRequest(String name, Date start) {
        ...
    }

    public String getName() {...}
    public Date getStart() {...}
    ...
    public void setEnd(Date end) {...}
    public void setOffset(int offset) {...}
    ...
}
3
  • 3
    This is a good general approach, but in this case seems to just push the problem from IEventGetter to ISearchFootRequest. I'd instead suggest something like public IFooFilter with member public bool include(FooType item) which returns whether or not to include the item. Then individual interface implementations can decide how they'll do that filtering Jul 11, 2014 at 16:29
  • @Ben Aaronson I just edit the code to show how Request parameter object is created
    – InformedA
    Jul 11, 2014 at 17:39
  • The builder is unnecessary here (and quite frankly, an overused/over-recommended pattern in general); there are no complex rules around how the parameters need to be set up, so a parameter object is perfectly fine if there's a legitimate concern over the method parameters being complex or frequently-changed. And I agree with @BenAaronson, although the point of using a parameter object is not to include the unnecessary parameters right off the bat, but make them very easy to add later (even if there are multiple interface implementations).
    – Aaronaught
    Jul 12, 2014 at 15:57
7

You don't need it now, so don't add it. If you need it later, extend the interface:

public interface IEventGetter {

    public List<FooType> getFooList(String fooName, Date start)
         throws Exception;
    ....

}

public interface IBoundedEventGetter extends IEventGetter {

    public List<FooType> getFooList(String fooName, Date start, Date end)
        throws Exception;
    ....

}
1
  • +1 for not changing the existing (and probably already tested/shipped) interface. Jul 13, 2014 at 8:29
4

No design principle is absolute, so while I mostly agree with the other answers, I thought I'd play devil's advocate and discuss some conditions under which I would consider accepting your colleague's solution:

  • If this is a public API, and you anticipate the feature being useful to third-party developers, even if you don't use it internally.
  • If it has considerable immediate benefits, not just future ones. If the implied end date is Now(), then adding a parameter removes a side effect, which has benefits for caching and unit testing. Maybe it allows for a simpler implementation. Or maybe it is more consistent with other APIs in your code.
  • If your development culture has a problematic history. If processes or territoriality make it too difficult to change something central like an interface, then what I've seen happen before is people implementing client-side workarounds instead of changing the interface, then you're trying to maintain a dozen ad hoc end date filters instead of one. If that sort of thing happens a lot at your company, it makes sense to put a little more effort into future proofing. Don't get me wrong, it's better to change your development processes, but this is usually easier said than done.

That being said, in my experience the biggest corollary to YAGNI is YDKWFYN: You Don't Know What Form You'll Need (yes, I just made that acronym up). Even if needing some limit parameter might be relatively predictable, it might take the form of a page limit, or number of days, or a boolean specifying to use the end date from a user preferences table, or any number of things.

Since you don't have the requirement yet, you have no way of knowing what type that parameter should be. You often end up either having an awkward interface that isn't quite the best fit for your requirement, or having to change it anyway.

2

There is not enough information to answer this question. It depends on what getFooList actually does, and how it does it.

Here is an obvious example of a method which should support an additional parameter, used or not.

void CapitalizeSubstring (String capitalize_me, int start_index);

Implementing a method that operates on a collection where you can specify the start but not the end of the collection is often silly.

You really have to look at the problem itself and ask whether the parameter is nonsensical in the context of the whole interface, and really how much burden the additional parameter imposes.

1

I'm afraid your colleague may actually have a very valid point. Although his solution is actually not the best.

From his proposed interface it is clear that

public List<FooType> getFooList(String fooName, Date start, Date end) throws Exception;

is returning instances found within an interval in time. If clients currently don't use the end parameter, that does not change the fact that they expect instances found within an interval in time. It just means that currently all clients use open ended intervals (from start to eternity)

So a better interface would be :

public List<FooType> getFooList(String fooName, Interval interval) throws Exception;

If you supply interval with a static factory method :

public static Interval startingAt(Date start) { return new Interval(start, null); }

then clients won't even feel the need to specify an end time.

At the same time your interface more correctly conveys what it does, since getFooList(String, Date) does not communicate that this deals with an interval.

Note that my suggestion follows from what the method currently does, not from what it should or might do in the future, and as such the YAGNI principle (which is very valid indeed) does not apply here.

0

Adding an unused parameter is confusing. People might call that method assuming this feature will work.

I wouldn't add it. It is trivial to later add it using a refactoring and mechanically fixing up the call sites. In a statically typed language this is easy to do. Not necessary to eagerly add it.

If there is a reason to have that parameter you can prevent the confusion: Add an assert in the implementation of that interface to enforce that this parameter be passed as the default value. If someone accidentally uses that parameter at least he will immediately notice during testing that this feature is not implemented. This takes away the risk of slipping a bug into production.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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