Dicas para iniciantes de boas práticas de desenvolvimento de software em Ruby
Registrar dicas de programação que surgiram por conta de uma conversa/pergunta que rolou no grupo Ruby Brasil do Telegram.
Por favor registre dúvidas e/ou sugestões como issues. 😉
Contribuições são mais que bem-vindas! 😊
A ideia deste projeto é demonstrar pequenas dicas/mudanças para aprimorar o exemplo abaixo.
Por conta disso, seguiremos a proposta original implementando o código com palavras/termos em português.
Porém, como é de conhecimento de mercado é recomendado que você pratique/desenvolva em inglês (confira alguns motivos).
class User
attr_accessor :nome, :idade, :cidade
def boasvindas
puts 'Seja bem-vindo ' << nome << '!'
puts 'Você quer jogar?'
puts 'Digite S ou N'
resposta = gets
if resposta.downcase[0] == ('s')
jogar = true
else
jogar = false
end
return jogar
end
end
# Uso:
user = User.new
user.nome = 'André'
user.idade = '30'
user.cidade = 'São José do Rio Preto'
if user.boasvindas
puts 'Iniciando jogo...'
end
Configurando o projeto
gem install bundler
bundle install
Executando os testes
bundle exec rspec
Sugestão:
Após configurar, acesso o primeiro commit git checkout f42f6b92
e aplique os conceitos deste guia. Feito isso, use a suite de testes para verificar se o comportamento da aplicação continua sendo o garantido.
Exemplos de boas práticas de desenvolvimento de software em Ruby + técnicas de refatoração.
400d121
Dica 1 - Interpolação 🔗
11445ef
Dica 2 - Use métodos privados 🔗
824501f
Dica 3 - Remova variáveis desnecessárias 🔗
f6300e9
Dica 4 - Defina métodos predicativos 🔗
9f8f36e
Dica 5 - Ternário 🔗
b7fb557
Dica 6 - Elimine a condicional quando o retorno for um boolean 🔗
baeb46e
Dica 7 - return é opcional quando usado na última linha de um método 🔗
f86e29d
Dica 8 - Use o construtor + getters (métodos somente leitura) 🔗
4d79c49
Dica 9 - Use keywords arguments 🔗
ec016e0
Dica 10 - Escreva métodos e variáveis no formato snake_case 🔗
66b1048
Dica 11 - Separe classes por responsabilidade (coesão) 🔗
b690234
Dica 12 - Organize os métodos por responsabilidade (coesão) 🔗
Faça uso de interpolação, é mais performático que concatenar strings.
Link para aprender mais sobre o assunto.
class User
attr_accessor :nome, :idade, :cidade
def boasvindas
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar um jogo?'
puts 'Digite S ou N'
resposta = gets
if resposta.downcase == 's'
jogar = true
else
jogar = false
end
return jogar
end
end
Use métodos privados, para:
class User
attr_accessor :nome, :idade, :cidade
def boasvindas
imprime_pergunta
resposta = gets
return prosseguir_para_o_jogo(resposta)
end
private
def imprime_pergunta
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo(resposta)
if resposta.downcase[0] == 's'
jogar = true
else
jogar = false
end
return jogar
end
end
Remova variáveis caso o valor atribuído seja o último a ser retornado.
(Mudança: Foi removido a variável jogar
do método prosseguir_para_o_jogo
).
class User
attr_accessor :nome, :idade, :cidade
def boasvindas
imprime_pergunta
resposta = gets
return prosseguir_para_o_jogo(resposta)
end
private
def imprime_pergunta
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo(resposta)
if resposta.downcase[0] == 's'
true
else
false
end
end
end
Defina métodos predicativos (terminam com ?
- interrogação) quando o resultado do mesmo for um boolean
(true
ou false
).
class User
attr_accessor :nome, :idade, :cidade
def boasvindas
imprime_pergunta
resposta = gets
return prosseguir_para_o_jogo?(resposta)
end
private
def imprime_pergunta
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo?(resposta)
if resposta.downcase[0] == 's'
true
else
false
end
end
end
Use um ternário para expressar condicionais simples/curtas.
Link para aprender mais sobre o assunto.
class User
attr_accessor :nome, :idade, :cidade
def boasvindas
imprime_pergunta
resposta = gets
return prosseguir_para_o_jogo?(resposta)
end
private
def imprime_pergunta
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo?(resposta)
resposta.downcase[0] == 's' ? true : false
end
end
Elimine o ternário/expressão condicional quando o retorno for um boolean.
class User
attr_accessor :nome, :idade, :cidade
def boasvindas
imprime_pergunta
resposta = gets
return prosseguir_para_o_jogo?(resposta)
end
private
def imprime_pergunta
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo?(resposta)
resposta.downcase[0] == 's'
end
end
Métodos sempre retornam o resultado da última linha, logo o uso de return se torna opcional/desnecessário.
class User
attr_accessor :nome, :idade, :cidade
def boasvindas
imprime_pergunta
resposta = gets
prosseguir_para_o_jogo?(resposta)
end
private
def imprime_pergunta
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo?(resposta)
resposta.downcase[0] == 's'
end
end
Faça uso do construtor + getters (métodos de leitura) para evitar que o estados do(s) seu(s) objetos se corrompa por conta de uma manipulação indevida/equivocada.
class User
attr_reader :nome, :idade, :cidade
def initialize(nome, idade, cidade)
@nome = nome
@idade = idade
@cidade = cidade
end
def boasvindas
imprime_pergunta
resposta = gets
prosseguir_para_o_jogo?(resposta)
end
private
def imprime_pergunta
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo?(resposta)
resposta.downcase[0] == 's'
end
end
# Uso:
user = User.new('André', '30', 'São José do Rio Preto')
if user.boasvindas
puts 'Iniciando jogo...'
end
Faça uso de keywords arguments para tornar os argumentos/dependências de seus métodos mais expressivos.
class User
attr_reader :nome, :idade, :cidade
def initialize(nome:, idade:, cidade:)
@nome = nome
@idade = idade
@cidade = cidade
end
def boasvindas
imprime_pergunta
resposta = gets
prosseguir_para_o_jogo?(resposta)
end
private
def imprime_pergunta
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo?(resposta)
resposta.downcase[0] == 's'
end
end
# Uso:
user = User.new(nome: 'André', idade: '30', cidade: 'São José do Rio Preto')
if user.boasvindas
puts 'Iniciando jogo...'
end
Por convensão Ruby faz uso de snake_case
na declaração de métodos e variáveis, quando o mesmo contém mais de um termo. Com isso o método boasvindas se torna boas_vindas.
class User
attr_reader :nome, :idade, :cidade
def initialize(nome:, idade:, cidade:)
@nome = nome
@idade = idade
@cidade = cidade
end
def boas_vindas
imprime_pergunta
resposta = gets
prosseguir_para_o_jogo?(resposta)
end
private
def imprime_pergunta
puts "Seja bem-vindo #{nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo?(resposta)
resposta.downcase[0] == 's'
end
end
Separe suas classes de acordo com suas responsabilidades (que conceito elas expressam?). Isso poderá facilitar a manutenção e entendimento do código.
PS: Responsabilidade única/pouca tem haver com o que chamamos de coesão.
class User
attr_reader :nome, :idade, :cidade
def initialize(nome:, idade:, cidade:)
@nome = nome
@idade = idade
@cidade = cidade
end
end
class Game
attr_reader :user
def initialize(user)
@user = user
end
def start
boas_vindas
# Poderá receber mais métodos que venham fazer sentindo ao jogo/projeto...
end
private
def boas_vindas
imprime_pergunta
resposta = gets
prosseguir_para_o_jogo?(resposta)
end
def imprime_pergunta
puts "Seja bem-vindo #{user.nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo?(resposta)
resposta.downcase[0] == 's'
end
end
user = User.new(nome: 'André', idade: '30', cidade: 'São José do Rio Preto')
game = Game.new(user)
if game.start
puts 'Iniciando jogo...'
end
Organize os métodos de acordo com suas responsabilidades. Assim como as classes isso poderá facilitar a manutenção e entendimento do código.
PS: Essa prática também tem haver com o que chamamos de coesão.
Mudança:
Perceba que na versão anterior, o método boas_vindas
tem diversas responsabilidades.
Além disso, ele retorna um boolean e não aplica a convenção de predicado (nem sempre isso será necessário).
O que fizemos para melhorar?
Nessa versão o início do jogo (start), tem duas etapas:
Dada essa estrutura, criamos métodos para representar cada uma dessas etapas.
class User
attr_reader :nome, :idade, :cidade
def initialize(nome:, idade:, cidade:)
@nome = nome
@idade = idade
@cidade = cidade
end
end
class Game
attr_reader :user
def initialize(user)
@user = user
end
def start
resposta = pergunta_se_deseja_jogar
prosseguir_para_o_jogo?(resposta)
end
private
def pergunta_se_deseja_jogar
imprime_pergunta
gets
end
def imprime_pergunta
puts "Seja bem-vindo #{user.nome}!"
puts 'Você quer jogar?'
puts 'Digite S ou N'
end
def prosseguir_para_o_jogo?(resposta)
resposta.downcase[0] == 's'
end
end