Caixa eletrônico em Ruby

Criado por Joshua Paling, @joshuapaling

*Traduzido por Ricardo Yasuda, @shadowmaru

Neste exercício você vai escrever uma função para lidar com saques em um caixa eletrônico. A intenção é desafiar você! Prepare-se para fazer muitas perguntas, pesquisar, e sair um pouco do computador. Mas você aprenderá bastante!

Você deverá usar programação em par, grupo ou remota, para ter outras pessoas para discutir ideias. Você vai usar Test Driven Development. No entanto, todos os testes já foram pré-escritos para você, para que você possa focar no código em si.

Começaremos com algo simples, mas vamos aumentar muito a dificuldade perto do final!

Fluxo

Para cada passo, você vai precisar executar as ações a seguir:

1. Rodar os novos testes: Apague os testes do passo anterior, e cole os testes pré-escritos para o passo atual. (Testes para cada passo são exibidos no fim do mesmo.) Rode os testes para ver quais deles falham. No Sublime Text, você pode usar Ctrl+B para rodar os testes. Ou, você pode abrir um terminal, cd para o seu diretório de trabalho, e executar ruby caixa_eletronico.rb.

2. Faça os testes passarem: Edite seu código para satisfazer os requisitos funcionais do passo atual. Você saberá que conseguiu quando todos os testes ficarem verdes.

3. Refatore: Veja se você pode reescrever algo para fazer o código ficar o mais limpo e fácil de entender possível. Alguns passos têm pontos de discussão. Discuta-os com seus pares - eles vão ajudar a seguir em frente.

Você deve ter reconhecido estes passos como o fluxo red, green, refactor do TDD.

1. Notas de R$5

Imagine um caixa eletrônico que possui apenas notas de R$5. Escreva uma função que retorne true se um valor pode ser sacado, senão retorne false.

Exemplos:

Dicas para fazer os testes ficarem verdes:

A operação módulo, % obtém o resto de uma divisão. Por exemplo, 9 % 4 resulta em 1 (nove dividido por quatro tem resto 1).

Código inicial:

Crie um arquivo chamado caixa_eletronico.rb. Cole o seguinte código nele. Ele contém a casca da sua função withdraw(), com os testes.

def withdraw(amount)
  if amount <= 0 # isso aqui lida com algumas das situações...
    return false
  end
  # ToDo: descubra como fazer esta parte
end

Testes para o passo 1:

# importe as bibliotecas de teste obrigatórias
require 'minitest/spec'
require 'minitest/autorun'

# SEGUEM ABAIXO OS TESTES PARA VERIFICAR AUTOMATICAMENTE A SUA SOLUÇÃO.
# ESTES TESTES SÃO PARA O PASSO 1.
# ELES DEVEM SER SUBSTITUÍDOS A CADA PASSO.
describe 'caixa eletrônico' do
  [
    [-1, false],
    [0, false],
    [1, false],
    [43, false],
    [17, false],
    [5, true],
    [20, true],
    [35, true],
  ].each do |input, expected|
    it "deve retornar #{expected} quando R$#{input} é sacado" do
      withdraw(input).must_equal expected
    end
  end
end

2. Quantas notas?

Agora, modifique sua função para que caso o valor possa ser sacado, retorne o número apropriado de notas, em vez de simplesmente true

Exemplos:

Dicas para fazer os testes ficarem verdes:

O operador / faz uma divisão. Por exemplo, se você quer saber a metade da sua idade, e guardar em uma variável, faça isso:

my_age = 28
half_my_age = my_age / 2

Em Ruby, quando você faz uma divisão com dois números inteiros (integers), o resultado é arredondado para baixo para o número inteiro mais próximo.

new_number = 25/10
# new_number é 2, porque 25/10 = 2.5, e o Ruby vai arredondar para baixo para 2.

Testes para o passo 2:

# Substitua seus testes existentes por estes abaixo.
describe 'caixa eletrônico' do
  [
    [-1, false],
    [0, false],
    [1, false],
    [43, false],
    [7, false],
    [5, 1],
    [20, 4],
    [35, 7],
  ].each do |input, expected|
    it "deve retornar #{expected} quando R$#{input} é sacado" do
      withdraw(input).must_equal expected
    end
  end
end

3. Array de notas

Em programação, um array é basicamente uma coleção de coisas. É como se fosse uma lista.

Ao invés de retornar o número de notas, modifique o código para ele retornar um array de notas (neste caso, todas de R$5).

Exemplos

Dicas para fazer os testes ficarem verdes:

[] define um array vazio. [1, 2] define um array com dois elementos (1 e 2).

O operador << adiciona um elemento a um array. Por exemplo, [10, 20] << 30 vai adicionar 30 ao array, resultando em [10, 20, 30].

O método times executa um bloco de código várias vezes - por exemplo, 5.times { puts 'hello' } vai imprimir ‘hello’ 5 vezes.

Juntando tudo:

my_array = [] # cria um array vazio e guarda em my_array
my_array << 20 # agora my array contém [20]
my_array << 30 # agora my_array contém [20, 30]
remainder = 13 % 5 # remainder é 3
remainder.times { my_array << 5 } # agora my_array contém [20, 30, 5, 5, 5]

Testes para o passo 3:

# Substitua seus testes existentes por estes abaixo.
describe 'caixa eletrônico' do
  [
    [-1, false],
    [0, false],
    [1, false],
    [43, false],
    [20, [5, 5, 5, 5]],
    [35, [5, 5, 5, 5, 5, 5, 5]],
  ].each do |input, expected|
    it "deve retornar #{expected} quando R$#{input} é sacado" do
      withdraw(input).must_equal expected
    end
  end
end

4. Notas de R$10

Agora imagine que o caixa retorne apenas notas de R$10. Modifique sua função para refletir isso.

Exemplos

Testes para o passo 4:

# Substitua seus testes existentes por estes abaixo.
describe 'caixa eletrônico' do
  [
    [-1, false],
    [0, false],
    [7, false],
    [45, false],
    [20, [10, 10]],
    [40, [10, 10, 10, 10]],
  ].each do |input, expected|
    it "deve retornar #{expected} quando R$#{input} é sacado" do
      withdraw(input).must_equal expected
    end
  end
end

Refatore

Uma vez que seus testes passarem, é hora de refatorar.

Em programação, ‘Números Mágicos’ são algo ruim (não se deixe enganar pelo nome!). Eles se referem a um valor fixo que meio que aparece do nada, sem nenhuma explicação do que aquele número específico representa.

Considere o quão fácil/difícil é entender os seguintes pedaços de código:

# RUIM - número mágico!
balance = balance * 4.45
# RUIM - nome de variável não descritivo não é muito melhor!
value = 4.45
balance = balance * value
# BOM - não é bem mais fácil de entender?
interest_rate = 4.45
balance = balance * interest_rate

Números mágicos são particularmente problemáticos quando os mesmos valores fixos aparecem em vários lugares.

Você usou números mágicos? Você pode refatorar seu código para eliminá-los?

5. Notas de R$5 e R$10

Imagine que seu caixa agora tem notas de R$5 e de R$10. As pessoas vão querer o menor número de notas possível.

Exemplos

Testes para o passo 5:

# Substitua seus testes existentes por estes abaixo.
describe 'caixa eletrônico' do
  [
    [-1, false],
    [0, false],
    [7, false],
    [20, [10, 10]],
    [25, [10, 10, 5]],
    [35, [10, 10, 10, 5]],
  ].each do |input, expected|
    it "deve retornar #{expected} quando R$#{input} é sacado" do
      withdraw(input).must_equal expected
    end
  end
end

6. Notas de R$5, R$10 e R$20

Seu caixa agora tem notas de R$5, R$10 e R$20. Modifique seu código para refletir isso.

Nota: a esta altura, cada valor maior pode ser dividido por cada valor menor - por exemplo, R$20 / R$10 = 2. As coisas ficam mais difíceis quando não é o caso (por exemplo, notas de R$50 e de R$20). Para este passo, intencionalmente não lidaremos com este caso para deixar mais fácil.

Exemplos

Dicas para fazer os testes ficarem verdes:

Para saber se um array está vazio: my_array.empty?

Para saber se um array não está vazio: !my_array.empty? ou my_array.any?

Para remover o primeiro elemento de um array: my_array.shift. Por exemplo, [1, 2, 3].shift resulta em [2, 3]

Testes para o passo 6:

# Substitua seus testes existentes por estes abaixo.
describe 'caixa eletrônico' do
  [
    [-1, false],
    [0, false],
    [7, false],
    [53, false],
    [35, [20, 10, 5]],
    [40, [20, 20]],
    [65, [20, 20, 20, 5]],
    [70, [20, 20, 20, 10]],
    [75, [20, 20, 20, 10, 5]],
  ].each do |input, expected|
    it "deve retornar #{expected} quando R$#{input} é sacado" do
      withdraw(input).must_equal expected
    end
  end
end

Refatore

7. Discussões Finais

Desafio! Notas de R$50 e de R$20

Até agora, nós evitamos intencionalmente o caso onde uma nota menor não encaixava em uma nota maior. Por exemplo, nós evitamos o caso de ter apenas notas de R$50 e R$20 (onde 20 não ‘cabe’ exatamente em 50). Você consege ver o porquê deste caso ser mais difícil? Se o seu código atual fosse incluir apenas notas de R$50 e R$20, o que aconteceria quando você tentasse sacar R$60, ou R$110? Na sua cabeça, ou no papel, você pode pensar qual lógica seria necesária para lidar com esses casos corretamente? Se estiver a fim de um desafio, tente lidar com este caso no seu código! (Você precisará escrever seus próprios testes para este passo).