3 ms·
The article gives some examples about Go, and lists interfaces as something you should use, because the performance cost is tiny. As someone who was formerly i
by t8sr 3y ago
The article gives some examples about Go, and lists interfaces as something you should use, because the performance cost is tiny.
As someone who was formerly involved with the canonical Go style, I wanna clear up the reason why we said you shouldn't overuse interfaces. It has nothing to do with performance. (The difference between a "virtual" and "static" dispatch is not worth thinking about in Go.) It has everything to do with readability.
If you return an interface, I can't figure out what happens when I call it. That's fine with something like io.Writer, because it's a good abstraction and, anyway, I can guess. But hiding business logic behind an interface is bad, because I usually should understand what happens when I do something like foo.IssueReceipt(), and if foo is an interface, then I can't really know.
The rule in Go has for a long time been "return concrete values, accept interfaces" for precisely this reason.
Another relevant soundbite is that Go interfaces are an "accept-side" construct, unlike Java's, which are a "declare-side" construct. This is a way of saying you shouldn't define an interface near its implementation, you should define it near where it's accepted.
You might notice that this makes it really hard to do dependency injection in Go, and this, too, is intentional.
- Cthulhu_ 3y agoWhen writing code in any language, readability and maintainabilty should always trump performance. Go is fast enough, if it's slow then it's more likely you need to rethink how you're solving the problem then optimize your code. Make it work, make it pretty, make it fast, in that order. And don't optimize without measuring, another trap that many people (present company included) fall into.
- t8sr 3y ago>Make it work, make it pretty, make it fast, in that order. And don't optimize without measuring, another trap that many people (present company included) fall into. This view is common nowadays, but I think people need to dial that back by maybe 30%. Too often, it's used as an excuse for software that's extremely wasteful, when with minimal tweaks it could be efficient. I am a "simplicity uber alles" kinda guy, and even I think you should add a line of code if it'll shave off half the runtime. I've heard people quote this, and the infamous Donald Knuth line, as an excuse for not knowing how to do basic things, like binary search. I've even heard people complain about caching network IO without a benchmark to show RAM is faster than DNS. Programmers, more than other people, have a tendency to take a pithy soundbite and make it their life philosophy, and I'm saying it's better not to.
- toast0 3y agoThere's premature optimization and there's mature optimization... Searching with something other than linear scan when searches happen often over meaningfully large search spaces is a mature optimization. Avoiding network requests by using a cache is a mature optimization (with all of the pitfalls that come with caching)
- t8sr 3y ago/me sips Alamo from the can Yep.
- thinkharderdev 3y ago> And don't optimize without measuring This is of course excellent advice > Make it work, make it pretty, make it fast While I agree somewhat with the sentiment, I dislike the implication that "fast" is somehow not relevant to whether something "works"
- jzwinck 3y agoIf you don't like returning an interface because the caller can't tell what it does, why is accepting an interface any better? Don't you end up in the same conundrum of not knowing whatever it is that you need to know about foo.IssueReceipt() if someone passed foo into your function?
- t8sr 3y agoI think there are a few differences. One is call-site flexibility - if I have a function that takes an interface, I can call it without an explicit cast with either an interface or a concrete value. If it returns a concrete value, I can assign that to an interface or a concrete value. So far so good. But both taking a concrete value, or returning an interface, force the call site to match the decision. Another reason is that you usually read the call site first, then you go inside the function to look at it in detail, so you already know what concrete values is being passed, even if the function only sees an interface. Finally, it's a local change to change an API to accept a concrete type after it has been accepting an interface, but changing an API that used to return an interface to return a concrete value could potentially involve a refactor of the whole codebase. (In fact, one such refactor motivated this rule.) Of course it's not a hard-and-fast rule. Go style also has you return error, which is an interface, and the standard library passes around io.Writer and os.Stat all the time. But for business logic, I think it's the right rule 95% of the time.
- aleksiy123 3y agoWhy is Go anti dependency injection? Accept side interfaces seems to be compatible with DI. Just maybe not with the registration/automatic injection. But I'm not too familiar with go idioms.
- t8sr 3y agoI probably over-stated that a bit. I don't think there's a hard stance against it. I do think it's rarely a good way to structure your program, because it makes it harder to know what's happening, and the goal should be to make it easier to see what's happening, both for the programmer and the poor devops guy who has to debug it when it goes down in production and people are yelling. Of course the real world is complicated, and DI isn't always avoidable. You are right that Go's accept-side interfaces are neutral w.r. to DI.