Trilhas
/
Go
Go
/
Exercícios
/
A fazenda
A fazenda

A fazenda

Exercício de aprendizagem

Introdução

A interface de erro

O tratamento de erros não é feito com exceções em Go. Em vez disso, erros são valores comuns de tipos que implementam a interface error embutida. A interface error é bastante enxuta. Ela contém apenas um método, Error(), que retorna a mensagem de erro como uma string.

type error interface {
  Error() string
}

Sempre que você define uma função em que um erro pode acontecer durante a execução e precisa chegar até quem chamou, você precisa incluir error como um dos tipos de retorno. Se a função tem vários valores de retorno, por convenção error é sempre o último.

func DoSomething() (int, error) {
  // ...
}

Criando e retornando um erro simples

Você não precisa implementar a interface de erro por conta própria o tempo todo. Para criar um erro simples, você pode usar a função errors.New(), que faz parte do pacote errors da biblioteca padrão. A única coisa que você precisa passar é a mensagem de erro como uma string, e errors.New() se encarrega de criar um valor que contém a sua mensagem e implementa a interface error.

Se a função retorna um erro, é uma boa prática retornar o valor zero para todos os outros parâmetros de retorno:

func DoSomething() (SomeStruct, int, error) {
  // ...
  return SomeStruct{}, 0, errors.New("failed to calculate result")
}
Caution

Você não deve presumir que todas as funções retornam valores zero para os outros valores de retorno quando há um erro. A melhor prática é presumir que não é seguro usar nenhum dos outros valores de retorno se ocorreu um erro. As únicas exceções são os casos em que a documentação afirma claramente que os outros valores de retorno fazem sentido em caso de erro.

Se você quiser usar um erro simples como esse em vários lugares, declare uma variável para o erro em vez de usar errors.New na própria linha. Por convenção, o nome da variável deve começar com Err ou err (dependendo se ela é exportada ou não). Essas variáveis de erro costumam ser chamadas de erros sentinela.

import "errors"

var ErrNotFound = errors.New("resource was not found")

func DoSomething() error {
  // ...
  return ErrNotFound
}

Retorne nil para o erro para sinalizar que não houve erros durante a execução da função:

func Foo() (int, error) {
  return 10, nil
}

Verificação de erros

Se você chama uma função que retorna um erro, é comum guardar o valor do erro em uma variável chamada err. Antes de usar o resultado de fato da função, você precisa verificar que não houve erro.

Para evitar aninhar o "caminho feliz" do seu código, os casos de erro devem ser tratados primeiro. Podemos usar == e != para comparar o erro com nil, e sabemos que houve um erro quando err não é nil.

func processUserFile() error {
	file, err := os.Open("./users.csv")
	if err != nil {
		return err
	}

	// do something with file
}

Na maioria das vezes, o erro será retornado para cima na pilha de funções, como mostra o exemplo acima. Outra forma de tratar o erro é registrá-lo em log e continuar com alguma outra operação. É uma boa prática retornar ou registrar o erro em log, nunca os dois.

Como a maioria das funções em Go inclui um erro como um dos valores de retorno, você vai ver e usar o padrão if err != nil em todo lugar no código Go.

Tipos de erro personalizados

Se você quer que o seu erro inclua mais informações do que apenas a string da mensagem de erro, você pode criar um tipo de erro personalizado. Como mencionado antes, tudo que implementa a interface error (ou seja, tem um método Error() string) pode servir como um erro em Go.

Normalmente, uma struct é usada para criar um tipo de erro personalizado. Por convenção, os nomes de tipos de erro personalizados devem terminar com Error. Além disso, é melhor definir o método Error() string com um receptor de ponteiro; veja este comentário no Stackoverflow para entender o motivo. Note que isso significa que você precisa retornar um ponteiro para o seu erro personalizado, caso contrário ele não conta como error, porque o valor não-ponteiro não fornece o método Error() string.

type MyCustomError struct {
  message string
  details string
}

func (e *MyCustomError) Error() string {
  return fmt.Sprintf("%s, details: %s", e.message, e.details)
}

func someFunction() error {
  // ...
  return &MyCustomError{
    message: "...",
    details: "...",
  }
}

Instruções

O dia que você esperou por tanto tempo finalmente chegou, e agora você é o orgulhoso dono de uma bela fazenda nos Alpes.

Você ainda não gosta de acordar cedo demais para alimentar suas vacas. Como você é um excelente engenheiro, você constrói um distribuidor de comida, o FEED-M-ALL.

A última coisa necessária para terminar seu projeto é um trecho de código que calcula a quantidade de forragem que cada vaca deve receber. É importante que cada vaca receba a mesma quantidade, você precisa evitar conflitos. Vacas são muito sensíveis.

Felizmente, você não precisa calcular todas as fórmulas de forragem por conta própria. Você usa uma biblioteca externa misteriosa que encontrou na internet. Ela deveria resultar nas vacas mais felizes. A biblioteca expõe um tipo que cumpre a seguinte interface. Você vai contar com isso no código que escrever.

type FodderCalculator interface {
	FodderAmount(int) (float64, error)
	FatteningFactor() (float64, error)
}

À medida que você trabalha no seu código, vai melhorar o tratamento de erros para torná-lo mais robusto e mais fácil de depurar depois, quando usá-lo na sua vida diária na fazenda.

1. Divida a comida igualmente

Em primeiro lugar, você foca em escrever o código necessário para calcular a quantidade de forragem por vaca.

Implemente uma função DivideFood que aceita um FodderCalculator e um número de vacas como inteiro como argumentos. Para esta tarefa, você assume que o número de vacas passado é sempre maior que zero. A função deve retornar a quantidade de comida por vaca como um float64 ou um erro, se ocorrer algum.

Para fazer o cálculo, você primeiro precisa obter a quantidade total de forragem para todas as vacas. Isso é feito chamando o método FodderAmount e passando o número de vacas. Além disso, você precisa de um fator pelo qual essa quantidade precisa ser multiplicada. Você obtém esse fator chamando o método FatteningFactor. Com esses dois valores e o número de vacas, você pode agora calcular a quantidade de comida por vaca (como um float64). É isso que deve ser retornado da função DivideFood.

Se um dos métodos que você chama retornar um erro, a execução deve parar e esse erro deve ser retornado (como está) da função DivideFood.

// For this example, we assume FodderAmount returns 50
// and FatteningFactor returns 1.5.
DivideFood(fodderCalculator, 5)
// => 15 <nil>

// Now assuming FodderAmount returns an error with message "something went wrong".
DivideFood(fodderCalculator, 5)
// => 0 "something went wrong"

2. Verifique o número de vacas

Enquanto trabalhava na primeira tarefa acima, você percebeu que a biblioteca externa que usa não tem a qualidade que você imaginava. Por exemplo, ela não consegue lidar corretamente com entradas inválidas. Você quer contornar essa limitação adicionando uma verificação para o valor de entrada no seu próprio código.

Escreva uma função ValidateInputAndDivideFood que tem a mesma assinatura que DivideFood acima.

  • Se o número de vacas passado for maior que 0, a função deve chamar DivideFood e retornar os resultados dessa chamada.
  • Se o número de vacas for 0 ou menos, a função deve retornar um erro com a mensagem "invalid number of cows".
ValidateInputAndDivideFood(fodderCalculator, 5)
// => 15 <nil>

ValidateInputAndDivideFood(fodderCalculator, -2)
// => 0 "invalid number of cows"

3. Melhore o tratamento de erros

Verificar o número de vacas antes de passá-lo adiante foi uma boa ideia, mas você não está muito satisfeito com a mensagem de erro pouco específica. Você decide fazer melhor criando um tipo de erro personalizado chamado InvalidCowsError.

O erro personalizado deve armazenar o número de vacas (int) e uma mensagem personalizada (string), e o método Error deve serializar os dados no seguinte formato:

{number of cows} cows are invalid: {custom message}

De posse do seu erro personalizado, implemente uma função ValidateNumberOfCows que aceita o número de vacas como um inteiro e retorna um erro (ou nil).

  • Se o número de vacas for menor que 0, a função retorna um InvalidCowsError com a mensagem personalizada definida como "there are no negative cows".
  • Se o número de vacas for 0, a função retorna um InvalidCowsError com a mensagem personalizada definida como "no cows don't need food".
  • Caso contrário, a função retorna nil para indicar que a validação foi bem-sucedida.
err := ValidateNumberOfCows(-5)
err.Error()
// => "-5 cows are invalid: there are no negative cows"

Depois do trabalho duro de montar essa função de validação, você percebe que já é noite e sai da sua mesa para curtir o pôr do sol sobre as montanhas. Você deixa para outro dia a tarefa de de fato adicionar a nova função de validação ao seu código.

Editar via GitHub O link abre em uma nova janela ou aba
Go Exercism

Tudo pronto para começar A fazenda?

Crie sua conta no Exercism para aprender e dominar Go com 34 conceitos166 exercícios e mentoria humana de verdade, tudo de graça.