From 10f2accf24ddda5a5e08eb8300243f66a3b83810 Mon Sep 17 00:00:00 2001 From: Lasse Martin Jakobsen Date: Thu, 11 Jul 2019 10:15:10 +0200 Subject: [PATCH 1/4] added suggestions from issue #1 --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6e5d0b8..b5ad653 100644 --- a/README.md +++ b/README.md @@ -1068,6 +1068,27 @@ func NewNullWriter() io.Writer { The above function ensures that the `NullWriter` struct implements the `Writer` interface. If we were to delete the `Write` method from `NullWriter`, we would get a compilation error. This is a good way of ensuring that our code behaves as expected and that we can rely on the compiler as a safety net in case we try to write invalid code. +In certain cases, it might not be desirable to write a constructor, or perhaps we would like for our constructor to return the concrete type, rather than the interface. As an example, the `NullWriter` struct has no properties to populate on initialisation and therefore writing a constructor is a little redundant. Therefore, we can use the less verbose method of checking interface compatibility: + +```go +type Writer interface { + Write(p []byte) (n int, err error) +} + +type NullWriter struct {} +var _ io.Writer = &NullWriter{} +``` + +In the above code, we are initialising a variable with the Go `blank identifier`, with the type assignment of `io.Writer`. This results in our variable being checked to fulfill the `io.Writer` interface contract, before being discarded. This method of checking interface fulfillment also makes it possible to check that several interface contracts are fulfilled: + +```go +type NullReaderWriter struct{} +var _ io.Writer = &NullWriter{} +var _ io.Reader = &NullWriter{} +``` + +From the above code, it's very easy to understand which interfaces must be fulfilled and will ensure that the compiler is helping us out on compile time. Therefore, this is generally the preferred solution for checking interface contract fulfillment. + There's yet another method of trying to be more explicit about which interfaces a given struct implements. However, this third method actually achieves the opposite of what we want. It involves using embedded interfaces as a struct property. > Wait what? – Presumably most people @@ -1141,6 +1162,8 @@ func (metadata *Metadata) AddUpdateInfo(user types.User) { Again, without breaking the rest of our codebase, we've managed to introduce new functionality. This kind of programming makes implementing new features very quick and painless, which is exactly what we are trying to achieve by writing clean code. +// TODO : Rewrite this section to be more sober + Now, I am sorry to break this streak of happiness—it's time that we enter the smelly forbidden forest of Go. Let's revisit the original problem of our interfaces: Trying to explicitly show which interfaces are being implemented by a given structure. Instead of embedding a struct, we can embed an interface: ```go @@ -1217,7 +1240,7 @@ func TestFn(t *testing.T) { > NOTE: There is actually already a null writer implementation built into the `ioutil` package named `Discard`. -When constructing our `Pipe` struct with `NullWriter` (rather than a different writer), when invoking our `Save` function, nothing will happen. The only thing we had to do was add four lines of code. This is why you're encouraged to make interfaces as small as possible in idiomatic Go—it makes it especially easy to implement patterns like the one we just saw. However, this implementation of interfaces also comes with a huge downside. +When constructing our `Pipe` struct with `NullWriter` (rather than a different writer), when invoking our `Save` function, nothing will happen. The only thing we had to do was add four lines of code. This is why it is encouraged to make interfaces as small as possible in idiomatic Go—it makes it especially easy to implement patterns like the one we just saw. However, this implementation of interfaces also comes with a huge downside. ### The Empty `interface{}` Unlike other languages, Go does not have an implementation for generics. There have been many proposals for one, but all have been turned down by the Go language team. Unfortunately, without generics, developers must try to find creative alternatives, which very often involves using the empty `interface{}`. This section describes why these often too creative implementations should be considered bad practice and unclean code. There will also be examples of appropriate usage of the empty `interface{}` and how to avoid some pitfalls of writing code with it. From 828fd65cfae14b04b0b5f6228c7715da14606937 Mon Sep 17 00:00:00 2001 From: Lasse Martin Jakobsen Date: Thu, 11 Jul 2019 10:16:50 +0200 Subject: [PATCH 2/4] added suggestions from issue #1 --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index b5ad653..39e7e24 100644 --- a/README.md +++ b/README.md @@ -1162,8 +1162,6 @@ func (metadata *Metadata) AddUpdateInfo(user types.User) { Again, without breaking the rest of our codebase, we've managed to introduce new functionality. This kind of programming makes implementing new features very quick and painless, which is exactly what we are trying to achieve by writing clean code. -// TODO : Rewrite this section to be more sober - Now, I am sorry to break this streak of happiness—it's time that we enter the smelly forbidden forest of Go. Let's revisit the original problem of our interfaces: Trying to explicitly show which interfaces are being implemented by a given structure. Instead of embedding a struct, we can embed an interface: ```go From cf168cce6eaf2ad3d6338d30bd70151407144204 Mon Sep 17 00:00:00 2001 From: Lasse Martin Jakobsen Date: Thu, 11 Jul 2019 13:20:04 +0200 Subject: [PATCH 3/4] Update README.md Co-Authored-By: Aleksandr H --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 39e7e24..d2d6314 100644 --- a/README.md +++ b/README.md @@ -1068,7 +1068,7 @@ func NewNullWriter() io.Writer { The above function ensures that the `NullWriter` struct implements the `Writer` interface. If we were to delete the `Write` method from `NullWriter`, we would get a compilation error. This is a good way of ensuring that our code behaves as expected and that we can rely on the compiler as a safety net in case we try to write invalid code. -In certain cases, it might not be desirable to write a constructor, or perhaps we would like for our constructor to return the concrete type, rather than the interface. As an example, the `NullWriter` struct has no properties to populate on initialisation and therefore writing a constructor is a little redundant. Therefore, we can use the less verbose method of checking interface compatibility: +In certain cases, it might not be desirable to write a constructor, or perhaps we would like for our constructor to return the concrete type, rather than the interface. As an example, the `NullWriter` struct has no properties to populate on initialisation, so writing a constructor is a little redundant. Therefore, we can use the less verbose method of checking interface compatibility: ```go type Writer interface { From 8530359ce440f21fdb705b2dbe3506d37db3e458 Mon Sep 17 00:00:00 2001 From: Lasse Martin Jakobsen Date: Thu, 11 Jul 2019 13:20:14 +0200 Subject: [PATCH 4/4] Update README.md Co-Authored-By: Aleksandr H --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2d6314..b16c8a0 100644 --- a/README.md +++ b/README.md @@ -1087,7 +1087,7 @@ var _ io.Writer = &NullWriter{} var _ io.Reader = &NullWriter{} ``` -From the above code, it's very easy to understand which interfaces must be fulfilled and will ensure that the compiler is helping us out on compile time. Therefore, this is generally the preferred solution for checking interface contract fulfillment. +From the above code, it's very easy to understand which interfaces must be fulfilled; this ensures that the compiler will help us out during compile time. Therefore, this is generally the preferred solution for checking interface contract fulfillment. There's yet another method of trying to be more explicit about which interfaces a given struct implements. However, this third method actually achieves the opposite of what we want. It involves using embedded interfaces as a struct property.