Alterando mensagens de commits anteriores do Git com os comandos amend e rebase

O versionamento distribuído e o estimulo a criação de branches locais independentes são propriedades que tornam o sistema Git uma das ferramentas mais promissoras para a realização de controle e versionamento de linhas de desenvolvimento. Assim, a partir do uso dessa ferramenta, a criação, o merge e a remoção de linhas de código acabam se tornando uma atividade mais rápida e menos onerosa do que seria com outras ferramentas com características opostas pois lançamos mão de recursos como commits locais. Neste artigo, quero introduzir dois comandos do Git que tornam possível a edição de uma mensagem de commit anterior.

Para começarmos, repetiremos alguns dos passos de criação do repositório criado durante a explicação do guia prático dos comandos básicos do Git para o dia-a-dia do desenvolvedor da seguinte maneira:

mkdir novorepositorio
cd novorepositorio

git init
touch exemplo.txt
git add exemplo.txt
git commit -m "preparacao do repositorio"
git checkout -b novobranch

echo -n "Minha primeira linha de codigo" >> exemplo.txt
git add exemplo.txt
git commit -m "Mensagem 1"

echo -e "nMinha segunda linha de codigo" >> exemplo.txt
git add exemplo.txt
git commit -m "Mensagem 2"

git checkout master
git merge novobranch

Após a execução dos passos anteriores estaremos de posse de um novo repositório e com dois commits realizados com as seguintes mensagens: “Mensagem 1”“Mensagem 2”. Apenas para nos certificamos que tudo ocorreu conforme esperávamos vamos verificar o log de commits com o comando:


git log

E, se tudo deu certo, será impresso na tela algo como a seguinte mensagem indicando os dois commits já realizados.

commit 1a9515e10144238e043711a25e9a804c4cd473b8
Author: Seu nome <seu@email.com>
Date: Tue Aug 7 22:10:16 2012 -0300

Mensagem 2

commit 62f3a3b59f1fbed669f08b265d0a18698b7f015d
Author: Seu nome <seu@email.com>
Date: Tue Aug 7 22:07:30 2012 -0300

Mensagem 1

Sabemos que a descrição de um commit realizado é de extrema importância para o controle de versão de um sistema. Não por outro motivo, ela precisa ser altamente descritiva de forma a nos remeter rapidamente às alterações realizadas em uma determinada entrega. Sendo assim melhoraremos a descrição da última alteração feita mudando a “Mensagem 2” para “Inclusão da segunda linha de código para o exemplo de uso do ammend” usando o comando:

git commit --ammend

Ao executar esse comando somos levados ao editor de texto no qual alteramos a mensagem antiga e gravamos a mensagem alterada. Portanto,  ao concluir os comandos anteriores e executar o commando git log novamente veremos agora a mensagem já alterada para o último commit:

commit 62f86c87e57991d5b815f10d13239d82270361c0
Author: Seu nome <seu@email.com>
Date: Tue Aug 7 22:10:16 2012 -0300

Inclusão da segunda linha de código para o exemplo do uso do comando ammend

commit 62f3a3b59f1fbed669f08b265d0a18698b7f015d
Author: Seu nome <seu@email.com>
Date: Tue Aug 7 22:07:30 2012 -0300

Mensagem 1

Já se quiseremos alterar a descrição da mensagem do commit anterior ou alterar várias mensagens de uma só vez usamos o rebase interativo indicando uma profundidade da seguinte maneira:

git rebase -i HEAD~2

O modificador HEAD~2 usado no comando anterior indica que estamos dispostos a mudar os 2 últimos commits realizados e, portanto, se houver a necessidade de atingir mais do que isso basta mudar o número para um outro que atenda a profundidade desejada. Ao executar esse comando somos levados novamente ao editor de texto que dessa vez, para as mensagens que quisermos alterar, mudaremos onde estiver escrito “pick” para “edit” ou seja, teremos algo como o editor a seguir:

pick 814d75c Mensagem 1
pick 7b1e040 Inclusão da segunda linha de código para o exemplo do uso do comando ammend

# Rebase 114c261..7b1e040 onto 114c261
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

E mudaremos os commits que nos interessam assim:

edit 814d75c Mensagem 1
pick 7b1e040 Inclusão da segunda linha de código para o exemplo do uso do comando ammend

# Rebase 114c261..7b1e040 onto 114c261
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Agora todas as linhas que mudaram para edit serão iterativamente alteradas por você! E basta agora usar o ammend para cada uma delas conforme o console irá sugerir:

Stopped at 814d75c... Mensagem 1
You can amend the commit now, with

git commit --amend

Once you are satisfied with your changes, run

git rebase --continue

Ou seja, basta agora seguir os passos do ammend para os próximos commits e, se tudo der certo, você receberá uma informação como:

[detached HEAD 174412e] Alteração da segunda mensagem
 1 file changed, 1 insertion(+)

Nesse ponto deve-se concluir o processo de rebase usando:

git rebase --continue

E a confirmação do  mensagem de sucesso será:

Successfully rebased and updated refs/heads/master.

Por fim, se realizarmos novamente o comando git log veremos que as descrições foram alteradas conforme o esperado !

 

Artigo relacionados

Guia prático dos comandos básicos do Git para o dia-a-dia do desenvolvedor

GIT: Configurando Meld como ferramenta de Diff externa no Linux

Links sugeridos

http://git-scm.com/book
http://schacon.github.com/history.html

Anúncios

Escolhendo entre injeção de dependências por construtores e injeção de dependências por setters

Durante o post anterior tivemos a oportunidade de entender o porque de a inversão de controle (IoC) vir a ser considerada uma boa prática e pudemos esclarecer a diferença conceitual entre inversão de controle e injeção de dependência. Pudemos também entender que, em geral, para se alcançar a IoC fazemos o uso de algum framework que facilite uma injeção de dependências tal quais o Spring, o Guice e o PicoContainer. Neste artigo tenho a intenção de discutir características, vantagens e desvantagens das duas formas mais conhecidas de injeção de dependências: a injeção por construtor e a injeção por setters.

Dentre as duas técnicas mais amplamente conhecidas para a injeção de dependências temos que a mais amplamente utilizada, segundo os dados da SpringSource, é a que faz o uso de setters e a justificativa é histórica. Basicamente, acontece que o Spring (hoje o framework de IoC mais utilizado no mercado) nasceu focando principalmente essa característica pois na época em que as primeiras versões desse framework foram lançadas, em meados de junho de 2003, havia o consenso de que o uso de setters estimularia uma ferramenta mais facilmente configurável e resultaria em melhor entendimento da ferramenta pois proveria configurações com valores padrões para os colaboradores das classes do sistema. O sucesso então alcançado por eles foi o que influenciou tantos outros a copiar e disseminar essa idéia para vários outros sistemas.

Já em um pensamento oposto ao do Spring, surgiram de forma pioneira outros frameworks que lidavam fortemente com injeção de dependências através do uso de métodos construtores como é o caso hoje do Picocontainer e o Guice (leia-se Juice). Para estas, focar em injeção por construtores privilegia o desenvolvimento de código de aplicação e não no próprio framework.

Qual alternativa é a melhor? Depende. Cada situação é particular. Vejamos um exemplo de injeção de dependências através do uso de setters:

public class MinhaClasse {

  private MeuRecurso meuRecurso;

  public MinhaClasse() { }

  public MeuRecurso getMeuRecurso(){
    return this.meuRecurso;
  }

  public setMeuRecurso(MeuRecurso meuRecurso){
    this.meuRecurso = meuRecurso;
  }

  public seuMetodoQueUsaORecurso(){
    meuRecurso.fazAlgumaCoisa();
  }
}

A partir do exemplo anterior percebemos que a MinhaClasse faz o uso de um método construtor vazio, sem argumentos, porém define o meuRecurso a partir de um método set tornando possível instanciar a classe sem a necessidade de definir qualquer propriedade padrão. Essa técnica ainda viabiliza mudarmos o meuRecurso a medida em que precisarmos mas, por outro lado, também permite que essa propriedade nunca seja definida. Essa propriedade vem da ideia de que uma classe deva ser configurável e possa ser instanciada a partir de valores defaults previamente definidos.

De forma oposta, com o uso de injeção por construtores, temos em geral algo como o seguinte exemplo:

public class MinhaClasse {

  private final MeuRecurso meuRecurso;

  public MinhaClasse(final MeuRecurso meuRecurso) {
    this.meuRecurso = meuRecurso;
  }

  public seuMetodoQueUsaORecurso(){
    meuRecurso.fazAlgumaCoisa();
  }
}

A injeção de dependências por método construtor tem como característica considerar que os colaboradores de uma classe são obrigatórios e estáticos (não variam). Em outras palavras, entendem que uma classe não deve ser instanciada se não possuir seus colaboradores previamente definidos. É também característica dessa técnica tornar a classe mais objetiva, com menos linhas de código de verificação, fazendo o uso do modificador final. Assim, a classe MinhaClasse só é instanciada quando contiver um meuRecurso definido.

Portanto, entre as duas alternativas, geralmente a injeção por construtores parece mais elegante para código de aplicação. Afinal, além de tornar o código fonte mais objetivo evitando o uso abusivo de getters e setters pouco usados, só permite a instanciação de classes que tenham todos os colaboradores obrigatórios satisfeitos evitando assim muitos dos erros de NullPointer causados por colaboradores não previamente setados. Essa técnica também garante que os colaboradores não serão variados durante o ciclo de vida da aplicação, o que é uma característica recorrente por exemplo de um DAO já que são frequentemente injetados em classes, mas raramente são variados durante o ciclo de vida da aplicação. Outra vantagem do uso de injeção de dependências por construtores é que ao combinar variáveis com o modificador final, imediatamente nos beneficiamos de maior segurança face a um ambiente multithread.

Ainda no que diz respeito as diferenças entre as duas abordagens, devemos levar em consideração que ao usarmos injeção por construtores acabamos evitando a ocorrência de dependências circulares, pois é garantida uma hierarquia bem definida, em contraposição àquela que faz o uso de setters que não deixam isso bem claro. Além disso, quando o último objeto for instanciado e estamos usando a injeção por construtores, significa que a aplicação está completa. Por outro lado, é verdade que com essa técnica uma classe pode vir a ter inúmeros colaboradores e, portanto, muitos argumentos, mas também é verdade que, ao chegar nesse ponto, temos fortes indícios que ela tem muitas responsabilidades e portanto precisa ser refatorada seguindo o principio da responsabilidade única.

Então é sempre melhor usar injeção de dependências através de construtores? Existem casos em que isso não é verdade e está aí o Spring para comprovar. Frameworks, classes altamente configuráveis e classes que fazem muito uso de herança tendem a recorrer à injeção por setters de forma a torná-las mais configuráveis. Sendo assim, podemos partir do pressuposto de que devemos sempre recorrer à injeção de dependências através de métodos construtores quando temos colaboradores que são indispensáveis para a criação de uma determinada classe e usar getters e setters para aquelas propriedades variáveis configuráveis. Em outras palavras, devemos fazer uso do método construtor para garantir que todas as dependências obrigatórias serão satisfeitas sem que nos tornemos reféns de um mecanismo dedicado (o famoso if objeto != null) para garantir que as propriedades foram realmente definidas.

Posts relacionados

Boas práticas de programação com a aplicação do princípio da responsabilidade única
Desmistificando o conceito de inversão de controle e sua relação com a injeção de dependência

Artigos relacionados

Arendsen, Alef – Setter injection versus constructor injection and the use of @Required
Cuesta, Alex – Constructor Injection vs Setter Injection
Hevery, Misko – Constructor Injection vs. Setter Injection

Desmistificando o conceito de inversão de controle e sua relação com a injeção de dependência

O acoplamento fraco entre unidades distintas de um sistema já faz parte do bom senso de um bom desenvolvedor. Sabemos que é muito mais fácil lidar com o conhecimento pontual de uma correção do que a necessidade de conhecimento de uma vasta hierarquia de relacionamento entre unidades e, uma das técnicas que promove o tão procurado sistema flexível de baixo acoplamento é a inversão de controle.

A inversão de controle é a técnica que inverte o paradigma de uma programação tradicional. Com a aplicação dessa técnica, o que antes era feito de forma direta é agora delegado para uma infraestrutura dedicada que muitas vezes é também conhecida como container. Em geral, e de forma menos abstrata, o que temos é que a instanciação das dependências de uma determinada classe se dará fora dela. Vejamos um exemplo prático de como isso deve acontecer:

Imagine a sintonização de um canal através de uma antena comum conforme o exemplo abaixo:

public class Televisao {

  public void sintonizar canal(final int canal){
    Sintonizador sintonizador = new SintonizadorPorAntena();
    sintonizador.sintonizar(canal) ;
  }

}

Se a primeira vista não foi possível observar qualquer problema nesse design então suponha que agora você queira que a sintonização da sua TV seja realizada por um aparelho a cabo e não mais por antena. Acontece que, pelo exemplo anterior, será necessário realizar uma manutenção na sua TV que a principio não tinha nenhum problema, ou seja, uma unidade externa absolutamente independente do seu aparelho exigiu uma manutenção nele. Sendo um um pouco mais claro, tal mudança exigiria algo como a seguinte manutenção:

public class Televisao {

  public void sintonizar canal(final int canal){
    Sintonizador sintonizador = new SintonizadorPorCabo();
    sintonizador.sintonizar(canal) ;
  }

}

Em outras palavras, toda vez que houver a necessidade de se alterar o sintonizador de uma televisão, teremos que realizar uma manutenção na classe televisão o que deixa evidente um alto grau de acoplamento entre unidades. Um pouco mais a fundo, outro desvio dessa classe é que ela fere um principio SOLID pois não respeita o “principio aberto fechado” (open close principle) pois a medida que um sintonizador novo surge somos obrigados a refazer a classe televisão pois a lógica que trata o sintonizador está chumbada nela.

Entidades de software (classes, módulos, funções, etc) devem ser abertas para a extensão, mas fechadas para modificação – Meyer, Bertrand (1988)

Para resolver esse problema realizaremos uma inversão de controle que prontamente removerá a lógica de instanciação do sintonizador na classe televisão. Para isso podemos usar duas abordagens: obter a instância de sintonizador a partir do uso de um ServiceLocator ou então, realizando uma injeção de dependência. Optaremos neste artigo por uma injeção de dependência do tipo “constructor injection” o que significa que injetaremos a dependência chamando um método construtor.

public class Televisao {

  public final Sintonizador sintonizador;

public class Televisao(final Sintonizador sintonizador){
    this.sintonizador = sintonizador;
  }

public void sintonizar canal(final int canal){
    sintonizador.sintonizar(canal) ;
  }

}

Muito melhor agora! Já não há mais a necessidade de manutenção da classe Televisão pelo simples motivo de se querer atender um novo sintonizador. Afinal, toda vez que surgir um novo sintonizador e houver a necessidade de variá-lo bastará instanciar a televisão com o novo sintonizador, o que ficará a cargo do seu framework de IoC. Outro grande ganho, além da diminuição da carga de manutenção sobre a classe Televisão, se deve ao fato de que uma classe menos complexa possui menor combinação de cenários que deve atender e, justamente por essa característica, tende a ser menos propícia a falhas. Indo um pouco mais além, no que diz respeito aos testes unitários, um objeto que faz o uso de inversão de controle é amplamente mais testável do que um que não o faz! É muito mais fácil simular comportamento da colaboração entre classes a partir do uso de mocks do que sobre objetos concretos.

Por fim, basta agora encontrar um framework que suporta IoC e que melhor se adapte às necessidades do seu sistema como por exemplo o Spring que é amplamente usado no mercado e se dá muito bem com injeção por setters, o Picocontainer que trabalha bem com injeção por construtores assim como o Guice (“juice“) do Google e o VRaptor.

Artigos relacionados

Boas práticas de programação com a aplicação do princípio da responsabilidade única

Referências

OODesign – Open Close Principle

Separando testes unitários de testes integrados usando diretórios distintos com Maven

A qualidade do software está intimamente relacionada à qualidade dos testes empregados sobre ele durante suas mais diversas fases de evolução. Dessa maneira, quanto mais cedo os testes passam a ser elaborados e aplicados, menores são as chances de um sistema gerar “desvios” inesperados.  Isso torna indispensável o fracionamento do tempo de programadores para a elaboração de testes e é por esse motivo que para Uncle Bob:

Desenvolvedor que não testa é como um cirurgião que não lava as mãos.

Durante o processo de desenvolvimento de sistemas, os programadores são essenciais na produção e automatização de duas fases iniciais do processo de testes: os testes unitários e os testes integrados. Afinal, são eles que desenvolvem as entradas e saídas das menores unidade testáveis do programa: as classes e métodos. Além disso, são também eles que entendem como essas unidades se inter-relacionam de maneira a gerar uma determinada funcionalidade. Deve ser, portanto, uma atribuição deles uma correta e eficiente organização dos testes de forma a torná-los facilmente executáveis e diferenciáveis.

Quando se trata de Java, o maven é hoje o framework amplamente difundido, podendo ser considerado um conhecimento básico necessário para o dia-a-dia da programação em Java. Uma das grandes vantagens dele é o gerenciamento simples de dependências (bibliotecas) e a padronização da estrutura do projeto perante o uso de pastas bem definidas, diferenciando código de produção, código de teste, arquivos de recursos como scripts, arquivos de propriedades entre outros conforme a estrutura representada na imagem a seguir:

maven folder structure organization

Enquanto os sistemas muito pequenos ou em estágio embrionário se encaixam muito bem nessa estrutura, sistemas maiores (ou a medida em que os micro-sistemas evoluem) demandam uma separação entre testes rápidos (testes unitários) e testes lentos (testes integrados) e por isso essa preocupação deveria ser pensada desde cedo. Nesse sentido, várias formas distintas foram descritas para permitir essa organização dentro do maven e em geral são alternativas que envolvem a criação de perfis distintos, pasta distintas, configuração de fases no maven ou aplicação de anotações e isso tudo porque o framework é um tanto limitado pois foi planejado para manter testes em uma única pasta: src/test/java.

Neste post quero mostrar uma alternativa que permitirá organizar testes unitários e de integração com o maven em diretórios diferenciados, sem tornar obrigatória a padronização de nomenclatura porém usando anotações conforme a proposta de estrutura a seguir:

folder structure propose for separate testing cycles

Para isso usaremos o recurso de categorização de testes das versões mais recentes do JUnit começando com a criação de uma interface que definirá uma categoria de testes especificas.

public interface TesteDeIntegracao {
}

Com isso, todas as classes que quisermos que se tornem um teste de integração deverão ser anotadas conforme o seguinte exemplo:

@Category(TesteDeIntegracao.class)
public class MeuTeste {
  
  @Test
  public void testName() throws Exception {
    assertTrue(true);
  }

}

Agora precisamos trabalhar a organização dos aquivos e de posse do arquivo pom.xml incluiremos algumas propriedades que usaremos adiante:

<!-- Propriedades de compatibilidade do sistema -->
<project.sourceEncoding>UTF-8</project.sourceEncoding>
<java.version>1.6</java.version>

<!-- Plugins auxiliares -->
<build.helper>1.7</build.helper>
<compiler.plugin>2.5.1</compiler.plugin>
<surefire.plugin>2.12</surefire.plugin>
<failsafe.plugin>2.12</failsafe.plugin>

<!-- Configurações especificas para testes -->
<integration.dir>${basedir}/src/integration-test/java</integration.dir>
<junit.version>4.10</junit.version>

A primeira configuração que devemos fazer é adicionar o diretório ${integration.dir} de forma automática ao nosso build_path e para isso, usaremos o build-helper-maven-plugin que configuraremos da seguinte maneira:

<!-- Adição de novas pastas ao build_path da aplicação -->
<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <version>${build.helper}</version>
  <executions>
    <execution>
      <id>default</id>
      <phase>process-test-sources</phase>
      <goals>
        <goal>add-test-source</goal>
      </goals>
      <configuration>
        <sources>
          <source>${integration.dir}</source>
        </sources>
      </configuration>
    </execution>
  </executions>
</plugin>

Em sequência usamos o plugin maven-surefire-plugin. Esse plugin será responsável por executar os testes unitários durante o fase “test” do maven. Nele definimos que tudo o que estiver dentro das pastas “src/test/java” ou “src/integration-test” deve ser testado. Deve ser configurado nessa sessão a exclusão de classes de teste de integração que estão anotadas com a marcação @Configuration(TesteDeIntegracao.class)

<!-- Configuração de testes unitários -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>${surefire.plugin}</version>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>${surefire.plugin}</version>
    </dependency>
  </dependencies>
  <configuration>
    <includes>
      <include>**/*.class</include>
    </includes>
    <excludedGroups>pacoteDaClasse...TesteDeIntegracao</excludedGroups>
  </configuration>
</plugin>

Agora configuraremos o plugin maven-failsafe-plugin para gerenciar a fase integration-test do maven. Esse plugin garante que todos os testes aconteçam mesmo caso um deles falhe o que é um comportamento importante para os testes integrados. Ele também será responsável por filtrar os testes e executar apenas os categorizados com a interface TesteDeIntegracao.

<!-- Configuração de testes integrados -->
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>${failsafe.plugin}</version>
  <dependencies>
    <dependency>
    <groupId>org.apache.maven.surefire</groupId>
      <artifactId>surefire-junit47</artifactId>
      <version>${failsafe.plugin}</version>
    </dependency>
  </dependencies>
  <configuration>
    <includes>
      <includes>**/*.class</includes>
    </includes>
    <groups>pacoteDaClasse...TesteDeIntegracao</groups>
  </configuration>
</plugin>

Com isso, para executar somente os testes unitários, usaremos o comando:

mvn clean test

E se quisermos somente os testes integrados, usaremos o comando:

mvn clean test-compile failsafe:integration-test

Por fim, para testar tanto testes unitários quanto testes de integração, usamos o comando:

mvn clean verify

Concluíndo, o maven ainda não permite uma fácil diferenciação entre as várias fases de teste mas já há rumores que essa funcionalidade será incluída numa versão futura da ferramenta. Enquanto isso, o exemplo mostrado neste artigo pode ser usado e ampliado para outras fases como testes funcionais por exemplo.

Para uma melhor observação, essa abordagem está disponível para visualização e download no github em:

https://github.com/fpierin/maven-testes-integrados.git

Boas práticas de programação com a aplicação do princípio da responsabilidade única

No que diz respeito ao desenvolvimento de software, vivemos em um universo no qual transformações em sistemas são cada vez mais frequentes. Cada nova funcionalidade, até mesmo cada simples variação dela que se adapta melhor ao usuário final gera valor e, se gera valor, produz dinheiro. Assim, cada sinergia de sucesso eleva o grau de diferenciação perante concorrentes e garante melhor vantagem competitiva. Nesse contexto, a velocidade com que um produto consegue se adaptar a uma determinada necessidade é um fator crucial de sucesso. Quanto mais rápido uma necessidade é desenvolvida, melhor é para todos os envolvidos. Assim, um dos fatores que devem ser considerados para um sistema amplamente adaptável é a sua capacidade de manutenibilidade.

A capacidade de manutenibilidade é a característica que mede a facilidade com que um sistema pode ser modificado, isto é, o quão fácil se dá o desenvolvimento de melhorias, a criação de extensões ou, quando necessário, correções para ele. É uma qualidade que pode ser alcançada e que, em geral, depende da devida aplicação de abstrações como padrões de projeto e princípios de orientação a objetos bem conhecidos tais como o KISS (Keep it simple, Stupid!), DRY ( Don’t Repeat Yourself ) e, o objeto de estudo deste artigo, o princípio SOLID.

O princípio SOLID diz respeito ao acrônimo mnemônico introduzido por Robert C. Martin em meados de 2000 para definir cinco princípios que auxiliam a garantir melhor qualidade de software com melhor capacidade de manutenção. São princípios que, quando aplicados corretamente, guiam os desenvolvedores a tornar o código fonte mais extensível, legível e flexível. Das cinco regras: Single responsibility, Open-closed, Liskov substitution, Interface segregation e Dependency inversion, abordaremos neste artigo apenas o primeira delas.

Mas o que vem a ser o princípio da responsabilidade única? O Single responsibility principle (SRP) é o postulado que define que uma classe deve conhecer apenas a sua responsabilidade e ter apenas uma razão para ser modificada. Dessa maneira, é responsável por aumentar o grau coesão entre objetos tornando mais fácil a leitura e reuso de código ao passo em que mantém a complexidade em um nível gerenciável. Para um melhor entendimento, vejamos um exemplo prático de como aplicar essa regra:

Imagine que você esteja desenvolvendo um sistema para ser utilizado em uma portaria. Uma interface perfeitamente razoável que poderia representar alguma das necessidades a serem desenvolvidas seria a seguinte:

public interface Portaria {

	void cadastraVisitante();

	void autorizaEntrada();	

	void recebeCorrespondencia();	

}

Essa interface por sua vez poderia ser implementada da seguinte maneira:

public class PortariaImpl implements Portaria {

	public void cadastraVisitante() {
		// Implementação do cadastro de visitante.
	}

	public void autorizaEntrada() {
		// Implementação da rotina de autorização de entrada.
	}

	public void recebeCorrespondencia() {
		// Implementação da rotina de recepção de correspondência
	}

}

Nesse cenário, podemos observar que a classe que representa a portaria possui um grande número de responsabilidades pois nela são definidos como cadastrar um visitante e como autorizar a entrada de uma pessoa e como receber uma correspondência. O grande número de responsabilidades acarreta que, qualquer modificação pontual de uma determinada função envolve a manutenção de toda uma classe que, no melhor dos casos, terá a legibilidade comprometida dado o crescimento exponencial que ela pode tomar. Já no pior dos casos, pode vir a gerar problemas em outras funcionalides em virtude de sua baixa coesão.

Então como poderíamos melhor o grau de coesão dessa classe? Basta que criemos novos objetos que as encapsulam. Veja como:

public class GerenciadorDeCadastro {
	
	void cadastra(){
		// Aqui vai a implementação do cadastro de visitante.
	}

}
public class AutorizadorDeEntrada {

	public boolean autoriza(){
		// Aqui vai a implementação da rotina de autorização de entrada.
	}
	
}
public class ReceptorDeCorrespondencia {
	
	void recebe(){
		// Aqui a implementação da rotina de recepção de correspondência
	}

}

Em conclusão, cada responsabilidade é um ponto único de alteração o que torna mais fácil executar uma manutenção posterior além de nos guiar a um código mais interpretável. Por outro lado é importante ter em mente que, apesar de raros, existem casos que não demandam essa complexidade. Mesmo assim aplique esse princípio sempre que puder.

Referências

Código Limpo: Uma primeira abordagem por Gilmar Borba
Single Responsibility Principle por oodesign.com
SOLID – Princípio da Responsabilidade Única de Alexandre Gama
Taking the Single Responsibility Principle Seriously por Ralf Westphal

Bibliografia

MARTIN, Robert. Design Principles and Design Patterns. Disponível em: http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf
MARTIN, Robert. Código Limpo: habilidades práticas do Agile Software. Rio de Janeiro, RJ : Alta Books, 2011.
SHORE, James; WARDEN, Shane. A Arte do Desenvolvimento Ágil. Rio de Janeiro, RJ : Alta Books, 2008.

Artigos relacionados

Entendendo os padrões de projeto a partir da análise de frameworks populares de mercado: Padrões comportamentais

Behavioral design patterns

Dando prosseguimento ao estudo sobre a aplicação de padrões de projeto em ferramentas de mercado, falamos anteriormente sobre alguns casos de uso que tratam a instanciação de novos objetos a partir da aplicação de padrões de projeto criacionais como é o caso do StringBuilder, falamos também sobre alguns cenários nos quais abstrações de padrões de projeto estruturais determinaram estruturas como o FilterInputStream. Neste artigo, tenho a intenção de expor aplicações de padrões de projeto comportamentais.

Como é de praxe, começaremos com uma breve introdução ao que são os padrões de projeto comportamentais. Em resumo, as abstrações enumeradas por Gamma et al. e que possuem como principal caracteristica a preocupação com a forma pela qual objetos satisfazem a comunicação entre dois ou mais são classificadas como padrões comportamentais. Em geral classes que pertencem a esse grupo tendem a descrever padrões comuns de comunicação entre entidades enaltecendo maneiras flexíveis de carregar essa propriedade e por isso nos auxiliam, mais uma vez, a desenvolver sistemas de baixo acoplamento. Veremos agora aplicações dos onze modelos mais conhecidos desse grupo: Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method e Visitor.

  1. Chain of responsibility é um padrão que permite mais de um objeto tratar uma solicitação a partir de um encadeamento. A partir do uso dele, é menor o acoplamento entre o remetente e o destinatário de uma ação. Um exemplo do uso desse padrão seria o encadeamento de filtros no Java (javax.servlet.Fiter.doFilter()). Outro exemplo é o tratamento de exceções, no qual uma exceção recursivamente escala a pilha de métodos até encontrar um capaz de tratá-la.

  2. Command (também conhecido como Action ou Transaction) pode ser traduzido como uma forma de encapsular uma ação como um objeto. Com isso ele torna simples a execução encadeada de comandos, a forma com que diferentes ações dentro de um sistema são chamadas, além de dar suporte a operações que possam ser desfeitas. Alguns exemplos encontrados em Java seriam as implementações de javax.swing.Action.

  3. Interpreter é um modelo que auxilia a interpretação de sentenças de uma linguagem partindo de uma representação gramatical e, justamente por essa razão, ele pode ser encontrado na implementação em alguns compiladores. Um exemplo de uso desse padrão em Java seria a classe java.util.Pattern.

  4. Iterator é um padrão muito utilizado em loops como, por exemplo, o for-each, proveniente do Java. Trata-se de uma maneira para se acessar sequencialmente os elementos de um conjunto de objetos sem a necessidade de conhecimento dos elementos subsequentes. Todas as implementações de java.util.Iterator fazem uso dele.

  5. Mediator atua como uma solução que encapsula a forma de comunicação entre um conjunto de objetos. Ele é interessante porque evita que um objeto se refira explicitamente a outro, e isso permite que as interações entre eles sejam variadas independentemente. Por exemplo, a interface PanelMediator, do pacote java.awt.event.ActionListener, é útil para implementar uma janela com vários componentes que interagem entre si. Ela centraliza a comunicação no Mediador que é responsável por despachá-las aos componentes certos e desse modo faz com que não precisem se comunicar diretamente.

  6. Memento é um padrão comportamental que modela uma maneira de se capturar o estado interno de um objeto, permitindo a possibilidade de restaurá-lo posteriormente. Nesse sentido, uma classe bem conhecida é a java.util.Date, que faz uso de um tipo long interno para guardar o valor da data, mas que é acessado por métodos que entendem Date.

  7. Observer resolve a dificuldade que existe quando um objeto muda de estado e necessita notificar vários outros. Nesse padrão, quando o objeto pai é modificado, todos os seus dependentes são automaticamente notificados e atualizados. Geralmente estes padrões são encontrados como implementações de Java.util.EventListener.

  8. State é um padrão que permite um objeto alterar seu comportamento quando o seu estado interno mudar. Outra forma de entendê-lo seria imaginando que no momento da mudança do estado do objeto, é como se ele transformasse numa instância de uma classe completamente diferente. Por exemplo, o comportamento de javax.faces.Lyfecycle que é dependente do estado do ciclo de vida do JSF.

  9. Strategy é outro padrão bastante conhecido que torna possível a mudança comportamental de um objeto a partir do encapsulamento de algoritmos. Um exemplo de aplicação seria na ordenação de listas, onde uma comparação, como o compare(), do pacote Java.util.Comparator, decide qual é elemento de maior valor dentro de subconjuntos numéricos, alfabéticos ou outros.

  10. Template Method está relacionado com a possibilidade de se criar o esqueleto de um algoritmo realizando uma operação de forma a postergar alguns passos para as subclasses que o implementa. Em outras palavras, são as subclasses dele que redefinem alguns passos da operação, sem a necessidade de mudança na estrutura. Os métodos não abstratos de java.util.AbstractList usam esse padrão. Eles são rotinas pré-produzidas que fazem o uso de métodos abstratos implementados nas subclasses delas.

  11. Visitor é um padrão que representa uma operação que é executada sobre os elementos de uma estrutura de um objeto, ou seja, sobre objetos que constituem o objeto original. Com aaplicação dele é possível acessar cada parte do objeto pai, sem modificar as classes dos elementos filho. Um exemplo seria o ElementVisitor do pacote javax.lang.model.element que opera em conjunto com o objeto Element do mesmo pacote.

E assim terminamos nossa análise sobre aplicações de padrões de projeto em frameworks de mercado. Espero que tenham gostado e até a próxima…

Entendendo os padrões de projeto a partir da análise de frameworks populares de mercado: Padrões estruturais

Estructural Gof Design Patterns

Enquanto no artigo anterior observamos algumas aplicações populares de padrões de projeto criacionais e como que eles definem melhores maneiras de se instanciar objetos agregando a inteligência de como dependências e propriedades se interrelacionam de forma a originar novos objetos,  nesta sequencia demonstrarei algumas aplicações de padrões de projeto estruturais.

Mas o que são e do que tratam padrões de projeto estruturais? Em resumo, são abstrações que exemplificam mecanismos inteligentes de organização de entidades de um determinado sistema de maneira a descrever como que os objetos e classes poderiam ser combinados para formar estruturas maiores.  São modelos que, em geral, lidam com a responsabilidade de definir as relações entre classes e objetos com o propósito de obter uma determinada nova funcionalidade e que são organizados em sete principais grupos: adapter pattern, bridge pattern, composite pattern, decorator pattern, façade pattern, flyweight pattern e, por fim, proxy pattern dos quais veremos algumas aplicações reais em seguida:

  1. Adapter diz respeito à abstração que resolve o problema de incompatibilidade entre interfaces distintas, tornando possível que elas trabalhem de forma colaborativa. Uma maneira bastante simples de se pensar sobre esse padrão é imaginar os adaptadores de tomada que permitem que diferentes padrões se comuniquem de forma a distribuir energia independentemente da interface da tomada. Por exemplo, o java.io.InpuStreamReader, que recebe uma cadeia de bytes (InputStream) e as adapta para caracteres (Reader).

  2. Bridge é um padrão intimamente relacionado ao anterior. A diferença entre ambos é que no segundo a conexão se dá por meio de abstrações e implementações fixas, o que não ocorre no primeiro. O objetivo principal do Bridge não é somente permitir que interfaces diferentes se comuniquem, mas principalmente trata de abstrair um modelo pelo qual diferentes abstrações e implementações possam variar independentemente umas das outras. Esse é o motivo pelo qual frequentemente está relacionado ao uso de drivers. Por exemplo, conexões com o banco de dados que fazem uso de drivers JDBC devem funcionar para diferentes sistemas operacionais ao mesmo tempo em que operam em diferentes bancos de dados.

  3. Composite é um padrão que compõe objetos em uma estrutura de árvore para representar uma hierarquia do tipo parte de um todo. A partir desse padrão se torna simples a tarefa de lidar com objetos únicos ou com composição de objetos, pois ambos serão trados de maneira uniforme, ignorando suas diferenças. Um exemplo simples seria o do java.util.List com o método addAll(Collection).

  4. Decorator é um padrão que permite estender uma funcionalidade usando composição em detrimento de herança, ou seja, ele fornece uma alternativa flexível a subclasses para extensão de funcionalidades. Geralmente são constituídos de uma classe que implementa uma interface, mas que ao mesmo tempo a recebe como instância em seus métodos construtores. Um exemplo de aplicação real desse modelo é a classe FilterInputStream que recebe um objeto InputStream em seu método construtor e o “decora”.

  5. Façade (ou fachada) é um padrão que centraliza em uma única interface um conjunto de interfaces. Ele abstrai em um modelo de alto nível a necessidade de interação entre várias representações internas. Em geral é reconhecido por um método comportamental que quando invocado interage com diferentes instâncias, como o javax.faces.context.FacesContext, que dentro dele se comunica com LifeCycle, ViewHandler e NavigationHandler entre outros tipos pelos quais o usuário final não precisa se preocupar.

  6. Flyweight é uma opção para se trabalhar, de forma eficiente, com grandes quantidades de objetos de granularidade fina a partir do uso de compartilhamento de dados entre eles. O método Java.lang.Integer.valueOf(int) é um exemplo de aplicação desse padrão que pode melhorar bastante a performance da requisição de objetos Integer para um int em situações nas quais esses valores são requisitados frequentemente, pois faz uso de cache interno.

  7. Proxy é um padrão desenhado para postergar todo o custo de processamento da criação e inicialização de um objeto até que ele realmente seja preciso, tornando assim o sistema mais eficiente. Para isso, ele fornece um objeto substituto ao verdadeiro que possui uma interface idêntica a original e que gerencia a chamada ao verdadeiro. Essa é a intenção da classe java.lang.reflect.Proxy

É isso! Esses foram alguns exemplos de aplicações de padrões de projeto estruturais em sistemas reais e bastante usados e, na sequência, lidaremos com os padrões de projeto comportamentais!

Artigos relacionados:

Referências


Entendendo os padrões de projeto a partir da análise de frameworks populares de mercado: Padrões criacionais

Boas práticas com aplicação de padrões de projetoAmplamente aceitos como exemplos de arquiteturas a serem seguidas, os modelos  conhecidos como padrões de projeto (ou design patterns como preferir…) ainda são pouco utilizados na construção de novos sistemas ou, quando utilizados, passam despercebidos. Paradoxalmente, é muito incomum encontrar softwares de ampla magnitude que não façam o uso dos padrões estudados pela “gangue dos quatro” de Erich Gamma e seus parceiros. Isso decorre do fato de que padrões de projeto são análises profundas sobre os tipos de soluções mais aplicadas sobre situações frequentes e pontuais, além do mais, o mais importante, é que se tratam de alternativas que deram certo. Assim, quanto maior o grau de conhecimento dos envolvidos com a elaboração de um determinado programa, maiores são as chances de que este atinja seu objetivo de modo mais eficiente e com menor esforço individual.

Neste artigo, e nos subsequentes, pretendo demonstrar como que se faz necessário o entendimento, ao menos superficial, do conceito dos vinte e três padrões de projetos propostos pela equipe formada por: Erich GammaRichard Helm, Ralph Johnson, John Vlissides e Addison-Wesley a partir da análise da aplicação de cada um deles sobre ferramentas populares em organizações sejam elas com fins lucrativos ou não.

Porém, antes de começar a discorrer sobre o assunto, é importante ressaltar que os padrões “Gang of Four” (GoF) são subdivididos em 3 grupos distintos: os padrões criacionais que lidam com a forma pela qual os objetos são instanciados, os padrões estruturais que dizem respeito ao modo pelo qual as classes devem se organizar e, por fim, os padrões comportamentais que tratam a forma esperada de ação e reação de determinados objetos. Tendo em mente essa organização, agora sim analisaremos casos nos quais os padrões criacionais foram aplicados.

Neste artigo lidaremos com os padrões de projeto criacionais que são respectivamente conhecidos como: Abstract factoryBuilder patternFactory methodPrototype e, Singleton que em resumo são mecanismos de controle sobre a criação de objetos. Isso significa que as classes que veremos a seguir implementam esse modelo encapsulando em si a inteligência de criação de estruturas especificas e detendo conhecimento sobre quais classes concretas que o sistema pode utilizar. Vamos lá:

Padrões criacionais

  1. Abstract Factory é um padrão de criação de classes geralmente reconhecido por um método criacional que retorna uma interface ou uma classe abstrata. Ele torna possível a criação de famílias de objetos relacionados sem especificar classes concretas. Um exemplo muito utilizado é o java.util.Calendar.getInstance().

  2. Builder é outro padrão de projeto de criação, porém o seu foco está voltado para a montagem de tipos complexos. Sua meta é separar a construção de um objeto da necessidade de respeitar os parâmetros do método construtor, ou seja, sua representação. Isso torna possível formar a instância em passos simples e abre a possibilidade de o mesmo processo gerar diferentes representações. Em geral é uma classe que implementa o padrão Builder e que possui um ou mais métodos que retornam a instância dessa classe, por exemplo, o método append() e o java.lang.StringBuilder que resulta no próprio StringBuilder.

  3. Factory Method é um padrão de projeto que consegue delegar a instanciação de uma classe para uma subclasse a partir do uso de uma interface comum. Geralmente são constituídos de métodos que geram um novo tipo concreto de objeto, como java.lang.Object.toString().

  4. Prototype é um dos padrões GOF que têm um nome que melhor lembra o seu objetivo final. Ele trata o problema de criar cópias exatas de um tipo a partir de um protótipo que nada mais é do que uma instância do próprio tipo. Em outras palavras, ele usa uma instância de uma classe para criar uma diferente, mas que possui propriedades idênticas à inicial. É disso que se tratam as classes que implementam a interface java.lang.Cloneable e fazem uso do método java.lang.Object.clone().

  5. Singleton é o último, e talvez o mais conhecido, dentre todos os padrões criacionais GOF. Diferente dos outros, seu objetivo não é a criação indiscriminada de novas instâncias, mas sim a gerência de uma única, a qual ele fornece um ponto global de acesso para uso em toda a aplicação. Em outras palavras, pode ser reconhecido a partir da existência de um método que retorna o mesmo objeto (frequentemente uma instância próprio objeto) em todas às vezes pelas quais é invocado. Um objeto que tem comportamento semelhante é o java.lang.RunTime.getRuntime(), que retorna o objeto associado a uma aplicação Java.

E fechamos aqui a primeira parte para no próximo post enfatizarmos os padrões de projeto estruturais !

Artigos relacionados:

Referências

  • oodesign.com – Coletânea de exemplos práticos com UML de padrões de projeto aplicados a linguagem Java

  • sourcemaking.com/design_patterns – Coletânea de exemplos práticos com UML de padrões de projeto aplicados a linguagem Java

Livros

  • Padrões de projeto: Soluções reutilizáveis de software orientado a objetos, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Addison-Wesley, 1995 – O livro que popularizou o catalogo de padrões para o desenvolvimento de software

  • Use a cabeça! Padrões de projetos (Design Patterns), Erich Freeman, Elisabeth Freeman, 2005 – Literatura que faz o uso de comparações na vida real e detalha comparações entre padrões semelhantes

A ascensão dos webservices: as arquiteturas SOA e REST e a definição do contrato WSDL

O mundo conectado a partir de serviços web

Com o passar dos anos, as grandes organizações vem convergindo cada vez mais para a adoção de medidas que as permitam aumentar o grau de sinergia para que, dessa maneira, possam elaborar sistemas mais robustos e eficientes, construídos com maior agilidade e menor custo. Esse comportamento é evidente, afinal, a reimplementação de artefatos já produzidos é onerosa e exige esforços desnecessários: um novo entendimento sobre um determinado negócio, uma nova implementação, novos testes, manutenção em geral… Nesse contexto, torna-se vantajoso tanto reaproveitar serviços como expô-los publicamente pois, desse modo, as corporações passam a concentrar seus esforços naquilo que é a raiz do próprio negócio agregando assim maior valor no que produzem e, por fim, tornam-se mais competitivas.

A troca de informações entre sistemas geograficamente distribuídos já pode, em muitas vezes, ser tratada como tarefa de baixa complexidade. No entanto, embora os gigantes como Amazon.com, eBay, Yahoo! e Twitter ou até mesmo os pequenos como eu ou você sermos capazes de disponibilizar meios de transferência de dados através da web, essa realidade só se concretizou face aos esforços que foram acontecendo com maior intensidade a partir da década de 90 com a introdução da arquitetura CORBA. Por outro lado, a interface de transmissão só se consolidou com o enraizamento das arquiteturas SOA (service-oriented architeture) e, mais recentemente, REST (representational state transfer).

Com o surgimento das arquiteturas orientadas a serviço novos problemas se consolidaram. Ao passo em que surgiam e a complexidade se mantinha alta, consequentemente a popularização era baixa. Assim, gradativamente foram surgindo ferramentas para facilitar a implementação de webservices como é o caso do Axis, do Spring WS, do VRaptor, do Resteasy, do JAX-WS e do Apache CXF. Outro ponto de conflito diz respeito a ordem pela qual deveriam ser construídos: se seria necessário a definição de um contrato (leia-se aqui WSDL) antes ou depois da conclusão do desenvolvimento do sistema, top-down ou bottom-up.

Em geral, um sistema é exposto publicamente somente após a sua construção partindo do principio bottom-up. A principio, as classes e interfaces que identificam o serviço já estão criadas e são elas que serão usadas para gerar um artefato WSDL e definir o contrato de comunicação entre sistemas. Em contrapartida, tem se tornado comum a necessidade de construção em paralelo de um servidor e consumidor sem pré-existência de algo concreto e, essa é a razão da crescente necessidade de entendimento da abordagem top-down na qual primeiro cria-se um artefato WSDL que define o contrato entre as partes envolvidas para que, com o documento previamente definido, ambas as partes iniciam o desenvolvimento propriamente dito.

Nos próximos artigos demonstrarei como construir e disponibilizar serviços na internet a partir de uma abordagem top-down, introduzindo boas práticas de programação Java e elaboração de artefatos WSDL.

Sugestões:

GIT: Configurando Meld como ferramenta de Diff externa no Linux

Icone do aplicativo MeldAo longo dos últimos anos, o Git vem ganhando cada vez mais espaço entre os desenvolvedores e assim, gradativamente, estabelece-se como uma poderosa ferramenta para versionamento. Tal ascensão pode ser explicada por algumas conta de algumas de suas características como rapidez, pois permite com que operações como commits e diffs sejam realizadas localmente, autonomia já que a rede só é necessária para a troca de revisão entre diferentes repositórios e confiabilidade, afinal, os repositórios dos desenvolvedores podem ser considerados como backups.  Por outro lado, exige um entendimento minimo prévio por parte daqueles que a usam e, justamente por essa razão, pode vir a tornar-se uma ferramenta complexa em ocasiões como a visualização da diferença entre arquivos, o famoso diff.

Vejamos um exemplo de comparação de arquivos com git puro:

git diff

Ao usar esse comando, obtemos algo como a imagem que segue em sequência:

Interface de comparação de arquivos

Nesse diff, em verde e marcados com o sinal de “+” estão destacadas as mudanças impostas pelo desenvolvedor, já em vermelho e marcadas com o sinal de “-” estão marcadas as linhas retiradas do arquivo.

É fácil perceber que para modificações pequenas e pontuais tal comando pode até a ser útil, por outro lado, imagine-se tendo que comparar várias modificações de uma só vez. Nesse cenário, uma ferramenta como o WinMerge para Windows ou o Meld do Linux são muito bem recebidas pois tornam mais óbvia as modificações que ocorreram como no caso da imagem abaixo:

Comparação de arquivos com Meld

Por sorte, essa configuração é possível e pode ser feita, levando em consideração um ambiente Linux, da seguinte maneira.

  1. Instalação do Meld qcomo ferramenta de comparação de arquivos
    sudo apt-get install meld
    
  2. Criar um arquivo de script
    touch /home/<seu usuario>/.config/git_diff_com_meld.sh
    
  3. Configurar o arquivo recém criado para informar os parâmetros que serão usados no Meld
    #!/bin/bash
    meld "$5" "$2"
    
  4. Configuração do Meld como ferramenta externa de diff para Git
    git config --global diff.external /home/<seu usuario>/.config/git_diff_com_meld.sh
    

Agora as próximas comparações entre arquivos usando o diff do Git vão fazer o uso do Meld tornando muito mais fácil a visualização das modificações realizadas entre arquivos.

E se você gostou desse artigo, talvez você goste desse também:
Guia prático dos comandos básicos do Git para o dia-a-dia do desenvolvedor

Referências:
Por que Git é Melhor que X?
Visualizando diff no Git com o Meld