Skip to content

Commit

Permalink
fix typo (hoanhan101#24)
Browse files Browse the repository at this point in the history
* method, interface typo fixed

* embedding & exporting typo fixed

* Grouping Types Typo fixed

* decoupling typo fixed

* concrete can be noun

* Conversion & Pollution typo fixed, reworded

* Mocking restructured, typo fixed

* Error 1-3 typo fixed

* error 4-6 typo fixed, error 5 repurposed

* error_5.go reverted for more discussion
  • Loading branch information
Thammachart authored and Hoanh An committed Nov 15, 2019
1 parent d5c89b1 commit f9d6c6f
Show file tree
Hide file tree
Showing 11 changed files with 76 additions and 64 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ or several links next to it.
- [Error variables](go/design/error_2.go)
- [Type as context](go/design/error_3.go)
- [Behavior as context](go/design/error_4.go)
- [Finding the bug](go/design/error_5.go)
- [Finding the bug/pitfall of nil value of error interface](go/design/error_5.go)
- [Wrapping Errors](go/design/error_6.go)
- Packaging:
[Guideline](https://github.com/ardanlabs/gotraining/blob/master/topics/go/design/packaging/README.md)
Expand Down
12 changes: 10 additions & 2 deletions go/design/conversion_1.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func main() {
// ------ ------
// | bike | bike | bike |
// ------ ------ ------
// | * | ---> | | <--- | |
// | * | ---> | | <--- | * |
// ------ ------ ------

// However, we cannot go in the other direction, like so:
Expand All @@ -82,11 +82,19 @@ func main() {
// value of type bike that was stored inside of it. Then assign the COPY of the concrete type
// to the MoveLocker interface.


// This is the syntax for type assertion.
// We are taking the interface value itself, dot (bike). We are using bike as an parameter.
// If there is a bike inside of m, we will get a copy of it since we are using value semantic.
// If m is not nil and there is a bike inside of m, we will get a copy of it since we are using value semantic.
// Or else, a panic occurs.
// b is having a copy of bike value.
b := m.(bike)

// We can prevent panic when type assertion breaks by destructuring
// the boolean value that represents type assertion result
b, ok := m.(bike)
fmt.Println("Does m has value of bike?:", ok)

ml = b

// It's important to note that the type assertion syntax provides a way to state what type
Expand Down
22 changes: 10 additions & 12 deletions go/design/conversion_2.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,26 @@ func main() {

// Perform a type assertion that we have a concrete type of cloud in the interface
// value we randomly chose.
// This shows us that this checking is at runtime, not compiled time.
// This shows us that this checking is at runtime, not compile time.
if v, ok := mvs[rn].(cloud); ok {
fmt.Println("Got Lucky:", v)
continue
}

// This shows us that this checking is at runtime, not compiled time. We have to decide
// if there should always be a value of some type that should never change.
// We wouldn't want to use that ok because we want it to panic if there is a integrity
// issue. We must shut it down immediately if that happen. If we cannot recover from a
// panic and give the guarantee that you are back at 100% integrity, the software has to be
// restarted. Shutting downs means you have to either call log.Fatal or os.exit, or panic
// for stack trace.
// When we use type assertion, we need to understand where or not it is okay that whatever
// We have to guarantee that variable in question (x in `x.(T)`) can always be asserted correctly as T type

// Or else, We wouldn't want to use that ok variable because we want it to panic if there is an integrity
// issue. We must shut it down immediately if that happens if we cannot recover from a
// panic and guarantee that we are back at 100% integrity, the software has to be restarted.
// Shutting down means you have to call log.Fatal, os.exit, or panic for stack trace.
// When we use type assertion, we need to understand when it is okay that whatever
// we are asking for is not there.

// Important note:
// ---------------
// When we are using type assertion, it raises a big red flag.
// If the type assertion is causing us to call the concrete value out, that should raise a
// If the type assertion is causing us to call the concrete value out, that should raise a big
// flag. We are using interface to maintain a level of decoupling and now we are using type
// asserting to go back to the concrete.
// assertion to go back to the concrete.
// When we are in the concrete, we are putting our codes in the situation where cascading
// changes can cause widespread refactoring. What we want with interface is the opposite,
// internal changes minimize cascading changes.
Expand Down
4 changes: 2 additions & 2 deletions go/design/error_1.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ package main
import "fmt"

// http://golang.org/pkg/builtin/#error
// This is built in the language so it looks like an unexported type. It has one active behavior,
// This is pre-included in the language so it looks like an unexported type. It has one active behavior,
// which is Error returned a string.
// Error handling is decoupled because we are always working with error interface when we are
// testing our code.
Expand Down Expand Up @@ -62,7 +62,7 @@ func main() {
// We are calling webCall and return the error interface and store that in a variable.
// nil is a special value in Go. What "error != nil" actually means is that we are asking if
// there is a concrete type value that is stored in error type interface. Because if error is
// not nil, there is a concrete value stored inside. If that's is the case, we've got an error.
// not nil, there is a concrete value stored inside. If it is the case, we've got an error.
// Now do we handle the error, do we return the error up the call stack for someone else to
// handle? We will talk about this latter.
if err := webCall(); err != nil {
Expand Down
12 changes: 6 additions & 6 deletions go/design/error_3.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// It is not always possible to be able to say the interface value itself will be enough context.
// Sometimes, it requires more context. For example, a networking problem can be really
// complicated. Error variables wouldn't work there.
// Only when the error variables wouldn't work are we allowed to go ahead and start working with
// Only when the error variables wouldn't work, we should go ahead and start working with
// custom concrete type for the error.

// Below are two custom error types from the json package in the standard library and see how we
Expand All @@ -27,11 +27,11 @@ type UnmarshalTypeError struct {
Type reflect.Type // type of Go value it could not be assigned to
}

// Error implements the error interface.
// UnmarshalTypeError implements the error interface.
// We are using pointer semantic.
// In the implementation, we are validating all the fields are being used in the error message. If
// not, we have a problem. Because why would you add a field to the custom error type and not
// displaying your log if this method would call. We only do this when we really need it.
// displaying on your log when this method would call. We only do this when we really need it.
func (e *UnmarshalTypeError) Error() string {
return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
}
Expand All @@ -43,7 +43,7 @@ type InvalidUnmarshalError struct {
Type reflect.Type
}

// Error implements the error interface.
// InvalidUnmarshalError implements the error interface.
func (e *InvalidUnmarshalError) Error() string {
if e.Type == nil {
return "json: Unmarshal(nil)"
Expand Down Expand Up @@ -97,8 +97,8 @@ func Unmarshal(data []byte, v interface{}) error {

// There is one flaw when using type as context here. In this case, we are now going back to the
// concrete. We walk away from the decoupling because our code is now bounded to these concrete
// types. If the developer who wrote the json package makes any changes to these conretey types,
// that's gonna create a cascading effect all the way through our code. We are no longer proteced
// types. If the developer who wrote the json package makes any changes to these concrete types,
// that's gonna create a cascading effect all the way through our code. We are no longer protected
// by the decoupling of the error interface.

// This sometime has to happen. Can we do something differnt not to lose the decoupling. This is
Expand Down
14 changes: 6 additions & 8 deletions go/design/error_4.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (c *client) TypeAsContext() {
line, err := c.reader.ReadString('\n')
if err != nil {
// This is using type as context like the previous example.
// What special here is the method Temporary. If it is, we can keep going but if not,
// What special here is the method named Temporary. If it is, we can keep going but if not,
// we have to break thing down and build thing back up.
// Every one of these cases care only about 1 thing: the behavior of Temporary. This is
// what important. We can switch here, from type as context to type as behavior if we
Expand Down Expand Up @@ -70,7 +70,7 @@ func (c *client) TypeAsContext() {
}

// temporary is declared to test for the existence of the method coming from the net package.
// Because Temporary this the only behavior we care about. If the concrete type has the method
// Because Temporary is the only behavior we care about. If the concrete type has the method
// named temporary then this is what we want. We get to stay decoupled and continue to work at the
// interface level.
type temporary interface {
Expand All @@ -86,7 +86,7 @@ func (c *client) BehaviorAsContext() {
switch e := err.(type) {
// We can reduce 3 cases into 1 by asking in the case here during type assertion: Does
// the concrete type stored inside the error interface also implement this interface.
// We can declare that interface for myself and we leverage it ourselves.
// We can declare and leverage that interface ourselves.
case temporary:
if !e.Temporary() {
log.Println("Temporary: Client leaving chat")
Expand All @@ -108,8 +108,6 @@ func (c *client) BehaviorAsContext() {
}

// Lesson:
// If we can one of these methods to our concrete error type, we can maintain a level of decoupling:
// - Temporary
// - Timeout
// - NotFound
// - NotAuthorized
// Thank to Go Implicit Conversion.
// We can maintain a level of decopling by creating an interface with methods or behaviors that we only want,
// and use it instead of concrete type for type assertion switch.
18 changes: 9 additions & 9 deletions go/design/error_6.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
// The main goal of logging is to debug.

// We only log things that are actionable. Only log the contexts that are allowed us to identify
// that is going on. Anything else ideally is noise and would be better suited up on the dashboard
// what is going on. Anything else ideally is noise and would be better suited up on the dashboard
// through metrics. For example, socket connection and disconnection, we can log these but these
// are not actionable because we don't necessary lookup the log for that.
// are not actionable because we don't necessarily lookup the log for that.

// There is a package that is written by Dave Cheney called errors that let us simplify error
// handling and logging that the same time. Below is a demonstration on how to leverage the package
// handling and logging at the same time. Below is a demonstration on how to leverage the package
// to simplify our code. By reducing logging, we also reduce a large amount of pressure on the heap
// (garbage collection).

Expand All @@ -29,7 +29,7 @@ type AppError struct {
State int
}

// Error implements the error interface.
// AppError implements the error interface.
func (c *AppError) Error() string {
return fmt.Sprintf("App Error, State: %d", c.State)
}
Expand All @@ -49,9 +49,9 @@ func main() {
// around it and push it up. This maintains the call stack of where we are in the code.
// Similarly, firstCall doesn't handle the error but wraps and pushes it up.

// In main, we are handling the call, which means the bug stops here and I have to log it.
// In main, we are handling the call, which means the error stops here and we have to log it.
// In order to properly handle this error, we need to know that the root cause of this error
// was. It is the original error that is not wrapped. Cause will bubble up this error out of
// was. It is the original error that is not wrapped. Cause method will bubble up this error out of
// these wrapping and allow us to be able to use all the language mechanics we have.

// We are not only be able to access the State even though we've done this assertion back to
Expand All @@ -77,23 +77,23 @@ func main() {
}
}

// firstCall makes a call to a second function and wraps any error.
// firstCall makes a call to a secondCall function and wraps any error.
func firstCall(i int) error {
if err := secondCall(i); err != nil {
return errors.Wrapf(err, "firstCall->secondCall(%d)", i)
}
return nil
}

// secondCall makes a call to a third function and wraps any error.
// secondCall makes a call to a thirdCall function and wraps any error.
func secondCall(i int) error {
if err := thirdCall(); err != nil {
return errors.Wrap(err, "secondCall->thirdCall()")
}
return nil
}

// thirdCall create an error value we will validate.
// thirdCall function creates an error value we will validate.
func thirdCall() error {
return &AppError{99}
}
18 changes: 12 additions & 6 deletions go/design/mocking_1.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@
// Package To Mock
// ---------------

// It is important to mock thing.
// It is important to mock things.
// Most things over the network can be mocked in our test. However, mocking our database is a
// different story because it is too complex. This is where Docker can come in and simplify our
// code by allowing us to launch our database while running our tests and have that clean database
// for everything we do.

// Every API only need to focus on its test. We no longer have to worry about the application user
// or user over API test. We used to worry about: if we don't have that interface, then the user
// or user over API test. We used to worry about: if we don't have that interface, the user
// who use our API can't write test. That is gone. The example below will demonstrate the reason.

// Imagine we are working at a company that decides to incorporate Go as a part of its stack. They
// have their internal pubsub system that all applications are supposed to used. Maybe they are
// doing event sourcing and there is a single pubsub platform they are using that is not going to
// be replaced. They need the pubsub API for Go that they can start building services that connect
// into this event source.
// So what can change? Can the even source change?
// So what can change? Can the event source change?
// If the answer is no, then it immediately tells us that we don't need to use interfaces. We can
// built the entire API in the concrete, which we would do it first anyway. We then write tests to
// make sure everything work.
Expand All @@ -31,7 +31,11 @@

// Package pubsub simulates a package that provides publication/subscription type services.

package main // should be pubsub, but leave main here for it to compile
package main

import (
"fmt"
)

// PubSub provides access to a queue system.
type PubSub struct {
Expand All @@ -50,14 +54,16 @@ func New(host string) *PubSub {
return &ps
}

// Publish sends the data for the specified key.
// Publish sends the data to the specified key.
func (ps *PubSub) Publish(key string, v interface{}) error {
// PRETEND THERE IS A SPECIFIC IMPLEMENTATION.
fmt.Println("Actual PubSub: Publish")
return nil
}

// Subscribe sets up an request to receive messages for the specified key.
// Subscribe sets up an request to receive messages from the specified key.
func (ps *PubSub) Subscribe(key string) error {
// PRETEND THERE IS A SPECIFIC IMPLEMENTATION.
fmt.Println("Actual PubSub: Subscribe")
return nil
}
13 changes: 8 additions & 5 deletions go/design/mocking_2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@
// Client
// ------

// Run: `go run ./go/design/mocking_1.go ./go/design/mocking_2.go`

// Sample program to show how we can personally mock concrete types when we need to for
// our own packages or tests.
package main

import (
"github.com/hoanhan101/ultimate-go/go/design/pubsub"
"fmt"
)

// publisher is an interface to allow this package to mock the pubsub package support.
// publisher is an interface to allow this package to mock the pubsub package.
// When we are writing our applications, declare our own interface that map out all the APIs call
// we need for the APIs. The concrete types APIs in the previous files satisfy it out of the box.
// We can write the entire application/mocking decoupling from themselves from conrete
// implementations.
// We can write the entire application with mocking decoupling from conrete implementations.
type publisher interface {
Publish(key string, v interface{}) error
Subscribe(key string) error
Expand All @@ -26,20 +27,22 @@ type mock struct{}
// Publish implements the publisher interface for the mock.
func (m *mock) Publish(key string, v interface{}) error {
// ADD YOUR MOCK FOR THE PUBLISH CALL.
fmt.Println("Mock PubSub: Publish")
return nil
}

// Subscribe implements the publisher interface for the mock.
func (m *mock) Subscribe(key string) error {
// ADD YOUR MOCK FOR THE SUBSCRIBE CALL.
fmt.Println("Mock PubSub: Subscribe")
return nil
}

func main() {
// Create a slice of publisher interface values. Assign the address of a pubsub.
// PubSub value and the address of a mock value.
pubs := []publisher{
pubsub.New("localhost"),
New("localhost"),
&mock{},
}

Expand Down
10 changes: 5 additions & 5 deletions go/design/pollution_1.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

// Why are we using an interface here?
// Myth #1: We are using interfaces because we have to use interfaces.
// No. We don't have to use interfaces. We use it when it is practical and reasonable to do so.
// Answer: No. We don't have to use interfaces. We use it when it is practical and reasonable to do so.
// Even though they are wonderful, there is a cost of using interfaces: a level of indirection and
// potential allocation, when we store concrete type inside of them. Unless the cost of that is
// worth whatever decoupling we are getting, then we shouldn't be taking the cost.
// potential allocation when we store concrete type inside of them. Unless the cost of that is
// worth whatever decoupling we are getting, we shouldn't be using interfaces.

// Myth #2: We need to be able to test our code so we need to use interfaces.
// No. We must design our API that are usable for user application developer first, not our test.
// Answer: No. We must design our API that are usable for user application developer first, not our test.

// Below is an example that creates interface pollution by improperly using an interface
// when one is not needed.
Expand Down Expand Up @@ -41,7 +41,7 @@ type server struct {
// interface value.
// It is not that functions and interfaces cannot return interface values. They can. But normally,
// that should raise a flag. The concrete type is the data that has the behavior and the interface
// normally should be used as accepting the input to the data, not necessary going out.
// normally should be used as accepting the input to the data, not necessarily going out.
func NewServer(host string) Server {
// SMELL - Storing an unexported type pointer in the interface.
return &server{host}
Expand Down
Loading

0 comments on commit f9d6c6f

Please sign in to comment.