4 ms·
Indirection is a tool, and there are both good reasons and bad reasons to use it (as with most tools). A good reason would be to clarify the why or the what by
by ubertaco 7y ago
Indirection is a tool, and there are both good reasons and bad reasons to use it (as with most tools).
A good reason would be to clarify the why or the what by "glossing over" the how:
# Unclear:
list_range = range(0, len(movies))
for i in list_range:
j = randint(list_range[0], list_range[-1])
movies[i], movies[j] = movies[j], movies[i]
# Clear:
movies = shuffle(movies)
Yes, you're "hiding" the actual steps (the "how"), but in doing so, you're elevating your intent (the "why"/"what") to the forefront, and so you're making it clear what your code is supposed to be doing. This makes it easier to understand overall, and makes it easier to determine when your implementation and your expectations don't match (because your expectations are clearly described).
A bad reason for indirection is one that's super-common in Java: namely, "we might need to make this swappable at some undetermined point in the future". It's the kind of thing that leads to
public interface UserLookupService {
// ...
}
public class UserLookupServiceImpl implements UserLookupService {
// ...
}
That's just repetition for reasons that are at best forced by technical limitations, and at worst by paranoid decision-making.
A mark of skill at coding (in particular, as a part of software development) is the ability to clearly convey intent to the reader. Sometimes, that means bringing a new concept into code that makes the problem easier to understand and talk about (to borrow an example from elsewhere in the comments here, introducing a DateInterval type can make common operations on two dates clearer). Sometimes, that means recognizing when the details get in the way of the point.
We often do the same things when we write prose for humans to read. When writing prose for humans, it may help to introduce new clarifying terms like "time complexity" and "memory usage" so that we're not constantly explaining that when we say "performance" this time we mean "performance specifically in terms of how much RAM is used" and that time we mean "performance in terms of how much time is required to for the function to complete". It certainly often helps to edit down overly-detailed explanations that distract from the main point (possibly putting the fuller explanation into a footnote); if someone asks where you've been, how helpful is it to tell someone that you opened your garage door, got into your car, started your car, shifted gears into "drive" (or 1st gear), drove down your driveway, turned left....etc, as opposed to telling them that you went to the store to buy milk?
The point I'm getting at here is that telling someone "avoid indirection" is a bit like telling someone "avoid summarizing." Sometimes, a full account is needed, sure -- like if you're on a witness stand -- but most of the time, summary is one of many useful tools to convey information clearly.
Likewise, sometimes it's necessary to see every single explicit step being performed -- when doing in-depth performance optimization, for example -- but most of the time, indirection (especially by functionalization) is one of many useful tools to convey information clearly.
Indeed, indirection in code is actually "safer" than summarization in writing. In code, you can always "go to definition", where in writing you may not have that option.
- asdfman123 7y agoHonest question: isn't that kind of separating classes out into interfaces necessary for proper testing? When should you avoid splitting them out to interfaces and implementation classes?
- ubertaco 7y agoI'm curious what you mean by "necessary for proper testing". I know a lot of people use that pattern (single-implementation interfaces) for use with a dependency-injection container, and then they mock the interfaces...but that doesn't buy you any more than mocking the implementation classes themselves would. In terms of when to split them out, my rule of thumb is "when there's more than one implementation class that does the same function". Good examples are things like Comparable/Comparator, Reader, Writer, Serializable -- these are all descriptions of behavior for which multiple implementations exist. Interfaces, in general, are adjectives (or at least adjective-ish). Bad examples are things like CheckoutService, ShoppingCartManager, JwtTokenManager -- these are all descriptions of specific components within an application that have a single implementation. These are nouns, and generally they're a sort of "proper noun" (in that they refer to one single thing by its name). If you don't already have many implementations for the behavior they implement, then you don't need an interface (since an interface is just a way of describing a set of "external-facing" contracts which many specific implementations may fulfill -- like List vs LinkedList/ArrayList). The reason why is simply that it's just waste. It's more code for more code's sake. At best, you're just clicking "go to definition" one extra time, and at worst your code is confusing (because someone might assume the presence of the interface means it's "swappable" behavior, rather than integral application logic). The "YAGNI" ("You Aren't Going To Need It") principle applies here: if you don't know you're going to need it, don't build it. Once you do know that you're going to need it, then you build it. Anything before that point winds up being wasted time/effort.
- asdfman123 7y agoOh, so basically don't use interfaces unless you actually need the functionality provided by interfaces (namely, polymorphism). Got it!