A pouco tempo escrevi a respeito do projeto Nubular, popularmente conhecido como Nu. Esse projeto visa criar uma solução para o gerenciamento de pacotes em .NET.
Hoje a Microsoft anunciou o release de uma prévia do projeto NuPack. Na verdade, o NuPack é um esforço conjunto da Microsoft com os desenvolvedores do projeto Nubular. Tanto a Microsoft quanto os desenvolvedores do Nu estavam tentando resolver o mesmo problema, sendo assim, juntaram esforços.
O projeto foi aceito pela fundação Outercurve, antiga fundação Codeplex (não confundir com o Codeplex.com). O mais interessante é que esse é um projeto OpenSource no qual a Microsoft participa mas que teve origem na própria comunidade. A gigante de Redmond já lançou diversos projetos OpenSource, mas normalmente estes não aceitam contribuições externas. O NuPack tem contribuições de pessoas de dentro e fora da Microsoft.
O Scott Hanselman descreveu em detalhes o funcionamento do projeto, veja mais detalhes aqui.
No vídeo abaixo pode-se observar o funcionamento do NuPack:
quarta-feira, 6 de outubro de 2010
quarta-feira, 15 de setembro de 2010
Usando RubyGems atrás de um proxy
Para usar o RubyGems ou o Nu (projeto que falei a respeito a algum tempo) atrás de um proxy é bastante simples, basta definir a variável de ambiente HTTP_PROXY com o endereço do servidor.
A configuração fica um pouco mais complicada quando o proxy é o Microsoft ISA Server e requer autenticação NTLM. Nesse caso, alguns passos adicionais são necessários.
O próximo passo é copiar o arquivo “spa.rb” dessa gem para o diretório “site_ruby”. Os caminhos serão parecidos com estes:
O original estava assim:
E, depois da alteração ficará assim:
Pronto, com isso já será possível atualizar as gems.
Se você utiliza o Nu, pode alterar o Nu.bat adicionando o parâmetro “-rspa” da mesma forma que foi feito no “gem.bat”.
A configuração fica um pouco mais complicada quando o proxy é o Microsoft ISA Server e requer autenticação NTLM. Nesse caso, alguns passos adicionais são necessários.
Configurando o proxy para usar autenticação NTLM
Antes de mais nada, defina o endereço do proxy na variável de ambiente HTTP_PROXY, conforme mostrado anteriormente. A seguir, instale a “gem” rubysspi. É necessário baixá-la e instalar manualmente direto do disco.O próximo passo é copiar o arquivo “spa.rb” dessa gem para o diretório “site_ruby”. Os caminhos serão parecidos com estes:
- Origem: C:\Ruby192\lib\ruby\gems\1.9.1\gems\rubysspi-1.3.1\spa.rb
- Destino: C:\Ruby192\lib\ruby\site_ruby\1.9.1\
O original estava assim:
@"%~dp0ruby.exe" "%~dpn0" %*
@"%~dp0ruby.exe" -rspa "%~dpn0" %*
Se você utiliza o Nu, pode alterar o Nu.bat adicionando o parâmetro “-rspa” da mesma forma que foi feito no “gem.bat”.
Marcadores:
Desenvolvimento
segunda-feira, 13 de setembro de 2010
Como funciona a autenticação OAuth
Nos últimos anos, com a tão aclamada Web 2.0 muitos sites passaram a fornecer APIs que podem ser usadas por terceiros para agregar valor ao sistema. Sites como Twitter e Facebook devem grande parte de seu sucesso pelo fato de ter essa possibilidade de extensão.
Nesses sites normalmente a API é acessada "em nome" de um usuário. Ou seja, é necessário ter um usuário autenticado para obter os dados. Vou tomar como exemplo o Twitter, que é bem conhecido de todos. Para que uma aplicação possa enviar um tweet em nome do usuário, ela precisa do login e senha dele.
Agora vem a questão, quem gosta de compartilhar sua senha com os outros? Mesmo que o outro seja uma aplicação. E, ao alterar sua senha, você deseja ir em cada aplicativo e reconfigurá-lo?
Para resolver esse problema surgiu o protocolo de autenticação OAuth.
Basicamente, a aplicação pede permissão de acesso para aquele usuário e o usuário concede ou não a permissão, sem que para isso tenha que informar a senha. Essa permissão independe da senha. Mesmo que a senha seja alterada a permissão continuará válida. Além disso, a permissão dada à aplicação cliente pode ser revogada a qualquer momento.
Eu tentei explicar o funcionamento em um nível bem alto, sem entrar em detalhes técnicos. Para uma explicação mais aprofundada recomendo a leitura deste link e deste. Deixei de lado aspectos como criptografia e HTTPS, focando basicamente no fluxo da autenticação. Então vamos lá...
A autenticação por meio do OAuth consiste de três passos:
Nesse momento a aplicação cliente solicita à aplicação servidora uma chave de autenticação. A aplicação servidora devolve duas chaves, uma pública e uma privada. Repare que essas chaves não dão permissão de acesso, elas são usadas apenas durante o processo de autenticação.
A seguir, a aplicação cliente redireciona o usuário para a aplicação servidora usando a chave pública (no caso do TwitPic, por exemplo, o usuário vai para o site do Twitter).
Assim que o usuário efetuou a autorização, ele é redirecionado de volta à aplicação cliente.
Repare que, para se comunicar com a aplicação servidora é utilizada a chave privada, desse modo, apenas a aplicação cliente consegue obter a chave de acesso.
A chave de acesso obtida é utilizada pelo cliente para acessar a API do servidor “em nome” do usuário.
O usuário deve ter cuidado ao autorizar qualquer aplicação cliente. Ele deve dar autorização apenas às aplicações que ele confia, pois com essa autorização, uma aplicação mal intencionada pode fazer tantos estragos quanto faria se o usuário passasse sua senha.
Um detalhe importante é revogar a autorização de aplicações que não são mais utilizadas ou de origem duvidosa. No Twitter, por exemplo, pode-se acessar esta página para remover a permissão de acesso.
Aproveite que você está lendo isso e olhe sua lista de aplicativos com acesso permitido ao Twitter, provavelmente alguns deles você nem lembra mais que existem.
Nesses sites normalmente a API é acessada "em nome" de um usuário. Ou seja, é necessário ter um usuário autenticado para obter os dados. Vou tomar como exemplo o Twitter, que é bem conhecido de todos. Para que uma aplicação possa enviar um tweet em nome do usuário, ela precisa do login e senha dele.
Agora vem a questão, quem gosta de compartilhar sua senha com os outros? Mesmo que o outro seja uma aplicação. E, ao alterar sua senha, você deseja ir em cada aplicativo e reconfigurá-lo?
Para resolver esse problema surgiu o protocolo de autenticação OAuth.
Objetivo
O principal objetivo do OAuth é permitir que uma aplicação se autentique em outra "em nome de um usuário", sem precisar ter acesso a senha dele.Basicamente, a aplicação pede permissão de acesso para aquele usuário e o usuário concede ou não a permissão, sem que para isso tenha que informar a senha. Essa permissão independe da senha. Mesmo que a senha seja alterada a permissão continuará válida. Além disso, a permissão dada à aplicação cliente pode ser revogada a qualquer momento.
Funcionamento da autenticação OAuth
Pesquisei na internet a respeito do funcionamento dessa autenticação e, na maioria das vezes, a explicação é bem técnica. Geralmente é explicado apenas como usar determinadas bibliotecas.Eu tentei explicar o funcionamento em um nível bem alto, sem entrar em detalhes técnicos. Para uma explicação mais aprofundada recomendo a leitura deste link e deste. Deixei de lado aspectos como criptografia e HTTPS, focando basicamente no fluxo da autenticação. Então vamos lá...
A autenticação por meio do OAuth consiste de três passos:
- Aplicação cliente obtém chave de autenticação;
- Usuário autoriza aplicação cliente na aplicação servidora;
- Aplicação cliente troca a chave de autenticação pela chave de acesso;
1 - Aplicação cliente obtendo chave de autenticação
No primeiro passo o usuário inicia o processo de autenticação. Por exemplo, no site TwitPic ele clicaria no botão “Login or Create an Account”.Nesse momento a aplicação cliente solicita à aplicação servidora uma chave de autenticação. A aplicação servidora devolve duas chaves, uma pública e uma privada. Repare que essas chaves não dão permissão de acesso, elas são usadas apenas durante o processo de autenticação.
A seguir, a aplicação cliente redireciona o usuário para a aplicação servidora usando a chave pública (no caso do TwitPic, por exemplo, o usuário vai para o site do Twitter).
2 - Usuário autorizando o acesso
Depois que o usuário foi redirecionado para a aplicação servidora, ele autoriza o acesso da aplicação cliente. No Twitter isso ocorre ao clicar em “Allow”.Assim que o usuário efetuou a autorização, ele é redirecionado de volta à aplicação cliente.
3 - Aplicação cliente trocando a chave de autenticação pela chave de acesso
Nesse momento, a aplicação cliente troca a chave de autenticação pela chave de acesso. A chave de acesso só é concedida se o usuário autorizar o acesso.Repare que, para se comunicar com a aplicação servidora é utilizada a chave privada, desse modo, apenas a aplicação cliente consegue obter a chave de acesso.
A chave de acesso obtida é utilizada pelo cliente para acessar a API do servidor “em nome” do usuário.
Cuidados e perigos
Um dos perigos desse protocolo é uma questão cultural. Como o usuário não fornece a senha ele se sente seguro. Ele muitas vezes não se dá conta que está dando acesso a aplicação cliente, mesmo sem informar a senha.O usuário deve ter cuidado ao autorizar qualquer aplicação cliente. Ele deve dar autorização apenas às aplicações que ele confia, pois com essa autorização, uma aplicação mal intencionada pode fazer tantos estragos quanto faria se o usuário passasse sua senha.
Um detalhe importante é revogar a autorização de aplicações que não são mais utilizadas ou de origem duvidosa. No Twitter, por exemplo, pode-se acessar esta página para remover a permissão de acesso.
Aproveite que você está lendo isso e olhe sua lista de aplicativos com acesso permitido ao Twitter, provavelmente alguns deles você nem lembra mais que existem.
Marcadores:
Desenvolvimento,
Internet,
Segurança
terça-feira, 7 de setembro de 2010
Páginas lentas no Firefox e Chrome ao abrir o sistema pelo Visual Studio
Aí vai uma dica simples para quem está desenvolvendo no Visual Studio usando o Web Server integrado (ASP.NET Development Server, também conhecido como Cassini).
Se você notou que ao acessar as páginas do sistema utilizando o Chrome ou Firefox elas estão lentas, demoram um segundo para abrir cada página, há uma solução muito simples para isso.
Edite o arquivo “C:\Windows\System32\drivers\etc\hosts” e adicione ao final dele a seguinte linha:
Pronto, problema resolvido. Reinicie o browser e ele já deverá estar muito mais rápido.
Se você notou que ao acessar as páginas do sistema utilizando o Chrome ou Firefox elas estão lentas, demoram um segundo para abrir cada página, há uma solução muito simples para isso.
Edite o arquivo “C:\Windows\System32\drivers\etc\hosts” e adicione ao final dele a seguinte linha:
127.0.0.1 localhost
Marcadores:
Desenvolvimento,
Visual Studio
domingo, 29 de agosto de 2010
O que define a qualidade do software?
Muito se fala em qualidade de software, mas afinal, o que determina se um software tem qualidade?
Lendo um artigo do Phil Haack essa questão veio novamente à tona. No artigo ele diz (tradução livre): “Nós não estamos aqui para escrever software, estamos aqui para desenvolver produtos e entregar valor. Escrever código é só um meio para esse fim”.
Nessa frase ele vai direto ao foco da questão, escrever o software não é o objetivo, é apenas um meio para prover valor ao cliente. O cliente não paga pelo software em si, mas pelo benefício que o software proporciona.
Nós como desenvolvedores muitas vezes esquecemos que a informática é apenas um meio. Ela por si só, não tem valor algum. O que tem valor é o que a informática pode fornecer às outras áreas e mais especificamente ao usuário.
Mas então, sabendo que o software por si só não tem valor, o que define a sua qualidade? Para responder isso eu separaria a qualidade do software em duas categorias:
Para definir a qualidade do software precisamos entender a necessidade do usuário e com isso verificar se o software está atendendo de forma eficiente e eficaz.
Muitos fatores contribuem para que o código possa ser considerado de qualidade. Alguns deles são:
Podemos argumentar que um software que não será evoluído não precisa ter um bom código, basta atender ao cliente. Certo? Certo!! Porém, quantas vezes podemos ter a certeza que o software realmente não continuará a ser desenvolvido após a primeira versão? Quantas vezes um sistema começa pequeno e quando vemos ele já está gigante e totalmente desorganizado?
Acredito que ter um software bem escrito vale a pena, mesmo quando o sistema só terá uma versão. Um software bem escrito normalmente apresenta menos bugs, o que por fim melhora a percepção do cliente e reduz o custo de retrabalho.
Precisamos ver que a qualidade do código acaba refletindo na qualidade do produto. Um software com difícil manutenção ou com bugs acabará reduzindo o valor provido ao cliente.
A questão é balancear os diversos fatores, tentado otimizar ao máximo os recursos para obtenção da qualidade máxima. E não esquecer de dar a devida atenção a qualidade do código, que influencia na qualidade do produto.
Lendo um artigo do Phil Haack essa questão veio novamente à tona. No artigo ele diz (tradução livre): “Nós não estamos aqui para escrever software, estamos aqui para desenvolver produtos e entregar valor. Escrever código é só um meio para esse fim”.
Nessa frase ele vai direto ao foco da questão, escrever o software não é o objetivo, é apenas um meio para prover valor ao cliente. O cliente não paga pelo software em si, mas pelo benefício que o software proporciona.
Nós como desenvolvedores muitas vezes esquecemos que a informática é apenas um meio. Ela por si só, não tem valor algum. O que tem valor é o que a informática pode fornecer às outras áreas e mais especificamente ao usuário.
Mas então, sabendo que o software por si só não tem valor, o que define a sua qualidade? Para responder isso eu separaria a qualidade do software em duas categorias:
- qualidade do produto;
- qualidade do código.
Qualidade do produto
Considerando que o software é um produto que visa prover valor ao usuário, a sua qualidade nada mais é que o atendimento das necessidades do seu usuário. Um software de qualidade é aquele que faz, de maneira correta, o que o cliente precisa que ele faça.Para definir a qualidade do software precisamos entender a necessidade do usuário e com isso verificar se o software está atendendo de forma eficiente e eficaz.
Qualidade do código
No lado técnico do software, podemos analisar sua qualidade pensando na capacidade de continuar provendo valor ao cliente no decorrer do tempo. Ou seja, o software poderá atender as futuras expectativas do cliente? Poderá continuar evoluindo de maneira eficiente?Muitos fatores contribuem para que o código possa ser considerado de qualidade. Alguns deles são:
- está legível e bem documentado – o desenvolvedor entende facilmente o que está acontecendo;
- tem baixa complexidade – deve ser fácil entender como ocorre a execução do código. Partes complexas devem ser destrinchadas em partes menores e mais simples (vide complexidade fabrica);
- há testes – ao desenvolver novas funcionalidades podemos verificar de forma fácil que as antigas continuarão funcionando;
- o sistema pode ser estendido – alterar um código existente sempre pode gerar problemas. Um sistema construído de forma extensível sempre será melhor para se trabalhar (vide Open/closed principle).
Podemos argumentar que um software que não será evoluído não precisa ter um bom código, basta atender ao cliente. Certo? Certo!! Porém, quantas vezes podemos ter a certeza que o software realmente não continuará a ser desenvolvido após a primeira versão? Quantas vezes um sistema começa pequeno e quando vemos ele já está gigante e totalmente desorganizado?
Acredito que ter um software bem escrito vale a pena, mesmo quando o sistema só terá uma versão. Um software bem escrito normalmente apresenta menos bugs, o que por fim melhora a percepção do cliente e reduz o custo de retrabalho.
Balanceando os diversos fatores
No desenvolvimento de software sempre temos que balancear diversos fatores. Custos, prazos, expectativas do cliente, tecnologias envolvidas, etc. A qualidade do produto é inegociável, afinal, é por isso que o cliente está pagando. Mas, infelizmente, nem sempre a qualidade do código tem a devida atenção.Precisamos ver que a qualidade do código acaba refletindo na qualidade do produto. Um software com difícil manutenção ou com bugs acabará reduzindo o valor provido ao cliente.
A questão é balancear os diversos fatores, tentado otimizar ao máximo os recursos para obtenção da qualidade máxima. E não esquecer de dar a devida atenção a qualidade do código, que influencia na qualidade do produto.
Marcadores:
Desenvolvimento
Nu – gerenciamento de pacotes em .NET
UPDATE: o projeto Nu deu lugar ao NuPack, que conta com o apoio da Microsoft. Veja maiores informações aqui.
Quando desenvolvi pela primeira vez em Ruby, uma das coisas que mais me chamaram a atenção foi o gerenciamento de pacotes. Com o gem (gerenciador de pacotes do Ruby), instalar um pacote é simples assim:
Na plataforma .NET as coisas são um pouco mais complicadas. Para obter uma biblioteca normalmente temos que passar por uma série de passos:
Recentemente surgiu a idéia: se o gem funciona tão bem, por quê não utilizá-lo na plataforma .NET? O projeto Nubular (ou simplesmente Nu) surgiu justamente para permitir que bibliotecas .NET usem o gem.
Primeiro instale o Ruby. Nesse link você pode fazer o download do instalador. Eu testei com a versão 1.8.7 que pode ser baixada diretamente aqui.
Para facilitar, adicione ao PATH (nas variáveis de ambiente) o diretório do Ruby.
Agora, instale o RubyGems. É só fazer o download do ZIP nesse link, descompactar, via linha de comando acessar o diretório descompactado e executar o seguinte comando:
Pronto, é só isso.
Onde “meu_pacote” é o nome da biblioteca a ser instalada.
Veja na imagem abaixo o StructureMap sendo adicionado ao projeto.
Ao instalar um pacote que tem dependências de outros pacotes, as dependências serão automaticamente instaladas. Ao instalar o FluentNHibernate, por exemplo, outros pacotes como o log4net, NHibernate e o Castle também são instalados.
Repare que na pasta do projeto foram adicionados os componentes e todas as suas dependências.
Imagine a seguinte situação: uso a biblioteca A na versão 1.0 e a B na versão 2.0, porém a biblioteca A também usa a biblioteca B, mas na versão 1.5. Ou seja, eu preciso da biblioteca A v2.0 e a biblioteca B precisa da biblioteca A v1.5.
Esse é um problema que não tem como ser resolvido pelo simples gerenciamento de pacotes. Para resolver problemas assim, normalmente temos que usar binding redirect ou acabar recompilando o código.
Para maiores informações e para saber como criar seu pacote, acesse o site do projeto. No Herding Code (podcast a respeito de software) também há uma entrevista com os desenvolvedores do projeto.
Quando desenvolvi pela primeira vez em Ruby, uma das coisas que mais me chamaram a atenção foi o gerenciamento de pacotes. Com o gem (gerenciador de pacotes do Ruby), instalar um pacote é simples assim:
gem install meu_pacote
Na plataforma .NET as coisas são um pouco mais complicadas. Para obter uma biblioteca normalmente temos que passar por uma série de passos:
- Procurar o site que contém a biblioteca;
- No site, achar o link para download da versão que queremos;
- Fazer o download;
- Descompactar a biblioteca;
- Copiar a biblioteca para o projeto.
Recentemente surgiu a idéia: se o gem funciona tão bem, por quê não utilizá-lo na plataforma .NET? O projeto Nubular (ou simplesmente Nu) surgiu justamente para permitir que bibliotecas .NET usem o gem.
Instalando o RubyGems
Para usar o Nu é necessário ter o Ruby (ou o IronRuby) e o RubyGems. Se você já tem, pode pular par ao próximo tópico.Primeiro instale o Ruby. Nesse link você pode fazer o download do instalador. Eu testei com a versão 1.8.7 que pode ser baixada diretamente aqui.
Para facilitar, adicione ao PATH (nas variáveis de ambiente) o diretório do Ruby.
Agora, instale o RubyGems. É só fazer o download do ZIP nesse link, descompactar, via linha de comando acessar o diretório descompactado e executar o seguinte comando:
ruby setup.rb
INSTALANDO O nu
Com o Ruby e o RubyGems devidamente instalados, instalar o Nu é simples, basta executar o seguinte comando:gem install nu
Usando o Nu
Para adicionar uma biblioteca ao seu projeto, acesse o diretório do projeto e execute o seguinte comando:nu install meu_pacote
Onde “meu_pacote” é o nome da biblioteca a ser instalada.
Veja na imagem abaixo o StructureMap sendo adicionado ao projeto.
Ao instalar um pacote que tem dependências de outros pacotes, as dependências serão automaticamente instaladas. Ao instalar o FluentNHibernate, por exemplo, outros pacotes como o log4net, NHibernate e o Castle também são instalados.
Repare que na pasta do projeto foram adicionados os componentes e todas as suas dependências.
O que ele não tenta resolver
Ao usar diversas bibliotecas, cedo ou tarde acabamos enfrentando o famoso problema apelidado de DLL Hell.Imagine a seguinte situação: uso a biblioteca A na versão 1.0 e a B na versão 2.0, porém a biblioteca A também usa a biblioteca B, mas na versão 1.5. Ou seja, eu preciso da biblioteca A v2.0 e a biblioteca B precisa da biblioteca A v1.5.
Esse é um problema que não tem como ser resolvido pelo simples gerenciamento de pacotes. Para resolver problemas assim, normalmente temos que usar binding redirect ou acabar recompilando o código.
Concluindo…
O Nu realmente facilita muito o gerenciamento de pacotes, agora resta aguardar para ver como será sua adoção. Nesse pequeno tempo de vida já há dezenas de projetos disponibilizados via Nu. Na página do projeto há uma lista deles.Para maiores informações e para saber como criar seu pacote, acesse o site do projeto. No Herding Code (podcast a respeito de software) também há uma entrevista com os desenvolvedores do projeto.
Marcadores:
Desenvolvimento
sábado, 26 de junho de 2010
Que preguiça de pesquisar!!!
É incrível o número de perguntas repetidas que encontro ao fazer uma pesquisa por algum assunto técnico. Todo fórum tem a mesma pergunta repetida inúmeras vezes.
Eu fico me perguntando, será que as pessoas não conhecem o Google ou será que é tão mais fácil postar uma dúvida do que fazer uma simples pesquisa?
Acho que todo site onde o usuário pode postar dúvidas deveria ter uma caixa de pesquisa como a abaixo, que deve ser preenchida obrigatoriamente antes do usuário postar qualquer dúvida. Teste e veja por quê (Se você está lendo pelo Feed, o formulário abaixo não irá aparecer. Clique aqui para acessar o site).
Além disso, o FAQ de diversos sites poderia ser substituído por links como os abaixo. Certamente seriam muito mais relevantes:
Eu fico me perguntando, será que as pessoas não conhecem o Google ou será que é tão mais fácil postar uma dúvida do que fazer uma simples pesquisa?
Acho que todo site onde o usuário pode postar dúvidas deveria ter uma caixa de pesquisa como a abaixo, que deve ser preenchida obrigatoriamente antes do usuário postar qualquer dúvida. Teste e veja por quê (Se você está lendo pelo Feed, o formulário abaixo não irá aparecer. Clique aqui para acessar o site).
Além disso, o FAQ de diversos sites poderia ser substituído por links como os abaixo. Certamente seriam muito mais relevantes:
terça-feira, 8 de junho de 2010
Como reutilizar componentes
Muito se fala de reutilização de componentes. Todos sabem que é muito importante reutilizar para reduzir o esforço, porém, o que muitos não entendem é que a reutilização não é tão simples.
Principalmente pessoas em cargos de chefia, que não estão diretamente ligados ao desenvolvimento, não entendem o que realmente é a reutilização. O que, à princípio parece ser algo simples, na verdade, deve ser feito com cuidado. Não adianta chegar um dia na empresa e dizer “vamos reutilizar componentes” sem ter uma política adequada.
Vejo três formas comuns de se reutilizar componentes:
Porém, o custo do desenvolvimento é apenas uma parte do custo total de utilização de um componente. Ao adotar um componente de terceiro deve-se ter cuidado com algumas outras questões:
Um detalhe muito importante é: não desenvolver um componente sem um caso de uso em mente. Já vi diversas vezes componentes ultra genéricos, que resolvem todos os problemas do mundo, serem criados e quando precisam ser utilizados, simplesmente não servem para nada.
Normalmente a criação de um componente reutilizável deve ocorrer de forma natural. Se um determinado componente foi útil para uma situação, e poderá ser aproveitado para outras, então sim, podemos reutilizá-lo. Antes de pensar em reutilizar é necessário utilizar.
A questão mais complexa dessa reutilização é o versionamento do componente. Deve-se ter muita atenção no controle das versões e alterações que fazem outros projetos deixarem de funcionar. Esse componente, afinal, estará sendo utilizado em mais de um projeto e consequentemente, cada alteração deve ser muito bem pensada.
Há situações onde determinado código é usado em várias situações, mas em todas com pequenas modificações, não sendo possível ou valendo a pena tentar criar um componente.
Por exemplo, nos últimos projetos que participei foi necessário fazer uso de um Application Controller. Apesar da base ser igual em todos os projetos, cada um tem suas peculiaridades. Nesse caso, foi muito mais simples copiar a classe e adaptá-la.
Deve-se tomar muito cuidado para o copiar e colar não virar regra. Essa forma de reutilização deve ser utilizada somente como exceção, e somente entre mais de um projeto, quando o custo de versionamento e gerenciamento do componente são maiores que o benefício da reutilização. Dentro de um mesmo projeto, o custo de versionamento é pequeno, por isso, normalmente o copiar e colar nunca será utilizado.
Sem um planejamento adequado, aquilo que deveria ser uma forma de reduzir custos acaba se tornando uma tormenta.
Principalmente pessoas em cargos de chefia, que não estão diretamente ligados ao desenvolvimento, não entendem o que realmente é a reutilização. O que, à princípio parece ser algo simples, na verdade, deve ser feito com cuidado. Não adianta chegar um dia na empresa e dizer “vamos reutilizar componentes” sem ter uma política adequada.
Vejo três formas comuns de se reutilizar componentes:
- Utilizar componentes de terceiros;
- Desenvolver componentes próprios reutilizáveis;
- Copiar e colar (sim, isso mesmo).
Utilizar componentes de terceiros
Essa forma de reutilização é, à princípio, a que demanda menos esforço por parte da equipe de desenvolvimento, afinal, não é necessário desenvolver nada.Porém, o custo do desenvolvimento é apenas uma parte do custo total de utilização de um componente. Ao adotar um componente de terceiro deve-se ter cuidado com algumas outras questões:
- Qual a curva de aprendizado dele?
- Quão extensível ele é? Pode ser customizado para atender as diversas necessidades?
- No caso de um problema nesse componente, como será feita a correção?
- Este componente está em desenvolvimento? Está maduro? Há previsão de continuidade no desenvolvimento dele?
Desenvolver componentes próprios reutilizáveis
Quando não há um componente de terceiros disponível, ou há alguma outra limitação como o preço ou inadequação do componente, a solução pode ser desenvolver seus próprios componentes reutilizáveis.Um detalhe muito importante é: não desenvolver um componente sem um caso de uso em mente. Já vi diversas vezes componentes ultra genéricos, que resolvem todos os problemas do mundo, serem criados e quando precisam ser utilizados, simplesmente não servem para nada.
Normalmente a criação de um componente reutilizável deve ocorrer de forma natural. Se um determinado componente foi útil para uma situação, e poderá ser aproveitado para outras, então sim, podemos reutilizá-lo. Antes de pensar em reutilizar é necessário utilizar.
A questão mais complexa dessa reutilização é o versionamento do componente. Deve-se ter muita atenção no controle das versões e alterações que fazem outros projetos deixarem de funcionar. Esse componente, afinal, estará sendo utilizado em mais de um projeto e consequentemente, cada alteração deve ser muito bem pensada.
Copiar e colar
Não, não estou de brincadeira. Copiar e colar é uma ótima forma de reutilização.Há situações onde determinado código é usado em várias situações, mas em todas com pequenas modificações, não sendo possível ou valendo a pena tentar criar um componente.
Por exemplo, nos últimos projetos que participei foi necessário fazer uso de um Application Controller. Apesar da base ser igual em todos os projetos, cada um tem suas peculiaridades. Nesse caso, foi muito mais simples copiar a classe e adaptá-la.
Deve-se tomar muito cuidado para o copiar e colar não virar regra. Essa forma de reutilização deve ser utilizada somente como exceção, e somente entre mais de um projeto, quando o custo de versionamento e gerenciamento do componente são maiores que o benefício da reutilização. Dentro de um mesmo projeto, o custo de versionamento é pequeno, por isso, normalmente o copiar e colar nunca será utilizado.
Conclusão
A reutilização de componentes não deve ser feita a esmo, sem nenhum planejamento. Não basta o diretor dizer “vamos reutilizar” e tudo sai funcionando. É necessário uma política de reutilização e acima de tudo, um ótimo versionamento.Sem um planejamento adequado, aquilo que deveria ser uma forma de reduzir custos acaba se tornando uma tormenta.
quinta-feira, 15 de abril de 2010
Plugin do Visual Studio 2010 para exibir o código fonte na margem
Há algum tempo sugeri o plugin RockScroll, que exibe o código fonte na barra de rolagem, destacando alguns elementos.
Agora, acabei de ver a dica do Scott Hanselman no twitter de um plugin para o Visual Studio 2010 que tem funções parecidas com o RockScroll.
Bem, não adianta eu falar muito a respeito desse plugin, o mais fácil é instalar e ver pessoalmente. Você pode fazer o download do plugin nesse link.
À primeira vista achei um pouco poluído demais, mas ele tem várias funções que podem ser desabilitadas se necessário, e o melhor, tem o código fonte disponível.
Agora, acabei de ver a dica do Scott Hanselman no twitter de um plugin para o Visual Studio 2010 que tem funções parecidas com o RockScroll.
Bem, não adianta eu falar muito a respeito desse plugin, o mais fácil é instalar e ver pessoalmente. Você pode fazer o download do plugin nesse link.
À primeira vista achei um pouco poluído demais, mas ele tem várias funções que podem ser desabilitadas se necessário, e o melhor, tem o código fonte disponível.
Marcadores:
Visual Studio
quarta-feira, 14 de abril de 2010
Usando reflection para simplificar nossa vida
Em um projeto que estou trabalhando (o mesmo que citei aqui), existe a necessidade de se comunicar com programas COBOL. A comunicação é feita de uma forma um tanto arcaica. Temos que passar um string contendo os diversos campos, cada um com o tamanho exato.
Digamos que precisamos passar as informações de uma nota fiscal, teríamos essa definição:
Essa classe tem alguns problemas:
Com isso temos uma classe de modelo, contendo os dados devidamente tipados e um mapeamento que define quais campos serão transportados para a string, o formato e a posição. Todas essas informações conseguimos obter usando reflection e o mapeamento definido.
Como você pode ver, não é necessário definir o tipo do campo pois ele é inferido à partir do tipo utilizado no modelo. Ou seja, sabemos que um DateTime deverá ser convertido para o formato yyyy-MM-dd, um inteiro deve ser preenchidos com zeros a esquerda e uma string com espaços a direita.
Para obter a string à partir de um modelo, o que precisa ser feito é chamar um método assim:
conversor.GetString(meuModelo);
O ponto mais importante é a conversão do valor tipado para a string. Como cada tipo tem um formato específico, criei um conversor para cada um. Por exemplo:
Digamos que precisamos passar as informações de uma nota fiscal, teríamos essa definição:
- Data de expedição – 10 caracteres;
- Nome do Cliente – 15 caracteres;
- Número da nota – 5 dígitos;
- Valor – 5 dígitos.
- Data: 01/01/2010;
- Nome: ALGUM NOME;
- Número: 123;
- Valor: 15.
A SOLUÇÃO ANTIGA
A solução que existia para facilitar isso era gerar uma classe de acordo com a definição dos campos, que retorna a string formatada. A classe gerada ficaria assim:class NotaFiscal { private string dataExpedicao; private string nomeCliente; private string numero; private string valor; public string DataExpedicao { get{return dataExpedicao;} set{dataExpedicao = Utils.FormataCaracteres(value, 10);} } public string NomeCliente { get{return nomeCliente;} set{nomeCliente = Utils.FormataCaracteres(value, 15);} } public string Numero { get{return numero;} set{numero = Utils.FormataDigitos(value, 5);} } public string Valor { get{return valor;} set{valor = Utils.FormataDigitos(value, 5);} } public string ObtemString() { return dataExpedicao + nomeCliente + numero + valor; } }
- todos os dados são strings, não é usada a tipagem da linguagem (int, string, DateTime);
- a definição de tipo e tamanho dos campos fica espalhada pelo código;
- a ordem dos campos precisa ser verificada dentro da função ObtemString;
- caso alguma modificação seja feita nos campos, fica ruim de dar manutenção nesse código.
A Nova solução
A solução que procurei desenvolver tinha alguns pré-requisitos, entre eles:- a classe não deveria ter informações relacionadas aos campos que seriam montados na string (definição de tipo, tamanho, etc);
- poder utilizar os tipos da linguagem (int, DateTime, Enum);
- a definição de mapeamento da classe para a string a ser gerada deveria ser clara e facilmente editável;
Com isso temos uma classe de modelo, contendo os dados devidamente tipados e um mapeamento que define quais campos serão transportados para a string, o formato e a posição. Todas essas informações conseguimos obter usando reflection e o mapeamento definido.
class NotaFiscal { public DateTime DataExpedicao { get; set; } public string NomeCliente { get; set; } public int Numero { get; set; } public int Valor { get; set; } }
<mapping class=”NotaFiscal”> <field name=”DataExpedicao” Length=”10” /> <field name=”NomeCliente” Length=”15” /> <field name=”Numero” Length=”5” /> <field name=”Valor” Length=”5” /> </mapping>
Como você pode ver, não é necessário definir o tipo do campo pois ele é inferido à partir do tipo utilizado no modelo. Ou seja, sabemos que um DateTime deverá ser convertido para o formato yyyy-MM-dd, um inteiro deve ser preenchidos com zeros a esquerda e uma string com espaços a direita.
Para obter a string à partir de um modelo, o que precisa ser feito é chamar um método assim:
conversor.GetString(meuModelo);
Implementando a solução
A implementação não tem muito segredo é apenas uma questão de obter cada valor do nosso modelo seguindo a definição do XML.O ponto mais importante é a conversão do valor tipado para a string. Como cada tipo tem um formato específico, criei um conversor para cada um. Por exemplo:
class IntConverter : ITypeConverter { public string GetString(Field field, object value) { int valorInteiro = Convert.ToInt32(value); return valorInteiro.ToString().PadLeft(‘0’, field.Length); } }
Conclusão
Muitas vezes vale a pena perder um pouco de tempo desenvolvendo uma infra-estrutura que facilite o desenvolvimento. Alguns minutos perdidos no início do projeto podem salvar muitos minutos mais adiante, e, o mais importante, evitar muita dor de cabeça.
Marcadores:
Desenvolvimento
segunda-feira, 12 de abril de 2010
Validando bindings usando Reflection
Em um projeto que estou trabalhando percebi a facilidade de criar problemas de binding. Basicamente, ao renomear uma simples propriedade do modelo os bindings ligados a ela param de funcionar. O pior disso é que, para saber que há um problema neles, só mesmo executando as telas uma a uma. Nenhum erro de compilação é gerado.
Pra resolver esse problema, resolvi criar um teste que seja executado automaticamente no build e verifique qualquer problema nos bindings.
Porém, há um pequeno problema, no componente não há nenhuma informação dizendo qual o modelo ao qual ele está relacionado. Sem essa informação, não tem como fazer a validação.
Nesse ponto procurei criar uma forma de descobrir qual modelo está relacionado ao componente.
Olhando a view pude ver um padrão na atribuição de valores utilizados no binding:
Fica claro que, ao fazer um binding temos:
O segundo passo é um pouco mais complicado. Precisamos analisar o código executado, buscando o componente ao qual é atribuído o valor. Via reflection não temos acesso fácil ao código que está sendo executado, para isso foi necessário uma biblioteca externa. Basicamente, foi necessário analisar o MSIL à procura do componente.
Tendo a validação pronta, bastou criar um teste que busca os problemas de binding e reporta cada erro encontrado.
Caso você ainda não conheça o Git, recomendo a leitura desse artigo.
Pra resolver esse problema, resolvi criar um teste que seja executado automaticamente no build e verifique qualquer problema nos bindings.
Relacionando o componente ao modelo
Fazer a validação do binding a princípio é bastante simples, basta buscar cada propriedade do componente que está ligada ao modelo e verificar se o modelo contém a propriedade com o nome definido no componente. Por exemplo, em um ComboBox, pegamos o nome definido no DisplayMember e buscamos no modelo se existe uma propriedade com o nome correspondente.Porém, há um pequeno problema, no componente não há nenhuma informação dizendo qual o modelo ao qual ele está relacionado. Sem essa informação, não tem como fazer a validação.
Nesse ponto procurei criar uma forma de descobrir qual modelo está relacionado ao componente.
A convenção
O projeto está sendo desenvolvido em WinForms e adotamos o padrão MVP, mais especificamente o Passive View. Como você já deve saber, no MVP temos a camada de View (exibição) separada do Presenter (lógica da tela). A view apenas recebe os dados a serem exibidos.Olhando a view pude ver um padrão na atribuição de valores utilizados no binding:
class View { public IEnumerable<Usuario> Usuarios { set{ algumGrid.DataSource = value; } } }
Fica claro que, ao fazer um binding temos:
- uma propriedade do tipo IEnumerable<T>;
- a propriedade permite escrita (contém um set);
- dentro da propriedade atribuímos o valor ao DataSource do componente.
Buscando o modelo correspondente ao componente
Com essa convenção, é possível descobrir quais componentes fazem binding e qual o modelo correspondente. Para isso temos de fazer duas coisas:- buscar todas as propriedades das telas que sejam do tipo IEnumerable<T> e que contenham um set;
- para cada propriedade, obter o componente para o qual está sendo atribuído o valor.
private IEnumerable<PropertyInfo> GetProperties(Type type) { return type.GetProperties() .Where(x => x.CanWrite && EhLista(x)) } private bool EhLista(PropertyInfo propertyInfo) { return TipoEhEnumerable(propertyInfo.PropertyType) || propertyInfo.PropertyType.GetInterfaces().Any(x => TipoEhEnumerable(x)); } private bool TipoEhEnumerable(Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>); }
O segundo passo é um pouco mais complicado. Precisamos analisar o código executado, buscando o componente ao qual é atribuído o valor. Via reflection não temos acesso fácil ao código que está sendo executado, para isso foi necessário uma biblioteca externa. Basicamente, foi necessário analisar o MSIL à procura do componente.
Finalmente, Validando os bindings
Agora que já temos todas as informações necessárias, fazer a validação dos bindings é simples. Basta pegar cada propriedade do componente que faz o binding e verificar se o campo existe no modelo. Essa verificação varia conforme o componente, por exemplo, em um ComboBox simplesmente verificamos o DisplayMember e o ValueMember. Em um grid, por sua vez, é necessário verificar essas propriedades para cada uma das colunas.Tendo a validação pronta, bastou criar um teste que busca os problemas de binding e reporta cada erro encontrado.
Código fonte de exemplo
Pra entender melhor isso, o mais fácil é olhar o código fonte. Disponibilizei o código no GitHub, nesse repositório.Caso você ainda não conheça o Git, recomendo a leitura desse artigo.
Marcadores:
Desenvolvimento,
UI
quinta-feira, 25 de fevereiro de 2010
Download do PHP Editor 2.22
Percebi que muita gente ainda procura um programa que desenvolvi a alguns anos chamado “PHP Editor” (nome muito criativo). Ele é um editor de HTML e PHP, mas também pode ser utilizado para editar outras linguagens como Javascript, CSS e XML. Você pode criar seus sites sem custo, o programa é grátis.
Vou deixar aqui o link para o download da versão em português, o arquivo está hospedado no RapidShare.
O download do programa pode ser feito nesse link: Programa de instalação em português.
A versão mais recente do programa é a 2.22. Esse programa foi desenvolvido a alguns anos e infelizmente perdi o código fonte dele. Deste modo, não terei como continuar o seu desenvolvimento e nem mesmo passar para outra pessoa. De qualquer forma, para quem está contente com ele, fica aí o link.
Vou deixar aqui o link para o download da versão em português, o arquivo está hospedado no RapidShare.
O download do programa pode ser feito nesse link: Programa de instalação em português.
A versão mais recente do programa é a 2.22. Esse programa foi desenvolvido a alguns anos e infelizmente perdi o código fonte dele. Deste modo, não terei como continuar o seu desenvolvimento e nem mesmo passar para outra pessoa. De qualquer forma, para quem está contente com ele, fica aí o link.
domingo, 24 de janeiro de 2010
Backup: você ainda vai querer ter o seu
No final do ano passado meu HD começou a dar problema, simplesmente parou de dar boot e aos poucos estava partindo dessa para uma melhor. Eu, assim como a maioria das pessoas, não tinha backup.
Curiosamente, na mesma semana o servidor onde estava hospedado os sites Coding Horror, o blog do Stack Overflow, o blog do Phil Haack e outros, também teve seu HD queimado. Mais detalhes aqui e aqui.
Por sorte consegui recuperar os dados do HD que estava morrendo, porém, isso serviu como um aviso alto e claro: faça backup!!
O primeiro passo foi identificar os tipos de arquivos que eu tenho e a melhor forma de backup para eles. Cheguei a essa lista:
Atualmente estou usando o SVN, mas ele é bastante lento ao trabalhar online. Nos testes que fiz com o GIT ele foi muito mais rápido. Em breve vou abandonar o SVN e converter meus projetos para o GIT.
O Windows 7 oferece um bom utilitário de backup. Além de fazer o backup das pastas selecionadas ele também faz backup do sistema operacional, configurações e drivers.
ATUALIZAÇÃO (07/01/2011): depois de algum tempo usando o backup do Windows, comecei a ter problemas e deixei de usá-lo. Leia esse artigo para mais detalhes.
A forma mais prática e segura que encontrei foi utilizar um mecanismo de backup online. Existem diversas empresas que oferecem o serviço, entre os diversos produtos que testei optei pelo IDrive.
O IDrive oferece 2GB no plano gratuito, e 150GB pagando USD 4,95/mês. Na versão gratuita do IDrive é possível ganhar 10GB adicionais, basta que após se cadastrar você compartilhe os contatos do seu e-mail, permitindo que eles enviem um convite aos conhecidos.
O backup inicial, como é de se esperar, pode demorar bastante. Isso vai depender da velocidade de conexão e a quantidade de dados a serem enviados. Mas, após esse envio inicial cada backup é bem rápido, já que ele precisa enviar apenas as alterações.
Desse modo, os arquivos essenciais ficam gravados em um HD secundário e em um backup online. Ter o mesmo arquivo gravado em três locais distintos oferece uma boa segurança.
Após fazer os backups, certifique-se que você consegue restaurar os arquivos. Guarde bem as chaves de criptografia e assegure-se que em uma emergência é possível obtê-las facilmente.
Curiosamente, na mesma semana o servidor onde estava hospedado os sites Coding Horror, o blog do Stack Overflow, o blog do Phil Haack e outros, também teve seu HD queimado. Mais detalhes aqui e aqui.
Por sorte consegui recuperar os dados do HD que estava morrendo, porém, isso serviu como um aviso alto e claro: faça backup!!
Minha estratégia de backup
Depois desse susto, resolvi montar uma estratégia de backup para o meu micro pessoal.O primeiro passo foi identificar os tipos de arquivos que eu tenho e a melhor forma de backup para eles. Cheguei a essa lista:
- Código fonte – a maioria dos meus projetos já estava em repositórios do SVN, porém, o repositório fica gravado localmente;
- Arquivos que gostaria de ter – são aqueles que seria interessante ter um backup, mas que não são essenciais. Se algo muito ruim acontecer posso obter esses arquivos por outros meios. Aqui entram as músicas, filmes, instalações de programas e assim por diante;
- Arquivos essenciais – são aqueles que eu NÃO POSSO perder. Isso envolve desde documentos pessoais, fotos e qualquer coisa que eu queira ter seguro, mesmo que um terremoto devaste a cidade;
Backup de Código Fonte
Para o código fonte, como eu já trabalho normalmente com o SVN, bastou achar um repositório online. Acabei optando pelo ProjectLocker, que oferece de forma gratuita 500MB de armazenamento para repositórios do SubVersion ou GIT.Atualmente estou usando o SVN, mas ele é bastante lento ao trabalhar online. Nos testes que fiz com o GIT ele foi muito mais rápido. Em breve vou abandonar o SVN e converter meus projetos para o GIT.
Backup de arquivos que gostaria de ter
Os arquivos que gostaria de ter não precisam de um backup altamente seguro, por isso optei por fazer o backup localmente em um HD secundário. No caso de uma pane no HD principal ainda tenho essa cópia.O Windows 7 oferece um bom utilitário de backup. Além de fazer o backup das pastas selecionadas ele também faz backup do sistema operacional, configurações e drivers.
ATUALIZAÇÃO (07/01/2011): depois de algum tempo usando o backup do Windows, comecei a ter problemas e deixei de usá-lo. Leia esse artigo para mais detalhes.
Backup de arquivos essenciais
Para os arquivos essenciais não basta uma segunda cópia, preciso de uma segurança maior. Caso ocorra um terremoto, um tsunami ou um avião caia sobre a casa, ainda quero ter esses dados. Para isso, é necessário um backup externo, geograficamente distante da minha casa.A forma mais prática e segura que encontrei foi utilizar um mecanismo de backup online. Existem diversas empresas que oferecem o serviço, entre os diversos produtos que testei optei pelo IDrive.
O IDrive oferece 2GB no plano gratuito, e 150GB pagando USD 4,95/mês. Na versão gratuita do IDrive é possível ganhar 10GB adicionais, basta que após se cadastrar você compartilhe os contatos do seu e-mail, permitindo que eles enviem um convite aos conhecidos.
O backup inicial, como é de se esperar, pode demorar bastante. Isso vai depender da velocidade de conexão e a quantidade de dados a serem enviados. Mas, após esse envio inicial cada backup é bem rápido, já que ele precisa enviar apenas as alterações.
Desse modo, os arquivos essenciais ficam gravados em um HD secundário e em um backup online. Ter o mesmo arquivo gravado em três locais distintos oferece uma boa segurança.
O importante não é o backup, mas sim a recuperação
Como disse Joel Spolsky em um de seus artigos recentes, o importante não é fazer backup, mas sim poder recuperar os dados.Após fazer os backups, certifique-se que você consegue restaurar os arquivos. Guarde bem as chaves de criptografia e assegure-se que em uma emergência é possível obtê-las facilmente.
Marcadores:
Diversos
quarta-feira, 6 de janeiro de 2010
Até a escolha das armas envolve política
Podemos acompanhar pelos noticiários a história dos caças que o governo brasileiro pretende comprar. Nesta semana a Força Aérea Brasileira finalizou o relatório técnico onde aponta como a melhor opção técnica o caça sueco, e a pior o francês. Lembrando que anteriormente o presidente Lula declarou apoio ao caça francês.
Agora, por quê estou falando disso aqui? Bem, na nossa área de desenvolvimento de software essa situação é bastante comum. Constantemente precisamos analisar quais armas, ou melhor, ferramentas e bibliotecas utilizaremos no desenvolvimento. Isso envolve tanto a parte técnica como "política" da empresa.
Quando temos a necessidade de uma ferramenta ou biblioteca, o primeiro passo é fazer uma análise técnica, verificar se ela atenderá as nossas necessidades. Muitas vezes a utilização de uma determinada biblioteca nos dá 20% mais de produtividade, mas por outro lado, gera 30% a mais de problemas, ou seja, sempre devemos pesar os prós e os contras.
Depois de uma análise técnica, a decisão final acaba sendo política. Normalmente são os gestores das empresas que dão a palavra final. Esse caminho normalmente está correto, afinal, a empresa sempre tem suas parcerias, suas metas a longo prazo e diversos aspectos além da solução do problema imediato. Nesse ponto, uma análise que não seja focada exclusivamente na solução imediata é muito importante.
Porém, só tem um pequeno detalhe, para que um gestor resolva escolher a ferramenta que custa o dobro do preço e foi a que obteve a pior avaliação técnica, tem que ter ótimos motivos políticos!!!!
Agora, por quê estou falando disso aqui? Bem, na nossa área de desenvolvimento de software essa situação é bastante comum. Constantemente precisamos analisar quais armas, ou melhor, ferramentas e bibliotecas utilizaremos no desenvolvimento. Isso envolve tanto a parte técnica como "política" da empresa.
Quando temos a necessidade de uma ferramenta ou biblioteca, o primeiro passo é fazer uma análise técnica, verificar se ela atenderá as nossas necessidades. Muitas vezes a utilização de uma determinada biblioteca nos dá 20% mais de produtividade, mas por outro lado, gera 30% a mais de problemas, ou seja, sempre devemos pesar os prós e os contras.
Depois de uma análise técnica, a decisão final acaba sendo política. Normalmente são os gestores das empresas que dão a palavra final. Esse caminho normalmente está correto, afinal, a empresa sempre tem suas parcerias, suas metas a longo prazo e diversos aspectos além da solução do problema imediato. Nesse ponto, uma análise que não seja focada exclusivamente na solução imediata é muito importante.
Porém, só tem um pequeno detalhe, para que um gestor resolva escolher a ferramenta que custa o dobro do preço e foi a que obteve a pior avaliação técnica, tem que ter ótimos motivos políticos!!!!
Marcadores:
Desenvolvimento
Assinar:
Postagens (Atom)