diff --git a/.cache/plugin/social/abbb864f7d61c8efb7242d6041360832.png b/.cache/plugin/social/abbb864f7d61c8efb7242d6041360832.png new file mode 100644 index 0000000..8eed61a Binary files /dev/null and b/.cache/plugin/social/abbb864f7d61c8efb7242d6041360832.png differ diff --git a/docs/book.md b/docs/book.md index 423276c..5f3aa77 100644 --- a/docs/book.md +++ b/docs/book.md @@ -12,7 +12,7 @@ Source code and community space of _100 Go Mistakes and How to Avoid Them_, publ If you're a Go developer looking to improve your skills, this book is for you. With a focus on practical examples, _100 Go Mistakes and How to Avoid Them_ covers a wide range of topics from concurrency and error handling to testing and code organization. You'll learn to write more idiomatic, efficient, and maintainable code and become a proficient Go developer. -### Quotes +## Quotes > This is an exceptional book. Usually, if a book contains either high-quality explanations or is written succinctly, I consider myself lucky to have found it. This one combines these two characteristics, which is super rare. It's another Go book for me and I still had quite a lot of "a-ha!" moments while reading it, and all of that without the unnecessary fluff, just straight to the point. @@ -26,7 +26,7 @@ If you're a Go developer looking to improve your skills, this book is for you. W – _Anupam Sengupta_ -### Where to Buy? +## Where to Buy? * _100 Go Mistakes and How to Avoid Them_ (🇬🇧 edition: paper, digital, or audiobook) * [Manning](https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them) @@ -39,29 +39,3 @@ If you're a Go developer looking to improve your skills, this book is for you. W ## About the Author Teiva Harsanyi is a senior software engineer at Google. He has worked in various domains, including insurance, transportation, and safety-critical industries like air traffic management. He is passionate about Go and how to design and implement reliable systems. - -## External Resources - -### English - -* Book Review: 100 Go Mistakes and How to Avoid Them: [Post](https://boldlygo.tech/posts/2023-08-09-review-100-go-mistakes/), [YouTube](https://www.youtube.com/watch?v=tcRYU9g5wtw) -* How to make mistakes in Go - Go Time #190: [Episode](https://changelog.com/gotime/190), [Spotify](https://open.spotify.com/episode/0K1DImrxHCy6E7zVY4AxMZ?si=akroInsPQ1mM5B5V2tHLUw&dl_branch=1) -* [8LU - 100% Test Coverage](https://youtu.be/V3FBDav6wgQ?t=1210) -* [Some Tips I learned from 100 Mistakes in Go](https://raygervais.dev/articles/2023/04/100_mistakes_in_go/) -* [What can be summarized from 100 Go Mistakes?](https://www.sobyte.net/post/2023-05/summarized-from-100-go-mistakes/) - -### Chinese - -* [深度阅读之《100 Go Mistakes and How to Avoid Them](https://qcrao.com/post/100-go-mistakes-reading-notes/) -* [100 Go Mistakes 随记](https://zhuanlan.zhihu.com/p/592602656) -* [我为什么放弃Go语言?](https://juejin.cn/post/7241452578125824061) - -### Japanese - -* [最近読んだGo言語の本の紹介:100 Go Mistakes and How to Avoid Them](https://qiita.com/kentaro_suzuki/items/c9c31dc81217f237433c) -* [『100 Go Mistakes and How to Avoid Them』を読む](https://zenn.dev/yukibobier/books/066f07c8a59fa0) -* [100 Go Mistakes 随记 - 01 Code and project organization](https://zhuanlan.zhihu.com/p/592602656) - -### Portuguese - -* [Um ÓTIMO livro para programadores Go](https://www.youtube.com/watch?v=34XShL_jWD4) diff --git a/docs/external.md b/docs/external.md new file mode 100644 index 0000000..33d09dc --- /dev/null +++ b/docs/external.md @@ -0,0 +1,25 @@ +# External Resources + +## English + +* Book Review: 100 Go Mistakes and How to Avoid Them: [Post](https://boldlygo.tech/posts/2023-08-09-review-100-go-mistakes/), [YouTube](https://www.youtube.com/watch?v=tcRYU9g5wtw) +* How to make mistakes in Go - Go Time #190: [Episode](https://changelog.com/gotime/190), [Spotify](https://open.spotify.com/episode/0K1DImrxHCy6E7zVY4AxMZ?si=akroInsPQ1mM5B5V2tHLUw&dl_branch=1) +* [8LU - 100% Test Coverage](https://youtu.be/V3FBDav6wgQ?t=1210) +* [Some Tips I learned from 100 Mistakes in Go](https://raygervais.dev/articles/2023/04/100_mistakes_in_go/) +* [What can be summarized from 100 Go Mistakes?](https://www.sobyte.net/post/2023-05/summarized-from-100-go-mistakes/) + +## Chinese + +* [深度阅读之《100 Go Mistakes and How to Avoid Them](https://qcrao.com/post/100-go-mistakes-reading-notes/) +* [100 Go Mistakes 随记](https://zhuanlan.zhihu.com/p/592602656) +* [我为什么放弃Go语言?](https://juejin.cn/post/7241452578125824061) + +## Japanese + +* [最近読んだGo言語の本の紹介:100 Go Mistakes and How to Avoid Them](https://qiita.com/kentaro_suzuki/items/c9c31dc81217f237433c) +* [『100 Go Mistakes and How to Avoid Them』を読む](https://zenn.dev/yukibobier/books/066f07c8a59fa0) +* [100 Go Mistakes 随记 - 01 Code and project organization](https://zhuanlan.zhihu.com/p/592602656) + +## Portuguese + +* [Um ÓTIMO livro para programadores Go](https://www.youtube.com/watch?v=34XShL_jWD4) diff --git a/docs/index.md b/docs/index.md index dd02b6b..05a1153 100644 --- a/docs/index.md +++ b/docs/index.md @@ -157,7 +157,7 @@ When creating a struct, Go offers the option to embed types. But this can someti In Go, a struct field is called embedded if it’s declared without a name. For example, -```cgo +```go type Foo struct { Bar // Embedded field } diff --git a/mkdocs.yml b/mkdocs.yml index d433c47..6d3dc2c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,4 +44,13 @@ nav: - jobs.md - Chinese (Simplified): - zh/index.md + - external.md +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences copyright: Copyright © 2022 - 2023 Teiva Harsanyi diff --git a/site/404.html b/site/404.html index dc518f2..4227c4c 100644 --- a/site/404.html +++ b/site/404.html @@ -404,6 +404,25 @@ + + + + + +
  • + + + + + External Resources + + + + +
  • + + + diff --git a/site/assets/images/social/external.png b/site/assets/images/social/external.png new file mode 100644 index 0000000..8eed61a Binary files /dev/null and b/site/assets/images/social/external.png differ diff --git a/site/book/index.html b/site/book/index.html index e8d4cae..ed0604a 100644 --- a/site/book/index.html +++ b/site/book/index.html @@ -358,26 +358,20 @@ Book Description - -
  • @@ -385,47 +379,6 @@ About the Author -
  • - -
  • - - External Resources - - - -
  • @@ -554,6 +507,25 @@ + + + + + +
  • + + + + + External Resources + + + + +
  • + + + @@ -585,26 +557,20 @@ Book Description - -
  • @@ -612,47 +578,6 @@ About the Author -
  • - -
  • - - External Resources - - - -
  • @@ -678,7 +603,7 @@

    Book Description

    If you're a Go developer looking to improve your skills, this book is for you. With a focus on practical examples, 100 Go Mistakes and How to Avoid Them covers a wide range of topics from concurrency and error handling to testing and code organization. You'll learn to write more idiomatic, efficient, and maintainable code and become a proficient Go developer.

    -

    Quotes

    +

    Quotes

    This is an exceptional book. Usually, if a book contains either high-quality explanations or is written succinctly, I consider myself lucky to have found it. This one combines these two characteristics, which is super rare. It's another Go book for me and I still had quite a lot of "a-ha!" moments while reading it, and all of that without the unnecessary fluff, just straight to the point.

    @@ -691,7 +616,7 @@

    Not having this will be the 101st mistake a Go programmer could make.

    Anupam Sengupta

    -

    Where to Buy?

    +

    Where to Buy?

    About the Author

    Teiva Harsanyi is a senior software engineer at Google. He has worked in various domains, including insurance, transportation, and safety-critical industries like air traffic management. He is passionate about Go and how to design and implement reliable systems.

    -

    External Resources

    -

    English

    - -

    Chinese

    - -

    Japanese

    - -

    Portuguese

    - diff --git a/site/chapter-1/index.html b/site/chapter-1/index.html index 9b1e29d..b1bbcac 100644 --- a/site/chapter-1/index.html +++ b/site/chapter-1/index.html @@ -564,6 +564,25 @@ + + + + + +
  • + + + + + External Resources + + + + +
  • + + + diff --git a/site/external/index.html b/site/external/index.html new file mode 100644 index 0000000..3c268a2 --- /dev/null +++ b/site/external/index.html @@ -0,0 +1,691 @@ + + + + + + + + + + + + + + + + + + + + + + 100 Go Mistakes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + Skip to content + + +
    +
    + +
    + + + + + + +
    + + +
    + +
    + + + + + + +
    +
    + + + +
    +
    +
    + + + + + +
    +
    +
    + + + +
    +
    +
    + + + +
    +
    +
    + + + + + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + \ No newline at end of file diff --git a/site/index.html b/site/index.html index 354d4e1..664620d 100644 --- a/site/index.html +++ b/site/index.html @@ -1351,6 +1351,25 @@ + + + + + +
  • + + + + + External Resources + + + + +
  • + + + @@ -2284,36 +2303,36 @@ -
    if foo() {
    -    // ...
    -    return true
    -} else {
    -    // ...
    -}
    -
    +
    if foo() {
    +    // ...
    +    return true
    +} else {
    +    // ...
    +}
    +

    Instead, we omit the else block like this:

    -
    if foo() {
    -    // ...
    -    return true
    -}
    -// ...
    -
    +
    if foo() {
    +    // ...
    +    return true
    +}
    +// ...
    +
    -
    if s != "" {
    -    // ...
    -} else {
    -    return errors.New("empty string")
    -}
    -
    +
    if s != "" {
    +    // ...
    +} else {
    +    return errors.New("empty string")
    +}
    +

    Here, an empty s represents the non-happy path. Hence, we should flip the condition like so:

    -
    if s == "" {
    -    return errors.New("empty string")
    -}
    -// ...
    -
    +
    if s == "" {
    +    return errors.New("empty string")
    +}
    +// ...
    +

    Writing readable code is an important challenge for every developer. Striving to reduce the number of nested blocks, aligning the happy path on the left, and returning as early as possible are concrete means to improve our code’s readability.

    Source code

    Misusing init functions (#3)

    @@ -2364,14 +2383,14 @@ What’s the main problem if we overuse interfaces? The answer is that they make

    TL;DR: Using type embedding can also help avoid boilerplate code; however, ensure that doing so doesn’t lead to visibility issues where some fields should have remained hidden.

    When creating a struct, Go offers the option to embed types. But this can sometimes lead to unexpected behaviors if we don’t understand all the implications of type embedding. Throughout this section, we look at how to embed types, what these bring, and the possible issues.

    In Go, a struct field is called embedded if it’s declared without a name. For example,

    -
    type Foo struct {
    -    Bar // Embedded field
    -}
    -
    -type Bar struct {
    -    Baz int
    -}
    -
    +
    type Foo struct {
    +    Bar // Embedded field
    +}
    +
    +type Bar struct {
    +    Baz int
    +}
    +

    In the Foo struct, the Bar type is declared without an associated name; hence, it’s an embedded field.

    We use embedding to promote the fields and methods of an embedded type. Because Bar contains a Baz field, this field is promoted to Foo. Therefore, Baz becomes available from Foo.

    @@ -2387,47 +2406,47 @@ promoted to Foo. Therefore, Baz becomes available from Foo.

    * An unexported struct holds the configuration: options. * Each option is a function that returns the same type: type Option func(options *options) error. For example, WithPort accepts an int argument that represents the port and returns an Option type that represents how to update the options struct.

    -
    type options struct {
    -  port *int
    -}
    -
    -type Option func(options *options) error
    -
    -func WithPort(port int) Option {
    -  return func(options *options) error {
    -    if port < 0 {
    -    return errors.New("port should be positive")
    -  }
    -  options.port = &port
    -  return nil
    -  }
    -}
    -
    -func NewServer(addr string, opts ...Option) ( *http.Server, error) { <1>
    -  var options options <2>
    -  for _, opt := range opts { <3>
    -    err := opt(&options) <4>
    -    if err != nil {
    -      return nil, err
    -    }
    -  }
    -
    -  // At this stage, the options struct is built and contains the config
    -  // Therefore, we can implement our logic related to port configuration
    -  var port int
    -  if options.port == nil {
    -    port = defaultHTTPPort
    -  } else {
    -      if *options.port == 0 {
    -      port = randomPort()
    -    } else {
    -      port = *options.port
    -    }
    -  }
    -
    -  // ...
    -}
    -
    +
    type options struct {
    +  port *int
    +}
    +
    +type Option func(options *options) error
    +
    +func WithPort(port int) Option {
    +  return func(options *options) error {
    +    if port < 0 {
    +    return errors.New("port should be positive")
    +  }
    +  options.port = &port
    +  return nil
    +  }
    +}
    +
    +func NewServer(addr string, opts ...Option) ( *http.Server, error) { <1>
    +  var options options <2>
    +  for _, opt := range opts { <3>
    +    err := opt(&options) <4>
    +    if err != nil {
    +      return nil, err
    +    }
    +  }
    +
    +  // At this stage, the options struct is built and contains the config
    +  // Therefore, we can implement our logic related to port configuration
    +  var port int
    +  if options.port == nil {
    +    port = defaultHTTPPort
    +  } else {
    +      if *options.port == 0 {
    +      port = randomPort()
    +    } else {
    +      port = *options.port
    +    }
    +  }
    +
    +  // ...
    +}
    +

    The functional options pattern provides a handy and API-friendly way to handle options. Although the builder pattern can be a valid option, it has some minor downsides (having to pass a config struct that can be empty or a less handy way to handle error management) that tend to make the functional options pattern the idiomatic way to deal with these kind of problems in Go.

    Source code

    Project misorganization (project structure and package organization) (#12)

    @@ -2481,19 +2500,19 @@ Meanwhile, we should also look at golangci-lint (https://github.com/golangci/ go

    Neglecting integer overflows (#18)

    TL;DR: Because integer overflows and underflows are handled silently in Go, you can implement your own functions to catch them.

    In Go, an integer overflow that can be detected at compile time generates a compila- tion error. For example,

    -
    var counter int32 = math.MaxInt32 + 1
    -
    -
    constant 2147483648 overflows int32
    -
    +
    var counter int32 = math.MaxInt32 + 1
    +
    +
    constant 2147483648 overflows int32
    +

    However, at run time, an integer overflow or underflow is silent; this does not lead to an application panic. It is essential to keep this behavior in mind, because it can lead to sneaky bugs (for example, an integer increment or addition of positive integers that leads to a negative result).

    Source code

    Not understanding floating-points (#19)

    TL;DR: Making floating-point comparisons within a given delta can ensure that your code is portable. When performing addition or subtraction, group the operations with a similar order of magnitude to favor accuracy. Also, perform multiplication and division before addition and subtraction.

    In Go, there are two floating-point types (if we omit imaginary numbers): float32 and float64. The concept of a floating point was invented to solve the major problem with integers: their inability to represent fractional values. To avoid bad surprises, we need to know that floating-point arithmetic is an approximation of real arithmetic.

    For that, we’ll look at a multiplication example:

    -
    var n float32 = 1.0001
    -fmt.Println(n * n)
    -
    +
    var n float32 = 1.0001
    +fmt.Println(n * n)
    +

    We may expect this code to print the result of 1.0001 * 1.0001 = 1.00020001, right? However, running it on most x86 processors prints 1.0002, instead.

    Because Go’s float32 and float64 types are approximations, we have to bear a few rules in mind: * When comparing two floating-point numbers, check that their difference is within an acceptable range. @@ -2589,14 +2608,14 @@ One additional note: we must remember that the standard library has some exist-

    Ignoring how arguments are evaluated in range loops (channels and arrays) (#31)

    TL;DR: Understanding that the expression passed to the range operator is evaluated only once before the beginning of the loop can help you avoid common mistakes such as inefficient assignment in channel or slice iteration.

    The range loop evaluates the provided expression only once, before the beginning of the loop, by doing a copy (regardless of the type). We should remember this behavior to avoid common mistakes that might, for example, lead us to access the wrong element. For example:

    -
    a := [3]int{0, 1, 2}
    -for i, v := range a {
    -    a[2] = 10
    -    if i == 2 {
    -        fmt.Println(v)
    -    }
    -}
    -
    +
    a := [3]int{0, 1, 2}
    +for i, v := range a {
    +    a[2] = 10
    +    if i == 2 {
    +        fmt.Println(v)
    +    }
    +}
    +

    This code updates the last index to 10. However, if we run this code, it does not print 10; it prints 2.

    Source code

    Ignoring the impacts of using pointer elements in range loops (#32)

    @@ -2615,73 +2634,73 @@ for i, v := range a {

    Ignoring how the break statement works (#34)

    TL;DR: Using break or continue with a label enforces breaking a specific statement. This can be helpful with switch or select statements inside loops.

    A break statement is commonly used to terminate the execution of a loop. When loops are used in conjunction with switch or select, developers frequently make the mistake of breaking the wrong statement. For example:

    -
    for i := 0; i < 5; i++ {
    -    fmt.Printf("%d ", i)
    -
    -    switch i {
    -    default:
    -    case 2:
    -        break
    -    }
    -}
    -
    +
    for i := 0; i < 5; i++ {
    +    fmt.Printf("%d ", i)
    +
    +    switch i {
    +    default:
    +    case 2:
    +        break
    +    }
    +}
    +

    The break statement doesn’t terminate the for loop: it terminates the switch statement, instead. Hence, instead of iterating from 0 to 2, this code iterates from 0 to 4: 0 1 2 3 4.

    One essential rule to keep in mind is that a break statement terminates the execu- tion of the innermost for, switch, or select statement. In the previous example, it terminates the switch statement.

    To break the loop instead of the switch statement, the most idiomatic way is to use a label:

    -
    loop:
    -    for i := 0; i < 5; i++ {
    -        fmt.Printf("%d ", i)
    -
    -        switch i {
    -        default:
    -        case 2:
    -            break loop
    -        }
    -    }
    -
    +
    loop:
    +    for i := 0; i < 5; i++ {
    +        fmt.Printf("%d ", i)
    +
    +        switch i {
    +        default:
    +        case 2:
    +            break loop
    +        }
    +    }
    +

    Here, we associate the loop label with the for loop. Then, because we provide the loop label to the break statement, it breaks the loop, not the switch. Therefore, this new version will print 0 1 2, as we expected.

    Source code

    Using defer inside a loop (#35)

    TL;DR: Extracting loop logic inside a function leads to executing a defer statement at the end of each iteration.

    The defer statement delays a call’s execution until the surrounding function returns. It’s mainly used to reduce boilerplate code. For example, if a resource has to be closed eventually, we can use defer to avoid repeating the closure calls before every single return.

    One common mistake with defer is to forget that it schedules a function call when the surrounding function returns. For example:

    -
    func readFiles(ch <-chan string) error {
    -    for path := range ch {
    -        file, err := os.Open(path)
    -        if err != nil {
    -            return err
    -        }
    -
    -        defer file.Close()
    -
    -        // Do something with file
    -    }
    -    return nil
    -}
    -
    +
    func readFiles(ch <-chan string) error {
    +    for path := range ch {
    +        file, err := os.Open(path)
    +        if err != nil {
    +            return err
    +        }
    +
    +        defer file.Close()
    +
    +        // Do something with file
    +    }
    +    return nil
    +}
    +

    The defer calls are executed not during each loop iteration but when the readFiles function returns. If readFiles doesn’t return, the file descriptors will be kept open forever, causing leaks.

    One common option to fix this problem is to create a surrounding function after defer, called during each iteration:

    -
    func readFiles(ch <-chan string) error {
    -    for path := range ch {
    -        if err := readFile(path); err != nil {
    -            return err
    -        }
    -    }
    -    return nil
    -}
    -
    -func readFile(path string) error {
    -    file, err := os.Open(path)
    -    if err != nil {
    -        return err
    -    }
    -
    -    defer file.Close()
    -
    -    // Do something with file
    -    return nil
    -}
    -
    +
    func readFiles(ch <-chan string) error {
    +    for path := range ch {
    +        if err := readFile(path); err != nil {
    +            return err
    +        }
    +    }
    +    return nil
    +}
    +
    +func readFile(path string) error {
    +    file, err := os.Open(path)
    +    if err != nil {
    +        return err
    +    }
    +
    +    defer file.Close()
    +
    +    // Do something with file
    +    return nil
    +}
    +

    Another solution is to make the readFile function a closure but intrinsically, this remains the same solution: adding another surrounding function to execute the defer calls during each iteration.

    Source code

    Strings

    @@ -2701,19 +2720,19 @@ func readFile(path string) error {

    TL;DR: Iterating on a string with the range operator iterates on the runes with the index corresponding to the starting index of the rune’s byte sequence. To access a specific rune index (such as the third rune), convert the string into a []rune.

    Iterating on a string is a common operation for developers. Perhaps we want to per- form an operation for each rune in the string or implement a custom function to search for a specific substring. In both cases, we have to iterate on the different runes of a string. But it’s easy to get confused about how iteration works.

    For example, consider the following example:

    -
    s := "hêllo"
    -for i := range s {
    -    fmt.Printf("position %d: %c\n", i, s[i])
    -}
    -fmt.Printf("len=%d\n", len(s))
    -
    -
    position 0: h
    -position 1: Ã
    -position 3: l
    -position 4: l
    -position 5: o
    -len=6
    -
    +
    s := "hêllo"
    +for i := range s {
    +    fmt.Printf("position %d: %c\n", i, s[i])
    +}
    +fmt.Printf("len=%d\n", len(s))
    +
    +
    position 0: h
    +position 1: Ã
    +position 3: l
    +position 4: l
    +position 5: o
    +len=6
    +

    Let's highlight three points that might be confusing: