The general principle of “accept the interface, return the structure”, which I wrote about in a previous article, has been introduced to my colleagues many times during code reviews, but the question of “why?” has often come up. Especially since it’s not a hard and fast rule. The key to this idea is to remain flexible while avoiding pre-abstraction, and to understand when to change it.

Pre-abstraction complicates the system

Any problem in computer science can be solved by adding an intermediate layer of indirection, except, of course, the problem of too much indirection – David J. Wheeler

Software engineers love abstraction. Personally, I have never seen a colleague who is more invested in writing code than in creating abstractions. In Go, the interface abstraction is removed from the structure, and the indirection layer does not have even the lowest level of embedding complexity. Following the philosophy of software design that you won’t use it, it makes no sense to create this complexity before you need it. A common reason for function calls to return interfaces is to keep the user focused on the API that the function is open to. Because of the implicit interface, Go does not need to do this. The structure’s public function IU is its API.

Always abstract when really needed, not when foreseen.

Some languages require you to anticipate every interface you’ll need in the future. One of the great advantages of implicit interfaces is that they allow for elegant abstractions after the fact, rather than up front.

Needs vary from person to person

When really needed

How do you define when abstraction is needed? For return types, this is easy. You’re the one who wrote the function, so you know exactly when you need to abstract the return value.

With function inputs, requirements are out of your control. You might think that database struct is sufficient, but the user might need to decorate it with something else. It is difficult, if not impossible, to predict the state of your function for everyone using it. Precise control of output, but no prediction of user input. This imbalance creates a stronger bias towards the abstraction of the input than the abstraction of the output.

Remove unnecessary code details

Another aspect of simplification is the removal of unnecessary details. Functions are like cooking recipes: given the input, you get a cake! No recipe lists ingredients that are not needed. Similarly, functions should not list unwanted inputs. What do you think of the following functions?

func addNumbers(a int, b int, s string) int { 
    return a + b 
}
Copy the code

It is obvious to most programmers that the parameter S is not appropriate. This is less obvious when the argument is a structure.

type Database struct{ } func (d *Database) AddUser(s string) {... } func (d *Database) RemoveUser(s string) {... } func NewUser(d *Database, firstName string, lastName string) { d.AddUser(firstName + lastName) }Copy the code

Like a recipe with too many ingredients, NewUser receives a Database object that can do too much. It only needs AddUser, but it also accepts RemoveUser as an argument. Functions created using interfaces can rely only on requirements.

type DatabaseWriter interface { 
    AddUser(string) 
} 
func NewUser(d DatabaseWriter, firstName string, lastName string) { 
    d.AddUser(firstName + lastName) 
}
Copy the code

Dave Cheney wrote about this when he described the interface isolation principle. He also describes other advantages of limiting input that are worth reading. The overall goal of getting the idea across is:

The result is also a function whose requirements are the most concrete — it needs only one thing to write — and whose function is the most general

I just want to add that the above function addNumber should obviously not have an argument string s, and the function NewUser ideally does not need a database that can delete users.

Summarize the causes and review the exceptions

The main reasons are as follows:

  • Remove unnecessary abstractions
  • The user’s requirements for function input are ambiguous
  • Simplified function input

The above reasons also allow us to define exceptions to the rule. For example, if a function needs to return multiple types, then obviously the returns need to be defined as interfaces. Similarly, if the function is private, then the input to the function is not ambiguous because you can control it, so it tends to be non-preabstract. For the third rule, go has no way to abstract the value of a struct member. Therefore, if your function needs to access the members of the structure (rather than just the functions on the structure), then you are forced to accept the structure directly.

原文 : What “accept interfaces, return structs” means in Go

This article is written by Cyningsun at www.cyningsun.com/08-08-2021/… Copyright Notice: All articles in this blog are under the CC BY-NC-ND 3.0CN license unless otherwise stated. Reprint please indicate the source!