Os piores códigos Ruby já vistos - TDC Florianópolis 2016
-
Upload
fernando-hamasaki-de-amorim -
Category
Technology
-
view
310 -
download
3
Transcript of Os piores códigos Ruby já vistos - TDC Florianópolis 2016
@Prodis
Os piores códigos Ruby já vistos
TDC Florianópolis 2016
@Prodis
@Prodis
Fernando Hamasaki de Amorim
• Desenvolvedor Ruby desde 2009
• Trabalho na Locaweb, a maior empresa de hospedagem do Brasil
• Desenvolvo aplicações web desde 2000
• .NET, Java, JavaScript, PHP, ASP.
@Prodis
Fernando Hamasaki de Amorim
@Prodis
Fernando Hamasaki de Amorim
@Prodis
WOP
@Prodis
WOP Workaround Oriented Programming
@Prodis
POG Programação Orientada a Gambiarras
@Prodis
POGPOG é uma técnica avançada de desenvolvimento de
software que tem como base a utilização de todo tipo
de gambiarra, remendo e tudo de pior que um código
pode ter.
POG se baseia em conceitos como duplicação de
código, fluxos redundantes, tarefas desnecessáriase reinvenção de rodas.
@Prodis
"The names have been changed to protect the innocent."
Os piores códigos Ruby já vistos
@Prodis
Um primeiro exemplo de POG:mascarando números de
cartão de crédito
@Prodis
describe '#mask_credit_card' do let(:number) { '5464193830403276' }
it 'returns masked credit card number' do masked = mask_credit_card(number) expect(masked).to eq '************3276' end end
@Prodis
def mask_credit_card(number) limit = number.length - 4 “#{'*' * limit}#{number[limit..-1]}” end
@Prodis
def mask_credit_card_pog(number) (number.length - 4).times do |i| number[i] = '*' end
number end
@Prodis
describe '#mask_credit_card_pog' do let(:number) { '5464193830403276' }
it 'returns masked credit card number' do masked = mask_credit_card_pog(number)
expect(masked).to eq '************3276' end
it 'does not change number variable' do mask_credit_card_pog(number) expect(number).to eq '5464193830403276' end end
@Prodis
#mask_credit_card_pog returns masked credit card number does not change number variable (FAILED - 1)
Failures:
1) #mask_credit_card_pog does not change number variable Failure/Error: expect(number).to eq '5464193830403276'
expected: "5464193830403276" got: "************3276"
(compared using ==) # ./spec/mask_credit_card/mask_credit_card_spec.rb:23:in `block (2 levels) in <top (required)>'
Finished in 0.0202 seconds (files took 0.17324 seconds to load) 2 examples, 1 failure
@Prodis
def mask_credit_card_pog(number) (number.length - 4).times do |i| number[i] = '*' end
number end
@Prodis
Fluxos obscuros
@Prodis
class Support::DomainsController < Support::BaseController def create site = Site.find_by(name: params[:domain][:site])
if site.blank? flash[:alert] = I18n.t('support.domains.errors.without_site') redirect_to new_support_domain_path return else domain = site.domains.build(address: params[:domain][:address]) domain.support_create = true
if domain.save flash[:success] = I18n.t('support.domains.success') redirect_to support_domains_path return else flash[:alert] = I18n.t('support.domains.errors.invalid') redirect_to new_support_domain_path return end end end end
@Prodis
class Support::DomainsController < Support::BaseController def create site = Site.find_by(name: params[:domain][:site])
if site.blank? flash[:alert] = I18n.t('support.domains.errors.without_site') redirect_to new_support_domain_path return else domain = site.domains.build(address: params[:domain][:address]) domain.support_create = true
if domain.save flash[:success] = I18n.t('support.domains.success') redirect_to support_domains_path return else flash[:alert] = I18n.t('support.domains.errors.invalid') redirect_to new_support_domain_path return end end end end
@Prodis
class Support::DomainsController < Support::BaseController def create site = Site.find_by(name: params[:domain][:site])
if site.blank? flash[:alert] = I18n.t('support.domains.errors.without_site') redirect_to new_support_domain_path return else domain = site.domains.build(address: params[:domain][:address]) domain.support_create = true
if domain.save flash[:success] = I18n.t('support.domains.success') redirect_to support_domains_path return else flash[:alert] = I18n.t('support.domains.errors.invalid') redirect_to new_support_domain_path return end end end end
@Prodis
class Support::DomainsController < Support::BaseController def create site = Site.find_by(name: params[:domain][:site])
if site.blank? flash[:alert] = I18n.t('support.domains.errors.without_site') redirect_to new_support_domain_path return else domain = site.domains.build(address: params[:domain][:address]) domain.support_create = true
if domain.save flash[:success] = I18n.t('support.domains.success') redirect_to support_domains_path return else flash[:alert] = I18n.t('support.domains.errors.invalid') redirect_to new_support_domain_path return end end end end
@Prodis
class Support::DomainsController < Support::BaseController def create site = Site.find_by(name: params[:domain][:site])
if site.blank? flash[:alert] = I18n.t('support.domains.errors.without_site') redirect_to new_support_domain_path return else domain = site.domains.build(address: params[:domain][:address]) domain.support_create = true
if domain.save flash[:success] = I18n.t('support.domains.success') redirect_to support_domains_path return else flash[:alert] = I18n.t('support.domains.errors.invalid') redirect_to new_support_domain_path return end end end end
@Prodis
class Support::DomainsController < Support::BaseController def create site = Site.find_by(name: params[:domain][:site])
if site.blank? flash[:alert] = I18n.t('support.domains.errors.without_site') redirect_to new_support_domain_path return else domain = site.domains.build(address: params[:domain][:address]) domain.support_create = true
if domain.save flash[:success] = I18n.t('support.domains.success') redirect_to support_domains_path return else flash[:alert] = I18n.t('support.domains.errors.invalid') redirect_to new_support_domain_path return end end end end
@Prodis
class Support::DomainsController < Support::BaseController def create site = Site.find_by(name: params[:domain][:site])
if site.blank? flash[:alert] = I18n.t('support.domains.errors.without_site') redirect_to new_support_domain_path return else domain = site.domains.build(address: params[:domain][:address]) domain.support_create = true
if domain.save flash[:success] = I18n.t('support.domains.success') redirect_to support_domains_path return else flash[:alert] = I18n.t('support.domains.errors.invalid') redirect_to new_support_domain_path return end end end end
@Prodis
Como corrigir isso?
@Prodis
class Support::DomainsController < Support::BaseController def create site = Site.find_by(name: params[:domain][:site])
unless site flash[:alert] = I18n.t('support.domains.errors.without_site') redirect_to new_support_domain_path return end
domain = site.domains.build(address: params[:domain][:address]) domain.support_create = true
unless domain.save flash[:alert] = I18n.t('support.domains.errors.invalid') redirect_to new_support_domain_path return end
flash[:success] = I18n.t('support.domains.success') redirect_to support_domains_path end end
@Prodis
class Support::DomainsController < Support::BaseController def create site = Site.find_by(name: params[:domain][:site])
unless site flash[:alert] = I18n.t('support.domains.errors.without_site') redirect_to new_support_domain_path return end
domain = site.domains.build(address: params[:domain][:address]) domain.support_create = true
unless domain.save flash[:alert] = I18n.t('support.domains.errors.invalid') redirect_to new_support_domain_path return end
flash[:success] = I18n.t('support.domains.success') redirect_to support_domains_path end end
@Prodis
No Ruby way
@Prodis
class PaymentGatewayWOP def initialize(options = {}) raise ArgumentError if options[:email].to_s.strip.empty? raise ArgumentError if options[:token].to_s.strip.empty?
@options = options end
def email @options[:email] end
def token @options[:token] end
def identification @options[:identification] end
def billing_type @options[:billing_type] end
def billing_status @options[:billing_status] end
def message @options[:message] end
def exists? @options[:message] =~ /Account found/ end
def is_active? @options[:billing_status] == 'active' end
def is_seller? @options[:billing_type] == 'seller' || @options[:billing_type] == 'company' end
# other methods omitted end
@Prodis
class PaymentGatewayWOP def initialize(options = {}) raise ArgumentError if options[:email].to_s.strip.empty? raise ArgumentError if options[:token].to_s.strip.empty?
@options = options end
def email @options[:email] end
def token @options[:token] end
def identification @options[:identification] end
def billing_type @options[:billing_type] end
# other methods omitted end
@Prodis
class PaymentGatewayWOP def initialize(options = {}) raise ArgumentError if options[:email].to_s.strip.empty? raise ArgumentError if options[:token].to_s.strip.empty?
@options = options end
def email @options[:email] end
def token @options[:token] end
def identification @options[:identification] end
def billing_type @options[:billing_type] end
# other methods omitted end
@Prodis
class PaymentGateway attr_reader :email, :token
def initialize(options = {}) @email = options.fetch(:email) @token = options.fetch(:token) @options = options end
def identification options[:identification] end
def billing_type options[:billing_type] end
# other public methods omitted
private
attr_reader :options
# other methods omitted end
@Prodis
class PaymentGateway attr_reader :email, :token
def initialize(options = {}) @email = options.fetch(:email) @token = options.fetch(:token) @options = options end
def identification options[:identification] end
def billing_type options[:billing_type] end
# other public methods omitted
private
attr_reader :options
# other methods omitted end
@Prodis
class PaymentGateway attr_reader :email, :token
def initialize(options = {}) @email = options.fetch(:email) @token = options.fetch(:token) @options = options end
[:identification, :billing_type, :billing_status, :message].each do |method| define_method(method) do options[method] end end
# other public methods omitted
private
attr_reader :options
# other methods omitted end
@Prodis
class PaymentGateway attr_reader :email, :token
# constructor omitted
[:identification, :billing_type, :billing_status, :message].each do |method| define_method(method) do options[method] end end
def exists? message =~ /Account found/ end
def is_active? billing_status == 'active' end
def is_seller? billing_type == 'seller' || billing_type == 'company' end
private
attr_reader :options
# other methods omitted end
@Prodis
class PaymentGateway attr_reader :email, :token
# other methods omitted
def exists? message =~ /Account found/ end
def active? billing_status == 'active' end
def seller? billing_type == 'seller' || billing_type == 'company' end
private
attr_reader :options
# other methods omitted end
@Prodis
class PaymentGatewayWOP def initialize(options = {}) raise ArgumentError if options[:email].to_s.strip.empty? raise ArgumentError if options[:token].to_s.strip.empty?
@options = options end
def email @options[:email] end
def token @options[:token] end
def identification @options[:identification] end
def billing_type @options[:billing_type] end
def billing_status @options[:billing_status] end
def message @options[:message] end
def exists? @options[:message] =~ /Account found/ end
def is_active? @options[:billing_status] == 'active' end
def is_seller? @options[:billing_type] == 'seller' || @options[:billing_type] == 'company' end
# other methods omitted end
@Prodis
class PaymentGateway attr_reader :email, :token
def initialize(options = {}) @email = options.fetch(:email) @token = options.fetch(:token) @options = options end
[:identification, :billing_type, :billing_status, :message].each do |method| define_method(method) do options[method] end end
def exists? message =~ /Account found/ end
def active? billing_status == 'active' end
def seller? billing_type == 'seller' || billing_type == 'company' end
private
attr_reader :options
# other methods end
@Prodis
Problemas de nomenclatura
@Prodis
class ImageWidgetImporter < WidgetImporter def import(img_element, row_number, position) return if img_element.blank? || img_element['src'].blank? create_image_widget(img_element, row_number, position) end
def import! @page.widgets.where(kind: 'text').each do |widget| content = Nokogiri::HTML(widget.content, nil, 'UTF-8') next unless has_internal_image?(content)
images = content.css('img').select do |image| internal_image?(image) end
images.each { |image| download_and_change_image_src(image) } widget.update_attribute(:content, content.inner_html) end end
private
def kind 'image' end
def create_image_widget(img_element, row_number, position) widget = create(row_number: row_number, position: position, remote_image_url: img_element['src']) source = (AppConfig.assets_host + widget.image.url) widget.content = @template_adapter.render_widget_content('image', alt: '', src: source) widget.save!
widget end
# Create widget_image to Text Widget def create_widget_image(url) widget_image = WidgetImage.new remote_image_url: url widget_image.site_id = @page.site.id widget_image.save!
widget_image end
# other methods omitted end
@Prodis
class ImageWidgetImporter < WidgetImporter def import(img_element, row_number, position) return if img_element.blank? || img_element['src'].blank? create_image_widget(img_element, row_number, position) end
def import! @page.widgets.where(kind: 'text').each do |widget| content = Nokogiri::HTML(widget.content, nil, 'UTF-8') next unless has_internal_image?(content)
images = content.css('img').select do |image| internal_image?(image) end
images.each { |image| download_and_change_image_src(image) } widget.update_attribute(:content, content.inner_html) end end
private
def kind 'image' end
# other methods omitted end
@Prodis
class ImageWidgetImporter < WidgetImporter def import(img_element, row_number, position) return if img_element.blank? || img_element['src'].blank? create_image_widget(img_element, row_number, position) end
def import! @page.widgets.where(kind: 'text').each do |widget| content = Nokogiri::HTML(widget.content, nil, 'UTF-8') next unless has_internal_image?(content)
images = content.css('img').select do |image| internal_image?(image) end
images.each { |image| download_and_change_image_src(image) } widget.update_attribute(:content, content.inner_html) end end
private
def kind 'image' end
# other methods omitted end
@Prodis
class ImageWidgetImporter < WidgetImporter def import(img_element, row_number, position) return if img_element.blank? || img_element['src'].blank? create_image_widget(img_element, row_number, position) end
def import_from_text_widget @page.widgets.where(kind: 'text').each do |widget| content = Nokogiri::HTML(widget.content, nil, 'UTF-8') next unless has_internal_image?(content)
images = content.css('img').select do |image| internal_image?(image) end
images.each { |image| download_and_change_image_src(image) } widget.update_attribute(:content, content.inner_html) end end
private
def kind 'image' end
# other methods omitted end
@Prodis
class ImageWidgetImporter < WidgetImporter # other public methods omitted
private
def create_image_widget(img_element, row_number, position) widget = create(row_number: row_number, position: position, remote_image_url: img_element['src']) source = (AppConfig.assets_host + widget.image.url) widget.content = @template_adapter.render_widget_content('image', alt: '', src: source) widget.save!
widget end
# Create image widget to text widget def create_widget_image(url) widget_image = WidgetImage.new remote_image_url: url widget_image.site_id = @page.site.id widget_image.save!
widget_image end
# other methods omitted end
@Prodis
class ImageWidgetImporter < WidgetImporter # other public methods omitted
private
def create_image_widget(img_element, row_number, position) widget = create(row_number: row_number, position: position, remote_image_url: img_element['src']) source = (AppConfig.assets_host + widget.image.url) widget.content = @template_adapter.render_widget_content('image', alt: '', src: source) widget.save!
widget end
# Create image widget to text widget def create_widget_image(url) widget_image = WidgetImage.new remote_image_url: url widget_image.site_id = @page.site.id widget_image.save!
widget_image end
# other methods omitted end
@Prodis
class ImageWidgetImporter < WidgetImporter # other public methods omitted
private
def create_image_widget(img_element, row_number, position) widget = create(row_number: row_number, position: position, remote_image_url: img_element['src']) source = (AppConfig.assets_host + widget.image.url) widget.content = @template_adapter.render_widget_content('image', alt: '', src: source) widget.save!
widget end
def create_image_widget_to_text_widget(url) widget_image = WidgetImage.new remote_image_url: url widget_image.site_id = @page.site.id widget_image.save!
widget_image end
# other methods omitted end
@Prodis
Orientação a Objetos
@Prodis
Orientação a Objetos Herança com propósito
de reuso de código
@Prodis
class Installation::FromFeed < Installation::FromBase def install(args) # implementation omitted end end
class Installation::FromHosting < Installation::FromBase def install(args) # implementation omitted end end
class Installation::FromMigration < Installation::FromBase def install(args) # implementation omitted end end
@Prodis
class Installation::FromFeed < Installation::FromBase def install(args) # implementation omitted end end
class Installation::FromHosting < Installation::FromBase def install(args) # implementation omitted end end
class Installation::FromMigration < Installation::FromBase def install(args) # implementation omitted end end
@Prodis
class Installation::FromBase include Rails::LabeledLog::Logging
attr_writer :customers_api, :installer, :mailer
def install(args) raise NotImplementedError end
def customers_api @customers_api ||= CustomersApi.new end
def installer @installer ||= Installation::Installer.new end
def mailer @mailer ||= Installation::Mailer.new end end
@Prodis
class Installation::FromBase include Rails::LabeledLog::Logging
attr_writer :customers_api, :installer, :mailer
def install(args) raise NotImplementedError end
def customers_api @customers_api ||= CustomersApi.new end
def installer @installer ||= Installation::Installer.new end
def mailer @mailer ||= Installation::Mailer.new end end
@Prodis
class Installation::FromFeed < Installation::FromBase def install(args) # implementation omitted end end
class Installation::FromHosting < Installation::FromBase def install(args) # implementation omitted end end
class Installation::FromMigration < Installation::FromBase def install(args) # implementation omitted end end
@Prodis
Como corrigir isso?
@Prodis
module Installation::Infra include Rails::LabeledLog::Logging
attr_writer :customers_api, :installer, :mailer
def customers_api @customers_api ||= CustomersApi.new end
def installer @installer ||= Provisioner::Installation::Installer.new end
def mailer @mailer ||= Provisioner::Installation::Mailer.new end end
@Prodis
module Installation::Infra include Rails::LabeledLog::Logging
attr_writer :customers_api, :installer, :mailer
def customers_api @customers_api ||= CustomersApi.new end
def installer @installer ||= Provisioner::Installation::Installer.new end
def mailer @mailer ||= Provisioner::Installation::Mailer.new end end
@Prodis
class Installation::FromFeed include Installation::Infra
def install(args) # implementation omitted end end
class Installation::FromHosting include Installation::Infra
def install(args) # implementation omitted end end
class Installation::FromMigration include Installation::Infra
def install(args) # implementation omitted end end
@Prodis
Orientação a Objetos Equívoco de herança
@Prodis
DNS Uma rápida introdução
@Prodis
@Prodis
class WsDns attr_reader :host, :user, :timeout
def initialize(args) @host = args[:host] @user = args[:user] @timeout = args.fetch(:timeout, 5) end
def create_entry(options) # implementation omitted end
def delete_entry(options) # implementation omitted end
def get_entry(options) # implementation omitted end
def has_entry?(options) # implementation omitted end
# other methods to DNS zone end
@Prodis
class CnameWsDns attr_reader :ws_dns, :zone, :content
def initialize(options) @ws_dns = WsDns.new(options) @zone = options[:zone] @content = options.fetch(:content, zone) end
def create_entry(subdomain) ws_dns.create_entry(type: type, content: content, name: subdomain, zone: zone) end
def delete_entry(subdomain) ws_dns.delete_entry(type: type, content: content, name: subdomain, zone: zone) end
def has_entry?(subdomain) ws_dns.has_entry?(type: type, name: subdomain, zone: zone) end
protected
def type 'CNAME' end end
@Prodis
class CnameWsDns attr_reader :ws_dns, :zone, :content
def initialize(options) @ws_dns = WsDns.new(options) @zone = options[:zone] @content = options.fetch(:content, zone) end
def create_entry(subdomain) ws_dns.create_entry(type: type, content: content, name: subdomain, zone: zone) end
def delete_entry(subdomain) ws_dns.delete_entry(type: type, content: content, name: subdomain, zone: zone) end
def has_entry?(subdomain) ws_dns.has_entry?(type: type, name: subdomain, zone: zone) end
protected
def type 'CNAME' end end
@Prodis
class CnameWsDns attr_reader :ws_dns, :zone, :content
def initialize(options) @ws_dns = WsDns.new(options) @zone = options[:zone] @content = options.fetch(:content, zone) end
def create_entry(subdomain) ws_dns.create_entry(type: type, content: content, name: subdomain, zone: zone) end
def delete_entry(subdomain) ws_dns.delete_entry(type: type, content: content, name: subdomain, zone: zone) end
def has_entry?(subdomain) ws_dns.has_entry?(type: type, name: subdomain, zone: zone) end
protected
def type 'CNAME' end end
@Prodis
class CnameWsDns attr_reader :ws_dns, :zone, :content
def initialize(options) @ws_dns = WsDns.new(options) @zone = options[:zone] @content = options.fetch(:content, zone) end
def create_entry(subdomain) ws_dns.create_entry(type: type, content: content, name: subdomain, zone: zone) end
def delete_entry(subdomain) ws_dns.delete_entry(type: type, content: content, name: subdomain, zone: zone) end
def has_entry?(subdomain) ws_dns.has_entry?(type: type, name: subdomain, zone: zone) end
protected
def type 'CNAME' end end
@Prodis
class CnameWsDns attr_reader :ws_dns, :zone, :content
def initialize(options) @ws_dns = WsDns.new(options) @zone = options[:zone] @content = options.fetch(:content, zone) end
def create_entry(subdomain) ws_dns.create_entry(type: type, content: content, name: subdomain, zone: zone) end
def delete_entry(subdomain) ws_dns.delete_entry(type: type, content: content, name: subdomain, zone: zone) end
def has_entry?(subdomain) ws_dns.has_entry?(type: type, name: subdomain, zone: zone) end
protected
def type 'CNAME' end end
@Prodis
class AWsDns < CnameWsDns protected
def type 'A' end end
@Prodis
class AWsDns < CnameWsDns protected
def type 'A' end end
@Prodis
class CnameWsDns attr_reader :ws_dns, :zone, :content
def initialize(options) @ws_dns = WsDns.new(options) @zone = options[:zone] @content = options.fetch(:content, zone) end
def create_entry(subdomain) ws_dns.create_entry(type: type, content: content, name: subdomain, zone: zone) end
def delete_entry(subdomain) ws_dns.delete_entry(type: type, content: content, name: subdomain, zone: zone) end
def has_entry?(subdomain) ws_dns.has_entry?(type: type, name: subdomain, zone: zone) end
protected
def type 'CNAME' end end
@Prodis
class TxtWsDns < CnameWsDns protected
def type 'TXT' end end
@Prodis
class TxtWsDns < CnameWsDns protected
def type 'TXT' end end
@Prodis
@Prodis
Como corrigir isso?
@Prodis
@Prodis
Orientação a Objetos Classe base conhecendo
seus filhos
@Prodis
class TransactionResponseParser attr_reader :xml
def initialize(xml) @xml = xml end
def parse # omitted implementation end
private
# specific transaction methods omitted end
@Prodis
class ResponseParser attr_reader :xml
def initialize(xml) @xml = xml end
def parse # omitted implementation end
# omitted protected methods end
@Prodis
class TransactionResponseParser < ResponseParser
private
# specific transaction methods omitted end
class AccountResponseParser < ResponseParser
private
# specific account methods omitted end
@Prodis
class ResponseParser def self.transaction?(xml) xml.include?('<transaction>') end
def self.get_parser(xml) ResponseParser.transaction?(xml) ? TransactionResponseParser.new(xml) : AccountResponseParser.new(xml) end
def initialize(xml) @xml = xml end
def parse # omitted implementation
end end
@Prodis
Como corrigir isso?
@Prodis
module ResponseParserFactory def self.build(xml) if xml.include?('<transaction>') TransactionResponseParser.new(xml) else AccountResponseParser.new(xml) end end end
@Prodis
A pior classe
@Prodis
class DomainChecker extend Memoist
DOMAIN_REGEXP = /^[a-z0-9]+(-[a-z0-9]+)*(\.[a-z0-9]+(-[a-z0-9]+)*)+$/
attr_accessor :domain
def initialize(args = {}) @domain = args[:domain] end
def check_new check_existing end
def status if dns_adapter.ns_locaweb? a_entry_locaweb = dns_adapter.a_entry_locaweb if a_entry_locaweb == AppConfig.ip_lvs_criador_de_sites return :ok elsif a_entry_locaweb == false return :unavailable else return :already_using end end
if domain_checker_result["error"] == "generic" return :generic_error end
if domain_checker_result["error"] == "unsupported_tld" return :unsupported_tld end
if domain_checker_result["available"] return :register end
if dns_adapter.a_value == AppConfig.ip_lvs_criador_de_sites return :ok else return :config_dns end end memoize :status
def available_domain_by_user(user) if domain.blank? return {valid: false, notice: :invalid, message: :blank} end
if !domain.match(DOMAIN_REGEXP) return {valid: false, notice: :invalid, message: :invalid} end
if forbidden_domain? return {valid: false, notice: :invalid, message: :forbidden_domain} end
if Domain.where(address: domain).count > 0 current_domain = Domain.where(address: domain).first if (current_domain.site.account.users.include?(user) rescue false) return {valid: false, notice: :invalid, message: :already_using} else return {valid: false, notice: :invalid, message: :already_exists} end end
if !domain_checker_result["valid"] && domain_checker_result["error"] != "unsupported_tld" return {valid: false, notice: :invalid, message: :invalid} end
if domain_checker_result["error"] == "unsupported_tld" return {valid: true, notice: :unsupported_tld} end
if domain_checker_result["available"] return {valid: true, notice: :register} end
if domain_checker_result["customer_login"].blank? return {valid: true, notice: :config_dns} end
if domain_checker_result["customer_login"].downcase == user.username.downcase Rails.logger.info "user owner domain"
if dns_adapter.a_entry_locaweb? if dns_adapter.a_entry_locaweb == AppConfig.ip_lvs_criador_de_sites_old return {valid: true, notice: :old_csit} else return {valid: true, notice: :already_using} end else Rails.logger.info "Without entry A" return {valid: true, notice: :owner_domain} end else Rails.logger.info "user does not owner domain" return {valid: false, notice: :not_owner} end end
def details { entry_a: dns_adapter.a_value, entry_ns: dns_adapter.ns_value, entry_cname: dns_adapter.cname_value }.merge(domain_checker_result) end
def check_existing return external_check if external_check["error"] == "generic" return external_check if external_check["error"] == "invalid_domain" return external_check if external_check["error"] == "unsupported_tld" return external_check if external_check["available"] return external_check if internal_check["available"] internal_check end
private
def dns_adapter DnsAdapter.new(domain: CGI.escape(domain)) end memoize :dns_adapter
def domain_checker_result domain_checker = DomainChecker.new(domain: CGI.escape(domain)) domain_checker_result = domain_checker.check_new end memoize :domain_checker_result
def get_token WsAuthentication.new(AppConfig.wsauthentication.url).authenticate(AppConfig.wsauthentication.user, AppConfig.wsauthentication.pass) end memoize :get_token
def external_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/external_check" begin JSON(http_get(url)) rescue RestClient::NotImplemented return { "valid" => false, "available" => false, "error" => 'unsupported_tld' } rescue RestClient::InternalServerError => exception Rails.logger.error "[ERROR] GET #{url}: #{exception.message}\n" \ "Response: #{exception.http_body}" return { "valid" => false, "available" => false, "error" => 'generic' } rescue => exception Rails.logger.error exception.print raise exception end end memoize :external_check
def internal_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/internal_check" JSON(http_get(url)) end memoize :internal_check
def forbidden_domain? uri = "#{AppConfig.registro_domain_url}/domain/#{domain}/check"
begin response = JSON(CasSaas::CasRestClient.new.get(uri))
!response["valid"] rescue => e Rails.logger.info e.message true end end memoize :forbidden_domain?
def http_get(url, headers = {}) Rails.logger.info "chamando GET #{url}, headers: #{headers}" response = RestClient.get url, headers Rails.logger.info "response #{response}" response end end
@Prodis
Cenário de negócio da classe DomainChecker
@Prodis
Verificação de domínio
@Prodis
class DomainChecker # ...
def check_new # omitted implementation end
def status # omitted implementation end memoize :status
def available_domain_by_user(user) # omitted implementation end
def details # omitted implementation end
def check_existing # omitted implementation end
# ... end
@Prodis
class DomainChecker extend Memoist
attr_accessor :domain
def initialize(args = {}) @domain = args[:domain] end
# ... end
@Prodis
class DomainChecker extend Memoist
# ...
def check_new check_existing end
def check_existing return external_check if external_check["error"] == "generic" return external_check if external_check["error"] == "invalid_domain" return external_check if external_check["error"] == "unsupported_tld" return external_check if external_check["available"] return external_check if internal_check["available"] internal_check
end
# ... end
@Prodis
class DomainChecker extend Memoist
attr_accessor :domain
def initialize(args = {}) @domain = args[:domain] end
# ... end
@Prodis
def status if dns_adapter.ns_locaweb? a_entry_locaweb = dns_adapter.a_entry_locaweb if a_entry_locaweb == AppConfig.ip_lvs_criador_de_sites return :ok elsif a_entry_locaweb == false return :unavailable else return :already_using end end
if domain_checker_result["error"] == "generic" return :generic_error end
if domain_checker_result["error"] == "unsupported_tld" return :unsupported_tld end
if domain_checker_result["available"] return :register end
if dns_adapter.a_value == AppConfig.ip_lvs_criador_de_sites return :ok else return :config_dns end end memoize :status
@Prodis
def dns_adapter DnsAdapter.new(domain: CGI.escape(domain)) end memoize :dns_adapter
def domain_checker_result domain_checker = DomainChecker.new(domain: CGI.escape(domain)) domain_checker_result = domain_checker.check_new end memoize :domain_checker_result
@Prodis
def get_token WsAuthentication.new(AppConfig.wsauthentication.url).authenticate(AppConfig.wsauthentication.user, AppConfig.wsauthentication.pass)
end memoize :get_token
def external_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/external_check" begin JSON(http_get(url)) rescue RestClient::NotImplemented return { "valid" => false, "available" => false, "error" => 'unsupported_tld' } rescue RestClient::InternalServerError => exception Rails.logger.error "[ERROR] GET #{url}: #{exception.message}\n" \ "Response: #{exception.http_body}" return { "valid" => false, "available" => false, "error" => 'generic' } rescue => exception Rails.logger.error exception.print raise exception end end memoize :external_check
@Prodis
def internal_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/internal_check" JSON(http_get(url)) end memoize :internal_check
def forbidden_domain? uri = "#{AppConfig.registro_domain_url}/domain/#{domain}/check"
begin response = JSON(CasSaas::CasRestClient.new.get(uri)) !response["valid"] rescue => e Rails.logger.info e.message true end end memoize :forbidden_domain?
@Prodis
class DomainChecker extend Memoist
attr_accessor :domain
def initialize(args = {}) @domain = args[:domain] end
# ... end
@Prodis
def status if dns_adapter.ns_locaweb? a_entry_locaweb = dns_adapter.a_entry_locaweb if a_entry_locaweb == AppConfig.ip_lvs_criador_de_sites return :ok elsif a_entry_locaweb == false return :unavailable else return :already_using end end
if domain_checker_result["error"] == "generic" return :generic_error end
if domain_checker_result["error"] == "unsupported_tld" return :unsupported_tld end
if domain_checker_result["available"] return :register end
if dns_adapter.a_value == AppConfig.ip_lvs_criador_de_sites return :ok else return :config_dns end end memoize :status
@Prodis
def available_domain_by_user(user) if domain.blank? return {valid: false, notice: :invalid, message: :blank} end
if !domain.match(DOMAIN_REGEXP) return {valid: false, notice: :invalid, message: :invalid} end
if forbidden_domain? return {valid: false, notice: :invalid, message: :forbidden_domain} end
if Domain.where(address: domain).count > 0 current_domain = Domain.where(address: domain).first if (current_domain.site.account.users.include?(user) rescue false) return {valid: false, notice: :invalid, message: :already_using} else return {valid: false, notice: :invalid, message: :already_exists} end end
if !domain_checker_result["valid"] && domain_checker_result["error"] != "unsupported_tld" return {valid: false, notice: :invalid, message: :invalid} end
if domain_checker_result["error"] == "unsupported_tld" return {valid: true, notice: :unsupported_tld} end
if domain_checker_result["available"] return {valid: true, notice: :register} end
if domain_checker_result["customer_login"].blank? return {valid: true, notice: :config_dns} end
if domain_checker_result["customer_login"].downcase == user.username.downcase Rails.logger.info "user owner domain"
if dns_adapter.a_entry_locaweb? if dns_adapter.a_entry_locaweb == AppConfig.ip_lvs_criador_de_sites_old return {valid: true, notice: :old_csit} else return {valid: true, notice: :already_using} end else Rails.logger.info "Without entry A" return {valid: true, notice: :owner_domain} end else Rails.logger.info "user does not owner domain" return {valid: false, notice: :not_owner} end end
@Prodis
def available_domain_by_user(user) if domain.blank? return {valid: false, notice: :invalid, message: :blank} end
if !domain.match(DOMAIN_REGEXP) return {valid: false, notice: :invalid, message: :invalid} end
if forbidden_domain? return {valid: false, notice: :invalid, message: :forbidden_domain} end
if Domain.where(address: domain).count > 0 current_domain = Domain.where(address: domain).first if (current_domain.site.account.users.include?(user) rescue false) return {valid: false, notice: :invalid, message: :already_using} else return {valid: false, notice: :invalid, message: :already_exists} end end
# ... end
@Prodis
def available_domain_by_user(user) if domain.blank? return {valid: false, notice: :invalid, message: :blank} end
if !domain.match(DOMAIN_REGEXP) return {valid: false, notice: :invalid, message: :invalid} end
if forbidden_domain? return {valid: false, notice: :invalid, message: :forbidden_domain} end
if Domain.where(address: domain).count > 0 current_domain = Domain.where(address: domain).first if (current_domain.site.account.users.include?(user) rescue false) return {valid: false, notice: :invalid, message: :already_using} else return {valid: false, notice: :invalid, message: :already_exists} end end
# ... end
@Prodis
def available_domain_by_user(user) if domain.blank? return {valid: false, notice: :invalid, message: :blank} end
if !domain.match(DOMAIN_REGEXP) return {valid: false, notice: :invalid, message: :invalid} end
if forbidden_domain? return {valid: false, notice: :invalid, message: :forbidden_domain} end
if Domain.where(address: domain).count > 0 current_domain = Domain.where(address: domain).first if (current_domain.site.account.users.include?(user) rescue false) return {valid: false, notice: :invalid, message: :already_using} else return {valid: false, notice: :invalid, message: :already_exists} end end
# ... end
@Prodis
def available_domain_by_user(user) # …
if domain_checker_result["customer_login"].downcase == user.username.downcase Rails.logger.info "user owner domain"
if dns_adapter.a_entry_locaweb? if dns_adapter.a_entry_locaweb == AppConfig.ip_lvs_criador_de_sites_old return {valid: true, notice: :old_csit} else return {valid: true, notice: :already_using} end else Rails.logger.info "Without entry A" return {valid: true, notice: :owner_domain} end else Rails.logger.info "user does not owner domain" return {valid: false, notice: :not_owner} end end
@Prodis
def available_domain_by_user(user) # …
if !domain_checker_result["valid"] && domain_checker_result["error"] != "unsupported_tld" return {valid: false, notice: :invalid, message: :invalid} end
if domain_checker_result["error"] == "unsupported_tld" return {valid: true, notice: :unsupported_tld} end
if domain_checker_result["available"] return {valid: true, notice: :register} end
if domain_checker_result["customer_login"].blank? return {valid: true, notice: :config_dns} end
# … end
@Prodis
class DomainChecker # ...
def check_new check_existing end
def check_existing return external_check if external_check["error"] == "generic" return external_check if external_check["error"] == "invalid_domain" return external_check if external_check["error"] == "unsupported_tld" return external_check if external_check["available"] return external_check if internal_check["available"] internal_check end
private
def domain_checker_result domain_checker = DomainChecker.new(domain: CGI.escape(domain)) domain_checker_result = domain_checker.check_new end memoize :domain_checker_result
# ... end
@Prodis
class DomainChecker # ...
def check_new check_existing end
def check_existing return external_check if external_check["error"] == "generic" return external_check if external_check["error"] == "invalid_domain" return external_check if external_check["error"] == "unsupported_tld" return external_check if external_check["available"] return external_check if internal_check["available"] internal_check end
private
def domain_checker_result domain_checker = DomainChecker.new(domain: CGI.escape(domain)) domain_checker_result = domain_checker.check_new end memoize :domain_checker_result
# ... end
@Prodis
class DomainChecker # ...
def check_new check_existing end
def check_existing return external_check if external_check["error"] == "generic" return external_check if external_check["error"] == "invalid_domain" return external_check if external_check["error"] == "unsupported_tld" return external_check if external_check["available"] return external_check if internal_check["available"] internal_check end
private
def domain_checker_result domain_checker = DomainChecker.new(domain: CGI.escape(domain)) domain_checker_result = domain_checker.check_new end memoize :domain_checker_result
# ... end
@Prodis
class DomainChecker # ...
def check_new check_existing end
def check_existing return external_check if external_check["error"] == "generic" return external_check if external_check["error"] == "invalid_domain" return external_check if external_check["error"] == "unsupported_tld" return external_check if external_check["available"] return external_check if internal_check["available"] internal_check end
private
def domain_checker_result domain_checker = DomainChecker.new(domain: CGI.escape(domain)) domain_checker_result = domain_checker.check_new end memoize :domain_checker_result
# ... end
@Prodis
class DomainChecker # ...
def check_new check_existing end
def check_existing return external_check if external_check["error"] == "generic" return external_check if external_check["error"] == "invalid_domain" return external_check if external_check["error"] == "unsupported_tld" return external_check if external_check["available"] return external_check if internal_check["available"] internal_check end
private
def domain_checker_result domain_checker = DomainChecker.new(domain: CGI.escape(domain)) domain_checker_result = domain_checker.check_new end memoize :domain_checker_result
# ... end
@Prodis
class DomainChecker # ...
def check_new check_existing end
def check_existing return external_check if external_check["error"] == "generic" return external_check if external_check["error"] == "invalid_domain" return external_check if external_check["error"] == "unsupported_tld" return external_check if external_check["available"] return external_check if internal_check["available"] internal_check end
private
def domain_checker_result domain_checker = DomainChecker.new(domain: CGI.escape(domain)) domain_checker_result = domain_checker.check_new end memoize :domain_checker_result
# ... end
@Prodis
def external_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/external_check" begin JSON(http_get(url)) rescue RestClient::NotImplemented return { "valid" => false, "available" => false, "error" => 'unsupported_tld' } rescue RestClient::InternalServerError => exception Rails.logger.error "[ERROR] GET #{url}: #{exception.message}\n" \ "Response: #{exception.http_body}" return { "valid" => false, "available" => false, "error" => 'generic' } rescue => exception Rails.logger.error exception.print raise exception end end memoize :external_check
def internal_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/internal_check" JSON(http_get(url)) end memoize :internal_check
@Prodis
def external_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/external_check" begin JSON(http_get(url)) rescue RestClient::NotImplemented return { "valid" => false, "available" => false, "error" => 'unsupported_tld' } rescue RestClient::InternalServerError => exception Rails.logger.error "[ERROR] GET #{url}: #{exception.message}\n" \ "Response: #{exception.http_body}" return { "valid" => false, "available" => false, "error" => 'generic' } rescue => exception Rails.logger.error exception.print raise exception end end memoize :external_check
def internal_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/internal_check" JSON(http_get(url)) end memoize :internal_check
@Prodis
class DomainChecker # ...
def http_get(url, headers = {}) Rails.logger.info "chamando GET #{url}, headers: #{headers}" response = RestClient.get url, headers Rails.logger.info "response #{response}" response end end
@Prodis
def external_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/external_check" begin JSON(http_get(url)) rescue RestClient::NotImplemented return { "valid" => false, "available" => false, "error" => 'unsupported_tld' } rescue RestClient::InternalServerError => exception Rails.logger.error "[ERROR] GET #{url}: #{exception.message}\n" \ "Response: #{exception.http_body}" return { "valid" => false, "available" => false, "error" => 'generic' } rescue => exception Rails.logger.error exception.print raise exception end end memoize :external_check
def internal_check url = "#{AppConfig.registro_service_url}/domain_availability/#{domain}/internal_check" JSON(http_get(url)) end memoize :internal_check
@Prodis
class DomainChecker # ...
def check_new check_existing end
def check_existing return external_check if external_check["error"] == "generic" return external_check if external_check["error"] == "invalid_domain" return external_check if external_check["error"] == "unsupported_tld" return external_check if external_check["available"] return external_check if internal_check["available"] internal_check end
private
def domain_checker_result domain_checker = DomainChecker.new(domain: CGI.escape(domain)) domain_checker_result = domain_checker.check_new end memoize :domain_checker_result
# ... end
@Prodis
class DomainChecker # ...
def details { entry_a: dns_adapter.a_value, entry_ns: dns_adapter.ns_value, entry_cname: dns_adapter.cname_value }.merge(domain_checker_result) end
private
def dns_adapter DnsAdapter.new(domain: CGI.escape(domain)) end memoize :dns_adapter
#... end
@Prodis
class DomainChecker # ... private
def get_token WsAuthentication.new(AppConfig.wsauthentication.url).authenticate(AppConfig.wsauthentication.user, AppConfig.wsauthentication.pass)
end memoize :get_token end
@Prodis
Problemas da classe DomainChecker
@Prodis
Classe longa
Problemas de DomainChecker
@Prodis
Construtor recebe um hash como parâmetro, mas somente usa
o valor de uma chave do hash.
Problemas de DomainChecker
@Prodis
Uma implementação de método que apenas chama
um método privado sem passagem de parâmetros
Problemas de DomainChecker
@Prodis
Memoize hell: métodos “memoizados" que são usados como variáveis
Problemas de DomainChecker
@Prodis
Um monte de ifs: ifs com ifs com else com if com else
(difícil até para explicar)
Problemas de DomainChecker
@Prodis
Código difícil para entender
(internal x external checks)
Problemas de DomainChecker
@Prodis
Um método privado não usado
Problemas de DomainChecker
@Prodis
Um método de instância que cria outra instância
da mesma classe
Problemas de DomainChecker
@Prodis
• Muitas responsabilidades:
✴ Validação de formato de domínio
✴ Validação de lógica de domínio
✴ Retornos com formato exclusivo para a view
✴ Faz requisições HTTP
✴ Parser de respostas HTTP
Problemas de DomainChecker
@Prodis
A classe DomainChecker introduz novos
padrões e princípios
@Prodis
Write-only codeUma vez escrito,
ninguém mais consegue ler
DomainChecker introduz novos padrões e princípios
@Prodis
Close Closed PrincipleFechado para modificação,
mais fechado para extensão.
DomainChecker introduz novos padrões e princípios
@Prodis
Inception PatternOnde uma instância de uma classe cria uma nova instância da mesma classe e agrega o
estado da nova instância para seu próprio estado
DomainChecker introduz novos padrões e princípios
@Prodis
A classe DomainChecker provavelmente é a pior classe em Ruby que eu já vi na minha vida
@Prodis
Como foi corrigido?
@Prodis
@Prodis
Por que POG é aplicada?
@Prodis
Falta de conhecimento
Causas de POG
@Prodis
Imaturidade no desenvolvimento
de software
Causas de POG
@Prodis
Ambiente não colaborativo
Causas de POG
@Prodis
Falta de coaching
Causas de POG
@Prodis
Prazos apertados
Causas de POG
@Prodis
Por que simplificar se você pode complicar?
Causas de POG
@Prodis
Uso de “coisas legais” porque elas são legais, mesmo se elas não oferecem uma solução para
o que você precisa.
Causas de POG
@Prodis
Alguns mistérios da mente humana
(que não podemos explicar)
Causas de POG
@Prodis
Como evitar POG?
@Prodis
Leia muito
Como evitar POG?
@Prodis
Como evitar POG?
@Prodis
Mas não aprenda somente Ruby
Como evitar POG?
@Prodis
Como evitar POG?
@Prodis
Use revisão de código
Como evitar POG?
@Prodis
Leia código de outros programadores
Como evitar POG?
@Prodis
Escreva código que você mesmo irá
conseguir ler no futuro
Como evitar POG?
@Prodis
Participe de projetos open source: contribuindo, discutindo, lendo código.
Como evitar POG?
@Prodis
Coaching de programadores menos experientes
(ensinar é uma boa maneira de aprender também)
Como evitar POG?
@Prodis
Não escreva código para você: escreva para a aplicação,
para sua equipe.
Como evitar POG?
@Prodis
Troque experiências, pergunte.
Como evitar POG?
@Prodis
Use programação pareada
(não 100% do tempo IMHO)
Como evitar POG?
@Prodis
Aprenda com erros: seus e dos outros
Como evitar POG?
@Prodis
Encare código ruim como uma oportunidade
de melhorar
Como evitar POG?
@Prodis
Não encare código ruim com reclamações ou
tirando sarro dos autores
Como evitar POG?
@Prodis
Este “código engraçado” causa desperdício de
tempo, recursos e dinheiro
Como evitar POG?
@Prodis
Ao invés disso, mostre para os autores do código
ruim o caminho certo
Como evitar POG?
@Prodis
Mostre a eles o Ruby way
Como evitar POG?
@Prodis
Obrigado! Espero que tenham gostado
@Prodis
@Prodis
https://twitter.com/prodis https://github.com/prodis
https://www.linkedin.com/in/prodis
http://prodis.blog.br
@Prodis