diff --git a/primeiros-passos-com-go/sync.md b/primeiros-passos-com-go/sync.md index ab1e0c1c..6f57a4f6 100644 --- a/primeiros-passos-com-go/sync.md +++ b/primeiros-passos-com-go/sync.md @@ -2,12 +2,11 @@ [**Você pode encontrar todo o código para esse capítulo aqui**](https://github.com/larien/learn-go-with-tests/tree/master/sync) -Queremos fazer um contador que é seguro para se usar concorrentemente. +Queremos fazer um contador que é seguro para ser usado concorrentemente. -Vamos começar com um contador inseguro e verificar se seu comportamento funciona em um ambiente com apenas uma thread. +Vamos começar com um contador não seguro e verificar se seu comportamento funciona em um ambiente com apenas uma _thread_. -Depois vamos testar sua falta de segurança com múltiplas *goroutines* tentando -usar o contador através de teste e consertar essa falha. +Em seguida, vamos testar sua falta de segurança com várias *goroutines* tentando usar o contador dentro dos testes e consertar essa falha. ## Escreva o teste primeiro @@ -15,15 +14,15 @@ Queremos que nossa API nos dê um método para incrementar o contador e depois r ```go func TestContador(t *testing.T) { - t.Run("incrementar o contador 3 vezes o deixa com valor 3", func(t *testing.T) { + t.Run("incrementar o contador 3 vezes resulta no valor 3", func(t *testing.T) { contador := Contador{} contador.Incrementa() contador.Incrementa() contador.Incrementa() if contador.Valor() != 3 { - t.Errorf("recebido %d, desejado %d", contador.Valor(), 3) - } + t.Errorf("resultado %d, esperado %d", contador.Valor(), 3) + } }) } ``` @@ -34,7 +33,7 @@ func TestContador(t *testing.T) { ./sync_test.go:9:14: undefined: Contador ``` -## Escreva a quantidade mínima de código para o teste rodar e verifique a saída do teste que falhou +## Escreva o mínimo de código possível para fazer o teste rodar e verifique a saída do teste que tiver falhado Vamos definir `Contador`. @@ -44,7 +43,7 @@ type Contador struct { } ``` -Tente de novo e ele falhará com o seguinte erro: +Tente rodar o teste de novo e ele falhará com o seguinte erro: ```text ./sync_test.go:14:10: contador.Incrementa undefined (type Contador has no field or method Incrementa) @@ -67,17 +66,15 @@ Agora tudo deve rodar e falhar: ```text === RUN TestContador -=== RUN TestContador/incrementar_o_contador_3_vezes_o_deixa_com_valor_3 +=== RUN TestContador/incrementar_o_contador_3_vezes_resulta_no_valor_3 --- FAIL: TestContador (0.00s) - --- FAIL: TestContador/incrementar_o_contador_3_vezes_o_deixa_com_valor_3 (0.00s) - sync_test.go:27: recebido 0, desejado 3 + --- FAIL: TestContador/incrementar_o_contador_3_vezes_resulta_no_valor_3 (0.00s) + sync_test.go:27: resultado 0, esperado 3 ``` ## Escreva código o suficiente para fazer o teste passar -Isso deve ser simples para _experts_ em Go como nós. Precisamos manter algum -estado do contador no nosso datatype e daí incrementá-lo em cada chamada do -`Incrementa`. +Isso deve ser simples para _experts_ em Go como nós. Precisamos criar uma instância do tipo Contador e incrementá-lo com cada chamada de `Incrementa`. ```go @@ -96,70 +93,60 @@ func (c *Contador) Valor() int { ## Refatoração -Não há muito o que refatorar, mas, dado que iremos escrever mais testes em -torno do `Contador`, vamos escrever uma pequena função de asserção `verificaContador` -para que o teste fique um pouco mais legível. +Não há muito o que refatorar, mas já que iremos escrever mais testes em torno do `Contador`, vamos escrever uma pequena função de asserção `verificaContador` para que o teste fique um pouco mais legível. ```go -t.Run("incrementar o contador 3 vezes o deixa com valor 3", func(t *testing.T) { - contador := Contador{} - contador.Incrementa() - contador.Incrementa() - contador.Incrementa() +t.Run("incrementar o contador 3 vezes resulta no valor 3", func(t *testing.T) { + contador := Contador{} + contador.Incrementa() + contador.Incrementa() + contador.Incrementa() - verificaContador(t, contador, 3) + verificaContador(t, contador, 3) }) -func verificaContador(t *testing.T, recebido Contador, desejado int) { - t.Helper() - if recebido.Valor() != desejado { - t.Errorf("recebido %d, quero receber %d", recebido.Valor(), desejado) - } +func verificaContador(t *testing.T, resultado Contador, esperado int) { + t.Helper() + if resultado.Valor() != esperado { + t.Errorf("resultado %d, esperado %d", resultado.Valor(), esperado) + } } ``` ## Próximos passos -Isso foi muito fácil, mas agora nós temos um requerimento que é: ele precisa -ser seguro o suficiente para ser usado em um ambiente com acesso concorrente. -Vamos precisar criar um teste para exercitar isso. +Isso foi muito fácil, mas agora temos um requerimento que é: o programa precisa ser seguro o suficiente para ser usado em um ambiente com acesso concorrente. Vamos precisar criar um teste para exercitar isso. ## Escreva o teste primeiro ```go -t.Run("roda concorrentemente com segurança", func(t *testing.T) { - contadorDesejado := 1000 - contador := Contador{} - - var wg sync.WaitGroup - wg.Add(contadorDesejado) - - for i:=0; i Um WaitGroup aguarda por uma coleção de *goroutines* terminar seu processamento. -A *goroutine* principal faz a chamada para o Add definir o número de *goroutines* -que serão esperadas. Então, cada uma das *goroutines* roda novamente e chama -Done quando terminar sua execução. Ao mesmo tempo, Wait pode ser usada para -bloquear a execução até que todas as *goroutines* tenham terminado. +> Um WaitGroup aguarda por uma coleção de *goroutines* terminar seu processamento. A *goroutine* principal faz a chamada para o `Add` definir o número de *goroutines* que serão esperadas. Então, cada uma das *goroutines* é executada e chama `Done` quando termina sua execução. Ao mesmo tempo, `Wait` pode ser usado para bloquear a execução até que todas as *goroutines* tenham terminado. -Ao esperar por `wg.Wait()` terminar sua execução antes de fazer nossas asserções, nós -podemos ter certeza que todas as nossas *goroutines* tentaram `Incrementa` o `Contador`. +Ao esperar por `wg.Wait()` terminar sua execução antes de fazer nossas asserções, podemos ter certeza que todas as nossas *goroutines* tentaram chamar o `Incrementa` no `Contador`. ## Tente rodar o teste @@ -167,18 +154,15 @@ podemos ter certeza que todas as nossas *goroutines* tentaram `Incrementa` o `Co === RUN TestContador/roda_concorrentemente_em_seguranca --- FAIL: TestContador (0.00s) --- FAIL: TestContador/roda_concorrentemente_em_seguranca (0.00s) - sync_test.go:26: recebido 939, desejado 1000 + sync_test.go:26: resultado 939, esperado 1000 FAIL ``` -O teste _provavelmente_ vai falhar com um número diferente, mas de toda forma -ele demonstra que não roda corretamente quando várias *goroutines* tentam -mudar o valor do contador ao mesmo tempo. +O teste _provavelmente_ vai falhar com um número diferente, mas de qualquer forma demonstra que não roda corretamente quando várias *goroutines* tentam mudar o valor do contador ao mesmo tempo. ## Escreva código o suficiente para fazer o teste passar -Uma solução simples é adicionar uma trava ao nosso `Contador`, um -[`Mutex`](https://golang.org/pkg/sync/#Mutex). +Uma solução simples é adicionar uma trava ao nosso `Contador`, um [`Mutex`](https://golang.org/pkg/sync/#Mutex). > Um Mutex é uma trava de exclusão mútua. O valor zero de um Mutex é um Mutex destravado. @@ -195,14 +179,11 @@ func (c *Contador) Incrementa() { } ``` -Isso significa que qualquer *goroutine* chamando `Incrementa` vai receber a trava em `Contador` -se for a primeira. Todas as outras *goroutines* vão ter que esperar por ele até -que ele esteja `Unlock`, ou destravado, antes de ganhar o acesso. +Isso significa que qualquer *goroutine* chamando `Incrementa` vai receber a trava em `Contador` se for a primeira chamando essa função. Todas as outras *goroutines* vão ter que esperar por essa primeira execução até que ele esteja `Unlock`, ou destravado, antes de ganhar o acesso à instância de `Contador` alterada pela primeira chamada de função. -Agora se você rodar o teste novamente, ele deve funcionar porque cada uma das *goroutines* -têm que esperar até que seja sua vez antes de fazer alguma mudança. +Agora, se você rodar o teste novamente, ele deve funcionar porque cada uma das *goroutines* tem que esperar até que seja sua vez antes de fazer alguma mudança. -## Eu vi outros exemplos nos quais `sync.Mutex` está embutido dentro da struct. +## Já vi outros exemplos em que o `sync.Mutex` está embutido dentro da struct. Você pode ver exemplos como esse: @@ -213,7 +194,7 @@ type Contador struct { } ``` -Há quem diga que isso pode tornar o código um pouco mais elegante. +Há quem diga que isso torna o código um pouco mais elegante. ```go func (c *Contador) Incrementa() { @@ -223,22 +204,15 @@ func (c *Contador) Incrementa() { } ``` -Isso _parece_ legal, mas, apesar de programação ser uma área altamente -subjetiva, isso é **feio e errado**. +Isso _parece_ legal, mas, apesar de programação ser uma área altamente subjetiva, isso é **feio e errado**. -Às vezes as pessoas esquecem que tipos embutidos significam que os métodos -daquele tipo se tornam _parte da interface pública_; e você geralmente não -quer isso. Não se esqueçam que devemos ter muito cuidado com as nossas APIs -públicas. O momento que tornamos algo público é o momento que outros códigos -podem acoplar-se a ele e nós queremos evitar acoplamentos desnecessários. +Às vezes as pessoas esquecem que tipos embutidos significam que os métodos daquele tipo se tornam _parte da interface pública_; e você geralmente não quer isso. Não se esqueçam que devemos ter muito cuidado com as nossas APIs públicas. O momento que tornamos algo público é o momento que outros códigos podem acoplar-se a ele e queremos evitar acoplamentos desnecessários. -Expôr `Lock` e `Unlock` é, no seu melhor caso, muito confuso e, no seu pior -caso, potencialmente perigoso para o seu software se quem chamar o seu tipo -começar a chamar esses métodos diretamente. +Expôr `Lock` e `Unlock` é, no seu melhor caso, muito confuso e, no seu pior caso, potencialmente perigoso para o seu software se quem chamar o seu tipo começar a chamar esses métodos diretamente. ![Demonstração de como um usuário dessa API pode chamar erroneamente o estado da trava](https://i.imgur.com/SWYNpwm.png) -_Isso parece uma péssima ideia_ +_Isso parece uma péssima ideia._ ## Copiando mutexes @@ -251,25 +225,19 @@ sync/v2/sync_test.go:16: call of verificaContador copies lock valor: v1.Contador sync/v2/sync_test.go:39: verificaContador passes lock by valor: v1.Contador contains sync.Mutex ``` -Uma rápida olhada na documentação do [`sync.Mutex`](https://golang.org/pkg/sync/#Mutex) -nos diz o porquê: +Uma rápida olhada na documentação do [`sync.Mutex`](https://golang.org/pkg/sync/#Mutex) nos diz o porquê: > Um Mutex não deve ser copiado depois do primeiro uso. -Quando passamos nosso `Contador` \(por valor\) para `verificaContador`, -ele vai tentar criar uma cópia do mutex. +Quando passamos nosso `Contador` \(por valor\) para `verificaContador`, ele vai tentar criar uma cópia do mutex. -Para resolver isso, devemos passar um ponteiro para o nosso `Contador`. -Vamos, então, mudar a assinatura de `verificaContador`. +Para resolver isso, devemos passar um ponteiro para o nosso `Contador`. Vamos, então, mudar a assinatura de `verificaContador`. ```go -func verificaContador(t *testing.T, recebido *Contador, desejado int) +func verificaContador(t *testing.T, resultado *Contador, esperado int) ``` -Nossos testes não vão mais compilar porque estamos tentando passar um `Contador` -em vez de um `*Contador`. Para resolver isso, é preferível criar um construtor -que mostra aos usuários da nossa API que seria melhor não inicializar o tipo -ele mesmo. +Nossos testes não vão mais compilar porque estamos tentando passar um `Contador` ao invés de um `*Contador`. Para resolver isso, é melhor criar um construtor que mostra aos usuários da nossa API que seria melhor ele mesmo não inicializar seu tipo. ```go @@ -280,24 +248,20 @@ func NovoContador() *Contador { Use essa função em seus testes quando for inicializar o `Contador`. -## Resumindo +## Resumo -Cobrimos algumas coisas do [pacote sync](https://golang.org/pkg/sync/): +Falamos sobre algumas coisas do [pacote sync](https://golang.org/pkg/sync/): -* `Mutex` que nos permite adicionar travas aos nossos dados -* `Waitgroup` que é uma maneira de esperar as *goroutines* terminarem suas tarefas +* `Mutex` nos permite adicionar travas aos nossos dados +* `WaitGroup` é uma maneira de esperar as *goroutines* terminarem suas tarefas ### Quando usar travas em vez de *channels* e *goroutines*? -[Anteriormente cobrimos *goroutines* no primeiro capítulo de concorrência](concorrencia.md) +[Anteriormente falamos sobre *goroutines* no primeiro capítulo sobre concorrência](concorrencia.md) que nos permite escrever código concorrente e seguro, então por que usar travas? -[A wiki do go tem uma página dedicada para esse tópico: Mutex ou Channel?](https://github.com/golang/go/wiki/MutexOrChannel) +[A wiki do Go tem uma página dedicada para esse tópico: Mutex ou Channel?](https://github.com/golang/go/wiki/MutexOrChannel) -> Um erro comum de um iniciante em Go é usar demais os *channels* e *goroutines* apenas -porque é possível e/ou porque é divertido. Não tenha medo de usar um `sync.Mutex` se -ele se encaixar melhor no seu problema. Go é pragmático em deixar você escolher as -ferramentas que melhor resolvem o seu problema e não te força em um único estilo -de código. +> Um erro comum de um iniciante em Go é usar demais os *channels* e *goroutines* apenas porque é possível e/ou porque é divertido. Não tenha medo de usar um `sync.Mutex` se for uma solução melhor para o seu problema. Go é pragmático em deixar você escolher as ferramentas que melhor resolvem o seu problema e não te força em um único estilo de código. Resumindo: @@ -306,16 +270,11 @@ Resumindo: ### go vet -Não se esqueça de usar `go vet` nos seus scripts de build porque ele pode te alertar a respeito de bugs mais -sutis no seu código antes que eles atinjam seus pobres usuários. +Não se esqueça de usar `go vet` nos seus scripts de _build_ porque ele pode te alertar a respeito de bugs mais sutis no seu código antes que eles atinjam seus pobres usuários. ### Não use códigos embutidos apenas porque é conveniente * Pense a respeito do efeito que embutir códigos tem na sua API pública. -* Você _realmente_ quer expôr esses métodos e ter pessoas acoplando o código -próprio delas a ele? -* Em relação a mutexes, pode ser potencialmente um desastre de maneiras -muito imprevisíveis e estranhas. Imagine algum código obscuro destravando um -mutex quando não deveria; isso causaria erros muito estranhos e que seriam bastante -difíceis de encontrar. +* Você _realmente_ quer expôr esses métodos e ter pessoas acoplando o código próprio delas a ele? +* Mutexes podem se tornar um desastre de maneiras muito imprevisíveis e estranhas. Imagine um código inesperado destravando um mutex quando não deveria? Isso causaria erros muito estranhos que seriam muito difíceis de encontrar. diff --git a/sync/v1/sync_test.go b/sync/v1/sync_test.go index 287cd900..1d2d4cff 100644 --- a/sync/v1/sync_test.go +++ b/sync/v1/sync_test.go @@ -5,7 +5,7 @@ import ( ) func TestContador(t *testing.T) { - t.Run("incrementar o contador 3 vezes o deixa com valor 3", func(t *testing.T) { + t.Run("incrementar o contador 3 vezes resulta no valor 3", func(t *testing.T) { contador := Contador{} contador.Incrementa() contador.Incrementa()