- We're going to start talking
about some design related material now,
and this is going to be around composition.
There's a lot of things to talk about around composition,
and composition is something
that you're really going to be focused on
quite a bit as a Go developer,
even much more than all of the concurrency
multithreaded stuff.
What we want to start off with here is the concept
of grouping,
because grouping is going to be one of these things
where I've really got to kind of break you down
and build you back up.
Things are going to be a little different here in Go.
Now, every once in a while, a couple times a year,
I'll have somebody who I don't know reach out to me,
maybe on Slack, and they'll say,
"Bill, I'm a new Go developer, and everything was good,
"but I've kind hit a roadblock
"and I don't understand what's happening.
"Do you think you can help?"
I'll be like, "Yeah, absolutely, sure, no problem.
"Show me the code."
9 times out of 10, exactly, this is exactly what I see.
I see this Animal type,
and normally, the conversation kind of goes like this.
"Oh, Bill, man, I'm learning Go, and I'm coming
"from some object-oriented programming language.
"I was really getting the hang of this,
"I really thought I could be a Go developer, Bill.
"Look, look what was happening.
"I have this Animal type, right, an Animal,
"it's a base type, Bill, for anything that's an Animal.
"It's got a Name, it's got an IsMammal, right.
"These are common fields, but right, you understand it.
"You know what, even Animals, I learned
"that I can add behaviors,
"so here's this Animal that knows how to Speak, there it is.
"I know, Bill, animals generically don't know how to speak,
"but I was able to add this behavior.
"This was all making sense to me.
"Bill, if you're going to have a Animal,
"then you have to have a Dog, and look,
"I was using embedding, just like,
"to be able to say that a Dog is everything an Animal is,
"and I extended it, right, Bill.
"We can do that with inheritance and stuff,
"but I'm doing it with embedding.
"Life was good, and then I really made
"this Dog be able to speak properly, 'Woof!'
"There it is, right there, Bill, right,
"like you see it there, Dog.
"Bill, if you're going to have a Dog,
"then you've got to have a Cat,
"and here's the Cat right there.
"Cat also embeds the Animal,
"because a Cat is everything an Animal is, right,
"anti-promotion, Bill.
"Then I made the Cat speak, 'Meow,' there it is right there.
"I got this Dog and I got this Cat, and everything was good,
"like this is exactly how I write code,
"and everything was good.
"But then, Bill, I ran into a problem.
"You see, I wanted to group the Dogs and Cats together
"and I tried to group them by what they are,
"which is an Animal.
"I could do this before, but Go is not letting me do this.
"This code is not compiling, Bill.
"Why is it that I cannot group Dogs and Cats
"by what they are, which is an Animal?"
OK, this is one of the very first things
you've got to realize.
It goes very strong on their type system,
and the fact that you've embedded Animal into Dog and Cat
means zero, it means nothing,
there is no subtyping, there is no subclassing in Go.
Animals are animals, dogs are dogs, and cats are cats.
I can understand how this developer has kind of
gotten stuck here.
How do we answer this question,
what is the problem here and how do we solve it?
The first thing I want you to understand
that Go is about convention over configuration.
Remember that.
Go says the configuration is limiting.
It really is.
I mean, if I'm in a room in a classroom situation
with 15, 20 people, I will ask them,
"Is anybody in this room a sibling,
"or does anybody have brothers, sisters, siblings?"
Normally the answer is no.
I say, "Well, look, if we really wanted to group people
"in this classroom based on who we are,
"based on configuration, we wouldn't be able to do it.
"We'd all be isolated from each other,
"because unless we have the same parents,
"there's no configuration in the room."
Go is very, very against that kind
of limiting configuration.
Go is really about diversity.
Go is about not who we are, animals, in a sense,
but Go is about what we do.
If we look and group things by what we do
and not by who we are, then we have a level of diversity.
We can all in that classroom be paired up together
individually, in different groups.
There's almost an infinite number of ways
we can group ourselves,
because I'm sure I can find some commonality with you
on something that we enjoy to do.
Obviously, I think we enjoy programming in Go.
That's a behavior that we can group ourselves behind.
Go is very much about, let's not limit ourselves
on configuration, but let's focus on what we do.
In this code, we have the behavior we're looking for.
It's Speak, dogs know how to speak, cats know how to speak.
We can group everything by what we do.
Now, I also, at this point, I also want to be able
to start reading code and identify smells.
This is very important.
When we see smells in code, it becomes also critical
for our ability to be able to talk about, verbalize,
explain, have the right language to help people understand
what the smells are and how to fix it.
There's a lot of smells in this code as well
when I go through it.
Look, just alone, when I see that Animal type right there,
when I see that Animal type,
I notice that this is really a generic type
just being placed in the code for reusable state.
I'm really always concerned about that.
The bigger smell, too, is that I've defined a type
called Animal, and yet we're not really using it.
I do not need a value of type Animal in this program
in order for the program to be able to work.
I could get rid of Animal,
embed those fields in Cat and Dog,
and I wouldn't have to break a single method or function.
That, again, is a smell.
I only want types where we need values of that type.
I want to be very, very clear
that to define a type, literally for reusable state,
is a smell, there are exceptions to every rule.
But because we don't need an Animal, guess what?
We don't need the type.
From my perspective, it's type pollution.
Believe it or not, a little copying and pasting
can go a very long way.
We've been taught these concepts of DRY,
Do not Repeat Yourself.
But I think the cost of Do not Repeat Yourself in Go
is worse than maybe some of these other languages
you've worked on.
Coupling is always going to be a bigger cost
than a little duplication.
We'll continue to see that as we move on.
Also, when we look at the Speak implementation for Animal,
it is completely useless.
There's no value in it.
Think about what we've done.
I've added a type, I've added a method called Speak,
these things need tests, which can't really be accurate.
They're not used.
This is code that we have to, now,
increase our lines of code, increase our bug potential,
increase our test cases,
and it adds zero value to the software.
We really, really, really don't want
to be polluting software like that.
I understand where this developer's coming from
with the idea of grouping things by animal
and creating this reasonable state.
But this is not how, at least, I want you
to design and architect and write code in the future.
Then, how do we correct this program?
What are we going to tell this developer?
Well, simply what I'm going to tell the developer is,
stop thinking about who you are.
Stop thinking about what cats and dogs are
and start focusing on what cats and dogs do.
This is what's important, what cats and dogs do.
It's configuration, no, convention,
convention over configuration.
Let's do this instead.
Let's remove the Animal type, it is pure pollution
as far as I'm concerned.
Let's just bring in an interface,
the Speaker interface, that behavior.
Remember, interfaces should define behavior,
not persons, places, and things.
I want the Speaker interface.
If I saw an Animal interface here,
I'd be very, very sad.
That's not it.
Let's keep the concrete types
as those persons, places, and things,
and let's have the interface describe the behavior.
That's the convention we're looking for.
OK, so we've got the Speaker interface,
one active behavior, Speak.
Then when what I'm going to do is a little copy and paste.
Yes, I'm going to copy those common fields into Dog and Cat.
You might say, "No, no, no, no, Bill, what are you doing?
"What if the common fields have to change?"
I'm going to then go and change what needs to be changed,
I'm going to refactor what needs to refactor.
This is going to make code more readable
and easier to debug and easier to test.
I promise you, because it's always there in front of you.
Dog is more precise, Cat is more precise.
These things I have learned over decades
of software development
that these benefits outweigh that cost of DRY.
There's my Dog, Name, IsMammal, Pack,
here's the Speak again, here's Cat again.
We just copied a couple of fields in there.
Here's Speak.
Now we're able to create a collection of Cats and Dogs
by what they do, what they do.
This is brilliant stuff,
because the compiler is dynamic in its ability
to do static code analysis.
We're able to leverage convention, very, very powerful here.
We're going to see, in our next section,
what we really show how composition
can help us design code that's really decoupled from change.
But before we look at that next example,
let's just go through these notes,
because I want to give you some guidelines
around declaring types that we're going to be using
as we move on.
I really want you to focus on declaring types
that represent something new or unique,
always new or unique.
I really want you to avoid the concepts of aliasing.
When I see a type like this,
let me just throw this out here.
If I see somebody do a type like this, handle int,
I start to cringe a little bit.
I try to say, "Is handle something really new and unique,
"or are you just trying to abstract the concept
"that a handle is an integer?"
If I don't see a method set after this type,
it's a good, good indication
that maybe we're trying to do some sort
of abstraction, really, of aliasing, right.
But what I'm going to say is this.
You might say, "Well, Bill, I want to do that,
"because I want to write a function
"that can only take a handle
"and I want the compiler to protect me from that."
I understand other programming languages,
this would really be possible.
But it's not possible in Go.
Remember, that constant of a kind
can be implicitly converted.
That code on line 89 there would be compilable.
We would be able to compile that.
Since you can't get that protection,
especially on a type like handle
that's based on a built in type,
then I don't want you to do it at all.
If you can't have it 100% of the time, you can't have it.
For me, a better API design here for handle
would be the variable name being called handle
and the type being int, because handles really are int.
Don't abstract or alias what the data actually is.
If you cannot define a type
that truly represents something new,
something that isn't truly the base type, well, guess what?
I don't want you to have it.
Again, the clear example of that is Duration.
Duration and the time package is an int64,
it's based on int64.
But really, Duration doesn't represent an integer,
it represents nanoseconds of time.
I want you to see the difference
between handle and Duration,
it does represent something new.
There's actually method sets around that.
We need to always validate, that if you define a type,
that we are creating values of that type,
and we need to use values of a type on their own.
If we're not, then you've defined a type
purely as an abstraction layer of reusable state,
and I want that to be a big, big smell.
Another thing here is we're going to learn,
we're going to embed.
Embed types for behavior, not for state.
Remember, we're decoupling, decoupling's based on behavior,
the concrete data that has the behavior drives it.
But if I see embedding and it's not clear to me
why we're doing it, the behavior,
I'm going to ask you, I want to know
what behavior we are trying to promote
or gain access to through the embedding?
Question any type that is an alias
or an abstraction of an existing type also.
Always question types whose sole purpose
is to share common state.
We always want to be focusing on behavior.