Arquivo da tag: modular

Vamos praticar orientação a objetos?

Tempos atrás li o livro Essays on Software Technology and Innovation da série Pragmatic Programmers. Gostei bastante do livro e vou hoje resumir um dos capítulos que considero uma ótima referência para escrever um código utilizando orientação a objetos. O capítulo foi escrito por Jeff Bay com o título Object Calisthenics.

Em termos gerais, apesar de termos diversos recursos de linguagens orientadas a objetos, ainda hoje escrevemos códigos extensos, difíceis de testar e repetitivos.
Para melhorar nossos códigos, Jeff Bay propõe um exercício no qual você deve escrever 1000 linhas de código, seguindo rigorosamente as 9 regras descritas abaixo. Acredite, este exercício pode transformar a maneira como você escreve seu código:

1) Um nível de identação por método
Se uma classe com mais de 500 linhas já assusta a primeira vista, o que dizer de um método? Uma boa referência para manter seu código compreensível é ter por volta de 5 linhas por método. Isto pode ser assustador, mas caso não seja possível, garanta que um método faça apenas uma única coisa. Para isso, não escreva métodos com mais de um nível de identação, como neste exemplo:

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();
	}
}

Após aplicação da regra ficaria:

	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”);
	}

2) Não use o comando ELSE
O comando IF/ELSE é simples para entender, porém, não é difícil encontrar uma sequência de IF/ELSE tão extensa, que é quase impossível de ser compreendida, o mesmo vale para o comando CASE.
Nas linguagens orientadas a objetos é possível reduzir o uso de IFs com polimorfismo, patterns como o Null Object, ou ainda em linguagens como Java, com Enums ou com auxílio da API Reflection.
Então a segunda regra do exercício é esta: não use o comando ELSE. Use a criatividade para pensar em outras soluções para esta restrição.

3) Use objetos para primitivos e Strings
A linguagem Java é orientada a objetos, então não faz sentido usar uma regra diferente para declarar tipos, no caso usar primitivos. A definição da classe, além disso, proporciona informações adicionais tanto para o compilador como para o desenvolvedor, não permitindo por exemplo passar uma variável Ano, para um método que espera receber uma variável Hora. Criar pequenos objetos como Hora, Nome ou CPF, permite também adicionar validações nos lugares corretos, evitando os famosos Utils.validaIsso() ou Utils.validaAquilo().

4) Use uma coleção por classe
A regra número 4 é simples: se a classe tiver alguma variável que seja uma coleção, esta não deve ter nenhuma outra variável de classe. Toda coleção é encapsulada em sua própria classe. Esta regra, em Java, pode parecer ter sentido apenas quando não existia o conceito de Generics, que garante que a coleção só terá os tipos definidos pelos métodos utilizados para adicionar ou remover um elemento. Porém, esta regra garante também que os códigos relacionados a manipulação ou iteração com a coleção, estarão nos lugares apropriados e não espalhados no meio de outras classes.

5) Um ponto por linha
Esta regra é a aplicação da lei de Demeter, que determina que uma unidade deve interagir apenas com quem conhece, ou com quem está próximo. Violar esta regra indica que a sua unidade de código não está com o encapsulamento adequado, além de criar acoplamentos desnecessários. Veja o exemplo abaixo:

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();
 }
 
}

O código refatorado ficaria assim:

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();
 }
}

6) Não abrevie
Abreviações fazem muito sentido apenas para quem as criou. Variáveis, métodos ou classe com nomes abreviados confundem. Se é necessário abreviar para reduzir a quantidade de caracteres digitados, talvez o código esteja sendo utilizado para fazer muitas coisas, o que quebra o princípio de coesão. Nomes muito longos também são um indicativo de que o código pode ser melhorado e que o método pode estar com mais responsabilidades do que deveria ter.
Nesta regra, utilize nomes com 1 ou 2 palavras, que representem fielmente o que o código faz. Evite também duplicar nomes para representar um contexto: Em uma classe com o nome Order, não é necessário ter um método com o nome Order.shipOrder(), basta chamar de Order.ship(), que é claro e representa bem o que será feito.

7) Mantenha todas as entidades pequenas
Esta regra restringe qualquer classe com mais de 50 linhas e qualquer pacote com mais de 10 classes. Uma classe extensa é um forte indício de que ela faz mais do que deveria fazer e suas responsabilidades poderiam ser quebradas em classes menores. Manter um pacote com poucas classes faz com que as classes neste pacote tenham forte relação, criando uma identidade para o pacote. Isto ajuda a manter unidades coesas, um contexto apropriado e responsabilidades únicas.

8) Nenhuma classe com mais de duas variáveis de instância
A ideia desta regra é fortalecer código coeso. Classes com mais de duas variáveis de instância terão responsabilidades além do necessário, além disso, quanto mais estado, mais riscos para problemas de concorrência. Veja o exemplo abaixo:

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

Ficaria assim:

class Name {
 Surname family;
 GivenNames given;
}
class Surname {
 String family;
}
class GivenNames {
 List<String> names;
}

9) Sem getters/setters/propriedades
Esta regra reforça a coesão, assim como a regra anterior. A ideia é manter comportamento e estados em lugares apropriados, mantendo assim objetos modulares, simples e fáceis de serem compreendidos. Objetos coesos também evitam código duplicado, uma vez que é mais fácil reaproveitar métodos e classes que tem responsabilidades únicas.

Conclusão
A aplicação dessas regras é nada mais nada menos que a aplicação da programação orientada a objetos. Aplicar as regras, lhe força a pensar em código modular e encapsulado, o que facilita o reaproveitamento e mantém o desacoplamento entre as classes. Pense em um jogo de Lego: qual peça é mais fácil encaixar com as outras? Quanto menor a peça, mais fácil de encaixar, maior o reaproveitamento e mais fácil é substituí-la.

Faça o teste: escreva 1000 linhas de código aplicando essas regras e conte um pouco como foi o resultado!

Anúncios