From f9d6c6f9a24ee6435786fe52a649a43b413513c2 Mon Sep 17 00:00:00 2001 From: Thammachart Chinvarapon <1731496+Thammachart@users.noreply.github.com> Date: Fri, 15 Nov 2019 12:16:17 +0700 Subject: [PATCH] fix typo (#24) * 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 --- README.md | 2 +- go/design/conversion_1.go | 12 ++++++++++-- go/design/conversion_2.go | 22 ++++++++++------------ go/design/error_1.go | 4 ++-- go/design/error_3.go | 12 ++++++------ go/design/error_4.go | 14 ++++++-------- go/design/error_6.go | 18 +++++++++--------- go/design/mocking_1.go | 18 ++++++++++++------ go/design/mocking_2.go | 13 ++++++++----- go/design/pollution_1.go | 10 +++++----- go/design/pollution_2.go | 15 +++++++-------- 11 files changed, 76 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 520eaea..8d7f3b0 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/go/design/conversion_1.go b/go/design/conversion_1.go index 412dfa3..bd70b1d 100644 --- a/go/design/conversion_1.go +++ b/go/design/conversion_1.go @@ -57,7 +57,7 @@ func main() { // ------ ------ // | bike | bike | bike | // ------ ------ ------ - // | * | ---> | | <--- | | + // | * | ---> | | <--- | * | // ------ ------ ------ // However, we cannot go in the other direction, like so: @@ -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 diff --git a/go/design/conversion_2.go b/go/design/conversion_2.go index ac6ed01..03e9b40 100644 --- a/go/design/conversion_2.go +++ b/go/design/conversion_2.go @@ -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. diff --git a/go/design/error_1.go b/go/design/error_1.go index 98385bc..e63b906 100644 --- a/go/design/error_1.go +++ b/go/design/error_1.go @@ -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. @@ -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 { diff --git a/go/design/error_3.go b/go/design/error_3.go index 2a579ed..a0f72d6 100644 --- a/go/design/error_3.go +++ b/go/design/error_3.go @@ -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 @@ -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() } @@ -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)" @@ -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 diff --git a/go/design/error_4.go b/go/design/error_4.go index 340e16b..b0c8f03 100644 --- a/go/design/error_4.go +++ b/go/design/error_4.go @@ -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 @@ -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 { @@ -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") @@ -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. diff --git a/go/design/error_6.go b/go/design/error_6.go index d697209..38f8058 100644 --- a/go/design/error_6.go +++ b/go/design/error_6.go @@ -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). @@ -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) } @@ -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 @@ -77,7 +77,7 @@ 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) @@ -85,7 +85,7 @@ func firstCall(i int) error { 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()") @@ -93,7 +93,7 @@ func secondCall(i int) error { 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} } diff --git a/go/design/mocking_1.go b/go/design/mocking_1.go index 37b4ab6..956d685 100644 --- a/go/design/mocking_1.go +++ b/go/design/mocking_1.go @@ -2,14 +2,14 @@ // 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 @@ -17,7 +17,7 @@ // 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. @@ -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 { @@ -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 } diff --git a/go/design/mocking_2.go b/go/design/mocking_2.go index 024556f..918cc8a 100644 --- a/go/design/mocking_2.go +++ b/go/design/mocking_2.go @@ -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 @@ -26,12 +27,14 @@ 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 } @@ -39,7 +42,7 @@ 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{}, } diff --git a/go/design/pollution_1.go b/go/design/pollution_1.go index cb45e06..95de811 100644 --- a/go/design/pollution_1.go +++ b/go/design/pollution_1.go @@ -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. @@ -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} diff --git a/go/design/pollution_2.go b/go/design/pollution_2.go index 25c7aaf..1af4395 100644 --- a/go/design/pollution_2.go +++ b/go/design/pollution_2.go @@ -2,19 +2,18 @@ // Remove Interface Pollution // -------------------------- -// This is basically just removing the improper interface usage from last file. +// This is basically just removing the improper interface usage from previous file pollution_1.go. package main -// Server is our Server implementation. +// Server implementation. type Server struct { host string // PRETEND THERE ARE MORE FIELDS. } -// NewServer returns an interface value of type Server with a server implementation. +// NewServer returns just a concrete pointer of type Server func NewServer(host string) *Server { - // SMELL - Storing an unexported type pointer in the interface. return &Server{host} } @@ -40,7 +39,7 @@ func main() { // Create a new Server. srv := NewServer("localhost") - // Use the API. + // Use the APIs. srv.Start() srv.Stop() srv.Wait() @@ -50,9 +49,9 @@ func main() { // -------------------------------------- // Use an interface: // - When users of the API need to provide an implementation detail. -// - When API’s have multiple implementations that need to be maintained. -// - When parts of the API that can change have been identified and require decoupling. +// - When APIs have multiple implementations that need to be maintained. +// - When parts of the APIs that can change have been identified and require decoupling. // Question an interface: -// - When its only purpose is for writing testable API’s (write usable API’s first). +// - When its only purpose is for writing testable API’s (write usable APIs first). // - When it’s not providing support for the API to decouple from change. // - When it's not clear how the interface makes the code better.