Desenvolvedores não pensam, desenvolvedores seguem casos de uso

September 23rd, 2008

Há algum tempo atrás eu participei de um projeto que se encontrava dentro de um processo absolutamente waterfall, para falar a verdade, a maioria dos softwares desenvolvidos aqui no mercado local seguem o processo em cascata, em sua grande maioria, através de fábricas de software. Bem, durante um certo dia na fase de codificação eu acabei tendo uma dúvida num caso de uso que me foi entregue, para ser mais exato a dúvida era em relação ao negócio da aplicação, ótimo, mesmo dentro de um processo em cascata ainda assim o analista de sistemas (negócios?) estava próximo da equipe de desenvolvedores, fui até ele conversar sobre o que não havia entendido no caso de uso, e ao debater com ele sobre o problema que não estava claro -pelo simples fato de não haver uma linguagem comum entre a equipe- eu ouço da boca do analista o seguinte comentário:

Para que tu quer entender isso? Desenvolvedores não pensam, desenvolvedores seguem casos de uso. Programadores só deveriam pegar o caso de uso, ler e seguir o que ele diz, nada mais. Não há porquê entender as regras de negócio, o caso de uso tem tudo que se precisa saber. Blah blah blah..

Wow! Depois de ouvir isso eu me calei por alguns segundos e pensei comigo mesmo, “Será que ele tem noção do que acabara de me dizer?”, e o pior é que este analista disse isso com uma certa arrogância, na frente de outros analistas (que calados pareciam concordar), logo começamos a discutir, eu tentando dizer que não havia fundamentos no comentário dele e que eu não entendia como ele acreditava naquilo.. depois de alguns minutos eu resolvi encerrar a discussão para evitar constrangimentos para ambos (pois estávamos no cliente).

Parece brincadeira, mas aquilo aconteceu mesmo (eu deveria estar realmente surpreso?!). Pergunto-me como em pleno ano da graça de 2008 um analista com anos de experiência em diversos projetos, formado em uma das melhores universidades do estado, e com certeza com mais alguns (ou até vários) diplomas e certificados debaixo do braço consegue verbalizar tamanha asneira. Mais incrível ainda é como um analista desses consegue acreditar tão fervorosamente que está certo, como ele consegue acreditar que um desenvolvedor é apenas mais um recurso comum para um trabalho manual, e facilmente substituível dentro da famigerada fábrica de software que ele tanto venera, alias, qual é a dificuldade em compreender que um programador não é um “digitador de luxo”? Será que depois de tanto tempo de profissão ele ainda não entendeu que desenvolver software é essencialmente um ato de criação? É, parece que não.

Um desenvolvedor mais do que ninguém deve entender sobre o negócio do cliente, um desenvolvedor deve estar mergulhado no negócio na qual pretende desenvolver o software ou parte dele, de fato, como será que este analista imagina que um desenvolvedor pode implementar algo minimamente decente sem entender o problema? Através da ajuda de alguma entidade sobrenatural?

No fundo, acho que ouvir isso de qualquer profissional no ramo de desenvolvimento de software é um absurdo, e para mim está óbvio que esta pessoa precisa urgentemente voltar a estudar sobre o assunto (pois ela parou de estudar há muito tempo, 10 anos talvez), e claro, mais do nunca ela precisa de uma “reciclagem”.

Engraçado é que este fato me fez lembrar de um acontecimento um tanto quanto embaraçoso interessante que ocorreu no inicio da minha carreira como desenvolvedor, há mais de 3 anos, eu possuía menos que um ano de experiência na época, e havia enviado o currículo a uma empresa que estava ofertando uma oportunidade para desenvolvedor Java, parecia uma boa empresa, depois de enviado o currículo eu recebi uma ligação alguns dias mais tarde para uma entrevista, na época eu fiquei super excitado, e parei para rever meu currículo cuidadosamente, ao reler o currículo vi que não sabia exatamente sobre o que se tratava 2 (dois) projetos na qual havia participado na primeira empresa que trabalhei, eu tinha uma vaga e distante idéia do que havia desenvolvido (em termos do negócio do cliente), mas ainda assim eu não conseguiria explicar a alguém (principalmente a quem fosse me entrevistar) sobre a finalidade dos projetos.

Então durante a semana, eu -na minha mais pura ingenuidade- enviei um e-mail ao meu antigo chefe (que além de ser um empresário, ele também era um talentoso desenvolvedor e amigo) pedindo informações sobre o que se tratavam os projetos que eu havia participado. A resposta dele não foi bem a que eu esperava [rs], porém me abriu os olhos na época. Alguns trechos do e-mail seguem abaixo:

De:rponte@gmail.com Para:boss

Fala amegão,

[...] Seguinte, tu pode me passar um resumo pequeno mesmo, porém decente das aplicações que eu participei ai na empresa para que eu possa colocar no currículo, pode? :))) [...]
De ante mão eu te agradeço, abraços.

De:boss Para:rponte@gmail.com

Você não sabe pra que serviam os projetos que participou? rs Então não vale a pena colocar no currículo né? Você coloca que nesses projetos só fez programação pura.

Depois desse e-mail eu fiquei realmente envergonhado, vi que por mais que gostasse de desenvolver software eu não me interessava pelo negócio do cliente, eu não me interessava em entender e aprender sobre ele, o que sempre me interessava eram as tecnologias e os frameworks mais “hypes” do mercado. Entendendo como funcionava o mais novo framework da época já valia a pena estar desenvolvendo algo.

A partir dai, eu resolvi mudar isso, foi um processo lento mas contínuo, graças a certos amigos, blogs (Shoes, Guilherme etc), discussões em listas e fóruns, e muitas linhas de código mal implementadas (claro, como eu poderia implementar algo decente sem entender o problema?) eu procurei compreender sobre o problema do cliente e como expressa-lo em código, eu procurei ler sobre metodologias agéis, abordagens e boas práticas de design de software, maneiras de melhorar a comunicação entre os membros da equipe e várias outras coisas que me ajudaram e me ajudam até hoje.

Depois de tudo que eu estudei e procurei aprender eu ainda tenho que ouvir de um analista que eu não preciso entender sobre o negócio do cliente? Ah, por favor, eu não sou um “programador” que não pensa e simplesmente segue algumas folhas de papel com vários diagramas UML e fluxos alternativos escritos por analistas de sistemas, eu -como desenvolvedor- devo estar tão próximo do negócio quanto for necessário, mas isso é bem complicado quando trabalhamos num modelo em cascata em que a comunicação entre os membros da equipe ocorre através da troca de documentos e diagramas, e pior, quando trabalhamos com “profissionais” que acreditam que isso funciona.

Enfim, já basta os softwares bolovos e sem testes que temos que manter, já basta os processos burocráticos que temos que participar, já basta estarmos rodeados de fábricas de software e code-monkeys preguiçosos, já basta a desvalorização e os péssimos salários pagos aos desenvolvedores, e agora me vêm com analistas de sistemas retrógrados (oops!, pareço redundante) ? Por favor, me perdoem, mas as vezes acho que o mercado local está caminhando para trás quando se fala em desenvolvimento de software, mesmo conhecendo algumas poucas empresas, grupos e bons profissionais dispostos a mudarem isso.

Entity Converters pra dar e vender

July 26th, 2008

Uma coisa que sempre aconselho aos desenvolvedores é que tentem sempre que possível trabalhar em JSF diretamente com os objetos como se estivessem em um ambiente stateful, pois um dos objetivos da tecnologia é tentar abstrair a natureza stateless do HTTP. Não que seja algo tão simples de se fazer algumas vezes, mas não é tão complexo ao ponto de abrir mão desta abstração.

Pensando no que eu disse acima, um dos problemas comuns e chatinhos quando se trabalha com SelectOneMenu (ou SelectManyMenu) e entidades em JSF ocorre quando queremos que o value do nosso SelectItem seja a própria entidade, e não o “id” da mesma. Bem, o que estou querendo dizer é exatamente isso:

public List<SelectItem> getEmpresas() {
	List<SelectItem> items = new ArrayList<SelectItem>();
	for (Empresa e : this.empresas) {
		// observem que o value do meu SelectItem é a própria entidade
		items.add(new SelectItem(e, e.getNome()));
	}
	return items;
}

Isso acaba se tornando trabalhoso pois somos obrigados a implementar um converter para cada entidade, o que particularmente eu não gosto. Levando em consideração que não queremos criar um converter para cada entidade, quais outras soluções temos?

Cenário

Antes de explicar cada solução, vou demonstrar um cenário qualquer para facilitar o entendimento, ou seja, vou demonstrar os artefatos necessários, eles seguem abaixo:

1) Nosso managed bean

public class EmpresaBean {

	private Empresa selectedEmpresa;
	private List<Empresa> empresas = new ArrayList<Empresa>();

	public EmpresaBean() {
		empresas.add(new Empresa(7, "Triadworks"));
		empresas.add(new Empresa(88, "Ivia"));
		empresas.add(new Empresa(921, "Thoughtworks"));
		empresas.add(new Empresa(15, "Caelum"));
		empresas.add(new Empresa(2, "ImproveIT"));
	}

	public List<Empresa> getEmpresas() {
		return empresas;
	}
	public Empresa getSelectedEmpresa() {
		return selectedEmpresa;
	}
	public void setSelectedEmpresa(Empresa selectedEmpresa) {
		this.selectedEmpresa = selectedEmpresa;
		System.out.println("Empresa selecionada: " + selectedEmpresa.getNome());
	}
}

2) Nossa entidade (já implementando a interface BaseEntity para a solução do SimpleEntityConverter)

public class Empresa implements BaseEntity, Serializable {

	private static final long serialVersionUID = 1L;

	private Integer codigo;
	private String nome;

	public Empresa(Integer codigo, String nome) {
		this.codigo = codigo;
		this.nome = nome;
	}

	public Long getId() {
		return new Long(codigo);
	}

	// Métodos getters e setters
	// Não esquecer os métodos equals e hashCode
}

Por favor, não esqueçam de implementar os métodos equals e hashCode, evitemos cair naquele velho probleminha, ok?

3) Nosso formulário

<h:form id="form">
	<h:panelGrid columns="2" border="1">
		<h:outputLabel value="Empresa" for="empresa"/>
		<h:column>
			<h:selectOneMenu id="empresa"
				value="#{empresaBean.selectedEmpresa}"
				converter="simpleEntityConverter" required="true"
				requiredMessage="Valor é obrigatório">
				<f:selectItem itemValue="" itemLabel="Selecione uma empresa"/>
				<t:selectItems value="#{empresaBean.empresas}" var="o" itemLabel="#{o.nome}" itemValue="#{o}"/>
			</h:selectOneMenu>
			<br/>
			<h:message for="empresa" errorStyle="color:darkred;font-size:11px;"></h:message>
		</h:column>
		<f:facet name="footer">
			<h:commandButton value="Submit"/>
		</f:facet>
	</h:panelGrid>
</h:form>

Reparem que nosso componente h:selectOneMenu possui um converter declarado, ou seja, será necessário alterar o converter de acordo com a solução escolhida.

Nada de complexo, certo? É um básico cenário comum.
Então vamos as possíveis soluções :)

EntityConverter

O componente/feature EntityConverter foi desenvolvido pelo Rogério Araújo, que é um dos coordenadores do JavaServer Faces International Group. O componente em si foi pensado inicialmente para o cenário em que é necessário carregar a entidade de um banco de dados, mas isso não impede que a entidade seja obtida de qualquer outro recurso externo, como um web service ou EJB, por exemplo.

O componente funciona perfeitamente bem e é muito simples de configurar, porém ainda assim eu o acho interessante somente em alguns casos específicos, como no caso em que a entidade precisa ser carregada do banco de dados com todos seus atributos e/ou relacionamentos ao submeter o formulário, evitando-se assim onerar o servidor com grandes entidades em memória.

Alguns desenvolvedores reclamam porque o componente -idealmente- efetua uma requisição ao bando de dados para obter a entidade, mas venhamos e convenhamos, isso não deveria ser uma preocupação, principalmente para quem vem de algum framework “action-like” como o Struts. Além do mais, se você possui algum recurso de cache na camada de persistência -como o fornecido pelo Hibernate- não deveria se preocupar tanto com isso.

Eu não pretendo explicar como configurar o componente, pois na wiki do Myfaces você tem tudo que precisa saber, além do mais o criador do componente é coordenador da Javasf e é brasileiro :)

SimpleEntityConverter

Esta solução foi baseada no caso mais comum de um converter para cada entidade, a única diferença é que tentei torna-la “genérica” para qualquer entidade, evitando-se escrever um converter para cada entidade (é esta nossa intenção, certo?).

Sendo, segue abaixo o código do nosso converter:

public class SimpleEntityConverter implements Converter {

	public Object getAsObject(FacesContext ctx, UIComponent component, String value) {
		if (value != null) {
			return this.getAttributesFrom(component).get(value);
		}
		return null;
	}

	public String getAsString(FacesContext ctx, UIComponent component, Object value) {

		if (value != null
				&& !"".equals(value)) {

			BaseEntity entity = (BaseEntity) value;

			// adiciona item como atributo do componente
			this.addAttribute(component, entity);

			Long codigo = entity.getId();
			if (codigo != null) {
				return String.valueOf(codigo);
			}
		}

		return (String) value;
	}

	protected void addAttribute(UIComponent component, BaseEntity o) {
		String key = o.getId().toString(); // codigo da empresa como chave neste caso
		this.getAttributesFrom(component).put(key, o);
	}

	protected Map<String, Object> getAttributesFrom(UIComponent component) {
		return component.getAttributes();
	}

}

Como podem ver, o que basicamente ocorre é que armazenamos as entidades como atributos do nosso componente no método getAsString(), e recuperamos a entidade correta através da chave (neste caso o Id) associada a ela, no método getAsObject(). Mas quem é este BaseEntity?

public interface BaseEntity {

	public Long getId();

}

BaseEntity é uma interface com um método em comum entre as entidades, ou seja, nossas entidades precisarão implementar esta interface para que nosso converter funcione de acordo.

A solução é funcional, porém há algumas ressalvas que gostaria de explicitar. 1) Eu particularmente não gosto da idéia de “sujar” minhas entidades estendendo alguma interface ou classe para poupar código ou resolver problemas não-funcionais, mas isso é uma opinião minha. 2) Outro problema que vejo é que este converter acaba consumindo um pouco mais de memória no servidor por você está alterando o estado do componente, principalmente se sua lista de itens for muito grande, mas não é nada que você deva se preocupar inicialmente, ou seja, somente esquente a cabeça com isso se for realmente necessário.

SimpleIndexConverter

Esta solução eu tomei emprestada dos componentes do Myfaces Trinidad, que por sinal é excelente. A diferença é que eu a simplifiquei um pouco (o código para ser mais exato), logo ela não está tão robusta quanto a original (que se preocupa com casos mais específicos), mas funciona muito bem na maioria dos casos.

Sua idéia principal é utilizar o index da lista de items como chave para cada entidade, assim o que submetemos no formulário é o index da lista, e não um valor (Id?) da entidade como de costume.

O código do converter ficou grandinho, mas é possível enxuga-lo movendo alguns métodos para alguma classe utils. Segue abaixo o código do nosso SimpleIndexConverter:

public class SimpleIndexConverter implements Converter {

	private int index = -1;

	/* (non-Javadoc)
	 * @see javax.faces.convert.Converter#getAsObject(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.String)
	 */
	public Object getAsObject(FacesContext ctx, UIComponent component, String value) {

		SelectItem selectedItem = this.getSelectedItemByIndex(component, Integer.parseInt(value));
		if (selectedItem != null)
			return selectedItem.getValue();

		return null;
	}

	/* (non-Javadoc)
	 * @see javax.faces.convert.Converter#getAsString(javax.faces.context.FacesContext, javax.faces.component.UIComponent, java.lang.Object)
	 */
	public String getAsString(FacesContext ctx, UIComponent component, Object value) {
		index++;
		return String.valueOf(index);
	}

	/**
	 * Obtem o SelecItem de acordo com a opção selecionada pelo usuário
	 */
	protected SelectItem getSelectedItemByIndex(UIComponent component, int index) {

		List<SelectItem> items = this.getSelectItems(component);
		int size = items.size();

		if (index > -1
				&& size > index) {
			return items.get(index);
		}

		return null;
	}

	protected List<SelectItem> getSelectItems(UIComponent component) {

		List<SelectItem> items = new ArrayList<SelectItem>();

		int childCount = component.getChildCount();
	    if (childCount == 0)
	      return items;

	    List<UIComponent> children = component.getChildren();
		for (UIComponent child : children) {
			if (child instanceof UISelectItem) {
				this.addSelectItem((UISelectItem) child, items);
			} else if (child instanceof UISelectItems) {
				this.addSelectItems((UISelectItems) child, items);
			}
		}

		return items;
	}

	protected void addSelectItem(UISelectItem uiItem, List<SelectItem> items) {

		boolean isRendered = uiItem.isRendered();
		if (!isRendered) {
			items.add(null);
			return;
		}

		Object value = uiItem.getValue();
		SelectItem item;

		if (value instanceof SelectItem) {
			item = (SelectItem) value;
		} else {
			Object itemValue = uiItem.getItemValue();
			String itemLabel = uiItem.getItemLabel();
			// JSF throws a null pointer exception for null values and labels,
			// which is a serious problem at design-time.
			item = new SelectItem(itemValue == null ? "" : itemValue,
					itemLabel == null ? "" : itemLabel, uiItem
							.getItemDescription(), uiItem.isItemDisabled());
		}

		items.add(item);
	}

	@SuppressWarnings("unchecked")
	protected void addSelectItems(UISelectItems uiItems, List<SelectItem> items) {

		boolean isRendered = uiItems.isRendered();
		if (!isRendered) {
			items.add(null);
			return;
		}

		Object value = uiItems.getValue();
		if (value instanceof SelectItem) {
			items.add((SelectItem) value);
		} else if (value instanceof Object[]) {
			Object[] array = (Object[]) value;
			for (int i = 0; i < array.length; i++) {
				// TODO test - this section is untested
				if (array[i] instanceof SelectItemGroup) {
					resolveAndAddItems((SelectItemGroup) array[i], items);
				} else {
					items.add((SelectItem) array[i]);
				}
			}
		} else if (value instanceof Collection) {
			Iterator<SelectItem> iter = ((Collection<SelectItem>) value)
					.iterator();
			SelectItem item;
			while (iter.hasNext()) {
				item = iter.next();
				if (item instanceof SelectItemGroup) {
					resolveAndAddItems((SelectItemGroup) item, items);
				} else {
					items.add(item);
				}
			}
		} else if (value instanceof Map) {
			for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) value).entrySet()) {
				Object label = entry.getKey();
				SelectItem item = new SelectItem(entry.getValue(),
						label == null ? (String) null : label.toString());
				// TODO test - this section is untested
				if (item instanceof SelectItemGroup) {
					resolveAndAddItems((SelectItemGroup) item, items);
				} else {
					items.add(item);
				}
			}
		}
	}

	protected void resolveAndAddItems(SelectItemGroup group, List<SelectItem> items) {
		for (SelectItem item : group.getSelectItems()) {
			if (item instanceof SelectItemGroup) {
				resolveAndAddItems((SelectItemGroup) item, items);
			} else {
				items.add(item);
			}
		}
	}

}

Não há muito a se explicar sobre o converter acima, a “mágica” toda está nos métodos auxiliares (que por sinal maior parte do código foi retirado do Trinidad, porém enxugado para nossas necessidades).

Este converter diferentemente do SimpleEntityConverter não altera o estado do componente, evitando assim a utilização de mais memória, ele busca as entidades -que já existem- dentro do componente (SelectOneMenu?) como estado do mesmo.

Assim como os outros converters, este também possui algumas ressalvas. O que podemos citar é 1) Por ele se utilizar do index da lista de items talvez não seja possível trabalhar com o componente no lado cliente (javascript) se você depender dos valores (Id?) de cada item.

Concluindo

Dizer que não é possível simplificar a vida com JSF é mentir, a tecnologia te fornece recursos para abstrair a natureza stateless do HTTP, e é interessante que se aproveite destes recursos.

As soluções explanadas acima não são únicas, existem outras com toda certeza, eu tentei demonstrar algumas delas quando não se tem algum framework (Seam?) ou conjunto de componentes (Trinidad?) para nos auxiliar, ou seja, quando contamos apenas com a implementação do JSF, o que -acredito eu- na maioria dos casos é o que ocorre.

É possível estende-las e até melhora-las, você é livre para isso, e dependendo da tua necessidade provavelmente será o melhor caminho, só não deixe de contribuir com o código para a comunidade.

Enfim, todas as soluções são plausíveis e funcionam para a maioria dos cenários, porém em determinados cenários cada uma se adequa melhor, resta a você analisar e ver qual se encaixa nas tuas necessidades. Além do mais, elas não são mutuamente excludentes.