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
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:
-
I don’t need
end
, so I’ll simply ignore its value, -
I don’t need
end
, so I’ll throw an exception if it is notnull
, -
I don’t need
end
, but will try to somehow use it, -
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
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
[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
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
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
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.
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.
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.
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.