The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

141
The worst Ruby codes I’ve seen in my life RubyKaigi 2015 @Prodis

Transcript of The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Page 1: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

The worst Ruby codes I’ve seen in my life

RubyKaigi 2015

@Prodis

Page 2: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Fernando Hamasaki de Amorim @Prodis

• Developing web applications for 15 years

• Writing Ruby code since 2009

• Working at Locaweb, the biggest hosting company in Brazil

• Playing basketball at the free time

Page 3: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

WOP

Page 4: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

WOP Workaround Oriented Programming

Page 5: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

WOPWorkaround Oriented Programming is an advance

technique of software development that uses of any

kind of workaround, patch e all of evil a code can have.

WOP is based on code duplication, redundant flows,

unnecessary tasks and wheels reinvention.

Page 6: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

The worst Ruby codes I’ve seen in my life

"The names have been changed to protect the innocent."

Page 7: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

A first example of WOP: masking credit card

numbers

Page 8: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 9: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

def mask_credit_card(number) limit = number.length - 4 “#{'*' * limit}#{number[limit..-1]}” end

Page 10: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

def mask_credit_card_wop(number) (number.length - 4).times do |i| number[i] = '*' end

number end

Page 11: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

describe '#mask_credit_card_wop' do let(:number) { '5464193830403276' }

it 'returns masked credit card number' do masked = mask_credit_card_wop(number)

expect(masked).to eq '************3276' end

it 'does not change number variable' do mask_credit_card_wop(number) expect(number).to eq '5464193830403276' end end

Page 12: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

#mask_credit_card_wop returns masked credit card number does not change number variable (FAILED - 1)

Failures:

1) #mask_credit_card_wop 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

Page 13: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

def mask_credit_card_wop(number) (number.length - 4).times do |i| number[i] = '*' end

number end

Page 14: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Unclear flow

Page 15: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 16: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 17: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 18: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 19: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 20: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 21: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

How to fix it?

Page 22: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 23: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 24: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

No Ruby way

Page 25: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 26: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 27: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 28: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 29: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 30: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 31: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 32: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 33: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 34: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 35: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Naming issues

Page 36: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 37: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 38: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 39: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 40: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 41: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 42: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 43: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

OOP

Page 44: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

OOP Inheritance for the purpose

of code reuse

Page 45: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 46: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 47: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

How to fix it?

Page 48: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 49: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 50: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

OOP Inheritance mistake

Page 51: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

DNS A quick introduction

Page 52: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
Page 53: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class WsDns attr_reader :host, :user, :timeout

def initialize(options) @host = options[:host] @user = options[:user] @timeout = options.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

Page 54: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 55: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class AWsDns < CnameWsDns protected

def type 'A' end end

Page 56: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class TxtWsDns < CnameWsDns protected

def type 'TXT' end end

Page 57: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
Page 58: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

How to fix it?

Page 59: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
Page 60: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

OOP Parent knowing your children

Page 61: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class TransactionResponseParser attr_reader :xml

def initialize(xml) @xml = xml end

def parse # omitted implementation end

private

# specific transaction methods omitted end

Page 62: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class ResponseParser attr_reader :xml

def initialize(xml) @xml = xml end

def parse # omitted implementation end

# omitted protected methods end

Page 63: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class TransactionResponseParser < ResponseParser

private

# specific transaction methods omitted end

class AccountResponseParser < ResponseParser

private

# specific account methods omitted end

Page 64: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 65: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

How to fix it?

Page 66: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

module ResponseParserFactory def self.build(xml) if xml.include?('<transaction>') TransactionResponseParser.new(xml) else AccountResponseParser.new(xml) end end end

Page 67: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

The worst class

Page 68: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 69: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Business scenario for DomainChecker class

Page 70: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 71: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class DomainChecker extend Memoist

attr_accessor :domain

def initialize(args = {}) @domain = args[:domain] end

# ... end

Page 72: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 73: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class DomainChecker extend Memoist

attr_accessor :domain

def initialize(args = {}) @domain = args[:domain] end

# ... end

Page 74: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 75: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 76: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 77: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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?

Page 78: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class DomainChecker extend Memoist

attr_accessor :domain

def initialize(args = {}) @domain = args[:domain] end

# ... end

Page 79: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 80: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 81: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 82: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 83: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 84: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 85: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 86: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 87: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 88: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 89: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 90: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 91: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 92: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 93: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

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

Page 94: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

class DomainChecker # ... private

def get_token WsAuthentication.new(AppConfig.wsauthentication.url).authenticate(AppConfig.wsauthentication.user, AppConfig.wsauthentication.pass)

end memoize :get_token end

Page 95: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

DomainChecker class problems

Page 96: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Long class

DomainChecker class problems

Page 97: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Constructor with hash parameter, but only use

one key of the hash.

DomainChecker class problems

Page 98: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

A method implementation that only call a private

method without parameters

DomainChecker class problems

Page 99: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Memoize (hell) dependency: memoized methods used

like variables.

DomainChecker class problems

Page 100: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

A lot ifs: ifs with ifs with else with if with else

(it’s hard until to explain)

DomainChecker class problems

Page 101: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Code hard to understand

(internal x external checks)

DomainChecker class problems

Page 102: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

An unused private method

DomainChecker class problems

Page 103: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Instance method creates another instance of itself

DomainChecker class problems

Page 104: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

• Many responsibilities:

✴ Validate domain format

✴ Validate domain logic

✴ Format return to use in view

✴ Do HTTP requests

✴ Parse HTTP responses

DomainChecker class problems

Page 105: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

DomainChecker class problems introduces new patterns and principles

Page 106: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Write-only codeOnce write no one can read

DomainChecker class introducesnew patterns and principles

Page 107: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Close Closed PrincipleClosed for modification,

more closed for extension.

DomainChecker class introducesnew patterns and principles

Page 108: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Inception PatternWhere an instance of a class creates

a new instance of itself and aggregates the new instance state to your state

DomainChecker class introducesnew patterns and principles

Page 109: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

DomainChecker class probably is the worst Ruby class

I've seen in my life

Page 110: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

How did we fix it?

Page 111: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
Page 112: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Why is WOP applied?

Page 113: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Lack of knowledge

Causes of WOP

Page 114: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Immaturity in software development

Causes of WOP

Page 115: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

No collaborative environment

Causes of WOP

Page 116: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

No coaching

Causes of WOP

Page 117: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Tight deadlines

Causes of WOP

Page 118: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Why simplify if you can complicate?

Causes of WOP

Page 119: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

To use "cool things" because they are cool, even

if they are not a solution.

Causes of WOP

Page 120: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Some mysteries of the human mind

(that we can't explain)

Causes of WOP

Page 121: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

How to avoid WOP?

Page 122: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Read a lot

How to avoid WOP?

Page 123: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

How to avoid WOP?

Page 124: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

But do not learn only Ruby

How to avoid WOP?

Page 125: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

How to avoid WOP?

Page 126: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Use code review

How to avoid WOP?

Page 127: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Read code from others programmers

How to avoid WOP?

Page 128: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Write code yourself can read in the future

How to avoid WOP?

Page 129: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Participate of open source projects: contributing, discussing, reading.

How to avoid WOP?

Page 130: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Do coaching of less experienced developers (teaching is a good way to learn too)

How to avoid WOP?

Page 131: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Do not write code for you: write it to the application,

to the team.

How to avoid WOP?

Page 132: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Exchange experiences, ask.

How to avoid WOP?

Page 133: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Use pair programming (not 100% IMHO)

How to avoid WOP?

Page 134: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Learn from your mistakes and others

How to avoid WOP?

Page 135: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Face bad code as an opportunity to get better

How to avoid WOP?

Page 136: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Do not face bad code with complaints or

making fun of the authors

How to avoid WOP?

Page 137: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

This funny code causes waste of time, resources

and money

How to avoid WOP?

Page 138: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Instead, show the authors of bad code the right way

How to avoid WOP?

Page 139: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Show them the Ruby way

How to avoid WOP?

Page 140: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

Thank you! I hope you enjoyed

@Prodis

Page 141: The worst Ruby codes I’ve seen in my life - RubyKaigi 2015

The worst Ruby codes I’ve seen in my life

RubyKaigi 2015

@Prodis