diff --git a/arrays-and-slices.md b/arrays-and-slices.md index 75f2e8264..e9c7943c0 100644 --- a/arrays-and-slices.md +++ b/arrays-and-slices.md @@ -39,9 +39,8 @@ We can initialize an array in two ways: * \[N\]type{value1, value2, ..., valueN} e.g. `numbers := [5]int{1, 2, 3, 4, 5}` * \[...\]type{value1, value2, ..., valueN} e.g. `numbers := [...]int{1, 2, 3, 4, 5}` -It is sometimes useful to also print the inputs to the function in the error -message and we are using the `%v` placeholder which is the "default" format, -which works well for arrays. +It is sometimes useful to also print the inputs to the function in the error message. +Here, we are using the `%v` placeholder to print the "default" format, which works well for arrays. [Read more about the format strings](https://golang.org/pkg/fmt/) @@ -95,9 +94,8 @@ func Sum(numbers [5]int) int { } ``` -`range` lets you iterate over an array. Every time it is called it returns two -values, the index and the value. We are choosing to ignore the index value by -using `_` [blank identifier](https://golang.org/doc/effective_go.html#blank). +`range` lets you iterate over an array. On each iteration, `range` returns two values - the index and the value. +We are choosing to ignore the index value by using `_` [blank identifier](https://golang.org/doc/effective_go.html#blank). ### Arrays and their type @@ -161,11 +159,11 @@ This does not compile The problem here is we can either * Break the existing API by changing the argument to `Sum` to be a slice rather - than an array. When we do this we will know we have potentially ruined - someone's day because our _other_ test will not compile! + than an array. When we do this, we will potentially ruin + someone's day because our _other_ test will no longer compile! * Create a new function -In our case, no-one else is using our function so rather than having two functions to maintain let's just have one. +In our case, no one else is using our function, so rather than having two functions to maintain, let's have just one. ```go func Sum(numbers []int) int { @@ -185,7 +183,8 @@ It turns out that fixing the compiler problems were all we need to do here and t ## Refactor -We had already refactored `Sum` and all we've done is changing from arrays to slices, so there's not a lot to do here. Remember that we must not neglect our test code in the refactoring stage and we have some to do here. +We already refactored `Sum` - all we did was replace arrays with slices, so no extra changes are required. +Remember that we must not neglect our test code in the refactoring stage - we can further improve our `Sum` tests. ```go func TestSum(t *testing.T) { @@ -224,12 +223,10 @@ In our case, you can see that having two tests for this function is redundant. If it works for a slice of one size it's very likely it'll work for a slice of any size \(within reason\). -Go's built-in testing toolkit features a [coverage -tool](https://blog.golang.org/cover), which can help identify areas of your code -you have not covered. I do want to stress that having 100% coverage should not -be your goal, it's just a tool to give you an idea of your coverage. If you have -been strict with TDD, it's quite likely you'll have close to 100% coverage -anyway. +Go's built-in testing toolkit features a [coverage tool](https://blog.golang.org/cover). +Whilst striving for 100% coverage should not be your end goal, the coverage tool can help +identify areas of your code not covered by tests. If you have been strict with TDD, +it's quite likely you'll have close to 100% coverage anyway. Try running @@ -278,7 +275,7 @@ func TestSumAll(t *testing.T) { ## Write the minimal amount of code for the test to run and check the failing test output -We need to define SumAll according to what our test wants. +We need to define `SumAll` according to what our test wants. Go can let you write [_variadic functions_](https://gobyexample.com/variadic-functions) that can take a variable number of arguments. @@ -288,7 +285,7 @@ func SumAll(numbersToSum ...[]int) (sums []int) { } ``` -Try to compile but our tests still don't compile! +This is valid, but our tests still won't compile! `./sum_test.go:26:9: invalid operation: got != want (slice can only be compared to nil)` @@ -311,7 +308,7 @@ func TestSumAll(t *testing.T) { \(make sure you `import reflect` in the top of your file to have access to `DeepEqual`\) -It's important to note that `reflect.DeepEqual` is not "type safe", the code +It's important to note that `reflect.DeepEqual` is not "type safe" - the code will compile even if you did something a bit silly. To see this in action, temporarily change the test to: @@ -327,19 +324,19 @@ func TestSumAll(t *testing.T) { } ``` -What we have done here is try to compare a `slice` with a `string`. Which makes +What we have done here is try to compare a `slice` with a `string`. This makes no sense, but the test compiles! So while using `reflect.DeepEqual` is a convenient way of comparing slices \(and other things\) you must be careful when using it. -Change the test back again and run it, you should have test output looking like this +Change the test back again and run it. You should have test output like the following `sum_test.go:30: got [] want [3 9]` ## Write enough code to make it pass What we need to do is iterate over the varargs, calculate the sum using our -`Sum` function from before and then add it to the slice we will return +existing `Sum` function, then add it to the slice we will return ```go func SumAll(numbersToSum ...[]int) []int { @@ -362,7 +359,7 @@ a starting capacity of the `len` of the `numbersToSum` we need to work through. You can index slices like arrays with `mySlice[N]` to get the value out or assign it a new value with `=` -The tests should now pass +The tests should now pass. ## Refactor @@ -370,7 +367,7 @@ As mentioned, slices have a capacity. If you have a slice with a capacity of 2 and try to do `mySlice[10] = 1` you will get a _runtime_ error. However, you can use the `append` function which takes a slice and a new value, -returning a new slice with all the items in it. +then returns a new slice with all the items in it. ```go func SumAll(numbersToSum ...[]int) []int { @@ -386,9 +383,9 @@ func SumAll(numbersToSum ...[]int) []int { In this implementation, we are worrying less about capacity. We start with an empty slice `sums` and append to it the result of `Sum` as we work through the varargs. -Our next requirement is to change `SumAll` to `SumAllTails`, where it now -calculates the totals of the "tails" of each slice. The tail of a collection is -all the items apart from the first one \(the "head"\) +Our next requirement is to change `SumAll` to `SumAllTails`, where it will +calculate the totals of the "tails" of each slice. The tail of a collection is +all items in the collection except the first one \(the "head"\). ## Write the test first @@ -427,11 +424,11 @@ func SumAllTails(numbersToSum ...[]int) []int { } ``` -Slices can be sliced! The syntax is `slice[low:high]` If you omit the value on -one of the sides of the `:` it captures everything to the side of it. In our -case, we are saying "take from 1 to the end" with `numbers[1:]`. You might want to -invest some time in writing other tests around slices and experimenting with the -slice operator so you can be familiar with it. +Slices can be sliced! The syntax is `slice[low:high]`. If you omit the value on +one of the sides of the `:` it captures everything to that side of it. In our +case, we are saying "take from 1 to the end" with `numbers[1:]`. You may wish to +spend some time writing other tests around slices and experiment with the +slice operator to get more familiar with it. ## Refactor @@ -498,7 +495,7 @@ func SumAllTails(numbersToSum ...[]int) []int { ## Refactor -Our tests have some repeated code around assertion again, let's extract that into a function +Our tests have some repeated code around the assertions again, so let's extract those into a function ```go func TestSumAllTails(t *testing.T) { @@ -526,7 +523,7 @@ func TestSumAllTails(t *testing.T) { ``` A handy side-effect of this is this adds a little type-safety to our code. If -a silly developer adds a new test with `checkSums(t, got, "dave")` the compiler +a developer mistakenly adds a new test with `checkSums(t, got, "dave")` the compiler will stop them in their tracks. ```bash @@ -553,7 +550,7 @@ too, including arrays/slices themselves. So you can declare a variable of `[][]string` if you need to. [Check out the Go blog post on slices][blog-slice] for an in-depth look into -slices. Try writing more tests to demonstrate what you learn from reading it. +slices. Try writing more tests to solidify what you learn from reading it. Another handy way to experiment with Go other than writing tests is the Go playground. You can try most things out and you can easily share your code if diff --git a/dependency-injection.md b/dependency-injection.md index c52fe5cc5..8d615a87b 100644 --- a/dependency-injection.md +++ b/dependency-injection.md @@ -160,7 +160,7 @@ func main() { What other places can we write data to using `io.Writer`? Just how general purpose is our `Greet` function? -### The internet +### The Internet Run the following diff --git a/hello-world.md b/hello-world.md index 8943b8f04..46e5ca603 100644 --- a/hello-world.md +++ b/hello-world.md @@ -544,4 +544,4 @@ By now you should have some understanding of: In our case we've gone from `Hello()` to `Hello("name")`, to `Hello("name", "French")` in small, easy to understand steps. -This is of course trivial compared to "real world" software but the principles still stand. TDD is a skill that needs practice to develop but by being able to break problems down into smaller components that you can test you will have a much easier time writing software. +This is of course trivial compared to "real world" software but the principles still stand. TDD is a skill that needs practice to develop, but by breaking problems down into smaller components that you can test, you will have a much easier time writing software. diff --git a/http-server.md b/http-server.md index 3c8a35eff..27b9236e7 100644 --- a/http-server.md +++ b/http-server.md @@ -491,12 +491,12 @@ func main() { ``` If you run `go build` again and hit the same URL you should get `"123"`. Not great, but until we store data that's the best we can do. +It also didn't feel great that our main application was starting up but not actually working. We had to manually test to see the problem. We have a few options as to what to do next - Handle the scenario where the player doesn't exist - Handle the `POST /players/{name}` scenario -- It didn't feel great that our main application was starting up but not actually working. We had to manually test to see the problem. Whilst the `POST` scenario gets us closer to the "happy path", I feel it'll be easier to tackle the missing player scenario first as we're in that context already. We'll get to the rest later. @@ -984,7 +984,7 @@ func TestRecordingWinsAndRetrievingThem(t *testing.T) { I am going to take some liberties here and write more code than you may be comfortable with without writing a test. -_This is allowed_! We still have a test checking things should be working correctly but it is not around the specific unit we're working with (`InMemoryPlayerStore`). +_This is allowed!_ We still have a test checking things should be working correctly but it is not around the specific unit we're working with (`InMemoryPlayerStore`). If I were to get stuck in this scenario, I would revert my changes back to the failing test and then write more specific unit tests around `InMemoryPlayerStore` to help me drive out a solution. diff --git a/integers.md b/integers.md index a91bd6e2e..019b38002 100644 --- a/integers.md +++ b/integers.md @@ -132,7 +132,7 @@ $ go test -v --- PASS: ExampleAdd (0.00s) ``` -Please note that the example function will not be executed if you remove the comment `//Output: 6`. Although the function will be compiled, it won't be executed. +Please note that the example function will not be executed if you remove the comment `// Output: 6`. Although the function will be compiled, it won't be executed. By adding this code the example will appear in the documentation inside `godoc`, making your code even more accessible. diff --git a/maps.md b/maps.md index 2c85dc0d1..29bce63e4 100644 --- a/maps.md +++ b/maps.md @@ -217,7 +217,6 @@ t.Run("unknown word", func(t *testing.T) { assertError(t, got, ErrNotFound) }) -} func assertError(t testing.TB, got, want error) { t.Helper() diff --git a/pointers-and-errors.md b/pointers-and-errors.md index dcfaa000b..0379814bf 100644 --- a/pointers-and-errors.md +++ b/pointers-and-errors.md @@ -79,9 +79,9 @@ type Wallet struct { } ``` -In Go if a symbol (so variables, types, functions et al) starts with a lowercase symbol then it is private _outside the package it's defined in_. +In Go if a symbol (variables, types, functions et al) starts with a lowercase symbol then it is private _outside the package it's defined in_. -In our case we want our methods to be able to manipulate this value but no one else. +In our case we want our methods to be able to manipulate this value, but no one else. Remember we can access the internal `balance` field in the struct using the "receiver" variable. @@ -95,13 +95,14 @@ func (w Wallet) Balance() int { } ``` -With our career in fintech secured, run our tests and bask in the passing test +With our career in fintech secured, run the test suite and bask in the passing test `wallet_test.go:15: got 0 want 10` ### ???? -Well this is confusing, our code looks like it should work, we add the new amount onto our balance and then the balance method should return the current state of it. +Well this is confusing, our code looks like it should work. +We add the new amount onto our balance and then the balance method should return the current state of it. In Go, **when you call a function or a method the arguments are** _**copied**_. @@ -137,7 +138,8 @@ func (w Wallet) Deposit(amount int) { } ``` -The `\n` escape character, prints new line after outputting the memory address. We get the pointer to a thing with the address of symbol; `&`. +The `\n` escape character prints a new line after outputting the memory address. +We get the pointer (memory address) of something by placing an `&` character at the beginning of the symbol. Now re-run the test @@ -148,7 +150,8 @@ address of balance in test is 0xc420012260 You can see that the addresses of the two balances are different. So when we change the value of the balance inside the code, we are working on a copy of what came from the test. Therefore the balance in the test is unchanged. -We can fix this with _pointers_. [Pointers](https://gobyexample.com/pointers) let us _point_ to some values and then let us change them. So rather than taking a copy of the Wallet, we take a pointer to the wallet so we can change it. +We can fix this with _pointers_. [Pointers](https://gobyexample.com/pointers) let us _point_ to some values and then let us change them. +So rather than taking a copy of the whole Wallet, we instead take a pointer to that wallet so that we can change the original values within it. ```go func (w *Wallet) Deposit(amount int) { @@ -172,10 +175,10 @@ func (w *Wallet) Balance() int { } ``` -and seemingly addressed the object directly. In fact, the code above using `(*w)` is absolutely valid. However, the makers of Go deemed this notation cumbersome, so the language permits us to write `w.balance`, without explicit dereference. +and seemingly addressed the object directly. In fact, the code above using `(*w)` is absolutely valid. However, the makers of Go deemed this notation cumbersome, so the language permits us to write `w.balance`, without an explicit dereference. These pointers to structs even have their own name: _struct pointers_ and they are [automatically dereferenced](https://golang.org/ref/spec#Method_values). -Technically you do not need to change `Balance` to use a pointer receiver as taking a copy of the balance is fine. However by convention you should keep your method receiver types to be the same for consistency. +Technically you do not need to change `Balance` to use a pointer receiver as taking a copy of the balance is fine. However, by convention you should keep your method receiver types the same for consistency. ## Refactor @@ -350,7 +353,7 @@ func TestWallet(t *testing.T) { What should happen if you try to `Withdraw` more than is left in the account? For now, our requirement is to assume there is not an overdraft facility. -How do we signal a problem when using `Withdraw` ? +How do we signal a problem when using `Withdraw`? In Go, if you want to indicate an error it is idiomatic for your function to return an `err` for the caller to check and act on. @@ -417,7 +420,7 @@ Remember to import `errors` into your code. ## Refactor -Let's make a quick test helper for our error check just to help our test read clearer +Let's make a quick test helper for our error check to improve the test's readability ```go assertError := func(t testing.TB, err error) { @@ -651,19 +654,19 @@ func assertError(t testing.TB, got error, want error) { ### Pointers -* Go copies values when you pass them to functions/methods so if you're writing a function that needs to mutate state you'll need it to take a pointer to the thing you want to change. -* The fact that Go takes a copy of values is useful a lot of the time but sometimes you won't want your system to make a copy of something, in which case you need to pass a reference. Examples could be very large data or perhaps things you intend only to have one instance of \(like database connection pools\). +* Go copies values when you pass them to functions/methods, so if you're writing a function that needs to mutate state you'll need it to take a pointer to the thing you want to change. +* The fact that Go takes a copy of values is useful a lot of the time but sometimes you won't want your system to make a copy of something, in which case you need to pass a reference. Examples include referencing very large data structures or things where only one instance is necessary \(like database connection pools\). ### nil * Pointers can be nil -* When a function returns a pointer to something, you need to make sure you check if it's nil or you might raise a runtime exception, the compiler won't help you here. +* When a function returns a pointer to something, you need to make sure you check if it's nil or you might raise a runtime exception - the compiler won't help you here. * Useful for when you want to describe a value that could be missing ### Errors * Errors are the way to signify failure when calling a function/method. -* By listening to our tests we concluded that checking for a string in an error would result in a flaky test. So we refactored to use a meaningful value instead and this resulted in easier to test code and concluded this would be easier for users of our API too. +* By listening to our tests we concluded that checking for a string in an error would result in a flaky test. So we refactored our implementation to use a meaningful value instead and this resulted in easier to test code and concluded this would be easier for users of our API too. * This is not the end of the story with error handling, you can do more sophisticated things but this is just an intro. Later sections will cover more strategies. * [Don’t just check errors, handle them gracefully](https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully) diff --git a/structs-methods-and-interfaces.md b/structs-methods-and-interfaces.md index c16b95cc1..3031e7546 100644 --- a/structs-methods-and-interfaces.md +++ b/structs-methods-and-interfaces.md @@ -122,7 +122,7 @@ func TestArea(t *testing.T) { } ``` -Remember to run your tests before attempting to fix, you should get a helpful error like +Remember to run your tests before attempting to fix. The tests should show a helpful error like ```text ./shapes_test.go:7:18: not enough arguments in call to Perimeter @@ -144,7 +144,7 @@ func Area(rectangle Rectangle) float64 { } ``` -I hope you'll agree that passing a `Rectangle` to a function conveys our intent more clearly but there are more benefits of using structs that we will get on to. +I hope you'll agree that passing a `Rectangle` to a function conveys our intent more clearly, but there are more benefits of using structs that we will cover later. Our next requirement is to write an `Area` function for circles. @@ -176,7 +176,9 @@ func TestArea(t *testing.T) { } ``` -As you can see, the 'f' has been replaced by 'g', using 'f' it could be difficult to know the exact decimal number, with 'g' we get a complete decimal number in the error message \([fmt options](https://golang.org/pkg/fmt/)\). +As you can see, the `f` has been replaced by `g`, with good reason. +Use of `g` will print a more precise decimal number in the error message \([fmt options](https://golang.org/pkg/fmt/)\). +For example, using a radius of 1.5 in a circle area calculation, `f` would show `7.068583` whereas `g` would show `7.0685834705770345`. ## Try to run the test @@ -216,7 +218,8 @@ We have two choices: So far we have only been writing _functions_ but we have been using some methods. When we call `t.Errorf` we are calling the method `Errorf` on the instance of our `t` \(`testing.T`\). -A method is a function with a receiver. A method declaration binds an identifier, the method name, to a method, and associates the method with the receiver's base type. +A method is a function with a receiver. +A method declaration binds an identifier, the method name, to a method, and associates the method with the receiver's base type. Methods are very similar to functions but they are called by invoking them on an instance of a particular type. Where you can just call functions wherever you like, such as `Area(rectangle)` you can only call methods on "things". @@ -381,7 +384,7 @@ In Go **interface resolution is implicit**. If the type you pass in matches what ### Decoupling -Notice how our helper does not need to concern itself with whether the shape is a `Rectangle` or a `Circle` or a `Triangle`. By declaring an interface the helper is _decoupled_ from the concrete types and just has the method it needs to do its job. +Notice how our helper does not need to concern itself with whether the shape is a `Rectangle` or a `Circle` or a `Triangle`. By declaring an interface, the helper is _decoupled_ from the concrete types and only has the method it needs to do its job. This kind of approach of using interfaces to declare **only what you need** is very important in software design and will be covered in more detail in later sections. @@ -412,13 +415,14 @@ func TestArea(t *testing.T) { } ``` -The only new syntax here is creating an "anonymous struct", areaTests. We are declaring a slice of structs by using `[]struct` with two fields, the `shape` and the `want`. Then we fill the slice with cases. +The only new syntax here is creating an "anonymous struct", `areaTests`. We are declaring a slice of structs by using `[]struct` with two fields, the `shape` and the `want`. Then we fill the slice with cases. We then iterate over them just like we do any other slice, using the struct fields to run our tests. You can see how it would be very easy for a developer to introduce a new shape, implement `Area` and then add it to the test cases. In addition, if a bug is found with `Area` it is very easy to add a new test case to exercise it before fixing it. -Table based tests can be a great item in your toolbox but be sure that you have a need for the extra noise in the tests. If you wish to test various implementations of an interface, or if the data being passed in to a function has lots of different requirements that need testing then they are a great fit. +Table driven tests can be a great item in your toolbox, but be sure that you have a need for the extra noise in the tests. +They are a great fit when you wish to test various implementations of an interface, or if the data being passed in to a function has lots of different requirements that need testing. Let's demonstrate all this by adding another shape and testing it; a triangle. @@ -456,7 +460,7 @@ Remember, keep trying to run the test and let the compiler guide you toward a so `./shapes_test.go:25:4: undefined: Triangle` -We have not defined Triangle yet +We have not defined `Triangle` yet ```go type Triangle struct { @@ -472,7 +476,7 @@ Try again Triangle does not implement Shape (missing Area method) ``` -It's telling us we cannot use a Triangle as a shape because it does not have an `Area()` method, so add an empty implementation to get the test working +It's telling us we cannot use a `Triangle` as a shape because it does not have an `Area()` method, so add an empty implementation to get the test working ```go func (t Triangle) Area() float64 { @@ -522,19 +526,22 @@ In [Test-Driven Development by Example](https://g.co/kgs/yCzDLF) Kent Beck refac > The test speaks to us more clearly, as if it were an assertion of truth, **not a sequence of operations** -\(emphasis mine\) +\(emphasis in the quote is mine\) -Now our tests \(at least the list of cases\) make assertions of truth about shapes and their areas. +Now our tests - rather, the list of test cases - make assertions of truth about shapes and their areas. ## Make sure your test output is helpful Remember earlier when we were implementing `Triangle` and we had the failing test? It printed `shapes_test.go:31: got 0.00 want 36.00`. -We knew this was in relation to `Triangle` because we were just working with it, but what if a bug slipped in to the system in one of 20 cases in the table? How would a developer know which case failed? This is not a great experience for the developer, they will have to manually look through the cases to find out which case actually failed. +We knew this was in relation to `Triangle` because we were just working with it. +But what if a bug slipped in to the system in one of 20 cases in the table? +How would a developer know which case failed? +This is not a great experience for the developer, they will have to manually look through the cases to find out which case actually failed. We can change our error message into `%#v got %g want %g`. The `%#v` format string will print out our struct with the values in its field, so the developer can see at a glance the properties that are being tested. -To increase the readability of our test cases further we can rename the `want` field into something more descriptive like `hasArea`. +To increase the readability of our test cases further, we can rename the `want` field into something more descriptive like `hasArea`. One final tip with table driven tests is to use `t.Run` and to name the test cases. @@ -584,10 +591,10 @@ This was more TDD practice, iterating over our solutions to basic mathematic pro * Declaring structs to create your own data types which lets you bundle related data together and make the intent of your code clearer * Declaring interfaces so you can define functions that can be used by different types \([parametric polymorphism](https://en.wikipedia.org/wiki/Parametric_polymorphism)\) * Adding methods so you can add functionality to your data types and so you can implement interfaces -* Table based tests to make your assertions clearer and your suites easier to extend & maintain +* Table driven tests to make your assertions clearer and your test suites easier to extend & maintain This was an important chapter because we are now starting to define our own types. In statically typed languages like Go, being able to design your own types is essential for building software that is easy to understand, to piece together and to test. Interfaces are a great tool for hiding complexity away from other parts of the system. In our case our test helper _code_ did not need to know the exact shape it was asserting on, only how to "ask" for its area. -As you become more familiar with Go you start to see the real strength of interfaces and the standard library. You'll learn about interfaces defined in the standard library that are used _everywhere_ and by implementing them against your own types you can very quickly re-use a lot of great functionality. +As you become more familiar with Go you will start to see the real strength of interfaces and the standard library. You'll learn about interfaces defined in the standard library that are used _everywhere_ and by implementing them against your own types, you can very quickly re-use a lot of great functionality.