This article will describe the preemptive interface pattern that is often used in code and why I think it is often incorrect to follow this pattern in Go.

What is a preemptive interface

Interfaces are a way of describing behavior and exist in most type languages. A preemptive interface is one in which developers code the interface before the actual need arises. An example might look like this.

type Auth interface {
  GetUser() (User, error)
}

type authImpl struct {
  // ...
}
func NewAuth() Auth {
  return &authImpl
}
Copy the code

When preemptive interfaces are useful

Preemption interfaces are commonly used in Java, and with great success, as most programmers think. I believe that many Go developers feel the same way. The main difference in this usage is that Java has an explicit interface, whereas Go is an implicit interface. Let’s look at some sample Java code that shows the difficulties that can arise if you don’t use preemptive interfaces in Java.

// auth.java public class Auth { public boolean canAction() { // ... } } // logic.java public class Logic { public void takeAction(Auth a) { // ... }}Copy the code

Now suppose you want to change an object of type Auth as a parameter in Logic’s takeAction method, as long as it has a canAction() method. Unfortunately, you can’t. Auth does not implement an interface with canAction() in it. You must now modify Auth to provide it with an interface, which you can then accept in takeAction, or wrap Auth in a class that does nothing but implement methods. Even if logic.java defines an Auth interface to accept in takeAction(), it may be difficult to get Auth to implement that interface. You may not be able to modify Auth, or Auth may be in a third-party library. Maybe the Auth author disagrees with your changes. Perhaps sharing Auth with colleagues in a code base now requires consensus before making changes. This is the desired Java code.

// auth.java
public interface Auth {
  public boolean canAction()
}
// authimpl.java
class AuthImpl implements Auth {
}
// logic.java
public class Logic {
  public void takeAction(Auth a) {
    // ...
  }
}
Copy the code

If Auth’s author had originally coded and returned an interface, you would never have had a problem trying to extend takeAction. It works naturally with any Auth interface. In languages with explicit interfaces, you’ll thank your old self for using preemptive interfaces.

Why isn’t this a problem in Go

Let’s set up the same situation in Go.

// auth.go 
type Auth struct { 
// ... 
}
// logic.go 
func TakeAction(a *Auth) { 
  // ... 
}
Copy the code

If Logic wants to make TakeAction generic, the Logic owner can do this unilaterally without disturbing others.

// logic.go 
type LogicAuth interface { 
  CanAction() bool 
}
func TakeAction(a LogicAuth) { 
  // ... 
}
Copy the code

Please note that auth.go does not need to be changed. This is the key to making preemptive interfaces unnecessary.

An unintended side effect of the preemptive interface in Go

Go’s interface definitions are small but powerful. In the standard library, most interface definitions are single methods. This allows for maximum reuse because the interface is easy to implement. When programmers code a preemptive interface like Auth above, the number of methods on the interface tends to explode, making the full meaning of the interface (interchangeable implementation) harder to realize.

Best use of interfaces in Go

A good rule of thumb for Go is to accept the interface and return the structure. The accept interface provides maximum flexibility for your API, and the return structure allows the caller to quickly navigate to the correct function.

Even if your Go code accepts the structure and returns it to start, the implicit interface allows you to extend your API later without breaking backward compatibility. Interfaces are abstractions, and abstractions can be useful. However, unnecessary abstractions can lead to unnecessary complications. Don’t make your code too complicated before you need it.