Evite singleton e estruturas estáticas

Segundo Alan Kay, orientação a objetos significa "passagem de mensagem, retenção local, proteção e ocultação de estado de um processo, e associação tardia de tudo". É uma maneira de pensar na construção de uma aplicação. Este paradigma oferece ferramentas que nos ajudam a pensar de acordo com ele.

No entanto, há um ponto que, neste texto, quero destacar para comentar um pouco sobre singleton e estruturas estáticas: ".. associação tardia de tudo". Ambas são baseadas em associação prematura. Vejamos cada uma delas.

Static

Os métodos estáticos de uma classe não podem ser usados por uma instância e nunca são virtuais, ou seja, não podem ser sobrescritos, pois qualquer referência é resolvida através de associação prematura (early binding) e não por associação tadia (late binding).

A associação tardia se dá quando um método a ser invocado é definido durante a compilação da aplicação e associação tardia se dá quando um método a ser invocado é definido em tempo de execução da aplicação.

Além do mais, qualquer método de classe, ou método estático, é executados no contexto de seus parâmetros, enquanto métodos de instância são executados no contexto do estado do objeto. Considerando, ainda, que o uso de associação prematura impede a implementação de poliformismo.

Estou cada vez mais contra o uso de métodos de classe. Eles levam, muitas vezes, a programação procedural. (Nick Sutterer)

Estruturas estáticas são, na verdade, estruturas globais.

Singleton

Em linhas gerais, este padrão oferece uma forma de garantir que existirá uma única instância de uma classe para sua aplicação. Alguns dizem que o padrão deveria se chamar globalton, anti-pattern.

Vejamos alguns problemas ao usá-lo em sua aplicação:

Estado único

Há propagação de estado por toda sua aplicação através do uso deste padrão, pois atributos estáticos pertencem a classe e não são compartilhados com as instâncias da mesma.

Testabilidade

Com estado único, seus testes começarão a chorar. Manter uma suite de testes de unidade de um classe que implementa singleton é terrível. Será necessário adaptações por todo lado, pois dependemos de como o estado será devolvido.

Singleton e estruturas estáticas são semelhantes a variáveis globais.

Mas, tenho alguma alternativa?

Sem dúvida! Antes de optar por implementar um singleton ou estruturas estáticas, leia sobre injeção de dependência. Ela o ajudará a eliminar a propagação de estado e garantirá maior flexibilidade aos seus testes.

E, acredito que o conselho é válido:

Estude, leia muito e busque sempre as melhores soluções para aplicar no seu contexto. Questione sempre e ofereça outras alternativas. Orientação a objetos é um campo muito rico! (Anônimo)

Object Calisthenics

Este post é uma tradução livre em português do artigo Object Calesthenics. Você pode ler a versão original aqui.

As regras:

  • Não tenha mais que um nível de indentação por método
  • Não use else
  • Envolva seus tipos primitivos
  • Envolva suas coleções em classes
  • Não fale com estranhos
  • Não use abreviações
  • Mantenha classes pequenas
  • Não tenha mais que duas variáveis de instância em sua classe
  • Não use getters e setters

Todos nós já encontramos códigos mal escritos que são difíceis de compreender, testar e manter. A programação orientada a objetos surgiu com a promessa de nos livrar de antigos códigos procedurais, permitindo a criação de aplicações de forma incremental, reutilizando à medida que avançamos na programação. Mas, às vezes, parece que continuamos seguindo conceitos antigos, complexos e acoplados, igual ao que fazíamos em C.

Não é fácil aplicar um bom design, pois a transição do paradigma de procedural para orientado a objetos é custosa. Requer mudanças na forma de pensar. Vários programadores assumem que estão aplicando um bom design, mas, na realidade, estão presos em hábitos antigos, difíceis de quebrar. Temos muitos exemplos e práticas que incentivam um design pobre em nome de desempenho, por exemplo. Lamentável!

Os conceitos centrais por trás de um bom design são claros. Alan Shalloway sugeriu sete qualidades que nossos códigos devem possuir: alta coesão, baixo acoplamento, redundância e ambiguidade zero, encapsulamento, testabilidade, legibilidade e foco. Porém, não é um trabalho fácil aplicar todos esses conceitos. Uma coisa é entendermos que encapsulamento significa esconder nossos dados e implementações. Algo completamente diferente é implementar um bom encapsulamento.

Sendo assim, estas regras são exercícios para internalizar princípios de design orientado a objetos na vida real.

Não tenha mais que um nível de indentação por método

É comum olharmos para um método muito grande e antigo, nos perguntando: "Por onde começar?". Há baixa coesão em métodos gigantes. Uma orientação que ofereço é que limite seu método a, no máximo, 5 linhas. Porém, acatar essa orientação pode ser complicado caso possua vários "monstros" de 500 linhas.

Procure garantir que cada método faça exatamente uma única coisa. Ter várias estruturas de controle em único método diz respeito a várias abstrações. Isso significa que está fazendo coisa demais e um lugar só.

Trabalhar com métodos que façam uma coisa só, em classes que possuem uma única responsabilidade, representa mudanças no seu código. Com unidades cada vez menores, o nível de reutilização começa a aumentar exponencialmente. Pode não ser fácil identificar pontos de reutilização dentro de um método que possui cinco responsabilidades diferentes e é implementado em 100 linhas.

Um pequeno método, de três linhas, gerenciando o estado de um único objeto pode se reutilizável em vários contextos diferentes.

Extraia comportamentos para outros métodos, garantindo um único nível de indentação. Veja um exemplo de como aplicar o conceito:

Código não recomendável:

class Board {
  String board() {
    StringBuffer buf = new StringBuffer();

    for (int i = 0; i < 10; i++) {
      for (int j = 0; j < 10; j++)
        buf.append(data[i][j]);

      buf.append("\n");
    }

    return buf.toString();
  }
}

Código recomendável:

class Board {
  String board() {
    StringBuffer buf = new StringBuffer();
    collectRows(buf);

    return buf.toString();
  }

  void collectRows(StringBuffer buf) {
    for (int i = 0; i < 10; i++)
      collectRow(buf, i);
  }

  void collectRow(StringBuffer buf, int row) {
    for (int i = 0; i < 10; i++)
      Buf.append(data[row][i]);

    buf.append("\n");
  }
}

Perceba que, com esta refatoração, cada método tornou-se simples e auto-explicativo pela própria implementação e nome do método. Encontrar erros em métodos pequenos é muito mais fácil.

Não use else

Qualquer programador entende uma construção condicional. Presente em quase todas as linguagens de programação, sua lógica é de fácil compreensão. A maioria dos programadores já encontrou condicionais com vários níveis de comparação, sendo muito difícil seguir o raciocínio.

É comum adicionar condição em outras já existentes, ao invés de refatorar e criar uma solução mais elegante. Condicionais geram duplicação com frequência. Verificação de estado é um exemplo que pode levar a este problema. Veja:

if (status == DONE) {
  doSomething();
} else {
  ...
}

Linguagens de programação orientada a objetos possui uma ferramenta poderosa para casos complexos de condicionais: Polimorfismo. Usar polimorfismo no design da sua aplicação pode trazer legibilidade e fácil manutenção, expressando intenções de forma mais clara. Porém, mais uma vez, não são transições fáceis de fazer, especialmente quando temos else.

Experimente o padrão Null Object. Este padrão pode ajudar em algumas situações.

Envolva seus tipos primitivos

Em Java, um int é considerado um tipo primitivo e não um objeto. Ele, por si só, é apenas uma variável sem significado. Quando um método recebe um int como parâmetro, o nome desse método precisa expressar toda a intenção do comportamento que será executado. Se esse mesmo método recebe um objeto Hour como parâmetro, por exemplo, se torna mais fácil entender o que será feito.

Objetos pequenos, como Hour, podem trazer a qualidade de fácil manutenção, uma vez que não podemos passar um objeto Year onde se espera Hour. O compilador não o ajudará a escrever aplicações mais semânticos com tipos primitivos. Com um objeto, mesmo que pequeno, está oferecendo, tanto ao compilador quando ao programador, informações adicionais sobre qual valor e por que ele está sendo usado.

Objetos pequenos, como Hour ou Year também nos dão indicativos de onde podemos colocar o comportamento que seria dado em outras classes. Isto se torna especialmente verdadeiro quando aplicamos a regra Não use getters e setters, onde apenas esse pequeno objeto pode acessar seu valor.

Envolva suas coleções em classes

A aplicação desta regra é simples: Qualquer classe que contém uma coleção não deve possuir outras variáveis de instância. Cada coleção é envolvida em sua própria classe. Portanto, comportamentos relacionados a coleção estão encapsulados na classe em questão.

Sua nova classe, que envolve sua coleção, pode lidar com comportamentos de joins com outras coleções ou aplicar uma regra para cada elemento da coleção.

Não fale com estranhos

Às vezes é difícil saber qual objeto deve assumir a responsabilidade por uma ação. Se começar a olhar para linhas de código com vários pontos (chamada de método), entenda que há responsabilidades fora de seus contextos. Se possui mais de um ponto em qualquer linha de código, a ação está sendo executada no lugar errado. Talvez seu objeto está lidando com outros objetos de uma só vez. Se este é o caso, seu objeto é apenas um intermediário; ele sabe muito sobre muitas pessoas. Considere mover a ação em um dos outros objetos.

Se todos os pontos estão em sequência, o objeto está "cavando" profundamente em outros objetos. Esses vários pontos indicam que está violando o encapsulamento. Pare e tente perguntar a esse objeto se ele pode fazer algo por você. Uma parte do encapsulamento diz respeito a não ir além das fronteiras de classes que não conhecemos.

Law of Demeter é uma ótima referência para começar. Pense desta maneira: Você pode jogar com os seus brinquedos e os brinquedos que alguém lhe der. Você nunca deve brincar com os brinquedos do seu brinquedo.

class Board {
  ...

  class Piece {
    ...

    String representation;
  }

  class Location {
    ...

    Piece current;
  }

  String boardRepresentation() {
    StringBuffer buf = new StringBuffer();

    for (Location l : squares())
    buf.append(l.current.representation.substring(0, 1));
    return buf.toString();
  }
}
class Board {
  ...

  class Piece {
    ...

    private String representation;

    String character() {
      return representation.substring(0, 1);
    }

    void addTo(StringBuffer buf) {
      buf.append(character());
    }
  }

  class Location {
    ...

    private Piece current;

    void addTo(StringBuffer buf) {
      current.addTo(buf);
    }
  }

  String boardRepresentation() {
    StringBuffer buf = new StringBuffer();

    for (Location l : squares())
      l.addTo(buf);

    return buf.toString();
  }
}

A alternativa, apresentada no segundo exemplo, é bastante suscetível a ser reutilizada e as probabilidades de representation.substring(0, 1) fazer parte de outros componentes foi reduzida drasticamente.

Não use abreviações

Temos a tentação de abreviar nomes de classes, métodos ou variáveis. Resista! Abreviações podem se tornar confusas e tendem a esconder problemas maiores. Pense no motivo que o leva a abreviar. Será que é pelo fato de estar digitando a mesma palavra várias vezes? Se este é o caso, talvez seu método está sendo muito usado e pode ser que esteja perdendo uma oportunidade de remover duplicações. Ou talvez, os nomes dos seus métodos estão ficando longos demais? Esse pode ser um sinal de uma responsabilidade fora de seu lugar ou de uma classe que esteja faltando.

Procure manter os nomes de classes e métodos com uma ou duas palavras. Considere uma classe chamada Order. O método não possui necessidade de se chamar shipOrder(). Basta chamá-lo de ship(), pois ele pode ser invocado como order.ship(), uma representação clara do que está sendo feito.

Mantenha classes pequenas

Tenha isto em mente: Sua classe não deve passar de 50 linhas e os pacotes não podem conter mais de 10 arquivos. Classes com mais de 50 linhas costumam ter mais de uma responsabilidade, tornando-as mais difíceis de entender e reutilizar.

O desafio em criar tais classes é que, muitas vezes, há comportamentos que fazem sentido quando estão no mesmo escopo. É aqui que vemos o aproveitamento dos pacotes. Como classes cada vez menores, com menos responsabilidades, e limitando o número de arquivos, eles passam a representar um conjunto de classes relacionadas, trabalhando para atingir um objetivo.

Pacotes, assim como classes, devem ser coesos e ter um propósito. Mantê-los pequenos nos obriga a ter uma identidade real.

Não tenha mais que duas variáveis de instância em sua classe

As classes devem ser responsáveis pelo tratamento de um único atributo, mas há casos em que será exigido até dois atributos. Adicionando um novo atributo em uma classe diminui, imediatamente, a coesão dela. Enquanto estiver programando sob estas regras, descobrirá que existem dois tipos de classes: as que mantêm o estado de um único atributo e as que coordenam duas variáveis ​​distintas. De qualquer forma, não misture os dois tipos de responsabilidades.

O leitor atento notará que as regras Envolva seus tipos primitivos e Envolva suas coleções em classes podem ser consideradas como isomórficas. De forma geral, existem poucos casos em que uma única descrição coesa da função podem ser criadas para uma classe com muitos atributos. Veja um exemplo do tipo da "dissecação" que deve estar pronto para se comprometer:

class Name {
  String first;
  String middle;
  String last;
}

Podemos decompor em outras duas classes, da seguinte forma:

class Name {
  Surname family;
  GivenNames given;
}

class Surname {
  String family;
}

class GivenNames {
  List<String> names;
}

Esta é a experiência dos autores. Criar outras classes através de um conjunto de atributos conduz diretamente para um eficiente "objeto modelo". Antes de compreender a regra, gastamos muitas horas tentando seguir fluxos de dados através de objetos enormes. Conseguimos extrair um "objeto modelo", porém foi trabalhoso entender vários grupos de comportamentos e ver o resultado. Por outro lado, a aplicação constante desta regra nos levou a decompor mais rapidamente objetos complexos em modelos muito mais simples.

O comportamento acompanhará naturalmente os atributos ao contexto apropriado.

Não use getters e setters

A última frase da regra anterior nos traz diretamente para este regra. Se seus objetos estão encapsulando um conjunto de atributos, porém o design continua complicado, é o momento de examinar as violações diretas do encapsulamento. O comportamento não seguirá o atributo se pode simplesmente pedir o valor contido em seu estado.

Há muitos benefícios quando aplicamos encapsulamento. Reduzimos a duplicação de regras de negócio e uma melhor compreensão para alterar e/ou implementar novas funcionalidades. Esta regra é normalmente reconhecida como "Tell, don’t ask".

As regras de Sandi Metz para desenvolvedores

Este post é uma tradução livre em português do artigo Sandi Metz' Rules For Developers. Você pode ler a versão original aqui.

As regras:

  • Classes não devem ter mais que cem linhas de código.
  • Métodos não devem ter mais que cinco linhas de código.
  • Não defina mais que quatro parâmetros na assinatura de um método. Coleções também são parâmetros.
  • Controllers podem instanciar apenas um único objeto. Portanto, as views podem saber apenas sobre uma instância e elas devem enviar mensagens apenas para esses objetos (respeitar a lei de Demeter).

Quando quebrar as regras

Parafraseando Sandi, “Você deve quebrar essas regras somente se tiver um bom motivo ou se seu par permitir.” Seu par ou a pessoa que revisa seu código é a pessoa para quem você deve perguntar. Pense nisso como um regra básica. É imutável.

Cem linhas de código

Apesar do grande número de métodos privados que escrevemos, manter classes pequenas tem sido fácil. Isto nos força a relembra qual é a única responsabilidade da nossa classe e o que devemos extrair dela. Isto se aplica para os testes.

Certa vez, encontramos um arquivo de testes que nos ajudou a perceber que estávamos testando muitas funcionalidades. Dividimos o arquivo em alguns outros mais especializados. Isto também nos fez perceber que git diff não necessariamente nos mostra quando excedemos as cem linhas de código.

Cinco linhas por método

Colocar um limite de cinco linhas por método é a regra mais interessante. Concordamos que if e else são sempre linhas a serem consideradas. Em um bloco condicional, cada condição pode ter apenas uma linha. Por exemplo:

def validate_actor
  if actor_type == 'Group'
    user_must_belong_to_group
  elsif actor_type == 'User'
    user_must_be_the_same_as_actor
  end
end

Cinco linhas de código assegura que nunca devemos usar else com else if.

Tendo apenas uma linha para cada condição, nos incitou a usar bons nomes para métodos privados. Método privado é uma ótima documentação. Eles precisam de nomes claros, que nos forcem a pensar sobre o conteúdo do código que extraímos.

Quatro parâmetros por método

A regra de quatro parâmetros por método foi particularmente desafiante usando Rails, especialmente nas views. Helpers de view como link_to ou form_for podem acabar exigindo vários parâmetros para funcionar corretamente. Enquanto nos esforçamos para não passar muitos parâmetros, voltamos na estaca zero e simplesmente deixamos os parâmetros caso não encontremos uma maneira melhor.

Instancie somente um objeto no controller

Esta regra assustou a maioria antes de começarmos a pôr em prática. Muitas vezes, precisávamos de mais de uma alguma coisa em uma página. Por exemplo, uma página inicial precisa tanto de um feed RSS e um contador de notificação. Resolvemos isto usando o padrão Facade. Ficou assim:

# app/facades/dashboard.rb:

class Dashboard
  def initialize(user)
    @user = user
  end

  def new_status
    @new_status ||= Status.new
  end

  def statuses
    Status.for(user)
  end

  def notifications
    @notifications ||= user.notifications
  end

  private

  attr_reader :user
end
# app/controllers/dashboards_controller.rb:

class DashboardsController < ApplicationController
  before_filter :authorize

  def show
    @dashboard = Dashboard.new(current_user)
  end
end
# app/views/dashboards/show.html.erb:

<%= render 'profile' %>
<%= render 'groups', groups: @dashboard.group %>

<%= render 'statuses/form', status: @dashboard.new_status %>
<%= render 'statuses', statuses: @dashboard.statuses %>

A classe Dashboard forneceu uma interface comum para os objetos de usuários colaboradores e passamos o estado do objeto Dashboard para as views.

Nós não contamos as variáveis de instância no cache do controller. Usamos uma convenção de prefixo para variáveis não utilizadas com um underline a fim de deixar claro o que será usado em uma view:

def calculate
  @_result_of_expensive_calculation ||= SuperCalculator.get_started(thing)
end

Great success!

Concluímos recentemente nossa experiência com sucesso e publicamos os resultados em nossa newsletter de pesquisa. Incorporamos as regras em nosso guia de boas práticas.

Páginas estáticas com Jekyll no Heroku

O Jekyll é um gerenciador de páginas estáticas. Esta fantástica engine oferece uma estrutura para se preocupar somente com os honestos HTML e CSS. Porém, como servir este conteúdo estático usando Jekyll no Heroku?

Existem várias maneiras para isto. Porém, eu separei em cinco pequenas e simples etapas. Veja cada uma delas:

Gemfile

Seu arquivo Gemfile deve conter, pelo menos, as seguintes gems:

source "https://rubygems.org"
ruby "2.1.0"

gem "jekyll"
gem "rack-jekyll"
gem "unicorn"

Estas gems são importantes, pois seus conteúdos serão servidos como uma aplicação Rack e alguns processos do unicorn. Após criar ou modificar seu arquivo, não esqueça de executar o bundle.

Unicorn

Crie o arquivo unicorn.rb com o seguinte conteúdo:

worker_processes 3
timeout 30
preload_app true

Para que entenda melhor o que cada um dos itens representam:

  • worker_processes: Quantidade de processos do unicorn.
  • timeout: Reiniciar os processos do unicorn após 30 segundos sem resposta.
  • preload_app: Evitar a inicialização da aplicação após os processos do unicorn serem estabelecidos.

Procfile

Crie o arquivo Procfile contendo a seguinte instrução:

web: bundle exec unicorn -p $PORT -c ./unicorn.rb

Neste caso, o Heroku será sempre o responsável por ler esta instrução e executá-la no momento do deploy.

Rack

Crie o arquivo config.ru contendo o seguinte conteúdo:

require "bundler/setup"
Bundler.require(:default)

run Rack::Jekyll.new(:destination => "_site")

Este arquivo identifica uma aplicação Rack. Porém, informaremos o path para todo o conteúdo estático gerado. Por padrão, o path para a pasta _site já está definido, mas pode ser alterado.

Conclusão

Nesta etapa final, será necessário modificar o arquivo _config.yml para não considerar todos estes arquivos no processo de inicialização do seu site ou blog, sendo necessário apenas para o Heroku gerar o ambiente da sua aplicação.

exclude:
  - unicorn.rb
  - Procfile
  - Gemfile
  - Gemfile.lock
  - config.ru
  - vendor

Vale lembrar que tudo isto pode rodar em um único e gratuito dyno.

Modificadores de acesso em Ruby

Existem diversos conceitos usados na programação orientada a objetos e os modificadores de acesso são um deles. Basicamente, estamos falando destes: public, protected e private. Para alguns, o uso deles causam certa confusão.

Conceitos

Por padrão, todos os métodos de uma classe são public. Isto quer dizer que os métodos podem ser chamados em qualquer escopo da sua aplicação, diferentemente dos outros dois modificadores.

Quando definimos um método como private, assume-se que este método será acessível apenas no escopo local da classe em que foi definido. Porém, este mesmo método poderá ser usado em uma outra classe por herança. Veja um exemplo:

class Apartment
  private
  def price
    250.50
  end
end

class Kitchen < Apartment
  def total_price
    price + 95.50
  end
end

kitchen = Kitchen.new
kitchen.total_price
#=> 346.0

No entanto, o método Apartment#price não pertence ao self de Kitchen, ou seja, não pertence ao escopo dele. Veja outro exemplo:

class Kitchen < Apartment
  def total_price
    self.price + 95.50
  end
end

kitchen = Kitchen.new
kitchen.total_price
#=> private method `price' called.. (NoMethodError)

Um método private pertence apenas à classe em que foi definido. Este conceito é um pouco diferente do modificador protected. Qualquer método protegido definido em uma classe pertence, além dela mesma, a todas as subclasses da classe em questão. Veja um exemplo:

class Apartment
  protected
  def price
    250.50
  end
end

class Kitchen < Apartment
  def total_price
    self.price + 95.50
  end
end

kitchen = Kitchen.new
kitchen.total_price
#=> 346.0

Além disto, um método protected pode ser chamado, através de uma instância, dentro da própria classe ou de suas subclasses. Veja outro exemplo:

class Person
  def initialize(age)
    @age = age
  end

  def older?(another_person)
    @age > another_person.age
  end

  protected
  def age
    @age
  end
end

john = Person.new(20)
smith = Person.new(22)

john.older?(smith)
#=> false