Interface pollution

This commit is contained in:
Teiva Harsanyi 2024-03-05 21:59:42 +01:00
parent 26fd68e02c
commit 1af0884ef4
28 changed files with 1975 additions and 44 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -0,0 +1,257 @@
---
title: Interface pollution (#5)
comments: true
hide:
- toc
---
# Interface pollution
Interfaces are one of the cornerstones of the Go language when designing and structuring our code. However, like many tools or concepts, abusing them is generally not a good idea. Interface pollution is about overwhelming our code with unnecessary abstractions, making it harder to understand. Its a common mistake made by developers coming from another language with different habits. Before delving into the topic, lets refresh our minds about Gos interfaces. Then, we will see when its appropriate to use interfaces and when it may be considered pollution.
## Concepts
An interface provides a way to specify the behavior of an object. We use interfaces to create common abstractions that multiple objects can implement. What makes Go interfaces so different is that they are satisfied implicitly. There is no explicit keyword like `implements` to mark that an object X implements interface Y.
To understand what makes interfaces so powerful, we will dig into two popular ones from the standard library: `io.Reader` and `io.Writer`. The `io` package provides abstractions for I/O primitives. Among these abstractions, `io.Reader` relates to reading data from a data source and `io.Writer` to writing data to a target, as represented in the next figure:
<figure markdown>
![](img/ioreaderwriter.svg)
</figure>
The `io.Reader` contains a single Read method:
```go
type Reader interface {
Read(p []byte) (n int, err error)
}
```
Custom implementations of the `io.Reader` interface should accept a slice of bytes, filling it with its data and returning either the number of bytes read or an error.
On the other hand, `io.Writer` defines a single method, Write:
```go
type Writer interface {
Write(p []byte) (n int, err error)
}
```
Custom implementations of `io.Writer` should write the data coming from a slice to a target and return either the number of bytes written or an error. Therefore, both interfaces provide fundamental abstractions:
* `io.Reader` reads data from a source
* `io.Writer` writes data to a target
What is the rationale for having these two interfaces in the language? What is the point of creating these abstractions?
Lets assume we need to implement a function that should copy the content of one file to another. We could create a specific function that would take as input two `*os.Files`. Or, we can choose to create a more generic function using `io.Reader` and `io.Writer` abstractions:
```go
func copySourceToDest(source io.Reader, dest io.Writer) error {
// ...
}
```
This function would work with `*os.File` parameters (as `*os.File` implements both `io.Reader` and `io.Writer`) and any other type that would implement these interfaces. For example, we could create our own `io.Writer` that writes to a database, and the code would remain the same. It increases the genericity of the function; hence, its reusability.
Furthermore, writing a unit test for this function is easier because, instead of having to handle files, we can use the `strings` and `bytes` packages that provide helpful implementations:
```go
func TestCopySourceToDest(t *testing.T) {
const input = "foo"
source := strings.NewReader(input) // Creates an io.Reader
dest := bytes.NewBuffer(make([]byte, 0)) // Creates an io.Writer
err := copySourceToDest(source, dest) // Calls copySourceToDest from a *strings.Reader and a *bytes.Buffer
if err != nil {
t.FailNow()
}
got := dest.String()
if got != input {
t.Errorf("expected: %s, got: %s", input, got)
}
}
```
In the example, source is a `*strings.Reader`, whereas dest is a `*bytes.Buffer`. Here, we test the behavior of `copySourceToDest` without creating any files.
While designing interfaces, the granularity (how many methods the interface contains) is also something to keep in mind. A [known proverb](https://www.youtube.com/watch?v=PAAkCSZUG1c&t=318s) in Go relates to how big an interface should be:
!!! quote "Rob Pike"
The bigger the interface, the weaker the abstraction.
Indeed, adding methods to an interface can decrease its level of reusability. `io.Reader` and `io.Writer` are powerful abstractions because they cannot get any simpler. Furthermore, we can also combine fine-grained interfaces to create higher-level abstractions. This is the case with `io.ReadWriter`, which combines the reader and writer behaviors:
```go
type ReadWriter interface {
Reader
Writer
}
```
???+ note
As Einstein said, “_Everything should be made as simple as possible, but no simpler._” Applied to interfaces, this denotes that finding the perfect granularity for an interface isnt necessarily a straightforward process.
Lets now discuss common cases where interfaces are recommended.
## When to use interfaces
When should we create interfaces in Go? Lets look at three concrete use cases where interfaces are usually considered to bring value. Note that the goal isnt to be exhaustive because the more cases we add, the more they would depend on the context. However, these three cases should give us a general idea:
* Common behavior
* Decoupling
* Restricting behavior
### Common behavior
The first option we will discuss is to use interfaces when multiple types implement a common behavior. In such a case, we can factor out the behavior inside an interface. If we look at the standard library, we can find many examples of such a use case. For example, sorting a collection can be factored out via three methods:
* Retrieving the number of elements in the collection
* Reporting whether one element must be sorted before another
* Swapping two elements
Hence, the following interface was added to the `sort` package:
```go
type Interface interface {
Len() int // Number of elements
Less(i, j int) bool // Checks two elements
Swap(i, j int) // Swaps two elements
}
```
This interface has a strong potential for reusability because it encompasses the common behavior to sort any collection that is index-based.
Throughout the `sort` package, we can find dozens of implementations. If at some point we compute a collection of integers, for example, and we want to sort it, are we necessarily interested in the implementation type? Is it important whether the sorting algorithm is a merge sort or a quicksort? In many cases, we dont care. Hence, the sorting behavior can be abstracted, and we can depend on the `sort.Interface`.
Finding the right abstraction to factor out a behavior can also bring many benefits. For example, the `sort` package provides utility functions that also rely on `sort.Interface`, such as checking whether a collection is already sorted. For instance:
```go
func IsSorted(data Interface) bool {
n := data.Len()
for i := n - 1; i > 0; i-- {
if data.Less(i, i-1) {
return false
}
}
return true
}
```
Because `sort.Interface` is the right level of abstraction, it makes it highly valuable.
Lets now see another main use case when using interfaces.
### Decoupling
Another important use case is about decoupling our code from an implementation. If we rely on an abstraction instead of a concrete implementation, the implementation itself can be replaced with another without even having to change our code. This is the Liskov Substitution Principle (the L in Robert C. Martins [SOLID](https://en.wikipedia.org/wiki/SOLID) design principles).
One benefit of decoupling can be related to unit testing. Lets assume we want to implement a `CreateNewCustomer` method that creates a new customer and stores it. We decide to rely on the concrete implementation directly (lets say a `mysql.Store` struct):
```go
type CustomerService struct {
store mysql.Store // Depends on the concrete implementation
}
func (cs CustomerService) CreateNewCustomer(id string) error {
customer := Customer{id: id}
return cs.store.StoreCustomer(customer)
}
```
Now, what if we want to test this method? Because `customerService` relies on the actual implementation to store a `Customer`, we are obliged to test it through integration tests, which requires spinning up a MySQL instance (unless we use an alternative technique such as [`go-sqlmock`](https://github.com/DATA-DOG/go-sqlmock), but this isnt the scope of this section). Although integration tests are helpful, thats not always what we want to do. To give us more flexibility, we should decouple `CustomerService` from the actual implementation, which can be done via an interface like so:
```go
type customerStorer interface { // Creates a storage abstraction
StoreCustomer(Customer) error
}
type CustomerService struct {
storer customerStorer // Decouples CustomerService from the actual implementation
}
func (cs CustomerService) CreateNewCustomer(id string) error {
customer := Customer{id: id}
return cs.storer.StoreCustomer(customer)
}
```
Because storing a customer is now done via an interface, this gives us more flexibility in how we want to test the method. For instance, we can:
* Use the concrete implementation via integration tests
* Use a mock (or any kind of test double) via unit tests
* Or both
Lets now discuss another use case: to restrict a behavior.
### Restricting behavior
The last use case we will discuss can be pretty counterintuitive at first sight. Its about restricting a type to a specific behavior. Lets imagine we implement a custom configuration package to deal with dynamic configuration. We create a specific container for `int` configurations via an `IntConfig` struct that also exposes two methods: `Get` and `Set`. Heres how that code would look:
```go
type IntConfig struct {
// ...
}
func (c *IntConfig) Get() int {
// Retrieve configuration
}
func (c *IntConfig) Set(value int) {
// Update configuration
}
```
Now, suppose we receive an `IntConfig` that holds some specific configuration, such as a threshold. Yet, in our code, we are only interested in retrieving the configuration value, and we want to prevent updating it. How can we enforce that, semantically, this configuration is read-only, if we dont want to change our configuration package? By creating an abstraction that restricts the behavior to retrieving only a config value:
```go
type intConfigGetter interface {
Get() int
}
```
Then, in our code, we can rely on `intConfigGetter` instead of the concrete implementation:
```go
type Foo struct {
threshold intConfigGetter
}
func NewFoo(threshold intConfigGetter) Foo { // Injects the configuration getter
return Foo{threshold: threshold}
}
func (f Foo) Bar() {
threshold := f.threshold.Get() // Reads the configuration
// ...
}
```
In this example, the configuration getter is injected into the `NewFoo` factory method. It doesnt impact a client of this function because it can still pass an `IntConfig` struct as it implements `intConfigGetter`. Then, we can only read the configuration in the `Bar` method, not modify it. Therefore, we can also use interfaces to restrict a type to a specific behavior for various reasons, such as semantics enforcement.
In this section, we saw three potential use cases where interfaces are generally considered as bringing value: factoring out a common behavior, creating some decoupling, and restricting a type to a certain behavior. Again, this list isnt exhaustive, but it should give us a general understanding of when interfaces are helpful in Go.
Now, lets finish this section and discuss the problems with interface pollution.
## Interface pollution
Its fairly common to see interfaces being overused in Go projects. Perhaps the developers background was C# or Java, and they found it natural to create interfaces before concrete types. However, this isnt how things should work in Go.
As we discussed, interfaces are made to create abstractions. And the main caveat when programming meets abstractions is remembering that **abstractions should be discovered, not created**. What does this mean? It means we shouldnt start creating abstractions in our code if there is no immediate reason to do so. We shouldnt design with interfaces but wait for a concrete need. Said differently, we should create an interface when we need it, not when we foresee that we could need it.
Whats the main problem if we overuse interfaces? The answer is that they make the code flow more complex. Adding a useless level of indirection doesnt bring any value; it creates a worthless abstraction making the code more difficult to read, understand, and reason about. If we dont have a strong reason for adding an interface and its unclear how an interface makes a code better, we should challenge this interfaces purpose. Why not call the implementation directly?
???+ note
We may also experience performance overhead when calling a method through an interface. It requires a lookup in a hash tables data structure to find the concrete type an interface points to. But this isnt an issue in many contexts as the overhead is minimal.
In summary, we should be cautious when creating abstractions in our code—abstractions should be discovered, not created. Its common for us, software developers, to overengineer our code by trying to guess what the perfect level of abstraction is, based on what we think we might need later. This process should be avoided because, in most cases, it pollutes our code with unnecessary abstractions, making it more complex to read.
!!! quote "Rob Pike"
Dont design with interfaces, discover them.
Lets not try to solve a problem abstractly but solve what has to be solved now. Last, but not least, if its unclear how an interface makes the code better, we should probably consider removing it to make our code simpler.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -116,20 +116,7 @@ Remember that Go is a unique language designed for many characteristics, includi
Abstractions should be discovered, not created. To prevent unnecessary complexity, create an interface when you need it and not when you foresee needing it, or if you can at least prove the abstraction to be a valid one.
An interface provides a way to specify the behavior of an object. We use interfaces to create common abstractions that multiple objects can implement. What makes Go interfaces so different is that they are satisfied implicitly. There is no explicit keyword like `implements` to mark that an object `X` implements interface `Y`.
In general, we can define three main use cases where interfaces are generally considered as bringing value: factoring out a common behavior, creating some decoupling, and restricting a type to a certain behavior. Yet, this list isn't exhaustive and will also depend on the context we face.
In many occasions, interfaces are made to create abstractions. And the main caveat when programming meets abstractions is remembering that abstractions should be discovered, not created. What does this mean? It means we shouldnt start creating abstractions in our code if there is no immediate reason to do so. We shouldnt design with interfaces but wait for a concrete need. Said differently, we should create an interface when we need it, not when we foresee that we could need it.
Whats the main problem if we overuse interfaces? The answer is that they make the code flow more complex. Adding a useless level of indirection doesnt bring any value; it creates a worthless abstraction making the code more difficult to read, understand, and reason about. If we dont have a strong reason for adding an interface and its unclear how an interface makes a code better, we should challenge this interfaces purpose. Why not call the implementation directly?
We should be cautious when creating abstractions in our code (abstractions should be discovered, not created). Its common for us, software developers, to overengineer our code by trying to guess what the perfect level of abstraction is, based on what we think we might need later. This process should be avoided because, in most cases, it pollutes our code with unnecessary abstractions, making it more complex to read.
!!! quote "Rob Pike"
Dont design with interfaces, discover them.
Lets not try to solve a problem abstractly but solve what has to be solved now. Last, but not least, if its unclear how an interface makes the code better, we should probably consider removing it to make our code simpler.
Read the full section [here](5-interface-pollution.md).
[:simple-github: Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/5-interface-pollution/)

View file

@ -68,6 +68,7 @@ nav:
- Go Mistakes:
- index.md
- Full Sections:
- 5-interface-pollution.md
- 9-generics.md
- 20-slice.md
- 28-maps-memory-leaks.md

View file

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block announce %}
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
{% endblock %}
{% block analytics %}

View file

@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -626,6 +626,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

View file

@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -626,6 +626,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

View file

@ -75,7 +75,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -570,6 +570,27 @@
<li class="md-nav__item">
<a href="/5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="/9-generics/" class="md-nav__link">

File diff suppressed because it is too large Load diff

View file

@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -626,6 +626,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

View file

@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -626,6 +626,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

View file

@ -11,7 +11,7 @@
<link rel="canonical" href="https://100go.co/9-generics/">
<link rel="prev" href="..">
<link rel="prev" href="../5-interface-pollution/">
<link rel="next" href="../20-slice/">
@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -624,6 +624,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>

View file

@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -626,6 +626,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

View file

@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -626,6 +626,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View file

@ -119,7 +119,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -697,6 +697,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

View file

@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -768,6 +768,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

View file

@ -119,7 +119,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -618,6 +618,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

View file

@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -856,6 +856,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -14,7 +14,7 @@
<link rel="prev" href="external/">
<link rel="next" href="9-generics/">
<link rel="next" href="5-interface-pollution/">
<link rel="icon" href="img/Go-Logo_LightBlue.svg">
@ -121,7 +121,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -1828,6 +1828,27 @@
<li class="md-nav__item">
<a href="5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="9-generics/" class="md-nav__link">
@ -3297,16 +3318,7 @@
<summary>TL;DR</summary>
<p>Abstractions should be discovered, not created. To prevent unnecessary complexity, create an interface when you need it and not when you foresee needing it, or if you can at least prove the abstraction to be a valid one.</p>
</details>
<p>An interface provides a way to specify the behavior of an object. We use interfaces to create common abstractions that multiple objects can implement. What makes Go interfaces so different is that they are satisfied implicitly. There is no explicit keyword like <code>implements</code> to mark that an object <code>X</code> implements interface <code>Y</code>.</p>
<p>In general, we can define three main use cases where interfaces are generally considered as bringing value: factoring out a common behavior, creating some decoupling, and restricting a type to a certain behavior. Yet, this list isn't exhaustive and will also depend on the context we face.</p>
<p>In many occasions, interfaces are made to create abstractions. And the main caveat when programming meets abstractions is remembering that abstractions should be discovered, not created. What does this mean? It means we shouldnt start creating abstractions in our code if there is no immediate reason to do so. We shouldnt design with interfaces but wait for a concrete need. Said differently, we should create an interface when we need it, not when we foresee that we could need it.
Whats the main problem if we overuse interfaces? The answer is that they make the code flow more complex. Adding a useless level of indirection doesnt bring any value; it creates a worthless abstraction making the code more difficult to read, understand, and reason about. If we dont have a strong reason for adding an interface and its unclear how an interface makes a code better, we should challenge this interfaces purpose. Why not call the implementation directly?</p>
<p>We should be cautious when creating abstractions in our code (abstractions should be discovered, not created). Its common for us, software developers, to overengineer our code by trying to guess what the perfect level of abstraction is, based on what we think we might need later. This process should be avoided because, in most cases, it pollutes our code with unnecessary abstractions, making it more complex to read.</p>
<div class="admonition quote">
<p class="admonition-title">Rob Pike</p>
<p>Dont design with interfaces, discover them.</p>
</div>
<p>Lets not try to solve a problem abstractly but solve what has to be solved now. Last, but not least, if its unclear how an interface makes the code better, we should probably consider removing it to make our code simpler.</p>
<p>Read the full section <a href="5-interface-pollution/">here</a>.</p>
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/5-interface-pollution/"><span class="twemoji"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg></span> Source code</a></p>
<h3 id="interface-on-the-producer-side-6">Interface on the producer side (#6)</h3>
<details class="info" open="open">

View file

@ -117,7 +117,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -612,6 +612,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

View file

@ -117,7 +117,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -614,6 +614,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">

File diff suppressed because one or more lines are too long

View file

@ -15,6 +15,11 @@
<lastmod>2024-03-05</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://100go.co/5-interface-pollution/</loc>
<lastmod>2024-03-05</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://100go.co/56-concurrency-faster/</loc>
<lastmod>2024-03-05</lastmod>

Binary file not shown.

View file

@ -117,7 +117,7 @@
<div class="md-banner__inner md-grid md-typeset">
📢 A new section is now fully available: <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
📢 Two new full sections are now available: <a href="https://100go.co/5-interface-pollution/">#5: Interface pollution</a> and <a href="https://100go.co/92-false-sharing/">#92: Writing concurrent code that leads to false sharing</a>.
</div>
@ -612,6 +612,27 @@
<li class="md-nav__item">
<a href="../5-interface-pollution/" class="md-nav__link">
<span class="md-ellipsis">
Interface pollution (#5)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../9-generics/" class="md-nav__link">