Melhorando como Pragramador com princípios SOLID
TL;DR
Defina uma única responsabilidade para as suas classes, isso vai tornar seu cógido mais legível e irá facilitar criação de testes unitários. Refatore suas classes extraindo comportamentos para outras classes e compartilhe esses comportamentos através da composição.
Melhorando como Pragramador com princípios SOLID
Vamos falar de SOLID? WTF? Vamos falar de estado solido da matéria? É sobre química ou desenvolvimento de software que vamos falar? Na verdade SOLID é um termo de desenvolvimento de software elaborado por Robert C. Martin (Uncle Bob) no começo dos anos 2000.
Mas afinal o que é SOLID?
Letra | Princípio |
---|---|
S | Single responsibility |
O | Open/Close |
L | Liskov substitution |
I | Interface segregation |
D | Dependency inversion |
Para não ficar muito pesado hoje vamos falar de sobre o Princípio de Responsabilidade Única.
Single responsibility
O princípio de responsabilidade única segundo Uncle Bob é que uma classe deve fazer apenas uma coisa e muito bem feita, ou seja, ela deve ter apenas uma responsabilidade. Isso também facilita a crição de seus testes, afinal o trabalho daquela classe é muito bem definido, ela tem um proposíto e realizar seus teste unitários ficam bem simples.
Agora vamos ilustrar melhor o cenário. Abaixo temos uma famosa classe sabe tudo.
class Relatorio
def relatorio_pedidos(pending_report_id)
#pega pedidos
lines = Pedidos.where("pedidos_da_semana >= ?", 1.week.ago)
#Gera arquivos csv
options[:col_sep] = ";"
file = CSV.generate(options) do |csv|
csv << "cabeçalho"
lines.each do |line|
csv << line
end
end
#Uploading para Amazon S3
s3 = AWS::S3.new(access_key_id: "ACCESS_KEY_ID",
secret_access_key: "SECRET_ACCESS_KEY")
bucket = s3.buckets.create("bucket_name")
o.write(file: file[:full_name], content_type: file[:mime_type],
expires: (DateTime.now() + 0.001).httpdate(), expiration_time: 30, single_request: true,
acl: :public_read)
#Gera mensagem
link = o.public_url.to_s
message = "Seu relatorio está disponivel em #{link}"
#Envia e-mail
Mail.deliver do
from "bo@gmail.com"
to "qq@example.com"
subject "Seu relatório"
body message
end
end
end
end
Esse código pegamos de produção de um nossos serviços da empresa onde trabalho, foram omitidas algumas partes e exagerada em outras para ilustrarmos melhor, no caso o programador teve de fazer vários comentários durante o código para que não ficasse perdido. Isso já pode ser um cheiro que seu código está fazendo mais do que deve.
Repare que esse código sabe desde as configurações de ambiente, informações sobre o relatório, busca, upload e envio de email, ou seja, isso não é nada bom.
O que podemos fazer para esse código ficar melhor
A primeira coisa que devemos fazer é começar a extrair determinados comportamentos para clases mais específicas. Exemplo devemos extrair a busca do relatório, envio para Amazon S3 e envio de e-mail, deixando nosso código mais limpo. Alguns detalhes de implementação vou pular pois nosso objetivo é apenas mostrar como podemos deixar o código mais elegante.
Buscando registros
A primeira coisa que podemos fazer é separar a query do activerecord para um scope.
class User
scope :order_weekly, -> { where("pedidos_da_semana >= ?", 1.week.ago) }
end
Gerar CSV
Um próximo passo bem interessante seria separar a classe responsável por gerar o CSV, podemos deixar da seguinte forma:
class CsvCompiler
attr_accessor :data
def initialize(data)
self.data = Array(data)
end
def format
options[:col_sep] = ";"
file = CSV.generate(options) do |csv|
csv << "cabeçalho"
lines.each do |line|
csv << line
end
end
end
end
Upload para Amazon S3
Outra parte que deve ser separada é o upload do relatório, apenas retornando sua URL pública.
class ReportUpload
def upload(file)
s3 = AWS::S3.new(access_key_id: "ACCESS_KEY_ID", secret_access_key: "SECRET_ACCESS_KEY")
bucket = s3.buckets.create("bucket_name")
o.write(file: file, content_type: "application/csv",
expires: (DateTime.now() + 0.001).httpdate(), expiration_time: 30, single_request: true,
acl: :public_read)
o.public_url
end
end
Envio de e-mail
Outra tarefa importante é o envio de e-mail
class ReportMailer
attr_accessor :report, :recipient
def intiialize(report:, recipient:)
self.report = report
self.recipient = recipient
end
def deliver!
mail = Mail.new do
from "teste@gmail.com"
to recipient
subject "Seu relátorio está pronto"
end
mail.deliver!
end
end
Juntando as partes
Bom agora precisamos de um cara para poder chamar todas as partes necessárias para montar esse relátorio. No caso estamos fazendo um relátorio de pedidos da semana então vamos criar uma classe para isso.
class OrderWeeklyReport
def self.name
"Relatorio de pedidos"
end
def self.formatter
CsvCompiler
end
def self.data
User.order_weekly
end
def public_link
ReportUpload
end
end
Para utilizar seria apenas
ReportMailer.new(OrderWeeklyReport, "breno@example.com")
Como pode observar dividimos o código em diversas partes, pense em dividir para conquistar, agora cada parte tem sua responsabilidade bem definida facilitando a legibilidade do código e criação de testes.