Mostrando postagens com marcador Desenvolvimento. Mostrar todas as postagens
Mostrando postagens com marcador Desenvolvimento. Mostrar todas as postagens

segunda-feira, 13 de janeiro de 2014

Rust, a linguagem do futuro

Você gostaria de ter uma linguagem que permite controle em baixo nível da memória, com alto desempenho e sem os problemas do C++? Então, Rust provavelmente é a linguagem que você procura.

No início de 2013 li o excelente tutorial Rust for Rubyists do Steve Klabnik. Na época gostei bastante da linguagem mas acabei não procurando mais informações. Desde então, ela vem evoluindo bastante e está se tornando bastante popular. Se você ainda não conhece, vou falar rapidamente de algumas coisas que me chamaram a atenção.

Quem deve usar Rust

Antes de mais nada é importante deixar claro qual o objetivo da linguagem e quem deve usá-la.

A linguagem Rust está sendo desenvolvida pela Mozilla para ser usada no lugar do C++. Os principais objetivos dela são segurança, concorrência e praticidade. A Mozilla usará a linguagem especialmente no Servo, um novo browser que estão desenvolvendo.

Pelo fato dela ser usada onde normalmente o C++ é adotado, ela precisa fornecer controles em baixo nível, especialmente no gerenciamento de memória, mas ela faz isso sem comprometer a estabilidade e segurança, ao contrário do C++.

Ainda não está totalmente claro em que áreas a linguagem será adotada, mas eu imagino que as principais serão:
  • Quem atualmente desenvolve em C ou C++ - para essas pessoas o Rust permitirá o mesmo controle que o C permite, porém, com muito mais segurança. Para essas pessoas a adoção do Rust será uma ótima escolha;
  • Aplicações web que fornecem APIs - no momento a linguagem Go está sendo bastante utilizada nesses casos. Eu não sou grande fã de Go (também não usei muito para poder opinar), imagino que o Rust acabará sendo adotado nesses casos também;
  • Otimizações - em Ruby é comum desenvolver em C partes do código que precisam de alta performance. Rust poderá vir a tomar esse lugar, por ser bem mais legível (coisa que rubistas valorizam bastante) e ser mais segura.

Variáveis

Ao definir as variáveis não é necessário especificar o tipo, na maioria dos casos o compilador consegue inferir o tipo a ser usado.

Por padrão as variáveis são imutáveis (por questões de performance e concorrência). Para que a variável seja mutável é necessário definí-la usando o "mut".
let hi = "hi";
let mut count = 0;

while count < 10 {
    println!("count is {}", count);
    count += 1;
}

Gerenciamento da memória

Não vou entrar em detalhes do gerenciamento da memória feita pelo Rust, mas o importante é que ele detecta durante a compilação possíveis problemas.

No exemplo abaixo, em C++, repare que é retornado um ponteiro para uma área de memória que logo ficará inválida. Esse erro é bastante comum e não é algo que a linguagem C++ previne.
{
    // A memória será liberada no final da função.
    map<int, Record> m; 

    // ...

    // Obtém uma referência para um item;
    Record &r = m[1]   
    // Retorna uma referência que logo será inválida,
    // pois a memória será liberada.
    return r;          
}
Abaixo você pode ver um código semelhante em Rust. Nesse caso, a linguagem detecta que um ponteiro inválido está sendo retornado e gera um erro de compilação. Repare bem, é um erro de compilação e não de execução.
{
    let mut m = HashMap::new();

    // ...

    let r = m.get(1);
    // Erro de compilação.
    return r; 
}

Structs

Em Rust, pode-se definir structs (que contém os dados) e adicionar funções a essas structs.
// Define a estrutura.
struct Rectangle {
    width: int,
    height: int
}

// Adiciona métodos
impl Rectangle {

    fn area(&self) -> int {
       // Não é necessário usar o "return".
       self.width * self.height
    }

}

Extension methods

Em C# "extension methods" permitem adicionar métodos a tipos existentes, sem ter que alterá-los ou criar uma sub-classe. Em Rust, a forma que a definição dos métodos funciona é igual aos "extension methods" do C#. Repare no exemplo acima que o método "area" recebe como parâmetro o "self", que representa o objeto ao qual ele está associado.

Com isso, é possível também implementar métodos para tipos já existentes, inclusive os tipos definidos pela própria biblioteca padrão.

Traits

Os traits são semelhantes a "interface" em C# ou Java. Porém, com uma diferença: não é necessário alterar a struct original para implementar uma trait (interface). Em outras palavras, você pode definir uma trait para um tipo existente.

No exemplo abaixo é definida a trait "Printable" e, a seguir, é feita a implementação dela para o "int".
trait Printable {
    fn print(&self);
}

impl Printable for int {
    fn print(&self) { 
        println!("{:?}", *self)
    }
}

Pattern matching

A funcão "match" do rust funciona como um "switch", porém bem mais avançado. O uso de pattern matching não é algo novo, linguagens como Erlang fazem uso extensivo disso, mas ao menos para mim, é uma novidade em linguagens derivadas do C.
match my_number {
    0     => println!("zero"),
    1 | 2 => println!("one or two"),
    3..10 => println!("three to ten"),
    _     => println!("something else")
}
Uma das coisas mais interessantes do match é que é obrigatório definir todos os possíveis valores para ele. Se você esquecer um valor, ocorre um erro de compilação. No exemplo abaixo o "_" significa "qualquer coisa".
enum Direction {
    North,
    East,
    South,
    West
}

match dir {
    North => println!("norte"),
    South => println!("sul"),
    _     => println!("outro")
}

Não existe NULL

Quantas vezes temos que adicionar aos métodos o famoso "if (x == null) throw ..."? Isso porquê na maioria das linguagens os objetos (ponteiros) podem ser nulos.

Até o invetor do Null, Tony Hoare diz "Esse foi meu erro de bilhões de dólares".

Em Rust, os ponteiros não podem ser nulos. Caso seja necessário definir uma variável que pode ou não ter valor pode-se usar o enum "Option"
let x: Option<int> = 10;
match x {
    Some(_) => println!("tem valor");
    None    => println!("não tem valor");
}

Onde aprender mais?

Aqui apenas destaquei alguns pontos interessantes da linguagem, não entrei em detalhes em nenhum deles. Se você deseja aprender mais, aqui vão alguns links:
  • Rust for Rubyists - é um excelente tutorial do Steve Klabnik, apesar do nome, mesmo quem não conhece Ruby consegue acompanhar bem;
  • Tutorial oficial - esse é um tutorial mais longo que vai apresentar os principais recursos da linguagem;
  • Memory Ownership and Lifetimes - vídeo explicando muito bem o gerenciamento de memória feito pelo Rust. Aqui tem o arquivo para download com qualidade melhor.

segunda-feira, 6 de janeiro de 2014

Evitando a renderização de áreas ocultas em layouts responsivos

Atualmente é bastante comum o uso de layouts responsivos, ou seja, layouts que se adaptam ao navegador do usuário.

Se um usuário acessa o site usando um smartphone, o site provavelmente irá exibir apenas o conteúdo essencial, deixando invisível boa parte do HTML.

Agora, por que devemos renderizar as áreas que não são visíveis? Evitando a renderização de áreas que sabemos estarem ocultas podemos melhorar o desempenho e economizar bateria dos dispositivos.

Ember Responsive Area

Para o Ember.js criei um componente bastante simples que resolve esse problema. Toda a área dentro do componente será renderizada apenas se estiver visível. O componente se chama Ember Responsive Area.

O uso dele é bastante simples:
<div class="hidden-phone">
  {{#responsive-area}}
    Aqui vem o conteúdo.
  {{/responsive-area}}
</div>

Como funciona

Quando o componente é carregado, ele verifica se o elemento está visível. Se estiver visível, renderiza a área. Caso contrário, não renderiza e monitora o redimensionamento do navegador.

Se o usuário redimensionar o navegador ou girar o dispositivo (tablet/smartphone) o componente irá verificar novamente se deve renderizar a área.

Note que isso só funciona se a visibilidade da região depende do tamanho da janela do navegador, isso não funcionará se você está ocultando e exibindo manualmente a área.

sábado, 14 de julho de 2012

Como adaptar seu site para telas retina

Telas com alta densidade de pixels, chamadas pela Apple de "telas Retina", já são uma realidade. Temos smartphones, tablets e notebooks com essa incrível resolução.

Se considerarmos a porcentagem de visitantes utilizando essas telas, pode parecer pouco, mas esse é um número que só vai crescer a cada dia. Como diz o Marco Arment, esses visitantes representam o futuro. Não é como a porcentagem de pessoas utilizando o Internet Explorer 6, que só tende a diminuir, nesse caso o número só vai aumentar.

Agora, como adequar seu site para essas telas? Como primeiro passo, siga esse guia. A biblioteca Retina.js pode lhe ajudar a prover imagens de alta resolução apenas em dispositivos compatíveis, economizando largura de banda para os outros dispositivos.

Alterar as imagens do site é apenas o primeiro passo. Com isso feito, cabe a você identificar as áreas que não estão com boa aparência. Talvez trocar a fonte utilizada, ajustar outros elementos do site... mas isso só vendo o site em uma tela Retina pra poder dizer.

quarta-feira, 2 de maio de 2012

Atualizando aplicações .NET de forma semelhante ao Google Chrome

O Google Chrome tem uma forma de atualização muito boa, que aos poucos está sendo adotada por várias outras aplicações. A grande vantagem das atualizações do Google Chrome é que elas ocorrem sem a intervenção do usuário e são constantes. Isso garante que o usuário sempre terá a versão mais nova da aplicação.

Pesquisei soluções para aplicações .NET que reproduzissem esse mesmo comportamento, permitindo atualizações constantes e sem a intervenção do usuário. Infelizmente não encontrei nenhuma solução pronta que atendesse minhas necessidades.

A primeira opção, logicamente, foi utilizar o ClickOnce. Mas, devido a suas limitações, ele não me atendeu. A seguir, procurei outras soluções, mas nenhuma atendia necessariamente aquilo que eu queria. Por fim, resolvi desenvolver eu mesmo um componente que resolvesse esse problema. Foi assim que surgiu o AppUpdater (sim, eu sei, esse nome é horrível).

Características do AppUpdater

As principais características do AppUpdater são:
  • as atualizações são feitas via web (download dos arquivos);
  • o processo de atualização não necessita da intervenção do usuário;
  • o tamanho de uma atualização é mínimo. A atualização faz o download apenas das partes modificadas de cada arquivo atualizado (deltas), mesmo em arquivos binários.
Vale ressalta um aspecto muito importante: o tamanho da atualização. O componente fará o possível para baixar a menor quantidade possível de dados.
  • Os novos arquivos são baixados de forma compactada (gzip). Com isso, um executável de 200 kB, fica com cerca de 60 kB;
  • Os arquivos que não foram alterados, não são baixados novamente;
  • No caso de arquivos modificados, apenas as partes alteradas são baixadas (delta). O componente faz o download do delta, aplica na versão antiga do arquivo e gera assim a nova versão. O tamanho do delta obviamente vai depender do que foi modificado. Apenas por curiosidade, adicionei a chamada a um MessageBox em uma aplicação de 200 kB, e o delta dessa alteração ficou com 639 bytes.

Utilizando o componente

Para utilizar o componente, recomendo a leitura da documentação. De forma resumida, o que deve ser feito para utilizá-lo é:
  1. a aplicação deve seguir uma certa estrutura de diretórios pré-determinada;
  2. deve existir um servidor web que irá disponibilizar as atualizações;
  3. a aplicação deve ativar o código que verifica as atualizações.
Para que o componente funcione, o único código necessário é o seguinte:
AutoUpdater autoUpdater = new AutoUpdater(UpdateManager.Default);
autoUpdater.SecondsBetweenChecks = 3600;
autoUpdater.Start();

Concluindo

Não vou repetir aqui o passo-a-passo de como utilizar o componente, acredito que a documentação já trate de todos os assuntos necessários para utilizá-lo.

Se algo não ficou claro, por favor, entre em contato. Eu altero a documentação ou posso criar um tutorial esclarecendo quaisquer dúvidas que surgirem.

Por fim, tenho que agradecer a Fernanda Moratelli que me ajudou no desenvolvimento do AppUpdater!

terça-feira, 29 de novembro de 2011

O que é melhor: liberações frequentes ou liberações maduras?

Hoje li um artigo falando à respeito da prática conhecida como "release early, release often". Nele o autor tentava expor as diferenças entre fazer liberações frequentes e fazer apenas liberações "maduras". Segundo ele, com liberações frequentes normalmente a versão inicial tem vários bugs, que são corrigidos à cada versão até chegar ao ponto onde tudo funciona corretamente. Por outro lado, se a prática escolhida é de apenas liberar releases maduros, são feitos poucos releases e neles o software deve estar totalmente livre de bugs.

Agora eu pergunto: o que você acha melhor, liberações frequentes ou liberações maduras?

Bem, será que as duas formas realmente são mutuamente exclusivas? Não podemos ter as duas ao mesmo tempo? Eu acredito que sim!

Para mim, liberações frequentes não significa liberar funcionalidades com bugs e deixar pra corrigir mais tarde, todo o release deve estar livre de bugs. A qualidade do software deve ser ponto indiscutível. O que muda é a quantidade de funcionalidades e não a qualidade delas.

A grande vantagem dos releases frequentes é ter um feedback o mais cedo possível do usuário. Quanto antes o usuário puder usar o software, ver se ele está adequado à realidade e dar o feedback, melhor. Um detalhe importante é que, o fato das funcionalidades estarem livres de bug não significa que elas necessariamente precisam estar "completas". E, por "completas" eu me refiro a "atender todas as necessidades possíveis e imagináveis". O mais importante é disponibilizar o quanto antes a parte principal da funcionalidade, obter o feedback e à partir dele ir expandindo o software.

Em resumo, pra mim o que melhor funciona é: liberar frequentemente, mesmo que a funcionalidade não esteja cobrindo todas as possibilidades, e obter o feedback o mais cedo possível. Sem que para isso a qualidade do software seja comprometida.

segunda-feira, 3 de outubro de 2011

Tutorial - Instalando o RVM no Ubuntu 11.04

Nesse rápido tutorial vou mostrar como instalar o RVM no Ubuntu 11.04. Esses passos devem servir para outras distribuições Linux, mas não testei.

Para quem não conhece, o RVM é um utilitário que permite que mais de uma versão do Ruby seja instalada na mesma máquina. Com ele é possível configurar cada projeto para usar uma versão diferente do Ruby.

A instalação do RVM não tem muito segredo, mas sempre tem algum detalhe que esqueço, por isso resolvi deixar aqui anotado o passo-a-passo. As informações aqui apresentadas retirei do guia oficial de instalação do RVM. Sugiro verificar esse link para informações sempre atualizadas.

Instalando

Antes de instalar o RVM é necessário ter alguns pacotes que por padrão não estão instalados no Ubuntu. Para obter esses pacotes, execute o comando abaixo:
sudo apt-get install git git-core curl

A seguir, instale o RVM executando os seguintes comandos:
bash < < (curl -s https://rvm.beginrescueend.com/install/rvm)

echo '[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm" # Load RVM function' >> ~/.bash_profile

source .bash_profile

Com isso feito, você pode executar o comando "rvm requirements" para listar as bibliotecas necessárias para executar cada versão do Ruby. Caso você deseje executar o Ruby MRI, instale as bibliotecas executando o comando abaixo:
sudo apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-0 libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison


Por fim, deve-se instalar a versão do Ruby desejada. Para instalar a versão 1.9.2 do Ruby MRI, execute o comando abaixo:
rvm install 1.9.2


Com isso a instalação do RVM está concluída.

Usando

Apenas para fins de exemplo, abaixo estão os comandos utilizados para criar uma nova aplicação Rails usando o RVM.

Primeiro, defina a gemset a ser usada. O comando abaixo cria uma gemset usando o Ruby 1.9.2 para o projeto "teste":
rvm use 1.9.2@teste --create


Instale o Rails:
gem install rails


Crie a aplicação:
rails new teste


E, por fim, dentro do diretório da aplicação, crie o arquivo .rvmrc:
cd teste
rvm use 1.9.2@teste --rvmrc


O arquivo .rvmrc é usado pelo RVM para carregar a gemset utilizada no seu projeto. Para que o arquivo seja detectado, execute o comando abaixo e confirme a mensagem que será exibida.
cd .

Finalizando

Os comandos apresentados aqui funcionam no Ubuntu 11.04, porém, eles também devem funcionar em outras versões do Linux. Para informações atualizadas, sugiro verificar o guia oficial de instalação do RVM.

segunda-feira, 26 de setembro de 2011

Dica: instalando gems no Ubuntu ao usar o RVM

Essa é uma dica rápida para um problema que me deparei ao utilizar o Ruby on Rails no Ubuntu 11.04.

Ao tentar instalar alguma gem (e.g. gem install bundler) você pode receber o erro abaixo. Pelo que percebi, esse erro ocorre apenas ao utilizar o RVM.
ERROR:  Loading command: install (LoadError)    no such file to load -- zlibERROR:  While executing gem ... (NameError)    uninitialized constant Gem::Commands::InstallCommand

Corrigir esse erro é bastante simples, basta instalar a biblioteca zlib e reinstalar a versão do Ruby que está instalada na RVM.

Para instalar a biblioteca zlib execute o comando abaixo:
rvm pkg install zlib

Para reinstalar a versão do Ruby instalada na RVM use os seguintes comandos:
rvm remove 1.9.2rvm install 1.9.2

Lembrando que a versão do Ruby pode ser diferente da exibida no comando acima (1.9.2). Para verificar qual a versão, utilize o comando "rvm list".

Com isso, você já pode prosseguir com a instalação das gems! Mais detalhes você encontra no StackOverflow, que foi onde encontrei essa solução.

quarta-feira, 6 de outubro de 2010

NuPack – gerenciamento de pacotes em .NET

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.
Nupack-logoHoje 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, 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.
Definir o proxy
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.
instalar - rubysspi
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\
Por fim, é necessário alterar o arquivo “gem.bat” para usar essa autenticação. Edite esse arquivo e adicione na chamada do ruby o parâmetro “-rspa”.

O original estava assim:
@"%~dp0ruby.exe" "%~dpn0" %* 
E, depois da alteração ficará assim:
@"%~dp0ruby.exe" -rspa "%~dpn0" %* 
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”.

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.

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:
  1. Aplicação cliente obtém chave de autenticação;
  2. Usuário autoriza aplicação cliente na aplicação servidora;
  3. 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).
 Aplicação cliente obtendo chave de autenticação

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.
Usuário autorizando o acesso

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.
Aplicação cliente trocando a chave de autenticação pela chave de acesso

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.

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).
Aguarde...
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
Pronto, problema resolvido. Reinicie o browser e ele já deverá estar muito mais rápido.

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:
  • 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).
Apesar dessas questões serem técnicas, a visão continua sendo no valor provido ao cliente. Se o software continuará a evoluir com o passar do tempo, não adianta a primeira versão atender perfeitamente ao cliente se na segunda versão o custo do desenvolvimento sobe imensamente e na terceira ainda mais. Um software bem escrito significa que com o passar do tempo o cliente continuará a ter suas necessidades atendidas de forma efetiva e com um custo adequado.

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.

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:
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.
Por quê ninguém tentou melhorar isso? Bem… vários projetos foram criados para tentar resolver esse problema, mas nenhum até o momento teve sucesso, seja por motivos técnicos ou por falta de adoção.

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.
path
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
Pronto, é só isso.

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.
nu - structuremap
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.
nu - fluentnhibernate Repare que na pasta do projeto foram adicionados os componentes e todas as suas dependências.
pasta projeto

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.

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:
  • Data de expedição – 10 caracteres;
  • Nome do Cliente – 15 caracteres;
  • Número da nota – 5 dígitos;
  • Valor – 5 dígitos.
Ao gerar a string contendo o seguintes valores:
  • Data: 01/01/2010;
  • Nome: ALGUM NOME;
  • Número: 123;
  • Valor: 15.
Temos isso como resultado: “2010-01-01ALGUM NOME     00012300015”.

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; 
  } 
}
Essa classe tem alguns problemas:
  • 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;
O caminho natural foi desenvolver algo semelhante a um Mapeamento Objeto-Relacional. Eu batizei-o de Mapeamento Objeto-Cobolar. ;)

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.

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.

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.
Com essas informações já temos uma base de como podemos verificar o binding.

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:
  1. buscar todas as propriedades das telas que sejam do tipo IEnumerable<T> e que contenham um set;
  2. para cada propriedade, obter o componente para o qual está sendo atribuído o valor.
O primeiro passo é bastante simples, via reflection conseguimos ter essa informação:
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.

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.

Gripen_ag1

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!!!!

segunda-feira, 7 de dezembro de 2009

Convention over Configuration no dia-a-dia

Recentemente fiz um curso de Ruby on Rails e estou brincando um pouco com esse Framework. Uma das características mais importantes do Rails é o extensivo uso de Convention over configuration.

Para quem não está familiarizado com o conceito, Convention over configuration em português seria algo como “convenção sobre configuração” no sentido de dar preferência a convenções ao invés de configurar tudo.

Grande parte da produtividade do Rails se dá devido as convenções. São coisas simples, mas que ajudam bastante na produtividade durante o desenvolvimento.
ConventionOverConfiguration Imagem retirada do site Geek & Poke.

Uso de convenções em .NET

Desenvolvedores .NET não estão muito acostumados ao uso de convenções. O mais comum é configurar explicitamente tudo, seja via código ou em arquivos XML. Porém, aos poucos estamos sendo influenciados por outras linguagens, adotando as boas práticas já adotadas por outros desenvolvedores a anos.

A própria Microsoft já está adotando convenções nos produtos dela, como por exemplo no ASP.NET MVC. Por exemplo, ao acessar a url http://site/usuario/cadastrar o framework entenderá que deve utilizar o controller UsuarioController e chamar a ação Cadastrar. Nenhuma configuração é necessária.

Outro exemplo do uso de convenções é o NHibernate. Ao mapear o atributo Nome da classe Pessoa, por padrão o NHibernate utilizará a tabela Pessoa e o campo Nome do tipo varchar. Ao mapear o atributo o framework irá verificar que o tipo dele é string e utilizará a convenção de nome de campo e tabela.

Criando convenções personalizadas

Alguns produtos definem uma convenção e temos que nos ater a ela. Porém, o ideal é que possamos criar nossas próprias convenções.

Um ótimo exemplo disso é o Fluent NHibernate. Para quem não conhece, o Fluent NHibernate permite criar o mapeamento do NHibernate diretamente via código, sem utilizar arquivos XML.

No Fluent NHibernate podemos mapear as classes de forma automatizada. Ao invés de definir manualmente cada tabela e campo, fazemos isso por meio de convenções. Podemos utilizar as convenções padrão ou criar nossas próprias. Por exemplo, se queremos que todas as tabelas sejam igual ao nome da classe com o prefixo “tbl”, simplesmente criamos uma nova convenção que define isso e pronto, todas as tabelas serão mapeadas dessa maneira.

Resolvendo problemas

O uso de convenções certamente nos auxiliam bastante, porém, em alguns momentos elas parecem “caixas pretas” ou simplesmente “mágica”. Há casos onde queremos saber exatamente o que está sendo feito por baixo dos panos.

Nesses casos, é essencial que o framework disponha de recursos para que possamos identificar qual a convenção utilizada e qual o resultado obtido. Por exemplo, no ASP.NET MVC, se criamos um Controller e esquecemos de criar a View recebemos um aviso conforme imagem abaixo. Repare que o framework detalha todos os possíveis locais onde a view poderia estar. Com essa informação fica claro qual o problema e sua solução.
erro_mvc

Em resumo

As convenções quando bem empregadas facilitam bastante o desenvolvimento e aumentam nossa produtividade. Só precisamos tomar cuidado para que essa “mágica” possa ser facilmente entendida quando precisamos resolver um problema.

A alguns meses, Jeremy Miller fez uma apresentação sobre o assunto. Recomendo o vídeo, muito interessante ouvir as experiências dele na área.

segunda-feira, 16 de novembro de 2009

Refatorar ou reescrever?

Uma situação que já presenciei algumas vezes é alguém dizendo: “devemos reescrever essa aplicação do zero”. Mas, realmente vale a pena? E quais as situações que levam a isso?

Nas situações que presenciei, normalmente haviam dois possíveis motivos: o código era muito ruim ou houve uma grande evolução tecnológica desde que a aplicação foi escrita.

Repare que ambos os motivos são de ordem tecnológica, não tendo nada a ver com o negócio em si.

Vamos reescrever!!!

Todo o desenvolvedor gosta de criar algo novo, dificilmente alguém gosta de manter um código existente e, principalmente, escrito por outros. Quando vemos um código ruim, sempre pensamos “eu poderia fazer muito melhor” e “é melhor jogar todo esse código fora e começar do zero”.

O mesmo ocorre quando uma tecnologia antiga é utilizada em um software.

Sempre temos algumas desculpas para reescrever:
  • A tecnologia atual é muito melhor;
  • Dessa vez vamos fazer bem feito, deixaremos o código correto;
  • Entendemos melhor o produto, não vamos cometer os mesmos erros;
  • Reescrever o produto vai ser rápido, afinal, todos os requisitos já foram levantados.

Mas, nem sempre é como esperamos

Certo, decidimos reescrever toda a aplicação. Vamos deixa o antigo código de lado. Utilizamos a antiga aplicação para olhar os requisitos e copiamos o funcionamento dela em uma aplicação novinha em folha.

Aos poucos alguns problemas vão surgindo. Alguns percebemos de imediato, outros só aparecem quando já é muito tarde:
  • Os requisitos estão embutidos no código fonte: mesmo que a aplicação tenha uma ótima documentação, não podemos confiar totalmente nela. Sempre há detalhes que só estão presentes na aplicação em si. Pequenas correções, algumas features e outros pequenos detalhes. Se olhamos apenas para a documentação, deixamos passar esses detalhes, e mesmo ao se basear no código fonte, certamente muitos detalhes passarão despercebidos;
  • O mundo não para de girar: enquanto a nova aplicação é escrita, a antiga continua sendo utilizada. Sempre será necessário incluir um novo recurso ou corrigir um bug. Nesse meio tempo, enquanto reescrevemos a aplicação, toda o desenvolvimento tem que ser duplicado, é feito na versão antiga e na nova. Além disso, evitamos adicionar qualquer recurso na versão antiga, afinal, o cliente que espere a versão nova e nela fazemos isso. Infelizmente, não é bem assim que funciona na prática;
  • Vai demorar tanto quanto a primeira versão ou ainda mais: a ilusão de que o desenvolvimento será rápido logo cai por terra. Criar uma aplicação baseando-se em outra é uma tarefa difícil, além disso, temos que manter as duas versões até que a nova esteja pronta;
  • A nova versão só poderá ser utilizada quando estiver pronta: para tirar proveito da nova aplicação precisamos esperar que ela esteja 100% concluída. Nesse meio tempo, ela não adiciona nenhum valor aos desenvolvedores e nem ao cliente. Imagine se, por algum motivo, o projeto é cancelado. Todo o código desenvolvido é perdido sem nunca ter tido nenhum valor.

Então, como fazer?

A melhor tática é refatorar a aplicação existente, fazendo ajustes de forma gradual até que todo o sistema esteja da forma que desejamos. Já escrevi a respeito de como utilizar a orientação a objetos para nos ajudar nessa refatoração (veja Parte 1, Parte 2 e Final).

Podemos alterar pequenas partes do sistema ou até mesmo jogar toda uma parte fora, porém, não o sistema inteiro.

Ao fazer ajustes graduais na aplicação evitamos toda a dor de cabeça gerada pela reescrita da aplicação inteira. É claro que normalmente é mais difícil ajustar uma aplicação do que reescrevê-la, mas no final das contas, vale a pena.

Quando reescrever?

Em algumas situações realmente vale a pena reescrever totalmente a aplicação. Uma aplicação desktop que precisa ser reescrita para web por exemplo, envolve uma mudança grande de tecnologia, nesse caso vale a pena reescreve-la totalmente.

Mesmo quando decidimos reescrever uma aplicação é recomendável ter uma estratégia de como fazer isso de forma gradual. Implementando as partes mais importantes na nova versão e deixando as duas versões viverem em conjunto enquanto essa migração é feita.

Um artigo bastante interessante escrito pelo Joel Spolsky também fala a respeito disso. Recomendo a leitura: Things You Should Never Do

quarta-feira, 28 de outubro de 2009

A teoria e prática caminham juntas

Frequentemente ouço alguém pedir: "devo aprender primeiro a tecnologia X ou Y?" ou, "o que é melhor eu conhecer, Java ou C# ?". Normalmente minha resposta vai ser: não importa qual você aprenda, desde que aprenda primeiro os conceitos por trás delas.

A importância da teoria

As formas de ensino as quais estamos acostumados normalmente separam a teoria da prática, nunca mostrando como aplicar toda a teoria no mundo real. A teoria e prática são separadas de tal modo que somos levados a crer que uma não tem relação com a outra.

Certamente você já ouvir frases do tipo "Isso é coisa de acadêmico", "No mundo real uma coisa dessas não se aplica" ou então "Eu sou uma pessoa pragmática não ligo pra essas teorias".

Essas frases são exemplos claros de como a teoria e prática são vistas como duas coisas distintas. Uma pessoa se diz pragmática e se afasta de qualquer teoria. A verdade é que a teoria está pros traz de tudo que fazemos, querendo ou não, ela é o fundamento.

O foco excessivo na tecnologia

Uma tendência que vemos atualmente é um foco excessivo na tecnologia em si e não nas motivações por traz dela. Todos querem estudar a nova ferramenta da moda.  As soluções são baseadas apenas em ferramentas.

O que adianta conhecer o framework de desenvolvimento web XYZ se a pessoa não sabe os conceitos básicos de desenvolvimento web? Ou aprender o lindo e maravilhoso framework de persistência recém lançado, sem saber quais seus objetivos e razões de existir.

Esse foco excessivo leva as pessoas a serem especialistas em uma determinada ferramenta, sendo incapazes de utilizar outras ferramentas de mesmo propósito ou até mesmo de fazer qualquer julgamento a respeito delas.

A teoria faz parte da prática

Sempre que preciso utilizar uma ferramenta procuro entender os problemas que motivaram o desenvolvimento dela e a forma que ela visa resolver esses problemas. Com isso em mente, fica fácil comparar diversas ferramentas e adotar outra se necessário.

Um exemplo prático é o desenvolvimento Web. Comecei a desenvolver sistemas web utilizando ASP.Net, e como todos devem saber, o conceito de WebForms adotado pelo ASP.Net tenta abstrair a complexidade da web e simular o desenvolvimento desktop. Nunca gostei muito dessa idéia de abstrair a web, por isso acabava estudando o funcionamento por traz daquela abstração. É importante saber como mostrar os dados em um Grid ou como utilizar um Repeater com dados aninhados, mas tem coisas muito mais importantes que isso.

Com o conhecimento de desenvolvimento web em geral que adquiri, e não apenas o conhecimento da abstração criada pelo ASP.Net, fui capaz de utilizar outros frameworks sem problemas. É só uma questão de traduzir a teoria para os comandos do novo framework.

Orientação a objetos na teoria e na prática

A orientação a objetos não é nova, porém somente na última década ela passou a ser amplamente utilizada.

Em qualquer projeto desenvolvido hoje, se alguém cogitar utilizar o desenvolvimento estruturado, certamente será motivo de piadas. Afinal, programação orientada a objetos (POO) é um paradigma muito superior a programação estruturada. Há apenas um problema: grande parte dos projetos que atualmente são desenvolvidos utilizando POO não utilizam os conceitos de POO, são apenas programas estruturados utilizando classes.

POO não se resume a conhecer o que é classe, objeto, método e herança. Há muitos outros princípios que se aplicados ajudam no bom desenvolvimento. Os princípios mais difundidos são os que conhecemos pelo acrônimo SOLID. São eles:
  • SRP - Single Responsibility Principle;
  • OCP - Open Closed Principle;
  • LSP - Liskov Substitution Principle;
  • ISP - Interface Segregation Principle;
  • DIP - Dependency Inversion Principle.
Apenas com estes princípios é que podemos realmente alcançar todo o potencial da POO e não apenas desenvolver programas estruturados utilizando classes. Em futuros posts devo falar mais sobre eles.
Mais uma vez, é um questão de mudar o foco da tecnologia para a teoria.

Em resumo

Para ter sucesso no desenvolvimento de software devemos tirar um pouco o foco da tecnologia e aprender os conceitos por traz dela. É uma pena que a cada dia as instituições de ensino estão focando menos nessa teoria e mesmo quando o fazem, é de forma distante da realidade, ficando difícil fazer uma ligação entre teoria e prática.

quinta-feira, 8 de outubro de 2009

O programador fita adesiva

Recentemente Joel Spolsky publicou um artigo falando a respeito do “programador fita adesiva”. Esse artigo gerou muita discussão pela internet. Algumas pessoas apoiando e muitas totalmente contra.

Nesse artigo Joel apresenta um tipo de programador, o “duct tape programmer”, que só se preocupa em terminar o produto. Esse programador não inventa moda, não enche o código de frescuras ou fica limpando o código para ficar melhor. Ele simplesmente fará da forma mais simples possível, e se necessário utilizará uma fita adesiva só pra deixar as diversas partes juntas.
duct tape programmer
Jeffrey Pallermo, assim como muitos outros na internet, publicou uma resposta ao artigo. Concordo com a resposta dele, e compartilho da mesma opinião. Recomendo a leitura dessa resposta.

Objetivo correto, atitude errada

Primeiramete tenho que concordar plenamente com o objetivo, que é simplesmente liberar o produto. Nós desenvolvedores estamos muito ligados ao software em si e nem sempre temos uma visão clara do negócio e das necessidades do cliente.

Deixar de liberar uma funcionalidade apenas porquê o código está ruim não é aceitável. Em alguns casos a necessidade do cliente nos obrigada a fazer o sistema funcionar, de um jeito ou de outro, mesmo que utilizando técnicas “não recomendáveis”. Ou seja, é o velho conhecido: “dá um jeito de liberar isso, seja como for”.

Entretando, esse caso deve ser a excessão e não a regra. Não podemos construir um sistema inteiro dessa forma. O padrão deve ser construir um sistema com qualidade e, se por algum motivo precisamos utilizar técnicas obscuras para liberar logo a funcionalidade, OK, desde que seja uma solução temporária e logo seja substituída pela solução correta.

A fita adesiva serve muito bem para tampar um burraco, mas não adianta querer montar uma estrutura inteira com ela que não dará certo. Mesmo esse burraco tampado precisa ser logo consertado.

Qualidade não é opcional

Softwares em geral não são bem vistos, encontrar erros se tornou normal. Anormal é um software com qualidade. Devemos mudar isso, e logo!!

Qualidade não deve ser algo a mais, deve ser o padrão. Um produto com qualidade não é mérito, é obrigação. Devemos investir em processos e práticas que possibilitem aumento na qualidade e produtividade.

Com melhores práticas quem sabe podemos evitar o uso de fita adesiva e mudar um pouco a forma como os softwares são vistos atualmente.