Boas práticas de desenvolvimento de software

Embora a internet esteja repleta de tutoriais e dicas sobre desenvolvimento, sempre surge a dúvida de como um sistema deve ser devidamente desenvolvido dentro de padrões. Na verdade, não há uma regra geral ou um processo único para o desenvolvimento de um sistema, mas existem boas práticas que, quando adotadas, podem trazer grandes vantagens em um software, tanto para o cliente quanto para o próprio desenvolvedor.

1. Facilidade de uso (Usabilidade)

Colocar vários botões e informações em excesso em uma janela pode comprometer a usabilidade da aplicação. Simplicidade e objetividade devem estar casados com a funcionalidade do sistema para proporcionar um maior “conforto” ao usuário. Procure simplificar o visual das janelas, adicionando somente os componentes necessários que o usuário de fato irá utilizar. Estruture a janela de forma que os campos fiquem em uma sequência objetiva, agrupados por assunto ou categoria.

Em um cadastro de clientes, por exemplo, divida os campos por seções, como dados pessoais, dados profissionais, contato e informações adicionais. Assim a localização de informações fica bem mais fácil e evita que o usuário fique confuso em meio a tantos campos. Procure também organizar a ordem de tabulação, para que a digitação de dados se torne mais rápida através do TAB ou ENTER para avançar o cursor entre os campos.

A questão do padrão visual das janelas deve ser discutida na fase inicial do projeto durante a análise de requisitos. Para garantir que o visual fique conforme a expectativa do usuário, os projetistas utilizam uma técnica conhecida como Prototipação, que consiste em elaborar uma prévia da tela desenhando-a em um documento. Este desenho é apresentado ao cliente para avaliação e, após a aprovação, o protótipo passa a ser desenvolvido.

2. Splash Screen

Durante o desenvolvimento de um software, é natural que haja a necessidade de executar uma série de instruções e validações durante a inicialização do sistema, como verificar o caminho do banco de dados, criar backups, carregar módulos e abrir tabelas. Essas instruções podem atrasar a inicialização e a exibição do sistema para o usuário. Consequentemente, o usuário pode pensar que o sistema não foi aberto e tentar abri-lo novamente, criando duas instâncias distintas do sistema na memória. Para evitar este problema, desenvolvedores criam telas de inicialização, também conhecidas como Splash Screen. Essa tela geralmente contém o nome do software e uma barra de progresso indicando o andamento da inicialização enquanto todas as instruções necessárias são executadas em segundo plano.

Splash Screen - NetBeans 7Splash Screen – NetBeans 7 

3. Backups constantes

Sem dúvidas, este item é indispensável! Manter cópias do banco de dados garante maior confiabilidade no sistema quando for necessário recuperar informações. Porém, de nada adianta criar backups no próprio computador do cliente quando existir a possibilidade do disco rígido local ser danificado. Neste caso, o banco de dados e todos os seus backups serão perdidos, ao menos que exista uma cópia em outro computador.

Portanto, procure disponibilizar no sistema a opção para criar backups em locais remotos, como discos externos ou em outro computador na rede local. Outra opção bastante segura é salvar o backup em um diretório virtual na internet, popularmente conhecido como “nuvem”. Mesmo que aconteça uma falha geral na rede e nos dispositivos móveis do cliente, o backup estará armazenado na internet e poderá ser recuperado através de um simples download.

Exemplo de tela para backupExemplo de tela para backup

 

Embora este recurso esteja disponível, é provável que o usuário esqueça de salvar backups do banco de dados periodicamente. A solução é criar caixas de diálogo para avisá-lo do backup ou realizar a cópia silenciosamente, sem a intervenção do usuário. Um bom exemplo disso é configurar o sistema para salvar o backup todas as vezes que o sistema for finalizado ao final do dia.

4. Teclas de atalho

Quando um menu é acessado com bastante frequência, pode ser interessante associar teclas de atalho para acessá-lo com mais agilidade. Quem utiliza o Microsoft Windows já deve conhecer algumas combinações de teclas disponíveis para abrir as janelas mais comuns do sistema, como o “Windows + E” para abrir o Windows Explorer e o “Windows + F” que abre a janela para pesquisa de arquivos.

O mesmo pode ser adaptado a um sistema, por exemplo, para abrir a tela de cadastro de clientes e a tela de consulta de vendas. As teclas de atalho também podem ser atribuídas a determinados eventos do sistema, como recalcular a soma de valores ou preencher um campo automaticamente. Usuários mais experientes geralmente preferem utilizar teclas de atalho ao invés de acessar as funções do sistema utilizando o mouse, principalmente por agilizar as operações rotineiras.

Exemplo de teclas de atalhoExemplo de teclas de atalho em um menu

5. Threads e telas de espera

Algumas operações do sistema podem demorar um certo tempo para serem processadas, principalmente instruções SQL complexas que envolvam cálculos ou consultas em tabelas com vários registros. Quando isso ocorre, normalmente o sistema “congela” ou pára de responder durante o processamento até que a operação seja finalizada. Porém, o usuário pode imaginar que a aplicação está travada e forçar o encerramento do processo, comprometendo a instrução em execução.

Para evitar este tipo de transtorno, é conveniente criar telas de espera para informar o usuário de que um processamento está em execução. Essa tela fica ainda mais intuitiva quando há alguma animação ou uma barra de progresso indicando o processamento. Entretanto, como a tela de espera e a instrução SQL compartilham o mesmo processo na memória, é provável que a aplicação fique travada da mesma forma, sem resposta.

A solução é implementar unidades chamadas Threads, capazes de criar fluxos paralelos ao processo principal para executar uma operação em segundo plano. Basta então exibir uma tela de espera e transferir o processamento (como uma instrução SQL) dentro de uma Thread para que o sistema não se torne instável.

A verificação automática de ortografia no Microsoft Word é um exemplo de Thread. Repare que o programa não trava enquanto a verificação é realizada paraa cada palavra digitada. No Microsoft Outlook, observe também que é possível utilizar normalmente o software ao mesmo tempo que novos e-mails são baixados na caixa de entrada. Portanto, Threads não servem apenas para desenvolver telas de espera, mas sempre quando for necessário executar instruções em paralelo sem afetar o desempenho do sistema. A figura abaixo (em inglês) apresenta uma breve demonstração de como as Threads se comportam dentro de um processo:

Funcionamento de uma ThreadIlustração do funcionamento de uma Thread

6. Tratamento de exceções

Por mais que o desenvolvedor faça testes na aplicação antes de liberar a versão, é natural que alguns erros ainda possam ocorrer inesperadamente para o usuário. Estes erros são decorrentes de problemas de semântica, gravação de dados inconsistentes, falhas de acesso à memória ou até mesmo eventos inesperados do sistema operacional. No ambiente de programação, os erros são conhecidos tecnicamente como exceções. Uma boa prática de programação exige que exista um tratamento de exceções em todos os pontos mais sensíveis do código.

Inserções e atualizações no banco de dados são exemplos de operações que possivelmente podem retornar algum erro para o usuário. A recomendação é envolver este código em um bloco de tratamento de exceção e executar uma operação de rollback (desfazer as alterações no banco de dados) caso algum erro seja encontrado.

Ao utilizar tratamento de exceções, também é possível criar mensagens mais significativas para reportar a exceção ao usuário. Dessa forma, faz mais sentido exibir a mensagem “Ocorreu um erro. Verifique os dados.” ao invés de “An error occurred. Access violation at address 004068EC.”. Estes tipos de mensagens em inglês técnico geralmente são desconhecidas para o usuário e não trazem nenhum tipo de informação para auxiliá-lo.

7. Atualização automática do sistema

A cada atualização do sistema, normalmente é necessário substituir o executável no computador do cliente, rodar scripts SQL e alterar parâmetros de configuração. Sendo assim, é preciso ir até o cliente e realizar todo o processo manualmente no computador local. Para evitar a viagem, uma alternativa é realizar a atualização remotamente através de softwares como o TeamViewer, LogMeIn ou VNC. Porém, é bastante inconveniente quando há dezenas ou até centenas de computadores para serem atualizados. Se a atualização for feita em um computador por vez, pode demorar dias para terminar a atualização em todos as máquinas.

Portanto, ao invés de realizar a atualização manualmente, crie um módulo exclusivo para automatizar este processo. O objetivo é permitir que o próprio sistema verifique novas versões, baixe o arquivo de atualização e realize todo o processo automaticamente, sem a intervenção do usuário. Observe que a atualização automática está presente na maioria dos softwares atuais, como navegadores e antivírus. Este tipo de módulo não é simples de ser desenvolvido, mas é plenamente funcional e reduz o trabalho de atualizar vários computadores simultaneamente.

Exemplo de tela de atualizaçãoExemplo de tela de atualização

8. Normalização de dados

Na maioria dos cursos relacionados a desenvolvimento de sistemas, é comum encontrar uma disciplina que mencione a Normalização de Dados, fundamental para a formação de analistas e programadores. Este assunto sugere uma série de procedimentos aplicados à modelagem de dados para garantir a boa estruturação de um banco de dados.

Essa modelagem é responsável pela integridade, confiabilidade e desempenho das operações realizadas nas tabelas. Alguns dos pontos mais importantes abordados pela normalização de dados envolve a utilização imprescindível de chaves primárias, chaves estrangeiras, criação de tabelas intermediárias para relacionamentos muitos-para-muitos (N:N) e criação de tabelas para campos multivalorados, como e-mails e telefones. Além de ser uma prática essencial para o projeto de um sistema, a normalização de dados também garante a motivação dos relacionamentos entre as tabelas e facilita futuras manutenções na estrutura.

A modelagem do banco de dados de um sistema devem passar basicamente por três formas normais da normalização de dados, que consistem em eliminar campos repetitivos entre tabelas, impedir valores redundantes e relacionar as tabelas por meio de chaves estrangeiras. É muito importante estudar a aplicar este conceito dentro do desenvolvimento de um sistema.

9. Integração com serviços Web

Com o crescimento da internet e de recursos online, tornou-se comum a integração de sistemas desktop com serviços web para facilitar ou agilizar operações. Um exemplo bem prático é o envio de arquivos de Nota Fiscal Eletrônica. Nos primórdios de sua utilização, os usuários geravam um arquivo XML pelo sistema, acessavam outro aplicativo para envio do arquivo, validavam o XML e por fim imprimiam a DANFE. Todo este processo era trabalhoso e gerava dificuldades para os usuários.

Felizmente, a integração dos sistemas desktop com os chamados WebServices ou APIs possibilitou que este procedimento fosse realizado diretamente pela aplicação principal, sem a intervenção do usuário para manipular os arquivos XML. Além deste exemplo, outros serviços web também podem ser agregados à aplicação, como consulta de endereços por CEP, mapas de localização (Google Maps), feed de notícias e outros tipos de informações online. A imagem abaixo ilustra o funcionamento do WebService da Serasa para consulta de CPF:

WebService - Consulta de CPFFonte: http://www.consultacpf.com/integracao.aspx

10. Triggers e Stored Procedures

Implementar algumas regras de negócio no banco de dados pode trazer grandes vantagens para a aplicação, tanto no sentido de automação de operações como no desempenho. Na verdade, Triggers e Stored Procedures nunca deixaram de ser recursos essenciais no desenvolvimento de um software. Vou citar apenas uma das vantagens: em uma aplicação Cliente/Servidor ou Multicamadas, criar Triggers e Stored Procedures no banco de dados evita que várias chamadas sejam feitas ao servidor.

Imagine que, ao excluir um pedido, seja necessário também excluir os itens do pedido (no sentido master/detail). Pela aplicação, faríamos duas solicitações ao banco de dados: uma para excluir os itens e outra pra excluir o pedido em questão, certo? Pois bem, se centralizarmos essa operação em uma Trigger no banco de dados, apenas uma solicitação será feita, ou seja, a Trigger se encarregará de excluir os itens antes de excluir o pedido, tornando-se um processo automatizado.

Para aplicações de pequeno porte pode não surtir tanta diferença, mas em uma aplicação com vários usuários conectados simultaneamente a vantagem é notável, inclusive pelo motivo de redução de tráfego na rede.
Só para efeito de conhecimento, segue o exemplo da criação de uma Trigger no Firebird:

11. Busca por fonema

Essa dica é bem interessante, embora a implementação dessa funcionalidade seja complexa. Antes de continuar, vale lembrar que um fonema, grosso modo, é a unidade de som produzida ao pronunciar uma determinada letra. Por exemplo, na língua portuguesa, as letras “i” e “y” possuem o mesmo som (mesmo fonema), e isso causa alguns problemas na busca de dados em um aplicativo. Sabe por quê?

Imagine que o sistema tenha vários clientes cadastrados, e três deles tenham o nome de Érica, Erica e Érika, respectivamente. Ao atender a ligação de uma dessas clientes, o usuário solicita o nome, ouve, e digita “Erica” (sem acento) no campo de dados para localizar o registro. Obviamente, apenas um dos nomes aparecerá na tela. Porém, a cliente que ligou é a Érika (com acento e com “k”)! Já que o resultado não apareceu na busca, o usuário irá informar que ela não está cadastrada no sistema.

Busca por fonema consiste em agrupar letras que tenham o mesmo som em uma mesma consulta. No exemplo acima, ao digitar “Erica”, os três registos seriam exibidos na tela, já que o acento e os fonemas de “c” e “k”, neste caso, produzem o mesmo som. Como disse anteriormente, a implementação é complexa e portanto leva muitos desenvolvedores a procurarem por soluções já codificadas.

12. Dicas de tela

As dicas de tela, também conhecidas como Hints, são textos que aparecem quando você posiciona o cursor do mouse em um componente da tela, como um campo ou botão. Esses textos trazem uma breve definição da função do componente, extremamente útil para barras de ferramentas que possuem apenas imagens nos botões.

Exemplo de Hint

O problema é que às vezes esses hints não estão disponíveis. Eu já fui vítima de colocar o cursor em um botão e ficar um bom tempo esperando o hint aparecer, haha. Por isso, procure adicionar hints bem explicativos na maioria dos componentes visuais da tela. Uma dica dessas pode evitar que o usuário execute uma operação incorreta ou entre em contato com o suporte para questionar sobre a funcionalidade.

13. ENTER ao invés de TAB?

Eis que chegamos a mais um dilema entre programadores…

Na minha opinião, o ENTER é uma tecla de confirmação, e não de entrada de dados. Partindo deste raciocínio, eu sempre configuro o ENTER para simular o clique de um botão, confirmar uma mensagem ou selecionar um registro, enquanto o TAB avança entre os campos. Observe que a tecla TAB já possui essa funcionalidade de modo tradicional, utilizada em vários outros sistemas, como o próprio Windows. Inclusive, para retroceder um campo, basta pressionar SHIFT + TAB, o que não é possível com o ENTER. Além disso, reflita: no campo de observações em uma tela de cadastro, o ENTER deverá pular a linha ou avançar para o próximo campo? 🙂

14. Favoritos

Quando possível, adicione uma barra de ferramentas com botões de acesso para as telas mais utilizadas, evitando que o usuário tenha que percorrer vários submenus para abri-las. Essa alternativa torna-se ainda melhor se a barra de ferramentas for personalizável, ou seja, permitir que o próprio usuário escolha os botões que ficarão disponíveis na barra. Estes atalhos são muito úteis em sistemas que integram dois ou mais departamentos de uma empresa, já que em cada departamento as telas acessadas com mais frequência são diferentes.

Vale lembrar que alguns softwares mais modernos apresentam menus do tipo Ribbon, parecidos com as versões mais recentes do Microsoft Office. Esse tipo de menu geralmente é personalizável, ou seja, permite que o próprio usuário “crie” uma aba e selecione os botões desejados.

15. Uso de mensagens com moderação

Excesso de Mensagens

As mensagens acima são irônicas, mas na realidade alguns sistemas parecem fazer uma entrevista com o usuário: para qualquer operação existe uma tela de confirmação, até para as operações mais rotineiras. Mensagens são necessárias, mas não em excesso. Há operações que são óbvias, e não precisam ter uma mensagem de informação ou confirmação para serem realizadas. É claro que isso não irá afetar o desempenho do software, mas já me deparei com muitos usuários que solicitaram a remoção de algumas mensagens pelo motivo de atrapalhar a usabilidade.

Se a intenção é exibir uma mensagem informativa, considere exibi-la no formulário de forma estática, utilizando uma Label, por exemplo. Mensagens de validação também podem ser substituídas por balões com hints (hint balloons) que, além de não exigir interação do usuário (para pressionar o OK), também são bem modernos.

Só para complementar, já trabalhei com sistemas que não exibem mensagem alguma ao gravar um registro, apenas limpam os campos para a inserção de novos dados. Se for uma tela que permite inserções sucessivas, essa modalidade pode ser bem adequada. Basta orientar o usuário de que, se o sistema “limpar” o conteúdo dos campos ao gravar o registro, significa que foi gravado com sucesso.

16. Formulários integrados

Vamos supor que, após aplicar a normalização de dados, você criou um cadastro de cidades no software com o objetivo de registrar as cidades utilizadas em outras tabelas, como clientes, fornecedores e funcionários. Assim sendo, no cadastro de clientes, por exemplo, haverá uma lista de opções (combobox) para que o usuário selecione a cidade do cliente, correto?

Pois bem, imagine que o usuário está preenchendo os dados de um novo cliente e ao selecionar a cidade… ops, a cidade não está cadastrada!
Como são telas diferentes, o usuário terá que:

  • Fechar a tela de cadastro de clientes;
  • Abrir a tela de cadastro de cidades;
  • Cadastrar a cidade;
  • Voltar na tela de clientes;
  • Preencher os dados novamente.

Chato, não?

Para simplificar este procedimento, procure “integrar” os cadastros, adicionando um botão próximo ao campo para abrir a tela quando necessário. No exemplo acima, adicionaríamos um “atalho” ao lado do campo “Cidade”, permitindo o cadastro de uma nova cidade sem a necessidade de fechar o cadastro de clientes.

Formulários integrados

17. Manter informações essenciais visíveis

Talvez seja necessário ativar a barra de rolagem horizontal para que o usuário veja todas as colunas visíveis em uma Grid. Em algumas ocasiões, essa barra de rolagem pode se tornar algo inconveniente, principalmente se a coluna for bastante utilizada pelo usuário.
Imagine que há 10 colunas em uma Grid e, ao abrir a tela, somente 4 delas são exibidas – para visualizar as outras, deve-se mover a barra de rolagem. Se estas forem colunas importantes, é bem provável que o usuário irá visualizá-las na maioria das vezes que abrir a tela, não é? Então, por que não facilitar essa visualização?

Uma das formas é fixar um painel com caixas de texto ou Labels logo abaixo da Grid, exibindo as informações importantes. Conforme o usuário navegar entre os registros, o texto destes componentes irão receber os dados do registro atual. Veja a imagem de exemplo abaixo:

Informações adicionais do registro no painel inferior

Outra forma é permitir que o usuário reposicione e redimensione as colunas, ou seja, arraste as colunas desejadas para a área visível da Grid. Em seguida, a aplicação armazena a posição dessas colunas em um arquivo de configuração. Dessa forma, todas as vezes que o usuário abrir a tela, basta ler o arquivo e ajustar a posição e tamanho das colunas conforme configurados previamente pelo usuário.

18. Menu pop-up

Você já notou que, ao clicar com o botão direito na maioria dos aplicativos, é mostrado um menu suspenso com funções genéricas? Normalmente são funções de uso frequente, como o Copiar, Colar, Recortar e Selecionar Tudo. Podemos empregar essa mesma diretriz e disponibilizar algumas operações corriqueiras em um menu pop-up (suspenso) em nosso software.

Certa vez, em um software que desenvolvi, notei que para gerar um relatório detalhado de um cliente, os usuários precisavam acessar uma tela específica, consultar o registro, selecionar alguns parâmetros e clicar em um botão. Na busca por melhorar a usabilidade, associei um menu pop-up à Grid de dados no cadastro de clientes. Dessa forma, ao clicar com o botão direito no registro selecionado, o usuário não apenas tinha acesso direto ao relatório, mas também às funções de edição, exclusão e cópia de dados sem a necessidade de acessar outras telas.

Menu pop-up

Menus pop-up são como atalhos que evitam a necessidade de realizar acessos intermediários. Considere a própria ferramenta de desenvolvimento que você utiliza como um exemplo. Clique com o botão direito no editor de código e confira a série de recursos disponibilizados para elevar a produtividade e reduzir o tempo de implementação. Eu, particularmente, uso bastante. Sendo assim, pense também na facilidade que estes menus proporcionariam nas telas do seu software e faça com que a produtividade dos seus usuários também seja aprimorada.

19. Número máximo de caracteres e intervalo numéricos

Dica básica, bem básica, mas que muitas vezes passa por entre os dedos dos desenvolvedores. Quando você deixa de configurar o tamanho máximo em um campo e o usuário digita um valor maior do que o permitido, podem ocorrer duas situações: o valor é “cortado” e gravado pela metade no banco de dados, ou o usuário recebe uma exceção semelhante ao “string truncation” do Firebird. Erros dessa natureza são bastante comuns, já que o usuário não sabe o limite de caracteres que podem ser digitados em um campo.

A definição do número máximo de caracteres pode ser realizada através das propriedades dos próprios componentes em tempo de projeto ou execução. No Delphi e C#, a propriedade se chama MaxLength. No Java, uma das alternativas é utilizar um Document personalizado, sobrescrevendo o método insertString.

Para valores numéricos a situação pode ser um pouco diferente. Suponha que você tenha um campo que represente a porcentagem de desconto em uma venda e esqueceu de estabelecer o intervalo entre 0% e 100%. Por um golpe de distração, o usuário digita 250% e grava os dados. Mesmo que seja um valor aceitável pelo banco de dados, não faz sentido que exista um desconto de 250%, concorda? Quando a porcentagem envolve cálculos importantes, é fundamental ter uma validação da faixa de valores.

20. Uso de constantes para evitar inconsistências

Algumas vezes fui questionado sobre a finalidade de se utilizar constantes no código-fonte. Em uma delas, o desenvolvedor disse que sempre usou variáveis, pois a única diferença é que as constantes não permitem que o seu conteúdo seja alterado, e este não era o seu caso. Sim, ele está correto. Mas a questão é que as constantes têm um motivo por não aceitarem a alteração de valores, ou seja, serem read only (somente leitura).

Considere um software desenvolvido para uma indústria de móveis. Nós sabemos que em determinadas épocas do ano há uma variação do valor do IPI, certo? Também sabemos que o IPI é utilizado em vários pontos do software, como pedidos, vendas, orçamentos, cálculo de preços e margens de lucro. Para controlar isso, suponha que os desenvolvedores decidiram colocar o valor do IPI manualmente em cada parte do código.

Agora imagine que no mês seguinte o valor do IPI baixou para 5%. Espere… isso significa que os desenvolvedores terão que alterar este valor em cada ponto do software que o utiliza? E se esquecerem de alterá-lo no formulário de pedidos? Bom, se isso ocorrer, o orçamento será emitido com acréscimo de 5% e o pedido será faturado com acréscimo de 10%. Nem preciso dizer o problema que vai dar, não é?
Constantes são úteis para casos como este. Basta concentrar o valor em um único local e utilizar a constante que o representa.

Extra: Design Patterns

Os Design Patterns, ou Padrões de Projeto, como são conhecidos, são conceitos ou modelos orientados a objetos visando solucionar problemas no desenvolvimento de softwares. Estes padrões possuem finalidades particulares que podem ser aplicadas para controlar a estrutura, a criação e o comportamento das classes e dos objetos em uma aplicação. Dependendo da situação em que esses projetos forem aplicados, é possível notar uma redução considerável na complexidade do software em virtude da reutilização de código-fonte e de componentes.

Apesar de existir 23 padrões de projeto, é praticamente inviável implementar todos eles em uma única solução, afinal, utilizar padrões de projeto sem um propósito é uma má prática. É preciso haver um motivo real para a implementação, ou seja, uma situação em que se pode comprovar de que o padrão de projeto será uma solução adequada para o problema. Caso contrário, a implementação pode aumentar a complexidade do código-fonte e afetar também o desempenho da aplicação.

Uma das dúvidas mais frequentes relacionadas a padrões de projeto é saber onde, quando e como utilizá-los. Em primeiro lugar, o engenheiro de software deve ter sólidos conhecimentos em Programação Orientada a Objetos e um bom nível de abstração, ou seja, a Orientação a Objetos é a base essencial para compreender os padrões de projeto.

Em segundo lugar, é necessário conhecer o objetivo principal de cada padrão de projeto para que seja possível fazer um estudo da viabilidade buscando solucionar um problema no software. Porém, mesmo com esse conhecimento técnico, é comum alguns engenheiros não conseguirem identificar as situações ou os módulos que devem receber a implementação dos padrões.

Portanto, em terceiro lugar, o profissional também deve ter um domínio satisfatório da regra de negócio do cliente. A consolidação de todas essas experiências é o que permite a seleção e a aplicação consciente dos padrões de projeto no desenvolvimento do software.

 

Bom, fico por aqui, pessoal!
Um abraço!


André Celestino