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?

Tulains Córdova
  • 39,201
  • 12
  • 97
  • 154
sveri
  • 585
  • 4
  • 9
  • 29
    "Prediction is very difficult, especially about the future." – Jörg W Mittag Jul 11 '14 at 09: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 '14 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 '14 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. – Boris the Spider Jul 11 '14 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 – Ben Aaronson Jul 11 '14 at 16:24
  • @BenAaronson Not sure what you mean by the quirks of IQueryable since I rarely work with .NET, but [Predicate](http://msdn.microsoft.com/en-us/library/bfcke1bz%28v=vs.110%29.aspx) was more in line with what I had in mind. – Doval Jul 11 '14 at 16:31
  • 1
    @Doval Essentially `IQueryable` allows you to write C# code which is automatically translated into SQL queries... but this can only be done for a small subset of possible C# expressions. Allowing somebody to pass in an arbitrary predicate would allow a lot of code that looks valid, but throws exceptions at run time. You can skip `IQueryable` and do all your filtering in memory instead with `IEnumerable`, but in many cases you may have enough data that returning all of it from the DB is prohibitive – Ben Aaronson Jul 11 '14 at 16:35
  • @BenAaronson Why not take an `IQueryable` then? I'm not sure what's the issue; it sounds like you don't want the client to know about what's a valid query but at the same time don't want to allow arbitrary queries to get through. You can only have one or the other. – Doval Jul 11 '14 at 16:59
  • There are times when it makes sense for interfaces to contain parameters for "future expansion", particularly in scenarios involving callbacks. For example, if a collection includes a "invoke some interface method on all items" method, it may be useful for such a method to include a parameter which it simply passes along to the interface method which it calls. Even if there's no particular expectation of how that parameter would be used, it's better to have such a thing than try to retrofit code to deal with it or muck about with `ThreadLocal` storage. On the other hand... – supercat Jul 11 '14 at 19:43
  • 1
    ...it's a very bad idea for methods in an interface to include parameters whose meaning is undefined. In the callback scenario, the parameters have meaning to the middle layer--what's received from the outer layer is the thing the outer layer wants the inner layer to have, and what's given to the inner layer is the thing the outer layer wants it to receive. The middle layer might not know anything beyond that, but it doesn't need to--those specifications would make clear what the middle layer should do with the parameter. In your scenario, the name "end" implies that... – supercat Jul 11 '14 at 19:47
  • 1
    ...the parameter should do something. It may be reasonable for implementations to allow filtering by end date even if that ability isn't needed at the moment, but it sounds as though implementations aren't expected to do that. That would seem dubious to me. BTW, something like `end` seems like something implementations should honor. Even if data could be filtered downstream, why encourage the system to fetch data that's never going to be used? If code doesn't want anything after July 9, the information-retrieval code should know that. – supercat Jul 11 '14 at 19:50
  • 1
    Pet peeve: Hungarian notation in variable and method names. `getFooList()` should be `getFoos()`. I have an IDE, it tells me the type is a list. The name should tell me information about _what the method does_, not the type of variable it returns. –  Jul 11 '14 at 21:27
  • @Doval You let the client know what a valid query is through the interface you expose to it. So instead of having a member `IEnumerable FilterBy(Predicate condition)`, you'd have `IEnumerable FilterByDate(Date start)`, etc. That way the client is forced by the compiler to only try to do what it can actually do, rather than passing in an arbitrary predicate and being told "sorry!" at runtime – Ben Aaronson Jul 11 '14 at 21:57
  • @JohnGaughan: Does `getFoos` create and return a new list, or a live view of a sequence that might change? How do you know? – supercat Jul 11 '14 at 23:00
  • 1
    @Doval: Is that always possible? Often, you absolutely don't know what is behind the interface. Maybe it accesses a local file or database. `FooType` does not necessarily map directly to what is stored in the database, in which case loading all the records to pass them to an arbitrary filter function may be prohibitive. In some cases, the interface may send a request to a remote place such as a webservice and cannot just transfer any executable code such as evaluation functions there. – O. R. Mapper Jul 12 '14 at 10:35
  • Thinking more about this question, I would suggest that you clarify what is meant by "not used in the code". If the parameter is not used by any *clients*, but is honored by *implementations*, then its inclusion would be reasonable if the burden on implementations is light and there's any realistic possibility of future clients benefiting. One of the big problems IMHO with `IEnumerable`/`IEnumerator` is that it doesn't have any methods to perform a "seek" operation nor to capture the state of a sequence. Forward seeks can be performed through repeated `MoveNext`, and state can be... – supercat Jul 13 '14 at 16:20
  • ...captured by creating a new `List` and calling `Add` with every item, but many kinds of collections could support those operations orders of magnitude more efficiently if they'd been provided. Perhaps likewise in this situation: even if present implementations read all the data from a database and ignore anything whose date is past `end` [which a client could do just as well], future implementations might be able to refrain from reading any data which would simply be discarded anyhow. So perhaps you should clarify what you mean by "not used". – supercat Jul 13 '14 at 16:22

9 Answers9

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.

Arseni Mourzenko
  • 134,780
  • 31
  • 343
  • 513
  • 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. – Jeroen Vannevel Jul 11 '14 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. – Arseni Mourzenko Jul 11 '14 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.

h.j.k.
  • 1,737
  • 1
  • 16
  • 20
  • 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 '14 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 '14 at 03: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.

l0b0
  • 11,014
  • 2
  • 43
  • 47
msw
  • 1,857
  • 10
  • 16
  • 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 '14 at 09:19
  • 3
    What about `StarDate sdStart` and `StarDate sdEnd`? We can't forget about the Federation after all... – WernerCD Jul 11 '14 at 14:32
  • All those are *obviously* either subclasses of or convertible to `Date`! – Darkhogg Jul 12 '14 at 13:29
  • If only it was that [simple](http://en.memory-alpha.org/wiki/Stardate). – msw Jul 12 '14 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 '14 at 00: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) {...}
    ...
}
InformedA
  • 2,991
  • 2
  • 19
  • 28
  • 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 – Ben Aaronson Jul 11 '14 at 16:29
  • @Ben Aaronson I just edit the code to show how Request parameter object is created – InformedA Jul 11 '14 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](http://c2.com/cgi/wiki?ParameterObject) 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 '14 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;
    ....

}
ToddR
  • 267
  • 1
  • 2
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.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
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.

QuestionC
  • 429
  • 3
  • 11
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.

bowmore
  • 321
  • 1
  • 2
  • 5
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.

usr
  • 2,734
  • 18
  • 15