Skip to content

Commit

Permalink
Documentation Tweaks (quii#461)
Browse files Browse the repository at this point in the history
* Update maps.md

Rogue parenthesis in refactor example

* Documentation Updates

* tab->spaces

* Removed an option that wasn't an option, just a description of the current state of affairs

* Char formatting better with exclamation mark italicised
  • Loading branch information
JamesPeiris authored Jun 10, 2021
1 parent e3f8ca9 commit 395712b
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 70 deletions.
67 changes: 32 additions & 35 deletions arrays-and-slices.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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.

Expand All @@ -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)`

Expand All @@ -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:

Expand All @@ -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 {
Expand All @@ -362,15 +359,15 @@ 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

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 {
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion hello-world.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 2 additions & 2 deletions http-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion integers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
1 change: 0 additions & 1 deletion maps.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
31 changes: 17 additions & 14 deletions pointers-and-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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**_.

Expand Down Expand Up @@ -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

Expand All @@ -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) {
Expand All @@ -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

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)

Expand Down
Loading

0 comments on commit 395712b

Please sign in to comment.