solid através de bdd: um guia prático para rubistas
DESCRIPTION
Palestra apresentada na RubyConf Brasil 2011, Dev in Sampa 2011, TDC Florianópolis 2011 e Dev in Vale 2011. Aborda aplicações práticas dos princípios Single Responsibility e Dependency Inversion, além de dicas de como identificar problemas no design do código orientado a objetos.TRANSCRIPT
![Page 1: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/1.jpg)
SOLID através de BDD
um guia prático para rubistas
Lucas Húngarosoftware developer
![Page 2: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/2.jpg)
SOLID
Conjunto de princípios desenvolvidos por Bob Martin que devem ser aplicados para melhorar a qualidade do código orientado a objetos.
![Page 3: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/3.jpg)
Rails, MVC
Rails, MVC e facilitam muito a vida do desenvolvedor, principalmente em aplicações simples. Mas também deixamos de prestar atenção à aspectos importantes enquanto produzimos código rapidamente.
![Page 4: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/4.jpg)
Fat Modelsaka
programação procedural
Assim acabamos desenvolvendo alguns anti-patterns. Pior do que isso é quando alguns desses anti-patterns são vistos como boas práticas mas, na verdade, são apenas atalhos enganosos (parecem vantajosos a princípio, mas acabam aumentando o custo de manutenção)
![Page 5: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/5.jpg)
OOP, BDD, SOLID
As soluções são simples mas, muitas vezes, acabam ficando de lado porque a maioria dos desenvolvedores pensam que são assuntos chatos.
![Page 6: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/6.jpg)
Acadêmico...(chato)
Um dos motivos para isso é a origem acadêmica, onde muitas vezes o assunto é discutido apenas na esfera teórica, com poucos exemplos de aplicação.
![Page 7: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/7.jpg)
Coisa de Javeiro...
Outro problema é que muitos associam essas práticas à plataformas como Java e .NET que, infelizmente, remetem a sentimentos de excesso de burocracia e baixa produtividade.
![Page 8: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/8.jpg)
Linguagem dinâmica
Mas, para nossa sorte, utilizar esses princípios em linguagens dinâmicas é muito mais fácil e menos burocrático.
![Page 9: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/9.jpg)
SRP e DIP
Pra facilitar as coisas, vamos falar apenas de dois princípios: Single Responsibility e Dependency Inversion - os que acredito causarem o maior impacto
![Page 10: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/10.jpg)
Coesão e Acoplamento
“Traduzindo” esses princípios, identificamos que eles falam sobre duas características do código: coesão e acoplamento.
![Page 11: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/11.jpg)
Baixa coesão:
executa parte de uma responsabilidade ou
mais de uma
Problemas que queremos eliminar
![Page 12: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/12.jpg)
Alto acoplamento:
código preso a um caso de uso e suas dependências
Problemas que queremos eliminar
![Page 13: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/13.jpg)
Code!
Exemplos
![Page 14: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/14.jpg)
Processo usual:
![Page 15: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/15.jpg)
rails new myapp
![Page 16: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/16.jpg)
migrations
![Page 17: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/17.jpg)
finders
![Page 18: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/18.jpg)
data-centric apps
fat modelsComeçamos pelos dados (schema) e depois tentamos derivar algum comportamento disso, resultando em objetos “gordos”, pouco coesos e muito acoplados.
![Page 19: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/19.jpg)
Rails apps ==
model User gigantemais algumas classes sem importância
Domínios anêmicos
![Page 20: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/20.jpg)
class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation has_secure_password validates_presence_of :password, :on => :create def send_password_reset generate_token(:password_reset_token) self.password_reset_sent_at = Time.zone.now save! UserMailer.password_reset(self).deliver end
def generate_token(column) begin self[column] = SecureRandom.urlsafe_base64 end while User.exists?(column => self[column]) endend
Exemplo clássico (railscasts 275). Falta coesão, sobra acoplamento.
![Page 21: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/21.jpg)
class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation has_secure_password validates_presence_of :password, :on => :create def send_password_reset generate_token(:password_reset_token) self.password_reset_sent_at = Time.zone.now save! UserMailer.password_reset(self).deliver end
def generate_token(column) begin self[column] = SecureRandom.urlsafe_base64 end while User.exists?(column => self[column]) endend
Dependências
![Page 22: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/22.jpg)
class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation has_secure_password validates_presence_of :password, :on => :create def send_password_reset generate_token(:password_reset_token) self.password_reset_sent_at = Time.zone.now save! UserMailer.password_reset(self).deliver end
def generate_token(column) begin self[column] = SecureRandom.urlsafe_base64 end while User.exists?(column => self[column]) endend
Dependências
![Page 23: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/23.jpg)
class User < ActiveRecord::Base attr_accessible :email, :password, :password_confirmation has_secure_password validates_presence_of :password, :on => :create def send_password_reset generate_token(:password_reset_token) self.password_reset_sent_at = Time.zone.now save! UserMailer.password_reset(self).deliver end
def generate_token(column) begin self[column] = SecureRandom.urlsafe_base64 end while User.exists?(column => self[column]) endend
Dependências
![Page 24: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/24.jpg)
describe User do describe "#send_password_reset" do let(:user) { Factory(:user) }
it "generates a unique password_reset_token each time" do user.send_password_reset last_token = user.password_reset_token user.send_password_reset user.password_reset_token.should_not eq(last_token) end
it "saves the time the password reset was sent" do user.send_password_reset user.reload.password_reset_sent_at.should be_present end
it "delivers email to user" do user.send_password_reset last_email.to.should include(user.email) end endend
Estrutural
![Page 25: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/25.jpg)
describe User do describe "#send_password_reset" do let(:user) { Factory(:user) }
it "generates a unique password_reset_token each time" do user.send_password_reset last_token = user.password_reset_token user.send_password_reset user.password_reset_token.should_not eq(last_token) end
it "saves the time the password reset was sent" do user.send_password_reset user.reload.password_reset_sent_at.should be_present end
it "delivers email to user" do user.send_password_reset last_email.to.should include(user.email) end endend
Estrutural
![Page 26: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/26.jpg)
describe User do describe "#send_password_reset" do let(:user) { Factory(:user) }
it "generates a unique password_reset_token each time" do user.send_password_reset last_token = user.password_reset_token user.send_password_reset user.password_reset_token.should_not eq(last_token) end
it "saves the time the password reset was sent" do user.send_password_reset user.reload.password_reset_sent_at.should be_present end
it "delivers email to user" do user.send_password_reset last_email.to.should include(user.email) end endend
Dependente de banco de dados == lento
![Page 27: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/27.jpg)
describe User do describe "#send_password_reset" do let(:user) { Factory(:user) }
it "generates a unique password_reset_token each time" do user.send_password_reset last_token = user.password_reset_token user.send_password_reset user.password_reset_token.should_not eq(last_token) end
it "saves the time the password reset was sent" do user.send_password_reset user.reload.password_reset_sent_at.should be_present end
it "delivers email to user" do user.send_password_reset last_email.to.should include(user.email) end endend
Muitas responsabilidades (isso porque o “has_secure_password” já esconde outras)
![Page 28: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/28.jpg)
describe User do describe "#send_password_reset" do let(:user) { Factory(:user) }
it "generates a unique password_reset_token each time" do user.send_password_reset last_token = user.password_reset_token user.send_password_reset user.password_reset_token.should_not eq(last_token) end
it "saves the time the password reset was sent" do user.send_password_reset user.reload.password_reset_sent_at.should be_present end
it "delivers email to user" do user.send_password_reset last_email.to.should include(user.email) end endend
Specs lentas, código “rígido” (difícil de modificar), custo de manutenção alto.
![Page 29: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/29.jpg)
Escrever specs isoladas para AR é
difícil
![Page 30: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/30.jpg)
ActiveRecord==
Repositório (classe)Model (objeto)BD (reflection)
AR faz coisas demais e quebra o SRP por design
![Page 31: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/31.jpg)
Uncle Bob:“ActiveRecord is a
Data Structure”
http://goo.gl/OEBvX
AR deve ser usado como estrutura de dados para não se tornar um “buraco negro” de comportamento. Minha guideline: AR contém apenas validação, associações e finders, nada mais.
![Page 32: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/32.jpg)
validaçõesassociações
findersnada mais
AR deve ser usado como estrutura de dados para não se tornar um “buraco negro” de comportamento. Minha guideline: AR contém apenas validação, associações e finders, nada mais.
![Page 33: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/33.jpg)
Comece pelo comportamento
Vamos fazer diferente. Primeiro, identificamos o comportamento desejado.
![Page 34: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/34.jpg)
describe UserAuthentication do let(:user) { Factory(:user) } let(:password) { "123456" }
context "with valid credentials" do subject { UserAuthentication.new(user.username, password) }
it "allows access" do subject.authenticate.should be_true end end
context "with invalid credentials" do subject { UserAuthentication.new(user.username, "invalid") }
it "denies access" do subject.authenticate.should be_false end endend
Isso é o que eu quero que faça
![Page 35: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/35.jpg)
class UserAuthentication def initialize(username, password) @username = username @password = password end
def authenticate if user = User.find_by_username(@username) user.password_hash == BCrypt::Engine.hash_secret(@password, user.password_salt) else false end endend
Uma primeira implementação
![Page 36: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/36.jpg)
class UserAuthentication def initialize(username, password) @username = username @password = password end
def authenticate if user = User.find_by_username(@username) user.password_hash == BCrypt::Engine.hash_secret(@password, user.password_salt) else false end endend
Melhoramos quanto à coesão... mas ainda há acoplamento
![Page 37: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/37.jpg)
“The negative effects on testability in the Active Record pattern can be
minimized by using mocking or dependency
injection”Wikipedia
![Page 38: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/38.jpg)
object doubles são essenciais
YAY for mocks, stubs, spies, proxies and friends!
![Page 39: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/39.jpg)
BDD == design
Eu costumava ser da turma do “mocks e stubs são apenas para isolamento de sistemas externos (como gateways) pq geram testes quebradiços quando usados internamente”
![Page 40: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/40.jpg)
Testes quebradiços==
Design ruim
Até que tomei vergonha na cara e fui estudar essa bagaça! ;)
![Page 41: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/41.jpg)
object doubles+
dependency injection
Através disso conseguimos: testes rápidos, objetos desacoplados e coesos, custo de manutenção reduzido.
![Page 42: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/42.jpg)
class UserAuthentication def initialize(username, password) @username = username @password = password end
def authenticate(user_repo = User, encryption_engine = BCrypt::Engine) if user = user_repo.find_by_username(@username) user.password_hash == encryption_engine. hash_secret(@password, user.password_salt) else false end endend
Como estamos aprendendo, vou inverter as coisas e mostrar a implementação primeiro: uma forma de utilizar o DIP é passar as dependências como parâmetros.
![Page 43: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/43.jpg)
class UserAuthentication def initialize(username, password) @username = username @password = password end
def authenticate(user_repo = User, encryption_engine = BCrypt::Engine) if user = user_repo.find_by_username(@username) user.password_hash == encryption_engine. hash_secret(@password, user.password_salt) else false end endend
![Page 44: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/44.jpg)
describe UserAuthentication do let(:user) { double("an user").as_null_object } let(:user_repo) { double("an user repository") } let(:encryption_engine) { double("an encryption engine") }
before(:each) do user.stub(:password_hash).and_return "the hash" user_repo.stub(:find_by_username).and_return user end
![Page 45: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/45.jpg)
describe UserAuthentication do let(:user) { double("an user").as_null_object } let(:user_repo) { double("an user repository") } let(:encryption_engine) { double("an encryption engine") }
before(:each) do user.stub(:password_hash).and_return "the hash" user_repo.stub(:find_by_username).and_return user end
São objetos dublês, não me importo se stub ou mock (ou outro tipo). Essas decisões tomo sobre mensagens (métodos), não sobre o objeto todo. Isso é uma feature muito legal do framework de mocks do RSpec
![Page 46: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/46.jpg)
describe UserAuthentication do let(:user) { double("an user").as_null_object } let(:user_repo) { double("an user repository") } let(:encryption_engine) { double("an encryption engine") }
before(:each) do user.stub(:password_hash).and_return "the hash" user_repo.stub(:find_by_username).and_return user end
![Page 47: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/47.jpg)
context "with valid credentials" do before(:each) { encryption_engine.
stub(:hash_secret). and_return user.password_hash }
subject { UserAuthentication.new("username", "123456") }
it "allows access" do subject.authenticate(user_repo, encryption_engine).should be_true end end
E agora as specs: sem banco de dados, dependências injetáveis, código simples. No fim das contas, tudo o que preciso é de três objetos que respondam a uma interface bem definida.
![Page 48: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/48.jpg)
context "with valid credentials" do before(:each) { encryption_engine. stub(:hash_secret). and_return user.password_hash }
subject { UserAuthentication.new("username", "123456") }
it "allows access" do subject.authenticate(user_repo, encryption_engine).should be_true end end
E agora as specs: sem banco de dados, dependências injetáveis, código simples. No fim das contas, tudo o que preciso é de três objetos que respondam a uma interface bem definida.
![Page 49: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/49.jpg)
context "with invalid credentials" do before(:each) { encryption_engine. stub(:hash_secret). and_return "another hash" }
subject { UserAuthentication.new("username", "invalid") }
it "denies access" do subject.authenticate(user_repo, encryption_engine).should be_false end end
![Page 50: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/50.jpg)
context "with invalid credentials" do before(:each) { encryption_engine. stub(:hash_secret). and_return "another hash" }
subject { UserAuthentication.new("username", "invalid") }
it "denies access" do subject.authenticate(user_repo, encryption_engine).should be_false end end
![Page 51: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/51.jpg)
describe MyBCryptAdapter do context "encrypting text with a salt" do it "respects the lib protocol" do BCrypt::Engine.expects(:hash_secret) MyBCryptAdapter.encrypt("text", "salt") end endend
class MyBCryptAdapter def self.encrypt(plain_text, salt) BCrypt::Engine.hash_secret(plain_text, salt) endend
Podemos abstrair ainda mais...
![Page 52: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/52.jpg)
describe MyBCryptAdapter do context "encrypting text with a salt" do it "respects the lib protocol" do BCrypt::Engine.expects(:hash_secret) MyBCryptAdapter.encrypt("text", "salt") end endend
class MyBCryptAdapter def self.encrypt(plain_text, salt) BCrypt::Engine.hash_secret(plain_text, salt) endend
... e usar um mock para garantir a interface.
![Page 53: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/53.jpg)
def authenticate(user_repo = User, encryption_engine = MyBCryptAdapter) if user = user_repo.find_by_username(@username) user.password_hash == encryption_engine. encrypt(@password, user.password_salt) else false endend
![Page 54: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/54.jpg)
Até onde?
Quanto de abstração vale a pena? Indireção em excesso é tão ruim quanto alto acoplamento. A dica é abstrair quando é um componente que mudará muito ou quando você não tem a mínima ideia disso, pois isso preserva seu “direito” de mudar caso seja necessário.
![Page 55: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/55.jpg)
def authenticate(encryption_engine = MyBCryptAdapter) if user = User.find_by_username(@username) ...end
Por ex: caso eu tenha certeza de que meu repositório de usuários será sempre um modelo AR (e nunca precisarei buscá-los em um LDAP, por exemplo), podemos eliminar essa injeção e deixar acoplado.
![Page 56: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/56.jpg)
Mudanças...
E se precisarmos mudar o algoritmo de criptografia?
![Page 57: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/57.jpg)
describe MyZYCryptAdapter do context "encrypting text with a salt" do it "respects the lib protocol" do ZYCrypt::Engine::Passwords. should_receive(:hash_password_with_salt) MyZYCryptAdapter.encrypt("text", "salt") end endend
class MyZYCryptAdapter def self.encrypt(plain_text, salt) ZYCrypt::Engine::Passwords. hash_password_with_salt(plain_text, salt) endend
![Page 58: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/58.jpg)
def authenticate(user_repo = User, encryption_engine = MyZYCryptAdapter) if user = user_repo.find_by_username(@username) user.password_hash == encryption_engine. encrypt(@password, user.password_salt) else false endend
![Page 59: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/59.jpg)
WIN! \o/
![Page 60: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/60.jpg)
Mais uma alteração
Logging!
![Page 61: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/61.jpg)
def authenticate(user_repo = User, encryption_engine = MyBCryptAdapter, logger = MyFancyLogger) ...end
Passando como parâmetro
![Page 62: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/62.jpg)
context "with invalid credentials" do ...
it "logs the authentication attempt" do logger = double("a logger") logger.should_receive(:log).with("something")
subject.authenticate(user_repo, encryption_engine, logger) end end
Um caso de uso meio forçado, apenas como exemplo. Vamos verificar as interações entre os componentes. O “como” fica à cargo do teste unitário do logger, não do autenticador.
![Page 63: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/63.jpg)
context "with invalid credentials" do ...
it "logs the authentication attempt" do logger = double("a logger") logger.should_receive(:log).with("something")
subject.authenticate(user_repo, encryption_engine, logger) end end
Um caso de uso meio forçado, apenas como exemplo. Vamos verificar as interações entre os componentes. O “como” fica à cargo do teste unitário do logger, não do autenticador.
![Page 64: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/64.jpg)
context "with valid credentials" do ...
it "doesn’t log the authentication attempt" do logger = double("a logger") logger.should_receive(:log).never
subject.authenticate(user_repo, encryption_engine, logger) endend
Um caso de uso meio forçado, apenas como exemplo. Vamos verificar as interações entre os componentes. O “como” fica à cargo do teste unitário do logger, não do autenticador.
![Page 65: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/65.jpg)
context "with valid credentials" do ...
it "doesn’t log the authentication attempt" do logger = double("a logger") logger.should_receive(:log).never
subject.authenticate(user_repo, encryption_engine, logger) endend
Um caso de uso meio forçado, apenas como exemplo. Vamos verificar as interações entre os componentes. O “como” fica à cargo do teste unitário do logger, não do autenticador.
![Page 66: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/66.jpg)
Sintomas
BDD é uma ótima forma de apontar problemas com o design do código - os sintomas costumam ser claros
![Page 67: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/67.jpg)
Muitos mocks/stubs numa mesma dependência
==superfície de
contato muito ampla
Essa é uma das principais reclamações dos desenvolvedores que são contra o uso pesado de dublês - criar muitos mocks/stubs nos setups dos cenários de testes. A questão é que isso, na verdade, está revelando problemas com o design. Exemplo: teste de controller fazendo stub de vários métods de um model => falta de encapsulamento e model quebrando SRP
![Page 68: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/68.jpg)
Superfície de contato
![Page 69: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/69.jpg)
class BlogController < ApplicationController def index @posts = Post.published.page params[:page] @posts_grouped_by_year = Post. all. group_by {|post| post.
created_at. beginning_of_year }
@posts_highlights = Post.published.featured.limit(3) @categories = Category.includes(:posts)
@faqs = Faq.tagged_with(@posts.map {|post| post.tag_list}. join(","). split, :any => true) endend
![Page 70: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/70.jpg)
tha fuuuuck??!?!?!
![Page 71: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/71.jpg)
class BlogController < ApplicationController def index @posts = Post.published.page params[:page] @posts_grouped_by_year = Post. all. group_by {|post| post.
created_at. beginning_of_year }
@posts_highlights = Post.published.featured.limit(3) @categories = Category.includes(:posts)
@faqs = Faq.tagged_with(@posts.map {|post| post.tag_list}. join(","). split, :any => true) endend
![Page 72: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/72.jpg)
Controller: quanto mais “burro”,
melhor
Minha sugestão para esse caso seria extrair toda essa “filtragem” para uma classe especializada em montar a página inicial do blog (poderia ser utilizado um Presenter).
![Page 73: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/73.jpg)
Muitos contextos em diferentes níveis
de abstração==
muitas responsabilidades
![Page 74: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/74.jpg)
describe User do context "persistence logic" do it "validates ..." end
context "data gathering" do it "finds records under certain conditions ..." end
context "making payments" do it "register an error in case the key is invalid"
it "writes some info to the log in case of success"
# ... endend
![Page 75: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/75.jpg)
describe User do context "persistence logic" do it "validates ..." end
context "data gathering" do it "finds records under certain conditions ..." end
context "making payments" do it "register an error in case the key is invalid"
it "writes some info to the log in case of success"
# ... endend
Isso nem sempre é “exato”, mas pode ser um ótimo indicativo de problemas.
![Page 76: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/76.jpg)
Dicas
Algumas formas rápidas de verificar se seu código está “sólido” :D
![Page 77: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/77.jpg)
Pequenas peças de comportamento
facilmente acessíveis
Isso garante que não precisemos de setups muito elaborados e nos “força” a abstrair conceitos de negócio em forma de código.
![Page 78: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/78.jpg)
app console
.buy(user, product, cart)
Dica: abra o console da sua aplicação e veja se os processos de negócio podem ser executados facilmente na linha de comando
![Page 79: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/79.jpg)
Modele processos, não se prenda apenas a entidades.
![Page 80: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/80.jpg)
class Customer def initialize(user, supplier) @user = user @supplier = supplier end
def pay(amount, gateway = SomeGatewayAdapter) ... endend
Exemplo: User assume o papel Customer para o processo de pagamento.
![Page 81: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/81.jpg)
Don't mock types you don't own
Write wrappers
Evite ao máximo colocar dublês em tipos externos. Caso necessário, crie um wrapper/adapter
![Page 82: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/82.jpg)
“Always check code in better than you
checked it out.”
— Uncle Bob
Seja um bom menino! :P
![Page 83: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/83.jpg)
Crie o hábito de passar
dependências como parâmetros
![Page 84: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/84.jpg)
“Não escreva ‘fat models’”
— OOP
![Page 85: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/85.jpg)
Próximos passos
Não basta apenas alguns slides pra mostrar como fazer isso. É preciso ler, estudar e praticar. Por isso, seguem algumas referências.
![Page 86: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/86.jpg)
Uncle BobMichael Feathers
Corey HainesGary Bernhardt
Pat MaddoxAvdi Grimm
Esses são apenas alguns nomes, que levarão a outros. Há muita gente altamente capacitada falando sobre e praticando essas técnicas.
![Page 87: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/87.jpg)
Ótimas guidelines (!= regras)
![Page 88: SOLID através de BDD: um guia prático para rubistas](https://reader033.vdocuments.mx/reader033/viewer/2022051212/5579a5e8d8b42ac1148b4ac8/html5/thumbnails/88.jpg)
Obrigado
@lucashungaro
github.com/lucashungaro