Arrays e slices
Você pode encontrar todos os códigos para esse capítulo aqui
Arrays te permitem armazenar diversos elementos do mesmo tipo em uma variável em uma ordem específica.
Quando você tem um array, é muito comum ter que percorrer sobre ele. Logo, vamos usar nosso recém adquirido conhecimento de for
para criar uma função Soma
. Soma
vai receber um array de números e retornar o total.
Também vamos praticar nossas habilidades em TDD.
Escreva o teste primeiro
Em soma_test.go
:
Arrays têm uma capacidade fixa que é definida quando você declara a variável. Podemos inicializar um array de duas formas:
[N]tipo{valor1, valor2, ..., valorN}, como
numeros := [5]int{1, 2, 3, 4, 5}
[...]tipo{valor1, valor2, ..., valorN}, como
numbers := [...]int{1, 2, 3, 4, 5}
Às vezes é útil também mostrarmos as entradas da função na mensagem de erro. Para isso estamos usando o formatador %v
, que é o formato "padrão" e funciona bem com arrays.
Leia mais sobre formatação de strings aqui
Execute o teste
Ao executar go test
, o compilador vai falhar com ./soma_test.go:10:15: undefined: Soma
Escreva o mínimo de código possível para fazer o teste rodar e verifique a saída do teste falhado
Em soma.go
:
Agora seu teste deve falhar com uma mensagem clara de erro:
soma_test.go:13: resultado 0, esperado 15, dado [1 2 3 4 5]
Escreva código o suficiente para fazer o teste passar
Para receber o valor de um array em uma posição específica, basta usar a sintaxe array[índice]
. Nesse caso, estamos usando o for
para percorrer cada posição do array (que tem 5 posições) e somar cada valor na variável soma
.
Refatoração
Vamos apresentar o range
para nos ajudar a limpar o código:
O range
permite que você percorra um array. Sempre que é chamado, retorna dois valores: o índice e o valor. Decidimos ignorar o valor índice usando _
blank identifier.
Arrays e seus tipos
Uma propriedade interessante dos arrays é que seu tamanho é relacionado ao seu tipo. Se tentar passar um [4]int
dentro da função que espera [5]int
, ela não vai compilar. Elas são de tipos diferentes e é a mesma coisa que tentar passar uma string
para uma função que espera um int
.
Você pode estar pensando que é bastante complicado que arrays tenham tamanho fixo, não é? Só que na maioria das vezes, você provavelmente não vai usá-los!
O Go tem slices, em que você não define o tamanho da coleção e, graças a isso, pode ter qualquer tamanho.
O próprio requerimento será somar coleções de tamanhos variados.
Escreva o teste primeiro
Agora vamos usar o tipo slice que nos permite ter coleções de qualquer tamanho. A sintaxe é bem parecida com a dos arrays e você só precisa omitir o tamanho quando declará-lo.
meuSlice := []int{1,2,3}
ao invés de meuArray := [3]int{1,2,3}
Execute o teste
Isso não vai compilar.
./soma_test.go:22:13: cannot use numbers (type []int) as type [5]int in argument to Soma
não é possível usar números (tipo []int) como tipo [5]int no argumento para Soma
Escreva o mínimo de código possível para fazer o teste rodar e verifique a saída do teste falhado
Para resolver o problema, podemos:
Alterar a API existente mudando o argumento de
Soma
para um slice ao invés de um array.Quando fazemos isso, vamos saber que podemos ter arruinado do dia de alguém, porque nosso outro teste não vai compilar!Criar uma nova função
No nosso caso, mais ninguém está usando nossa função. Logo, ao invés de ter duas funções para manter, vamos usar apenas uma.
Se tentar rodar os testes eles ainda não vão compilar. Você vai ter que alterar o primeiro teste e passar um slice ao invés de um array.
Escreva código o suficiente para fazer o teste passar
Nesse caso, para arrumar os problemas de compilação, tudo o que precisamos fazer aqui é fazer os testes passarem!
Refatoração
Nós já refatoramos a função Soma
e tudo o que fizemos foi mudar os arrays para slices. Logo, não há muito o que fazer aqui. Lembre-se que não devemos abandonar nosso código de teste na etapa de refatoração e precisamos fazer alguma coisa aqui.
É importante questionar o valor dos seus testes. Ter o máximo de testes possível não deve ser o objetivo e sim ter o máximo de confiança possível na sua base de código. Ter testes demais pode se tornar um problema real e só adiciona mais peso na manutenção. Todo teste tem um custo.
No nosso caso, dá para perceber que ter dois testes para essa função é redundância. Se funciona para um slice de determindo tamanho, é muito provável que funciona para um slice de qualquer tamanho (dentro desse escopo).
A ferramenta de testes nativa do Go tem a funcionalidade de cobertura de código que te ajuda a identificar áreas do seu código que você não cobriu. Já adianto que ter 100% de cobertura não deve ser seu objetivo; é apenas uma ferramenta para te dar uma ideia da sua cobertura. De qualquer forma, se você aplicar o TDD, é bem provável que chegue bem perto dos 100% de cobertura.
Tente executar go test -cover
no terminal.
Você deve ver:
Agora apague um dos testes e verifique a cobertura novamente.
Agora que estamos felizes com nossa função bem testada, você deve salvar seu trabalho incrível com um commit antes de partir para o próximo desafio.
Precisamos de uma nova função chamada SomaTudo
, que vai receber uma quantidade variável de slices e devolver um novo slice contendo as somas de cada slice recebido.
Por exemplo:
SomaTudo([]int{1,2}, []int{0,9})
deve retornar []int{3, 9}
ou
SomaTudo([]int{1,1,1})
deve retornar []int{3}
Escreva o teste primeiro
Execute o teste
./soma_test.go:23:9: undefined: SomaTudo
Escreva o mínimo de código possível para fazer o teste rodar e verifique a saída do teste falhado
Precisamos definir o SomaTudo de acordo com o que nosso teste precisa.
O Go te permite escrever funções variádicas em que a quantidade de argumentos podem variar.
Pode tentar compilar, mas nossos testes não vão funcionar!
./soma_test.go:26:9: invalid operation: got != want (slice can only be compared to nil)
operação inválida: recebido != esperado (slice só pode ser comparado a nil
O Go não te deixa usar operadores de igualdade com slices. É possível escrever uma função que percorre cada slice recebido
e esperado
e verificar seus valores, mas por praticidade podemos usar o reflect.DeepEqual
que é útil para verificar se duas variáveis são iguais.
(coloque import reflect
no topo do seu arquivo para ter acesso ao DeepEqual
)
É importante saber que o reflect.DeepEqual
não tem "segurança de tipos", ou seja, o código vai compilar mesmo se você tiver feito algo estranho. Para ver isso em ação, altere o teste temporariamente para:
O que fizemos aqui foi comparar um slice
com uma string
. Isso não faz sentido, mas o teste compila! Logo, apesar de ser uma forma simples de comparar slices (e outras coisas), você deve tomar cuidado quando for usar o reflect.DeepEqual
.
Volte o teste da forma como estava e execute-o. Você deve ter a saída do teste com uma mensagem tipo:
soma_test.go:30: recebido [], esperado [3 9]
Escreva código o suficiente para fazer o teste passar
O que precisamos fazer é percorrer as variáveis recebidas como argumento, calcular a soma com nossa função Soma
de antes e adicioná-la ao slice que vamos retornar:
Muitas coisas novas para aprender!
Há uma nova forma de criar um slice. O make
te permite criar um slice com uma capacidade inicial de len
de numerosParaSomar
que precisamos percorrer.
Você pode indexar slices como arrays com meuSlice[N]
para obter seu valor ou designá-lo a um novo valor com =
.
Agora o teste deve passar.
Refatoração
Como mencionado, slices têm uma capacidade. Se você tiver um slice com uma capacidade de 2 e tentar fazer uma atribuição como meuSlice[10] = 1
, vai receber um erro em tempo de execução.
No entanto, você pode usar a função append
, que recebe um slice e um novo valor e retorna um novo slice com todos os itens dentro dele.
Nessa implementação, nos preocupamos menos sobre capacidade. Começamos com um slice vazio somas
e o anexamos ao resultado de Soma
enquanto percorremos as variáveis recebidas como argumento.
Nosso próprio requisito é alterar o SomaTudo
para SomaTodoOResto
, onde agora calcula os totais de todos os "finais" de cada slice. O final de uma coleção é todos os itens com exceção do primeiro (a "cabeça").
Escreva o teste primeiro
Execute o teste
./soma_test.go:26:9: undefined: SomaTodoOResto
Escreva o mínimo de código possível para fazer o teste rodar e verifique a saída do teste falhado
Renomeie a função para SomaTodoOResto
e volte a executar o teste.
soma_test.go:30: resultado [3 9], esperado [2 9]
Escreva código o suficiente para fazer o teste passar
Slices podem ser "fatiados"! A sintaxe usada é slice[inicio:final]
. Se você omitir o valor de um dos lados dos :
ele captura tudo do lado omitido. No nosso caso, quando usamos numeros[1:]
, estamos dizendo "pegue da posição 1 até o final". É uma boa ideia investir um tempo escrevend outros testes com slices e brincar com o operador slice para criar mais familiaridade com ele.
Refatoração
Não tem muito o que refatorar dessa vez.
O que acha que aconteceria se você passar um slice vazio para a nossa função? Qual é o "final" de um slice vazio? O que acontece quando você fala para o Go capturar todos os elementos de meuSliceVazio[1:]
?
Escreva o teste primeiro
Execute o teste
pânico: erro em tempo de execução: fora da capacidade do slice
Oh, não! É importante perceber que o test foi compilado, esse é um erro em tempo de execução. Erros em tempo de compilação são nossos amigos, porque nos ajudam a escrever softwares que funcionam. Erros em tempo de execução são nosso inimigos, porque afetam nossos usuários.
Escreva código o suficiente para fazer o teste passar
Refatoração
Nossos testes têm código repetido em relação à asserção de novo. Vamos encapsular isso em uma função:
Um efeito colateral útil disso é que adiciona um pouco de segurança de tipos no nosso código. Se uma pessoa espertinha adicionar um novo teste com verificaSomas(t, resultado, "luisa")
o compilador vai pará-lo antes que algo errado aconteça.
não é possível usar "luisa" (tipo string) como tipo []int no argumento para verificaSomas
Resumindo
Falamos sobre:
Arrays
Slices
Várias formas de criá-las
Como eles têm uma capacidade fixa, mas é posível criar novos slices de antigos usando
append
Como "fatiar" slices!
len
obtém o tamanho de um array ou sliceFerramenta de cobertura de testes
reflect.DeepEqual
e por que é útil, mas pode diminuir a segurança de tipos do seu código
Usamos slices e arrays com inteiros, mas eles também funcionam com qualquer outro tipo, incluindo até os próprios arrays/slices. Logo, você pode declarar uma variável de [][]string
se precisar.
Dê uma olhada no post sobre slices no blog de Go para saber mais sobre slices. Tente escrever mais testes para demonstrar o que você aprendeu com a leitura.
Outra forma útil de brincar com Go ao invés de escrever testes é o Go playground. Você pode testar mais coisas lá e você pode compartilhar seu código facilmente se precisar tirar dúvidas. Criei um exemplo com um slice para testar lá.
Last updated