In the latter case, calling publish will return an error because we returned the HTTP response quickly.
In summary, propagating a context should be done cautiously.
-
TL;DR
@@ -3770,19 +3849,19 @@ the use case. However, we should see the two options as complementary.
Instead of signaling watcher that it’s time to close its resources, we now call this close method, using defer to guarantee that the resources are closed before the application exits.
In summary, let’s be mindful that a goroutine is a resource like any other that must eventually be closed to free memory or other resources. Starting a goroutine without knowing when to stop it is a design issue. Whenever a goroutine is started, we should have a clear plan about when it will stop. Last but not least, if a goroutine creates resources and its lifetime is bound to the lifetime of the application, it’s probably safer to wait for this goroutine to complete before exiting the application. This way, we can ensure that the resources can be freed.
-Source code
+Source code
Not being careful with goroutines and loop variables (#63)
TL;DR
To avoid bugs with goroutines and loop variables, create local variables or call functions instead of closures.
-Source code
+Source code
Expecting a deterministic behavior using select and channels (#64)
TL;DR
Understanding that select with multiple channels chooses the case randomly if multiple options are possible prevents making wrong assumptions that can lead to subtle concurrency bugs.
-Source code
+Source code
Not using notification channels (#65)
TL;DR
@@ -3793,7 +3872,7 @@ the use case. However, we should see the two options as complementary.
TL;DR
Using nil channels should be part of your concurrency toolset because it allows you to remove cases from select statements, for example.
-Source code
+Source code
Being puzzled about channel size (#67)
TL;DR
@@ -3805,78 +3884,78 @@ the use case. However, we should see the two options as complementary.
TL;DR
Being aware that string formatting may lead to calling existing functions means watching out for possible deadlocks and other data races.
-Source code
+Source code
Creating data races with append (#69)
TL;DR
Calling append isn’t always data-race-free; hence, it shouldn’t be used concurrently on a shared slice.
-Source code
+Source code
Using mutexes inaccurately with slices and maps (#70)
TL;DR
Remembering that slices and maps are pointers can prevent common data races.
-Source code
+Source code
Misusing sync.WaitGroup (#71)
TL;DR
To accurately use sync.WaitGroup, call the Add method before spinning up goroutines.
-Source code
+Source code
Forgetting about sync.Cond (#72)
TL;DR
You can send repeated notifications to multiple goroutines with sync.Cond.
-Source code
+Source code
Not using errgroup (#73)
TL;DR
You can synchronize a group of goroutines and handle errors and contexts with the errgroup package.
-Source code
+Source code
Copying a sync type (#74)
TL;DR
sync types shouldn’t be copied.
-Source code
+Source code
Standard Library
Providing a wrong time duration (#75)
TL;DR
Remain cautious with functions accepting a time.Duration. Even though passing an integer is allowed, strive to use the time API to prevent any possible confusion.
-Source code
+Source code
time.After and memory leaks (#76)
TL;DR
Avoiding calls to time.After in repeated functions (such as loops or HTTP handlers) can avoid peak memory consumption. The resources created by time.After are released only when the timer expires.
-Source code
+Source code
JSON handling common mistakes (#77)
- Unexpected behavior because of type embedding
Be careful about using embedded fields in Go structs. Doing so may lead to sneaky bugs like an embedded time.Time field implementing the json.Marshaler interface, hence overriding the default marshaling behavior.
-Source code
+Source code
- JSON and the monotonic clock
When comparing two time.Time structs, recall that time.Time contains both a wall clock and a monotonic clock, and the comparison using the == operator is done on both clocks.
-Source code
+Source code
To avoid wrong assumptions when you provide a map while unmarshaling JSON data, remember that numerics are converted to float64 by default.
-Source code
+Source code
Common SQL mistakes (#78)
- Forgetting that
sql.Open doesn't necessarily establish connections to a database
Call the Ping or PingContext method if you need to test your configuration and make sure a database is reachable.
-Source code
+Source code
- Forgetting about connections pooling
@@ -3885,42 +3964,42 @@ the use case. However, we should see the two options as complementary.
Not using prepared statements
Using SQL prepared statements makes queries more efficient and more secure.
-Source code
+Source code
Deal with nullable columns in tables using pointers or sql.NullXXX types.
-Source code
+Source code
- Not handling rows iteration errors
Call the Err method of sql.Rows after row iterations to ensure that you haven’t missed an error while preparing the next row.
-Source code
+Source code
Not closing transient resources (HTTP body, sql.Rows, and os.File) (#79)
TL;DR
Eventually close all structs implementing io.Closer to avoid possible leaks.
-Source code
+Source code
Forgetting the return statement after replying to an HTTP request (#80)
TL;DR
To avoid unexpected behaviors in HTTP handler implementations, make sure you don’t miss the return statement if you want a handler to stop after http.Error.
-Source code
+Source code
Using the default HTTP client and server (#81)
TL;DR
For production-grade applications, don’t use the default HTTP client and server implementations. These implementations are missing timeouts and behaviors that should be mandatory in production.
-Source code
+Source code
Testing
TL;DR
Categorizing tests using build flags, environment variables, or short mode makes the testing process more efficient. You can create test categories using build flags or environment variables (for example, unit versus integration tests) and differentiate short from long-running tests to decide which kinds of tests to execute.
-Source code
+Source code
Not enabling the race flag (#83)
TL;DR
@@ -3936,50 +4015,42 @@ the use case. However, we should see the two options as complementary.
TL;DR
Table-driven tests are an efficient way to group a set of similar tests to prevent code duplication and make future updates easier to handle.
-Source code
+Source code
Sleeping in unit tests (#86)
TL;DR
Avoid sleeps using synchronization to make a test less flaky and more robust. If synchronization isn’t possible, consider a retry approach.
-Source code
+Source code
Not dealing with the time API efficiently (#87)
TL;DR
Understanding how to deal with functions using the time API is another way to make a test less flaky. You can use standard techniques such as handling the time as part of a hidden dependency or asking clients to provide it.
-Source code
+Source code
Not using testing utility packages (httptest and iotest) (#88)
- The
httptest package is helpful for dealing with HTTP applications. It provides a set of utilities to test both clients and servers.
-Source code
+Source code
- The
iotest package helps write io.Reader and test that an application is tolerant to errors.
-Source code
-
+Source code
+Writing inaccurate benchmarks (#89)
+
+TL;DR
+Regarding benchmarks:
-- Not resetting or pausing the timer
+- Use time methods to preserve the accuracy of a benchmark.
+- Increasing benchtime or using tools such as benchstat can be helpful when dealing with micro-benchmarks.
+- Be careful with the results of a micro-benchmark if the system that ends up running the application is different from the one running the micro-bench- mark.
+- Make sure the function under test leads to a side effect, to prevent compiler optimizations from fooling you about the benchmark results.
+- To prevent the observer effect, force a benchmark to re-create the data used by a CPU-bound function.
-Use time methods to preserve the accuracy of a benchmark.
-Source code
-
-- Making wrong assumptions about micro-benchmarks
-
-Increasing benchtime or using tools such as benchstat can be helpful when dealing with micro-benchmarks.
-Be careful with the results of a micro-benchmark if the system that ends up running the application is different from the one running the micro-benchmark.
-Source code
-
-- Not being careful about compiler optimizations
-
-Make sure the function under test leads to a side effect, to prevent compiler optimizations from fooling you about the benchmark results.
-Source code
-
-- Being fooled by the observer effect
-
-To prevent the observer effect, force a benchmark to re-create the data used by a CPU-bound function.
-Source code
+
+Read the full section here.
+Source code
Not exploring all the Go testing features (#90)
- Code coverage
@@ -3989,17 +4060,17 @@ the use case. However, we should see the two options as complementary.
- Testing from a different package
Place unit tests in a different package to enforce writing tests that focus on an exposed behavior, not internals.
-Source code
+Source code
Handling errors using the *testing.T variable instead of the classic if err != nil makes code shorter and easier to read.
-Source code
+Source code
You can use setup and teardown functions to configure a complex environment, such as in the case of integration tests.
-Source code
+Source code
TL;DR
@@ -4016,18 +4087,18 @@ the use case. However, we should see the two options as complementary.
Cache line
Being conscious of the cache line concept is critical to understanding how to organize data in data-intensive applications. A CPU doesn’t fetch memory word by word; instead, it usually copies a memory block to a 64-byte cache line. To get the most out of each individual cache line, enforce spatial locality.
-Source code
+Source code
- Slice of structs vs. struct of slices
-Source code
+Source code
Making code predictable for the CPU can also be an efficient way to optimize certain functions. For example, a unit or constant stride is predictable for the CPU, but a non-unit stride (for example, a linked list) isn’t predictable.
-Source code
+Source code
@@ -4037,37 +4108,37 @@ the use case. However, we should see the two options as complementary.
TL;DR
Knowing that lower levels of CPU caches aren’t shared across all the cores helps avoid performance-degrading patterns such as false sharing while writing concurrency code. Sharing memory is an illusion.
-Source code
+Source code
Not taking into account instruction-level parallelism (#93)
TL;DR
Use instruction-level parallelism (ILP) to optimize specific parts of your code to allow a CPU to execute as many parallel instructions as possible. Identifying data hazards is one of the main steps.
-Source code
+Source code
Not being aware of data alignment (#94)
TL;DR
You can avoid common mistakes by remembering that in Go, basic types are aligned with their own size. For example, keep in mind that reorganizing the fields of a struct by size in descending order can lead to more compact structs (less memory allocation and potentially a better spatial locality).
-Source code
+Source code
Not understanding stack vs. heap (#95)
TL;DR
Understanding the fundamental differences between heap and stack should also be part of your core knowledge when optimizing a Go application. Stack allocations are almost free, whereas heap allocations are slower and rely on the GC to clean the memory.
-Source code
+Source code
Not knowing how to reduce allocations (API change, compiler optimizations, and sync.Pool) (#96)
TL;DR
Reducing allocations is also an essential aspect of optimizing a Go application. This can be done in different ways, such as designing the API carefully to prevent sharing up, understanding the common Go compiler optimizations, and using sync.Pool.
-Source code
+Source code
Not relying on inlining (#97)
TL;DR
Use the fast-path inlining technique to efficiently reduce the amortized time to call a function.
-
+
TL;DR
Rely on profiling and the execution tracer to understand how an application performs and the parts to optimize.
@@ -4152,11 +4223,11 @@ the use case. However, we should see the two options as complementary.
-
+
-