| |
| ▲ | wbl a day ago | parent [-] | | How on earth is that violated by error? error is implemented by types all over the standard library and beyond and consumed by functions that wrap errors in the errors package. It's exactly an example of what you claim is violated. Even more obviously your link isn't talking about functions but packages. There are some violations out there but generally when included packages define interfaces they are ones that get consumed like in io or it's dissolution. | | |
| ▲ | the_gipsy a day ago | parent [-] | | > The implementing package should return concrete (usually pointer or struct) types It's in the first paragraph. It goes on in the second: > Do not define interfaces on the implementor side of an API “for mocking”; instead, design the API so that it can be tested using the public API of the real implementation. And yes, io.Reader/Writer violate that too, because either the tenet is wrong or the design of interfaces in go is wrong. > Even more obviously your link isn't talking about functions but packages. It doesn't matter: your exporting errors behind the error interface, not your concrete error implementation. If you're just using one of the many ways to create stringly errors (!) like fmt.Errorf, you maybe don't notice but you are in fact returning interfaces all the time. | | |
| ▲ | 9rx a day ago | parent | next [-] | | The actual first paragraph states: > This page collects common comments made during reviews of Go code, so that a single detailed explanation can be referred to by shorthands. This is a laundry list of common style issues, not a comprehensive style guide. The document does not assert that you must not return interfaces or that it is incorrect to return interfaces. It only indicates that returning interfaces at inappropriate times has been a recurring issue found during code review. Sometimes returning an interface truly is the right choice, but when it isn't... Like most adages in programming, the aforementioned tenet holds validity in many cases, but, as always, "use your noggin" applies. | |
| ▲ | wbl a day ago | parent | prev [-] | | It does matter that packages and functions are different. It also matters what the io package actually does. https://pkg.go.dev/io . The io package has a very limited number of functions that return Readers. The vast majority of its functions take Readers or Writers as arguments and do useful things with them: e.g. Copy or LimitedReader. Most of the interfaces it defines (ReedSeeker, ReadWriteSeeker, etc) aren't instantiated by anything in it. os implements Reader and Writer for filehandles. net does the same for sockets etc. | | |
| ▲ | the_gipsy 21 hours ago | parent [-] | | It doesn't matter with `error` because it's returned everywhere, both functions and from packages by proxy. I'm not arguing that the tenet should be held true, to be clear. I'm saying that this tenet is misleading. If you can, return a concrete type. If several packages consume the same interface, then you it's not reasonable to define the interface at the consumer because you'd just have to copypaste it. | | |
| ▲ | 9rx 19 hours ago | parent [-] | | > I'm saying that this tenet is misleading. Doesn't that go without saying? There is no tenet that isn't misleading when presented to a general audience. Fair that if you come from a position where you understand the full context and nuance under which the tenet was built then you should be able to free yourself from being mislead, but, of course, this time is no exception. > If several packages consume the same interface, then you it's not reasonable to define the interface at the consumer because you'd just have to copypaste it. Where several packages find interface commonality, there is no doubt a set of "primary" functions that roll up shared functionality around that interface. The package of shared functions is understood to be the consumer under that scenario. Where several packages stumbled upon the same interface without any shared functionality, copy/pasting is warranted. In this case, while the interfaces may look the same, they do not carry the same intent and that needs to be communicated. Another oft-misunderstood tenet, do not repeat yourself, does not imply avoid repetitive code. | | |
| ▲ | the_gipsy 10 hours ago | parent [-] | | I don't quite see when a package is "understood to be the consumer" of... itself? We're talking about other packages importing an interface. I can give you a concrete example. I have a "storage" package, that exports multiple storage implementations/backends. Files, in-memory, S3, zip... Some other packages pick one of the implementations to instantiate, depending on the use case (this is NOT a case of mocking for testing or anything like that). Most other packages work with a "storage" interface, as they're only concerned with reading/writing to "storage". So the storage package, or in any case some package, has to export the interface. Otherwise, every consuming package would have to copypaste that interface, which is NOT warranted. | | |
| ▲ | 9rx 8 hours ago | parent [-] | | Actual code is always better, but based on the description it seems a bit strange that there would be a single package that exports multiple implementations. Presumably each distinct implementation should be its own package. None of these packages would export the interface. An additional package would export the interface and provide the common functionality around that interface. Those common functions would be considered the consumer. In fact, the standard library offers an example of exactly this: io/fs. | | |
| ▲ | the_gipsy 5 hours ago | parent [-] | | No, I cannot agree that this would be called the consumer. Yes, you have technically moved the interface type away from the implementation, but just for the sake of it, without any other upsides. The consumer is still the package that is using and importing this interface type, just from another package now. | | |
| ▲ | 9rx 2 hours ago | parent [-] | | That is the beauty of engineering: There is no universal truth, just different tradeoffs. Meaning that you don't need to agree, nor should you even seek agreement. You can and should forge your own path if different tradeoffs are warranted for your unique needs. But, this is the "idiomatic" approach. The upside is consistency for future readers. Code is for humans to read, after all. Most codebases follow this pattern, so it will be familiar when the next person encounters it. If you have a reason to do things differently, go for it. Nobody knows your problem better than you, so you cannot listen to others anyway. I am quite curious about what you see in the different tradeoffs you are accepting, though! What has you weighing them in favour? | | |
| ▲ | the_gipsy 16 minutes ago | parent [-] | | Sorry but I haven't really seen this pattern anywhere, care to give some examples? All libraries that I recall ever using always export a single package, including interfaces. I just took a look, and even io exports a bunch of structs along the interfaces they implement. And `error` is like a basic type of the runtime. |
|
|
|
|
|
|
|
|
|
|