Trabalhar em web sites e web apps que necessitam de suporte RTL (Right-to-Left) é difícil, porque assegurar a exibição RTL correta é mais difícil pelo fato de que ou não temos valores e propriedades CSS para fazer isso, ou porque o suporte existente ainda não é amplamente utilizado. Temos valores como start para text-align e temos propriedades como -moz-margin-start, mas elas não são suportadas em todos os lugares, apesar de RTL ser um aspecto importante em sites globais.
Então, o que temos que fazer em última instância? Repetir os seletores com novos valores RTL e às vezes até mesmo compensar:
/* ltr / default */
.some {
.thing {
> .is {
.here {
margin-left: 20px;
}
}
}
}
/* rtl */
html[dir=rtl] {
.some {
.thing {
> .is {
.here {
margin-right: 20px;
margin-left: 0;
}
}
}
}
}
Eu considero isso um pesadelo por algumas razões:
Se você tiver que mudar o padrão da estrutura CSS aninhada, você precisará se lembrar de fazê-la também para o bloco RTL.
Se você tiver que compensar a regra original, será preciso se lembrar de compensar a original e também ajustar a nova regra.
Ao consertar estilos, encontrei uma excelente solução para sanar todos esses problemas de uma vez com uma mistura simples:
/* mixin definition ; sets LTR and RTL within the same style call*/
bidi-style(margin-left, 20px, margin-right, 0); /* setting LRT and RTL! */
}
}
}
}
O mixin acima é simples, mas me deixa num estado de euforia. Em vez de ter que copiar e manter a estrutura aninhada, esse mixin me permite ajustar os valores das propriedades para LTR e RTL no mesmo lugar no código, evitando a necessidade de criar blocos RTL separados. E esse cenário, de uma simples troca de propriedades dependendo da direção, cobre 95% dos cenários de direções.
Eu sei que o LESS também favorece esse padrão, mas não estou certo de que SASS faz o mesmo. O que também é legal é que RTL não é o único cenário onde isso é útil; você pode usar isso também para coisas baseadas em recursos como classes CSS do Modernizr:
Brilhante! Essa estrutura simples torna escrever código CSS e organizar diferentes estados um milhão de vezes mais fácil. Em um mundo ideal, a propriedade e os valores “start” estariam no lugar, mas, até lá, use esse tipo de estratégia para tornar a sua vida mais fácil!
O Backbone.js é um framework Javascript que fornece componentes para melhorar a estrutura de aplicações web. Entre os componentes estão o Router e o History, responsáveis pela criação de rotas e gestão do histórico do browser via Javascript. Além deles componentes, existe a função Backbone.sync, que é utilizada para realizar toda a sincronização com o servidor, através dos métodos de cada componente (apresentados nos artigos anteriores), a API de eventos utilizada para gestão e disparo de eventos, tanto customizados, quanto os eventos definidos no framework. Existem também alguns métodos utilitários, que auxiliam na resolução de pequenos detalhes como, por exemplo, integração com outros frameworks.
Introdução
No primeiro artigo desta série foi apresentado o framework Backbone.js, seus principais conceitos e aspectos, e uma introdução rápida através de um “Hello World”. No segundo artigo da série, foi apresentada a classe Backbone.View, demonstrando sua utilização, templates e a construção de uma View para um exemplo simples de blog. No terceiro artigo, foi apresentada a classe Backbone.Model junto com um simples backend escrito em Sinatra, possibilitando o trabalho com dados dinâmicos no exemplo do blog, e também foi modificada a View para suportar o mecanismo de templates Mustache. Noquarto artigo da série, foi apresentada a classe Backbone.Collection, possibilitando trabalhar com coleções de dados, assim como alguns métodos utilitários da Underscore.js para trabalhar com essas coleções e algumas modificações no backend Sinatra. Neste quinto artigo da série de seis artigos sobre Backbone.js, serão apresentadas as classes Backbone.Router e Backbone.history, assim como a função Backbone.sync, a API de eventos e alguns métodos utilitários, ilustrando cada item com exemplos práticos, a teoria de funcionamento, integração com o backend Sinatra e mudanças no exemplo de blog para agregar as classes apresentadas.
Backbone.Router
O trabalho com rotas em aplicações web é algo trivial e comum em diversos frameworksserver-side. Podemos pegar como exemplo o Ruby on Rails, que define suas rotas no arquivo routes.rb, ou o Zend Framework 2, que define as rotas de acordo com seus módulos nos arquivos module.config.php. De forma simples, uma rota nada mais é do que o mapeamento entre uma URL e alguma ação/método do framework em questão. Isso pode incluir parâmetros da URL e/ou métodos HTTP, de acordo com o necessário. O exemplo abaixo apresenta a definição de duas rotas no arquivo routes.rb de um sistema que utiliza Ruby on Rails.
# routes.rb
match 'products/:id' => 'catalog#view'
match ':controller(/:action(/:id))(.:format)'
A classe Backbone.Router provê métodos para construir essas rotas no lado cliente, e conecta cada rota a ações e eventos definidos via Javascript. Uma rota do lado cliente pode ser definida através do uso de hashes (#pagina, por exemplo) ou com o uso daHistory API, introduzida no HTML5. Backbone.Router utilizará por padrão a History API e, no caso de o browser não suportar essa API, a própria classe irá modificar a forma de tratar as URLs, para que use então o formato de hashes. Assim como as demais classes do framework Backbone, para customizar a Backbone.Router basta utilizar o método extend().
var AppRouter = Backbone.Router.extend({
// router code..
});
Ao customizar esse método, diversos parâmetros podem ser definidos, como, por exemplo o parâmetro routes, que define as rotas que a classe irá tratar e seus devidos mapeamentos para ações. Uma boa prática é evitar o uso de / no início de uma rota.
Além de definir rotas simples, é possível definir rotas que receberão parâmetros dinâmicos através da URL. Um exemplo disso pode ser uma rota que exibe um determinado registro, em que um parâmetro contendo o código identificador do registro é definido na URL, algo como “/registers/1“.
Existem dois tipos de parâmetros possíveis na definição de uma rota dinâmica:
:parametro – Essa notação definirá que existirá somente este parâmetro definido. Para ilustrar, considere um mapeamento do tipo “posts/:id“. Isso significa que a URL mapeará somente o primeiro parâmetro após a “/”, definindo-o ao :id, ou seja, uma URL do tipo /posts/1/2 simplesmente desconsiderará o parâmetro 2. Caso mais de um parâmetro seja necessário, basta defini-lo, algo como posts/:id/:outroid.
*parametros – Em contrapartida, com a notação anterior, irá mapear diversos parâmetros de uma URL, desconsiderando seu prefixo. Isso significa que uma definição posts/*ids considerará todos os valores que aparecerem depois de posts/, definindo-os nos callbacks. Por exemplo, posts/1/2 irá ler os valores 1e 2, assim como posts/1/2/3, lerá 1, 2 e 3.
Outra maneira de se definir as rotas da classe Backbone.Router é através de seu construtor. Assim como outras classes do Backbone, o Backbone.Router define o método initialize() que obtém um hash como parâmetro. O método route() também pode ser utilizado para criar manualmente uma nova rota. Ele receberá dois argumentos obrigatórios e um opcional, sendo o primeiro argumento a rota que será definida, na mesma sintaxe apresentada anteriormente. O segundo argumento será o nome da ação representada pela rota e será utilizada como identificador do evento da rota, e caso o terceiro parâmetro seja omitido, será mapeado a uma função válida da classe Router. E o terceiro argumento é opcional e pode ser uma função a ser executada quando a rota for acessada.
Dentro da API de Backbone.Router, também existem alguns eventos definidos. Normalmente, esses eventos disparados conterão o nome da ação correspondente a uma rota. O disparo poderá ser realizado em diversos cenários, como quando o usuário pressionar o botão “Voltar” do browser ou entrar em uma URL válida, ou seja, que corresponde a uma rota. Dessa forma, outros objetos podem escutar os eventos de um Router para serem notificados e realizarem alguma operação. Considere o exemplo abaixo. Ao executar #dispatch, será lançado um evento “route:dispatch” do Router.
var AppRouter = Backbone.Router.extend({
routes: {
"dispatch": "dispatch"
},
dispatch: function() {}
});
router = new AppRouter();
router.on("route:dispatch", function() {});
Agora, para atualizar a URL da página manualmente, o método navigate() pode ser utilizado. Ele irá receber dois parâmetros: o primeiro é a rota a ser exibida na URL, e o segundo é um hash de opções que permite que seja executada a ação da rota (trigger: true) e, também que não seja armazenado no histórico do browser a URL (replace: true).
var AppRouter = Backbone.Router.extend({
//...
newPost: function() {
// ...
this.navigate("posts/add");
}
});
var app = new AppRouter();
// Exibe um novo post, executando a sua lógica de exibição, definida em uma ação
app.navigate("post/1", {trigger: true});
// Redireciona para a página de login, executando a ação, sem gravar no histórico
app.navigate("login", {trigger: true, replace: true});
Backbone.history
A classe Backbone.history fornece um router global para tratar eventos hashchangeou pushState; ele também irá escolher a rota apropriada para um determinado item de histórico e disparar callbacks. Um evento hashchange é associado à definição de rotas através de hashes (#). Essa abordagem dispara rotas para a URL atual, o que não requer o recarregamento da página. Com o surgimento do HTML e da History API, esse trabalho ficou mais transparente ao usuário, tirando a necessidade de definir as URLs com hashes. Dentro dessa nova API, encontra-se o método pushState, responsável por manipular e ativar itens do histórico do navegador, mantendo também uma URL amigável, bom para mecanismos de busca e deixando transparente se a aplicação manipula o histórico via Javascript ou não.
Uma boa prática ao se trabalhar com Backbone.history é a de não instanciá-la diretamente, já que, ao utilizar a classe Backbone.Router, uma referência aBackbone.history já é criada automaticamente. O suporte a pushState é oferecido por padrão no componente Backbone.history, e browsers que não suportam a API utilizarão a abordagem com hashes, no estilo explicado anteriormente. Por outro lado, caso uma URL utilizando hashes seja acessada em um browser que suporta pushState, o componente fará uma atualização transparente na URL.
Uma coisa a notar é que não adianta apenas habilitar ou desabilitar as rotas e o histórico do Backbone, também é necessário fazer algumas modificações no backend para que o servidor consiga retornar ao usuário a página esperada para uma determinada URL. Já para a renderização das páginas em conformidade com mecanismos de busca, uma URL direta deveria trazer o HTML completo da página. Em contraste, para uma aplicação web que não será incluída nos mecanismos de pesquisa, utilizar Views e JavaScript seria uma solução aceitável.
Agora que toda a teoria do Backbone.history já foi apresentada, utilizá-lo é tão simples quanto definir algumas rotas no componente Backbone.Router e executar o método Backbone.history.start(). O método recebe como parâmetro uma hash de opções para configurar o componente. Entre essas opções, pode-se definir a opção {pushState: true}, para garantir que o componente use a API pushState do HTML5.// Definição do Router...
// ...
Backbone.history.start();
// Garante que será utilizado o pushState
Backbone.history.start({pushState: true});
Outra opção que pode ser utilizada é a root, que define qual é o endereço base da aplicação. O método start irá retornar true caso a URL atual seja encontrada na lista de rotas, e false caso contrário. Se o servidor renderizar a página completa, sem a necessidade de disparar a rota root ao iniciar o componente History, basta definir o parâmetro silent: true. No Internet Explorer, o histórico baseado em hashes é definido em um iframe, portanto é necessário iniciar o histórico somente quando toda a árvore DOM já estiver pronta.// Endereço base é "index"
Backbone.history.start({root: "/index"});
// Verifica se o histórico foi iniciado corretamente
if (Backbone.history.start()) {
console.log("Histórico inicializado");
} else {
console.log("Não foi possível inicializar o histórico");
}
// Não dispara a URL "root"
Backbone.history.start({silent: true, root: "/index"});
Backbone.sync
Uma das principais características do Backbone.js é a comunicação remota através de uma API RESTful. Toda operação em que exista a necessidade de ler ou gravar um Model remotamente precisará de uma interface comum para executar as chamadas remotas utilizando corretamente os métodos HTTP, definir no corpo da requisição os parâmetros do Model etc. Apesar de essas chamadas serem executadas tanto porBackbone.Model quanto por Backbone.Collection, existe uma função em comum que sempre será executada por ambos os componentes, essa é a Backbone.sync().
Por padrão, a função sync() executará o método ajax() da biblioteca JavaScript sendo utilizada na aplicação (Zepto ou jQuery), executando uma requisição HTTP com JSON em seu corpo, cabeçalhos HTTP correspondendo à ação em questão e retornando um objeto jqXHR. Seguindo a principal característica do framework Backbone, que é a flexibilidade e a facilidade de extensão, a função sync() também pode ser estendida e customizada. Se, por exemplo, a aplicação utilizar offline storage, sem a necessidade de comunicação com um servidor remoto, o método sync() pode ser customizado para trabalhar com o banco de dados local, ou até, se o servidor suporta apenas transporte por XML, isso também pode ser implementado bastando sobrescrever a função. Ao sobrescrever Backbone.sync(), a assinatura sync(metodo, modelo, [opcoes]) deve ser utilizada, onde:
metodo – Corresponde ao método CRUD (“create”, “read”, “update”, “delete”) a ser executado
model – O objeto Model a ser gravado ou uma coleção a ser lida
opcoes – Argumento opcional, define callbacks de sucesso ou erro, e outras opções de requisição suportadas pela API ajax() do framework Javascript utilizado
O exemplo abaixo ilustra um código simples para estender Backbone.sync().
Backbone.sync = function(method, model, options) {
if (method == 'create') {
console.log('creating a new model...');
} else {
console.log('not creating, doing now a: ' + method);
}
};
O funcionamento padrão de Backbone.sync() pode ser capaz de suprir boa parte dos cenários comuns em aplicações web. Ao ser requisitado para gravar um Model, a função irá definir uma requisição contendo como corpo os atributos do Model serializados como JSON, com um content-type definido para application/json. A requisição retornará como resposta outro JSON, com os atributos já gravados no backend, para serem atualizados no lado cliente da aplicação. Quando uma Collection efetuar uma requisição read, a função Backbone.sync() precisará retornar um array de objetos com atributos que correspondam aos Models gerenciados pela Collection em questão, cabendo também ao backend responder à requisição GET com esses dados. O mapeamento REST padrão funciona da seguinte forma:
O create efetuará um POST para o endereço /collection
O read efetuará um GET para o endereço /collection[/id]
O update efetuará um PUT para o endereço /collection/id
O delete efetuará um DELETE para o endereço /collection/id
Além da sobrescrita global apresentada no trecho de código anterior, é possível sobrescrever a função sync() para os componentes mais específicos do framework. Seria possível, por exemplo, adicionar uma função sync() para as classes Backbone.Model e Backbone.Collection, conforme ilustrado no exemplo e seguir.
Backbone.Model.sync = function(method, model, options) {
// just do something...
};
Backbone.Collection.sync = function(method, model, options) {
// only collections...
}
Apesar de esses serem os aspectos principais da função Backbone.sync, ainda existem mais algumas configurações. Um exemplo disso é o atributo emulateHTTP. Ao definir esse atributo como true, a função irá emular requisições PUT e DELETE, ou seja, a requisição construída não utilizará nenhum destes como o método HTTP definido na requisição, utilizará no lugar um método POST, definindo então esses métodos em um atributo de cabeçalho chamado X-HTTP-Method-Override. Esse comportamento é útil para servidores que não oferecem suporte aos cabeçalhos HTTP RESTful. Outro atributo que pode ser configurado é o emulateJSON, que quando definido irá modificar o comportamento de serializar o Model e defini-lo como corpo da requisição HTTP. Em vez de utilizar essa abordagem, o Model será serializado e seu JSON será definido em um parâmetro POST chamado model, e a requisição utilizará o cabeçalho application/x-www-form-urlencoded, o que simula a requisição de um formulário HTML padrão. Esse atributo é útil para servidores que não suportam requisições do tipo application/json. Se ambos os atributos forem definidos como true, o método HTTP que antes era definido no cabeçalho HTTP chamado X-HTTP-Method-Override agora será definido em um parâmetro POST, nesse caso chamado _method.
Backbone.emulateHTTP = true;
Backbone.emulateJSON = true;
// Make a request just to show the behavior
var post = new PostModel(
title: 'Title',
content: 'Content'
);
post.save(););
Eventos
Nos artigos anteriores, foram apresentados diversos eventos disparados pelas classes do framework Backbone, assim como as formas de tratar esses eventos. Além desses eventos já pré-definidos, existe o módulo Events, que permite trabalhar com eventos customizados. Esse módulo é bem flexível, e os eventos não precisam ser pré-definidos para serem tratados ou lançados, e alguns eventos podem ser lançados com alguns argumentos definidos. Se uma aplicação necessita de um dispatcher customizado para tratar diversos eventos específicos da aplicação, o módulo de eventos da Backbone pode ser uma boa solução. Considere o código abaixo.
var object = {};
_.extend(object, Backbone.Events);
object.on("myevent", function() {
console.log("myevent was triggered");
});
object.trigger("myevent");
A situação ilustrada pode ser muito bem tratada por esse código, o objeto em questão ganha alguns novos métodos, como, por exemplo, o método on(), utilizado para vincular uma função de callback a um determinado evento. Caso exista um grande número de eventos na aplicação, uma boa prática é especializar cada um desses eventos através de um prefixo seguido do caractere :, como, por exemplo, users:add e users:edit. O método on() recebe dois parâmetros: o primeiro é o evento a ser escutado, e o segundo é a função de callback. Um terceiro parâmetro opcional pode ser definido, para que o contexto this corresponda ao escopo de classe do objeto, e não o escopo de função dacallback, que é o comportamento padrão.
callback = function(argument) {
console.log("Event triggered with the argument: " + argument);
};
object.on("myevent", callback, this);
Se houver a necessidade de que um callback seja executado para todos os eventos disparados, basta definir como primeiro parâmetro a string all.
object.on("all", globalCallback);
Para remover um callback definido anteriormente a um evento, basta utilizar o método off(), que recebe três parâmetros opcionais:
event – O evento anteriormente definido e que será removido
callback – A callback existente para o evento
context – O contexto definido no callback
object.off("all", globalCallback);
Todos esses métodos são úteis para executar uma determinada função quando um evento for disparado, e disparar o evento é muito simples, basta utilizar o método trigger(), definindo em seu primeiro parâmetro a string representando o evento. Opcionalmente, podem-se definir mais parâmetros nesse método, que serão lançados como argumentos do evento, que aparecerão como argumentos das callbacks definidas no método on().
object.trigger("myevent", "argument");
Utilitários
Outros métodos úteis do Backbone, porém não relacionados com nenhuma das classes já apresentadas até então, envolvem alguns pequenos utilitários para utilizar o framework em si. O primeiro método é o noConflict - seu conceito é simples, retornar o Backbone completo, com seus valores originais. Esse método permite que uma referência local ao framework seja utilizada. Esse cenário é útil, por exemplo, para evitar conflitos em versões diferentes do framework em uma mesma aplicação. Outro método que pode ser utilizado é o Backbone.$ (anteriormente setDomLibrary), que irá dizer ao Backbone qual objeto jQuery, Zepto ou outra variante, utilizar como biblioteca AJAX/DOM. Ou, até, para manter mais de uma versão de jQuery na mesma aplicação web. Acredito que serão raras as vezes em que esses utilitários serão necessários, mas cada caso é um caso, e eles estão presentes no framework para suprir alguma necessidade específica do desenvolvedor.
// noConflict
var localBackbone = Backbone.noConflict();
var model = localBackbone.Model.extend(...);
// $
Backbone.$ = jQuery();
Até agora, todo o conteúdo base do Backbone foi apresentado, abrangendo os principais tópicos da documentação do framework e alguns exemplos para ilustrar o que foi dito. O próximo passo é aplicar esses componentes na aplicação de blog que está sendo desenvolvida desde o primeiro artigo.
A serialização de objetos nos dias de hoje é essencial. Precisamos serializar hashes no banco; serializar JSON para saídas e entradas de chamadas Ajax e uma pá de necessidades que não seriam possíveis sem serializarmos objetos.
Recapitulando rapidamente, serializar um objeto é transformar sua estrutura (ou estado) que está em memória num formato adequado para transmissão e/ou armazenamento, para que ele possa ser transformado novamente, num segundo momento, em um objeto na memória, com o processo que chamamos de deserialização. Mais rapidamente ainda e de forma grosseira (e espero que didática): é como pegarmos um objeto (uma variável, array, hash…), transformá-la em texto (serialização) e depois transformar novamente em um objeto da memória (deserialização).
O processo se serialização/deserialização também é conhecido como marshalling/unmarshalling edeflating/inflating respectivamente.
Modulo Marshal
O Ruby conta com um módulo específico para serializar/deserializar chamado Marshal. Esse módulo já vem incluido no Ruby. Ele tem basicamente dois métodos:
Em nosso exemplo, temos uma variável chamada hash com um hash dentro, que serializamos com o método Marshal.dump e depois deserializamos com o método Marshal.load. A variável hash_deserializado agora é um clone idêntico, em valores e tipo, ao hash original. Prova disso é que se executarmos uma comparação de tipo e valor entre eles, o resultado é verdadeiro.
hash.eql?(hash_deserializado) # retorna TRUE pois os valores e o tipo do objeto são exatamente iguais
Fácil né? É fácil mesmo e sem pegadinhas. Quer dizer, exceto algumas…
Objetos complexos com métodos, associações e por ai vai…
O Marshal.dump não consegue fazer o dump de objetos que contenham binding, procedures, métodos de instância e objetos singleton. Se tentar fazer algo assim, uma exceção TypeError é lançada.
A serialização de uma instância de um modelo ActiveRecord com associações serializa/deserializa normalmente. Mas, logicamente, esse objeto não poderá ser usado em outro ambiente que não seja o seu por motivos óbvios: ao ser deserializado, o objetivo virá com o tipo/modelo original e caso você não o tenha no seu ambiente, vai dar pau. Ex.: você serializa um objeto ActiveRecord de um modelo chamado Livro e tenta deserializá-lo em outro ambiente qualquer.
Versão do módulo Marshal
O Marshal.load funcionará apenas para objetos serializados com uma versão igual ou menor à do modulo que está tentando carregar.
Ou seja, se você está com a versão 4.8 do módulo Marshal, você conseguirá fazer o load apenas quando o objeto for serializado por essa versão ou menor (4.7, 4.6 …). Por sorte (ou azar) o módulo tem um versionamento diferente do Ruby. Para fazer a checagem da versão serializada, veja os dois primeiros bytes da serialização, como abaixo:
Em alguns casos você irá serializar objetos que podem conter valores com encodings diferentes do padrão UTF-8, como ASCII-8BIT. Ao invés de interagir por todo o hash ou objeto corrigindo os encodings, você pode forçar a conversão diretamente pelo método dump.
Se você deseja serializar seu objeto para um arquivo texto, tenha cuidado na forma como você vai escrever esse conteúdo. Por algum motivo estranho, algumas vezes em que você joga o conteúdo serializado em um arquivo com o método write do módulo File, isso pode dar pau na hora de deserializar o conteúdo.
Para evitar o problema, escreva o dump no arquivo destino como abaixo.
File.open("arquivo.txt",'w') do |f|
Marshal.dump(objeto, f)
end
Para fazer a deserialização do objeto armazenado no arquivo:
objeto = File.open("arquivo.txt") do |file|
Marshal.load(file)
end
Outro dia eu fui a uma palestra de uma hora sobre erlang, apenas como observador; eu não sei nada sobre erlang, exceto que soa interessante e que a sintaxe é… bem… incomum. A palestra foi dada a alguns programadores Java que recentemente aprenderam erlang, e era uma crítica justa sobre seu primeiro projeto erlang, que eles estavam apenas completando. O apresentador disse que esses programadores precisavam parar de pensar como programadores Java e começar a pensar como programadores erlang1 e, em particular, deveriam interromper a programação defensiva e deixar processos falharem rapidamente e corrigirem o problema.
Agora, aparentemente, essa é uma boa prática em erlang, porque uma das características do erlang, e por favor me corrijam se eu estiver errado, é que o trabalho é dividido em supervisores e processos. Supervisores supervisionam os processos, criando-os, destruindo-os e reiniciando-os, se necessário. A ideia de falha rápida não é nada nova, e é definida como a técnica a ser usada quando o seu código vem através de uma entrada ilegal. Quando isso acontece, o código só fracassa e interrompe o ponto que você corrige a entrada do fornecedor em vez de seu código. O sub-texto que o apresentador disse é que Java e programação defensiva são ruins e falham rápido se forem boas, o que é algo que realmente necessita de uma investigação mais aprofundada.
A primeira coisa a fazer é definir a programação defensiva, e a primeira definição que eu encontrei foi no que é hoje, possivelmente, um livro lendário: Writing Solid Code, de Steve Maguire, publicado pela Microsoft Press. Eu li esse livro há muitos anos, quando eu era um programador C, que na época era a linguagem defacto de escolha. No livro, Steve demonstra a utilização de uma macro _Assert:
/* Borrowed from Complete Code by Steve Maguire */
#ifdef DEBUG
void _Assert(char *,unsigned) /* prototype */
#define ASSERT(f) \
if(f) \
{ } \
else
_Assert(__FILE__,__LINE__)
#else
#define ASSERT(f)
#endif
// ...and later on..
void _Assert(char *strFile,unsigned uLine) {
fflush(NULL);
fprintf(stderr, "\nAssertion failed: %s, line %u\n",strFile,uLine);
fflush(stderr);
abort();
}
/////// ...and then in your code
void my_func(int a). {
ASSERT(a != 0);
// do something...
}
…como a definição dele de programação defensiva. A ideia aqui é que estamos definindo uma macro C que, quando DEBUG é ligado, my_func (…) irá testar sua entrada usando ASSERT (f), e que irá chamar a função _ASSERT (…) se a condição falha. Assim, quando no modo DEBUG, nessa amostra my_func (int a) tem a capacidade de abortar a execução se arg a é zero. Quando DEBUG é desligado, verificações não são realizadas, mas o código é menor e mais rápido; algo que foi, provavelmente, mais de uma consideração em 1993.
Olhando para essa definição, várias coisas vêm à mente. Em primeiro lugar, esse livro foi publicado em 1993, e ainda assim é válido? Não seria uma boa ideia matar Tomcat usando um System.exit (-1) se um de seus usuários digitasse a entrada errada! Em segundo lugar, Java sendo mais recente também é mais sofisticado, tem exceções e manipuladores de exceção, por isso, em vez de abortar o programa, nós lançaríamos uma exceção que, por exemplo, exibiria uma página de erro com destaque para as entradas ruins.
O ponto principal que vem à mente, no entanto, é que essa definição de programação defensiva soa muito parecida com falha rápida para mim, na verdade, é idêntico.
Esta não é a primeira vez que eu ouço queixas de programadores em programação defensiva, então por que ela tem uma reputação tão ruim? Por que o apresentador da palestra sobre elrang denegriu-a tanto? Meu palpite é que há uma boa utilização de programação defensiva e mau uso de programação defensiva. Deixe-me explicar com algum código…
Neste cenário, eu estou escrevendo uma calculadora de Índice de Massa Corporal (IMC)para um programa que informa aos usuários se eles estão ou não com excesso de peso. Um valor de IMC entre 18,5 e 25 é aparentemente bom, enquanto tudo acima de 25 é de excesso de peso a obesidade grave. O cálculo do IMC usa a seguinte fórmula simples:
IMC = peso (kg) /altura² (m)
A razão por que eu escolhi essa fórmula é que ela apresenta a possibilidade de um erro de divisão por zero, o que o código que eu escrevo deve defender.
public class BodyMassIndex {
/**
* Calculate the BMI using Weight(kg) / height(m)2
*
* @return Returns the BMI to four significant figures eg nn.nn
*/
public Double calculate(Double weight, Double height) {
Validate.notNull(weight, "Your weight cannot be null");
Validate.notNull(height, "Your height cannot be null");
Validate.validState(weight.doubleValue() > 0, "Your weight cannot be zero");
Validate.validState(height.doubleValue() > 0, "Your height cannot be zero");
Double tmp = weight / (height * height);
BigDecimal result = new BigDecimal(tmp);
MathContext mathContext = new MathContext(4);
result = result.round(mathContext);
return result.doubleValue();
}
}
O código acima usa a ideia apresentada na definição de programação defensiva de Steve de 1993. Quando o programa chama calculate(Double weight,Double height), quatro validações são realizadas, testando o estado de cada argumento de entrada e lançando uma exceção apropriada em caso de falha. Como este é o século 21 eu não tenho como definir minhas próprias rotinas de validação, eu simplesmente usei aqueles fornecidas pelo commons-lang3 da biblioteca Apache e importei:
A biblioteca Apache commons lang contém a classe Validate, que fornece algumas validações básicas. Se você precisar de algoritmos de validação mais sofisticados, dê uma olhada na biblioteca validadora commons Apache.
Uma vez validado o método calculate(…), ele calcula o IMC e arredonda para quatro algarismos significativos (por exemplo, nn.nn). Em seguida, ele retorna o resultado para o caller. Usar Validate me permite escrever muitos testes JUnit para garantir que tudo corra bem em caso de problemas e para diferenciar cada tipo de falha:
public class BodyMassIndexTest {
private BodyMassIndex instance;
@Before
public void setUp() throws Exception {
instance = new BodyMassIndex();
}
@Test
public void test_valid_inputs() {
final Double expectedResult = 26.23;
Double result = instance.calculate(85.0, 1.8);
assertEquals(expectedResult, result);
}
@Test(expected = NullPointerException.class)
public void test_null_weight_input() {
instance.calculate(null, 1.8);
}
@Test(expected = NullPointerException.class)
public void test_null_height_input() {
instance.calculate(75.0, null);
}
@Test(expected = IllegalStateException.class)
public void test_zero_height_input() {
instance.calculate(75.0, 0.0);
}
@Test(expected = IllegalStateException.class)
public void test_zero_weight_input() {
instance.calculate(0.0, 1.8);
}
}
Uma das “vantagens” do código C é que você pode desligar o ASSERT (f) por meio de um comutador no compilador. Se você precisar fazer isso em Java, dê uma olhada no uso da palavra-chave assert do Java.
O exemplo acima é o que eu espero que nós concordemos que se trata de uma amostra bem escrita – o bom código. Então, o que é necessário agora é a amostra mal escrita. A principal crítica de programação defensiva é que ela pode esconder os erros, e isso é muito verdadeiro, se você escrever um código ruim.
public class BodyMassIndex {
/**
* Calculate the BMI using Weight(kg) / height(m)2
*
* @return Returns the BMI to four significant figures eg nn.nn
*/
public Double calculate(Double weight, Double height) {
Double result = null;
if ((weight != null) && (height != null) && (weight > 0.0) && (height > 0.0)) {
Double tmp = weight / (height * height);
BigDecimal bd = new BigDecimal(tmp);
MathContext mathContext = new MathContext(4);
bd = bd.round(mathContext);
result = bd.doubleValue();
}
return result;
}
}
O código acima verifica também contra argumentos null e zero, mas ele faz isso usando o seguinte argumento if:
Olhando pelo lado positivo, o código não irá falhar se as entradas estiverem incorretas, mas não vai dizer ao caller o que está errado, ele vai simplesmente esconder o erro e retornar null. Embora não tenha deixado de funcionar, você tem que perguntar o que o caller vai fazer com um valor de retorno null? Ou ele vai ignorar o problema ou processar o erro lá usando algo parecido com isto:
Olhando o lado bom, o código não vai travar se as entradas estiverem incorretas, mas ele não vai dizer ao caller o que deu errado – vai simplesmente esconder o erro e retornar null. Embora não tenha travado, você precisa perguntar o que o caller vai fazer com um valor de retorno null. Ou ele terá que ignorar o problema, ou processará o erro usando algo assim:
@Test
public void test_zero_weight_input_forces_additional_checks() {
Double result = instance.calculate(0.0, 1.8);
if (result == null) {
System.out.println("Incorrect input to BMI calculation");
// process the error
} else {
System.out.println("Your BMI is: " + result.doubleValue());
}
}
Se essa técnica “ruim” de codificação é usada em toda a base de código, então haverá uma grande quantidade de código extra necessária para verificar cada valor de retorno.
É uma boa ideia NUNCA retornar valores null a partir de um método.
Para concluir, eu realmente acho que não há diferença entre a programação defensiva e a programação de falha rápida, elas são realmente a mesma coisa. Não há, como sempre, apenas boa e má codificação? Eu deixo você decidir.
————————————————– ——————————
Este código de exemplo está disponível no Github.
1 – é sempre uma mudança de paradigma no pensamento quando aprende uma nova linguagem. Haverá o ponto onde as gotas de um centavo e você “pegam”, seja ele qual for.
Você é um profissional de TI que recebeu a tarefa de fazer mais com menos? Há muitos softwares de qualidade disponíveis no mercado que facilitam a vida dos profissionais de TI brasileiros em seu trabalho diário. Neste artigo, apresento onze ótimos exemplos de softwares grátis e poderosos que todos os profissionais de TI deveriam pensar em usar para ajudar a automatizar tarefas, economizar tempo e gerenciar as restrições orçamentárias.
Uma coleção online de mais de 30 ferramentas de gerenciamento de DNS para e-mail, rede, domínio e pesquisa de endereços IP. O livre acesso a essas ferramentas permite instantaneamente que os profissionais de TI diagnostiquem e resolvam problemas com DNS e serviços de e-mail, e façam análise forense em uma série de preocupações de domínio e e-mail, análise de caminho e autenticação e localização de domínio; além disso, ajuda a manter o controle das listas negras e com a conformidade e a conectividade da Web, do e-mail e do servidor de nome.
Para a maioria das equipes de TI, o gerenciamento de alertas é um processo manual e tedioso que envolve planilhas, calendários compartilhados, pagers e telefones para gerenciar alertas. No fim, esses métodos contam com pessoas para encaminhar problemas na hora certa, o que dá lugar a erros humanos e consome um tempo desnecessário. Com o SolarWinds Alert Central, os profissionais de TI podem agora centralizar os alertas de TI de múltiplos sistemas em uma visualização única e consolidada, dar aos membros da equipe acesso a um calendário de agendamento, encaminhar os alertas automaticamente e permitir que a equipe de TI responda de seus próprios computadores ou dispositivos móveis com ou sem acesso ao VPN, tudo de graça.
E derivados, como LibreOffice e IBM Lotus Symphony… No reino dos produtos robustos cheios de recursos, é certo que este programa pertence a esta lista simplesmente por causa de sua incrível funcionalidade. Esses produtos podem interessar os profissionais de TI em locais em que ainda são usadas versões mais antigas do Office e não existe verba para atualizá-los ou para justificar as despesas de TI, porque são pouco utilizados. Além disso, para as organizações que precisam dispor de um pacote de ferramentas de escritório, mas podem querer avaliar o nível de uso que vão fazer, um pacote gratuito como o OpenOffice, o LibreOffice ou o Symphony pode fornecer o cenário de produtividade e avaliação antes de se determinar o investimento em uma cópia completa do Microsoft Office.
Não há dúvida de que este é o programa único mais falado do ano passado. Sua força vem da capacidade de capturar qualquer coisa em qualquer lugar e armazenar em um único local. Imagine o arquivo de um projeto no computador do trabalho, quando surge uma epifania no momento em que você senta para ver um episódio de CSI. Pegue o smartphone, acrescente uma nota de texto sobre a epifania e retorne para o sofá antes do final do intervalo comercial (bem, para os que não têm gravador de vídeo). No dia seguinte, a nota aparece no arquivo do seu projeto (porque você tinha colocado o arquivo no Evernote-for-PC).
Diga o que quiser sobre a forma como a Microsoft está lidando com as migrações do WLM-Skype-Lync, o Skype foi a principal ferramenta de texto/voz/vídeo por bastante tempo. A partir deste mês, a Microsoft está integrando a funcionalidade do sistema de mensagens Windows Live no Skype. O Skype já está integrado no Facebook, e até o final do ano o Skype estará integrado com o Lync. O resultado dessa integração é que não será preciso instalar os clientes Lync em sistemas remotos, porque os usuários do Skype vão conseguir conversar diretamente com os usuários do Lync, com as credenciais corretas de autenticação do Lync.
Para preencher a lacuna entre o abandono do VirtualPC por parte da Microsoft e o abandono do VMWorkstation por parte da VMware, e a recente disponibilidade do Hyper-V no Windows8, o VirtualBox trouxe uma ferramenta de virtualização independente de plataforma para o ambiente de desktop. Foi o VMware Workstation e o VirtualPC que rapidamente ampliaram a capacidade dos profissionais de TI em todos os lugares de explorar novas tecnologias ao criar uma VM em suas máquinas pessoais e fazer uma instalação sem precisar seduzir um servidor inutilizado no meio do ferro-velho corporativo. Hoje o VirtualBox é o produto que está fornecendo essa experiência para os profissionais de TI.
No mundo bombando do podcasting “faça você mesmo”, é inestimável ter uma boa ferramenta de edição de áudio. Mas, se você não está fazendo podcasts, basta conseguir fazer mix de suas músicas, editar podcasts para uso pessoal (botar o lixo fora, guardar o que é importante) ou talvez até mesmo se aventurar no campo da gravação de snippet de áudio. Quantas vezes um técnico de suporte técnico lê, de forma automática, por telefone, os mesmos passos para consertar algo no sistema de computador do usuário final? Pense na opção de só enviar um arquivo WAV para esse usuário final com exatamente as mesmas etapas que estão sendo lidas para eles, só que agora o usuário final pode pausar o áudio, reproduzir o áudio ad naseum (até resolver o problema) e esse técnico de Help Desk pode ajudar alguém que realmente precisa de ajuda personalizada.
Qualquer profissional de TI que se encontra na posição de precisar diagnosticar o problema em um serviço ou aplicativo baseado na rede vai achar muito útil conseguir ver o que está realmente passando por essa conexão de rede.
O gerenciamento de senhas é um desafio até mesmo para o uso mais leve que se faz na Web, mas os profissionais de TI normalmente têm acesso a muito mais recursos, e muitas vezes a um nível administrativo que exige uma melhor proteção. Manter dezenas de senhas complexas pode ser uma pressão sobre o cérebro, ou às vezes evoluir para um padrão previsível que faz com que cada senha desse profissional de TI seja descoberta com força bruta como resultado da descoberta de uma senha. O LastPass é a principal geração de senha e uma ferramenta de armazenamento, e deve estar no arsenal de todos os profissionais de TI.
A mídia vem em todas as formas, tamanhos, formatos e quase sempre o momento em que você precisa ver esse vídeo, ou ouvir aquele áudio, “agora” é o momento em que ele foi gravado com um codec diferente, e seu player de mídia incorporado não sabe o que fazer com ele. Além disso, o VLC tem a capacidade de reproduzir vídeos em DVD (algo que está sendo removido dos players de mídia baseados em OS por causa dos custos de licenciamento), e a versão mais recente tem código experimental para reprodução de Blu-Ray (tomara que você tenha sorte de ter um reprodutor de Blu-Ray no seu notebook).
Rodo este programa em todos os meus ambientes de desktop pessoais desde seu lançamento. (Por que eu pagaria a Symantec ou a Trend Micro pela mesma coisa?). Embora não esteja disponível para organizações com mais de 10 desktops, todos os usuários domésticos ou pequenas empresas devem ter um, incluindo os profissionais de TI, e também todos os familiares que vão ligar para você com um problema de malware na volta das férias, porque esqueceram de renovar o AV/AM quando estavam na praia. O MSE é grátis – para sempre – e é automaticamente atualizado pelos mesmos recursos de Atualizações Automáticas que os consumidores usam para obter outros pacotes de segurança da Microsoft (não tem assinatura para expirar… nunca!).
Anos atrás, ganhei da minha madrinha um livrinho, a princípio bobo pela simplicidade (acho que ela me presenteava com livros desde antes de antes de eu aprender a ler).
Trata-se do livro “Ideias Geniais”, de Surendra Verma. Ele aborda (pelo menos tenta) princípios, teorias, leis e princípios científicos – do Teorema de Pitágoras à Teoria de Tudo, mas de uma forma muito superficial – é uma blasfêmia falar do “Principia” em uma única página. Mas pela minha fixação no assunto e carinho pela minha adorável madrinha, li o livro inteiro. E eis que deparo com “A Navalha de Ocam”, um princípio que remonta do século 14 e resumidamente diz que “Entidades não devem ser multiplicadas desnecessariamente”, que serviu de inspiração para, entre outras aplicações, oprincípio KISS.
Isaac Newton – Principia
O princípio KISS – de “Keep It Simple, Stupid” – postula que a simplicidade deve ser a palavra de ordem em projetos (de engenharia civil, elétrica, mecânica ou computacional) e que complexidades devem ser evitadas.
Em desenvolvimento de software, arquitetura de sistemas, modelagem de dados, design de websites, enfim, em qualquer segmento, esse princípio deve ser lembrado – aliás, é um mantra que repito para as equipes que lidero.
São inúmeros os exemplos que podemos apresentar para ilustrar os benefícios e aplicabilidades do principio KISS, a saber:
No paradigma procedural, temos o uso de bibliotecas com funções e procedures pequenas e SIMPLES reutilizáveis
O simples uso de CSS em um website
Acredito que a própria orientação a objetos foi motivada pelo principio KISS
Caros colegas, simplicidade está longe de ser sinônimo de facilidade; alias é exatamente o oposto: soluções simples são EUREKAS de criatividade e genialidade.
Alguns anos atrás, num desses trabalhos como arquiteto deparei com um sistema com sérios problemas de performance e manutenção. Diversos eram os problemas, mas o que de fato me chamou atenção e, tive meu momento EUREKA, foi o seguinte: no gerenciamento de eventos em uma agenda. Uma das características é aperiodicidade,podendo serúnica, diária, semanal e mensal. Suponhamos que na periodicidade semanal, além de informar a quantidade de semanas de ocorrência do evento, seja necessário especificar os dias da semana de ocorrência do evento, por exemplo:
As aulas do curso de c# ocorrerão a partir do dia 20/08/2013, pelas próximas 4 semanas às segundas, quartas e sextas-feiras.
As reuniões de acompanhamento do projeto, ocorrerão a partir do dia 01/12/2014, pelos próximos 4 meses.
A apresentação do projeto será realizada dia 20/12/2013.
Uma modelagem de dados muito utilizada nesse tipo de cenário, (verifique o que tem aí na sua empresa), seria algo semelhante ao apresentado abaixo, ou muito próximo a isso, com uma tabela somente com os dias da semana relacionada à tabela de eventos, e foi exatamente isso que encontrei.
EVENTO_TIPO_FREQUENCIA
Char
EVENTO_TOTAL_DIAS
Int
EVENTO_TOTAL_SEMANAS
Int
EVENTO_TOTAL_MESES
Int
EVENTO_DIA_SEGUNDA
Bit
EVENTO_DIA_TERCA
Bit
EVENTO_DIA_QUARTA
Bit
EVENTO_DIA_QUINTA
Bit
EVENTO_DIA_SEXTA
Bit
EVENTO_DIA_SABADO
Bit
EVENTO_DIA_DOMINGO
Bit
Tabela 1 – modelo parcial da tabela de eventos.
Onde o atributo TIPO_FREQUANCIA é definido em um domínio pré-estabelecido, por exemplo [1-único, 2 – diário, 3 – semanal, 4 - mensal].
Os atributos TOTAL_DIAS, TOTAL_SEMANAS, TOTAL_MESES contabilizam o período de ocorrência do evento baseado no tipo da frequência.
E finalmente os atributos DIA_XXX, que especificam os dias da semana de ocorrência do evento.
Essa modelagem é fácil e até mesmo intuitiva, mas gera uma série de problemas, tais como:
Temos uma quantidade razoável de atributos, em que sempre teremos desperdício de espaço em nosso repositório de dados.
Toda codificação terá que contemplar todos esses conjuntos de atributos [procedures com muitos parâmetros, classes com todas essas propriedades, métodos de validação e operações de CRUD com vários parâmetros].
Processo de alteração mais complexo – um evento inicialmente cadastrado como semanal, 4 vezes, às terças, quintas e sábados, ao ser modificado para mensal, 2 vezes, será necessário atualizar todos os atributos.
Momento EUREKA:
Redução drástica na quantidade de atributos na tabela de eventos.
EVENTO_TIPO_FREQUENCIA
Char
EVENTO_PERIODO
Int
EVENTO_DIAS_DA_SEMANA
Byte
Tabela 2 – modelo parcial da tabela de eventos – solução proposta.
O atributo TIPO_FREQUENCIA mantém a mesma definição da solução anterior.
Aqui, o atributo PERIODO armazena o total de ocorrência do evento, seu sentido será dado em função do TIPO_FREQUENCIA.
E, finalmente, a grande simplificação do cenário, em um único atributo, DIAS_DA_SEMANA, do tipo byte, armazenaremos os dias da semana e todas as suas combinações, caso tenhamos uma frequência do tipo semanal. Obviamente ainda teremos “desperdício” de atributos, mas somente um, e ainda apenas para tipo da frequência diferente de semanal.
Como isso é possível??
Lembram do filme “Matrix”, quando o personagem Neo passou a ver o mundo decodificado? Eis o caminho para nossa solução: precisamos recorrer ao cerne do mundo digital – os bits.
Recordar é viver – “Os tipos de dados são formados por conjuntos de bits” – um inteiro tem 4 bytes x 8 bits = 32 bits, um double tem 8 bytes x 8 bits = 64 bits, etc. No nosso caso, onde temos que manipular 7 informações (os dias da semana), a melhor opção é um campo do tipo byte que tem 8 bits – tudo bem, ainda desperdiçamos 1, mas nem tudo é perfeito.
Decodificando um campo byte temos:
0000 0001
1
0000 0010
2
0000 0100
4
0000 1000
8
0001 0000
16
0010 0000
32
0100 0000
64
Atribuindo um dia da semana para cada valor:
0000 0001
1
Domingo
0000 0010
2
Segunda-Feria
0000 0100
4
Terça-Feria
0000 1000
8
Quarta-Feria
0001 0000
16
Quinta-Feria
0010 0000
32
Sexta-Feria
0100 0000
64
Sábado
Decodificando os dias da semana:
0000 0001
01 ↔ 2 0
Domingo
0000 0010
02 ↔ 2 1
Segunda-feira
0000 0100
04 ↔ 2 2
Terça-feira
0000 1000
08 ↔ 2 3
Quarta-feira
0001 0000
16 ↔ 2 4
Quinta-feira
0010 0000
32 ↔ 2 5
Sexta-feira
0100 0000
64 ↔ 2 6
Sábado
Ora, concluímos que o valor associado a um dia corresponde a 2 elevado ao seu “peso”, 2 (dia da semana)
E para combinação de vários dias, como nos exemplos acima?? Simples, é só “ligar” os bits correspondentes a cada dia, vejamos:
Logo, em vez de manipular diversos atributos, propriedades e parâmetros na codificação, calculamos um único valor numérico que representa a combinação de todos os dias da semana e assim manipulamos somente um atributo, simplificando e otimizando nosso trabalho.
Finalmente a expressão para obtenção desse valor será : Valor += 2 (dia da semana) , onde o dia da semana será obtido de sua interface.
E o processo inverso: como recuperar o valor armazenado no repositório de dados e disponibilizá-lo ao usuário? Ora, precisamos desse valor representado em bits novamente, ou melhor, em um conjunto de bits (7), em seu devido estado fundamental – ligado x desligado, mais precisamente devido à interação com a interface, true x false.
Mais uma vez, recorremos à maravilhosa matemática. Nosso algoritmo foi baseado em funções exponenciais, nas quais uma das formas de análise dessas funções é o processo conhecido como fatoração, lembram?
64
2
32
2
16
2
8
2
4
2
2
2
1
64 = 2 6
Pois bem, precisamos de um conjunto ou array de bits ligados e desligados, correspondente ao valor armazenado; combinando as operações de fatoração e o resto das divisões sucessivas, teremos exatamente as informações de que precisamos:
42 = ?
O resto da divisão 42/2 = 0 (fatorando = 21)
O resto da divisão 21/2 = 1 (fatorando = 10)
O resto da divisão 10/2 = 0 (fatorando = 5)
O resto da divisão 05/2 = 1 (fatorando = 2)
O resto da divisão 02/2 = 0 (fatorando = 1)
O resto da divisão 01 1 (fim)
E esses “zeros” e “uns” gerados, tomados de baixo para cima, correspondem exatamente ao nosso conjunto inicial de bits (10 1010), complementando-os com zeros à esquerda, bingo: 0010 1010, ou melhor, um array do tipo [false – false – true – false true – false – true –false].
É isso aí, pessoal, o principio KISS é de uma utilidade tremenda em nosso dia a dia, seja implementando, coordenado ou gerindo atividades relacionadas ao mundo de tecnologia. Espero com este artigo ter lhes despertado a pensar de maneira KISS.
Nosso trabalho deve ir muito além de pesquisar códigos ou fórmulas no Google para resolução de problemas. Precisamos, sim, analisar, pensar um pouco e propor soluções da melhor forma possível. Como vimos no exemplo apresentado, a codificação utilizada foi mínima, e aí está o grande mérito da proposta – conseguimos muito com bem pouco, uma amostra das leis natural do mínimo esforço (não em vão, fizemos uso da matemática).
É claro que pensar de maneira a simplificar é ótimo, mas também devemos prestar atenção a Einstein que disse “Tudo deve ser tornado o mais simples possível, porém não mais simples que isso”.
Finalmente, meu muito obrigado à minha madrinha (in memoriam), que desde sempre me estimulou o hábito da leitura e fica a lição: não existe livro “bobo”, sempre tem algo interessante a ser aproveitado.
A usabilidade nos oferece diferentes técnicas para realizar uma tarefa de forma simples e rápida. Quando um usuário para pra pensar no que uma informação ou ação faz em um site, é sinal de que a usabilidade não foi aplicada de forma correta, e este é o seu trabalho, fazer com que os usuários não façam essas perguntas.
Quando o seu usuário fizer algumas dessas perguntas: “Onde esta o…?”, “Cadê o botão de…?”, “Por que isso tem esse nome?”, “Isso é um texto ou um link?”, “Onde está a informação principal?”, “Onde esta a informação que eu entrei para ver nesta página?”, ou “Onde estou?” é por que o seu trabalho de acessibilidade não foi bem aplicado.
Os usuários não devem sofrer sobrecarga de informação e quando ele visita seu site e não sabe por onde começar a ler, existe sobrecarga de informação.
Devemos utilizar na apresentação e na redação três características básicas:
Texto resumido;
Formate o texto para um melhor entendimento, como enumerar os pontos, separar títulos e cabeçalhos, utilizar negritos, destacados ou listas;
Linguagem objetiva.
Utilizar vinculação profunda, ou seja, não obrigar aos usuários a entrar pela página de início, e sim permitir que cheguem diretamente a pagina de seu interesse como banners e links voluntários. Se você criou um banner sobre informações do produto “X” e quando o usuário clica neste banner, ele é enviado para a página principal no qual não tem a informação do produto “X”, então, você não criou uma vinculação profunda. Você está fazendo com que ele pense, e ele terá que de alguma forma procurar o produto “X” dentro do seu site.
A navegação dentro do site deve permitir que usuário saiba onde está em cada momento.
Podemos mencionar as algumas técnicas de usabilidade:
Autonomia – os usuários devem ter o controle sobre o site e suas ações sobre ele;
O usuário manda – ele é o cliente, e a regra “o cliente que sempre tem a razão” se aplica;
Na web qualidade é igual à rapidez – mais de 20 segundos e 80% dos visitantes irão embora sem ver a página;
Site que não carrega imediatamente -se isso acontecer, deve-se colocar um pré-carregamento, assim o usuário permanece no site;
Devem funcionar todos os links (sim, existe links que não funcionam) ¹;
O site deve ser simples, para que os usuários se sintam cômodos e não se percam cada vez que necessitem encontrar algo na página;
Colocar breves conclusões – isso ajuda o usuário a encontrar o que busca em pouco tempo;
Escreva os conteúdos resumidos um 25% do que colocaria em um papel – ler em tela custa muito¹;
As cores devem ser utilizadas com precaução – assim, elas não dificultam o acesso por parte dos usuários daltônicos (aproximadamente 15% do total).
Utilizar interfaces conhecidas, os sites devem requerer um mínimo de processo de aprendizagem.
Legibilidade - a cor dos textos deve contrastar com a do fundo, e o tamanho de fonte deve ser suficientemente grande.
Concluindo, poderíamos dizer que os usuários não gostam de adivinhar as coisas. Eles querem as coisas de forma óbvia, alcançando, assim, seus objetivos com um mínimo esforço e com resultados rápidos.
Um exemplo simples, mas que ocorre frequentemente são os links onde o usuário “não sabe” se é realmente um link ou não. Veja o exemplo de menu.
Este menu é utilizado em um site. Temos que pensar para entender que o mesmo se trata de links; agora, considere o seguinte menu:
Neste menu o usuário entende rapidamente que se trata de links.
Poderíamos pensar “Basta colocar o mouse encima do link. Com isso o cursor vai mudar e o usuário vai entender”, porém, cada dúvida remove nossa atenção no trabalho que estamos fazendo, e mesmo sendo distrações pequenas elas podem ir se acumulando e às vezes confundir o usuário ou fazer com que ele esqueça o real motivo de estar naquela página.
Coisas sobre como os usuários leem as páginas
1.1. Usuários não leem as páginas por completo
Quando estamos na internet, nos não saímos lendo uma pagina por inteiro. Primeiro damos uma olhada superficial e se vemos que ela tem o que nos queremos, aí sim continuamos a ler todo o conteúdo (mas isso não garante uma leitura de 100%, pois, se em algum momento fizermos com que o usuário se perca um pouco, ele não pensará duas vezes em deixar o site).
E isso é informado na pesquisa na coluna “How Users Read on the Web” no site da “UseIt”. Os usuários simplesmente movem rapidamente seus olhos sobre o site buscando as principais informações que procuram no momento.
É comum o usuário que navega pela internet estar com “pressa sempre” e procurando economizar o máximo de seu tempo. Visando isso, ele olha de forma rápida as informações da página, a procura de partes relevantes para ele.
Através destas informações, foi definido o “Padrão de Leitura F”, onde ele adverte os seguintes padrões de leitura dos usuários:
Da esquerda para a direita;
De cima para baixo;
Maior atenção ao primeiro parágrafo e posteriormente procurando mais informação no próximo parágrafo.
Com isso, quando formos criarmos nossas páginas, temos como nos orientar sobre como criá-las e algumas diretrizes a serem tomadas, como:
Título claro e em destaque;
Valorizar os dois primeiros parágrafos (sendo o primeiro o de maior destaque), pois são eles que irão chamar a atenção do usuário;
Parágrafos curtos e com espaço vazio entre eles. Não use parágrafos colados; deixe que o usuário perceba que um paragrafo terminou e outro começou;
Destacar pontos importantes (negrito, itálico e lista);
Usar subtítulos para destacar e separar o conteúdo.
1.2. Os usuários não querem o perfeito, eles querem o aceitável
É natural pensarmos que, ao criar uma página, os usuários irão buscar a melhor opção, porém, não é bem isso que ocorre. Eles se contentam com a suficiente. Você pode perceber isso quando procura alguma informação na internet: você não fica por horas procurando. Você entra no Google e digita suas palavras na busca e abre umas duas ou no máximo três páginas e se elas tiverem a informação que procura, você para por ali mesmo.
Lembre-se que os usuários estão com pressa, e perfeição é difícil e nos custa muito tempo, logo, entendemos que fazer o suficiente é o mais eficiente.
1.3. Não entendemos como as coisas funcionam, nos apenas as usamos
Uma coisa que os usuários fazem muito (e não apenas na internet) é utilizar algo sem compreender completamente, ou pior, entendem da forma errada. São poucas as pessoas que leem manuais por completo antes de utilizar algum aparelho (o celular, por exemplo). Nós ligamos o aparelho e saímos usando. E em alguns casos quando não conseguimos fazer ou não sabemos se aquela ação é possível, nós recorremos ao manual.
Para o usuário, não importa como as coisas funcionam (nem todos são tão curiosos como as pessoas de TI), desde que no final ele tenha o resultado esperado. Ela então para de procurar uma forma melhor de realizar aquilo e este usuário só irá mudar a forma. Forma esta que ele encontrou “sem querer”, porque está insatisfeito com a que encontrou ou outro usuário o ensinou a fazer aquilo de uma forma mais rápida ou fácil.
2. O mais simples é o mais fácil
Após toda essa conversa, você pode pensar “Vou fazer de qualquer forma o que tenho que fazer; no final, o usuário se vira pra descobrir como faz mesmo”. Pois bem, se estiver pensando assim, você não entendeu o espírito da coisa.
Todos os apontamentos feitos até agora foram uma forma de indicar que temos que deixar as coisas o mais simples e óbvias para os usuários, pois eles não querem ficar pensando em como usar isso ou aquilo. Eles simplesmente querem usar.
Quando criamos algo simples e os usuários “entendem”, somos nós quem ganhamos:
Eles encontram aquilo ou atingem seu objetivo, o que é bom para ambos;
Você consegue direcioná-lo para o local mais apropriado;
Eles terão mais confiança em seu site;
Eles voltaram para o seu site, pois, o seu site foi mais eficiente que os outros.
Bem, vamos ficando por aqui e espero que tenham gostado do artigo.
Revisão Segura de Código (traga alguém de fora da equipe para revisar/auditar o código para vulnerabilidades de segurança) e pen test em aplicação (mais uma vez, trazendo um especialista em segurança de fora da equipe para testar o sistema) são as duas práticas importantes de programa de desenvolvimento de software seguro. Mas, se você só pudesse fazer um deles, se você tivesse tempo limitado ou orçamento limitado, qual você escolheria? Que abordagem vai encontrar mais problemas e lhe dizer mais sobre a segurança do seu aplicativo e sua equipe? Qual vai lhe trazer mais retorno sobre o investimento?
Pen tests e revisões de código são coisas muito diferentes – eles requerem um trabalho diferente de sua parte, encontram problemas diferentes e lhe dão informações distintas. E o custo pode ser bastante diferente também.
Porque eles podem olhar dentro da caixa, revisores de código podem zerar em um código de alto risco: interfaces públicas, gerenciamento de sessão, gerenciamento de senhas, controle de acesso e criptografia e outro encanamento de segurança, o código que lida com dados confidenciais, tratamento de erros, auditoria. Ao escanear o código, eles podem verificar se o aplicativo é vulnerável a ataques de injeção comuns (injeção de SQL, XSS etc.), e eles podem procurar por bombas-relógio e backdoors (que são praticamente impossíveis de testar de fora) e outros códigos suspeitos. Eles podem encontrar problemas com a simultaneidade e tempo e outras questões de qualidade de código que não são exploradas, mas que devem ser fixadas de todas as maneiras. E um bom revisor, como eles trabalham para entender o sistema e seu design e fazer perguntas, pode também apontar erros de projeto, suposições incorretas e inconsistências – e não apenas os erros de codificação.
Pen testers contam com scanners e proxies de ataque e outras ferramentas para ajudá-los a procurar por muitas das mesmas vulnerabilidades de aplicativos comuns (injeção de SQL, XSS etc.), bem como problemas de configuração em tempo de execução. Eles vão encontrar divulgação de informações e problemas de tratamento de erros à medida que eles invadem o sistema. E eles podem testar problemas em gerenciamento de sessão, manipulação de senha e gerenciamento de usuário, autenticação e autorização ignoram fraquezas, e até mesmo encontrar falhas de lógica de negócios, especialmente em fluxos de trabalho conhecidos, como compras online e funções bancárias. Mas porque eles não podem ver dentro da caixa, eles – e você – não saberão se terão coberto todas as peças de alto risco do sistema.
O tipo de teste de segurança que você já está fazendo por sua conta pode influenciar se um pen test ou uma revisão do código é mais útil. Você está testando o seu aplicativo web regularmente com uma ferramenta black box de escaneamento dinâmico de vulnerabilidade? Ou executando verificações de análise estática, como parte de integração contínua?
Um pen test manual vai encontrar muitos dos mesmos tipos de problemas que um scanner dinâmico automatizado, e muito mais. Uma boa ferramenta de análise estática vai encontrar pelo menos alguns dos mesmos erros que uma revisão de código manual – um monte de revisores utilizam ferramentas de verificação de análise estática de código fonte para procurar frutos mais baixos (erros de codificação comuns, funções inseguras, senhas codificadas , injeção de SQL simples etc.). Testes superficiais ou reviews podem não envolver muito mais do que alguém executando uma dessas ferramentas de digitalização automatizada e revisando e qualificando os resultados para você.
Então, se você está confiando em testes de análise dinâmica, faz sentido obter uma revisão do código para procurar por problemas que você ainda não tenha testado sozinho. E se você estiver escaneando o código com ferramentas de análise estática, então um pen test pode ter uma melhor chance de encontrar problemas diferentes.
Custos e complicações
Um pen test é fácil de configurar e gerenciar. Ele não deve exigir muito tempo e cuidado de sua equipe; mesmo que você faça isso direito, certifique-se de explicar as principais funções do aplicativo para a equipe de pen test e caminhe com eles através da arquitetura, e lhes dê todo o acesso que precisarem.
As revisões de código são geralmente mais caras do que os pen tests, e vão exigir mais tempo e esforço de sua parte – você não pode apenas dar a um estranho uma cópia do código e esperar que ele descubra tudo por conta própria. Há maior necessidade de um acompanhamento bem de perto, e de dias vias. Você segura a mão dele, o acompanha, explica a arquitetura. e como o código é estruturado, e como funcionam o sistema e os drivers de conformidade e de risco, respondendo a perguntas sobre o design e a tecnologia à medida que eles vão avançando; e ele segura sua mão, pacientemente explicando o que encontrou e como corrigiu e trabalhou com sua equipe para entender se cada achado vale a pena ser corrigido, eliminando falsos positivos e outros mal-entendidos.
Esse cuidado é importante. Você deseja obter o máximo de valor do tempo de um revisor – você quer que ele se concentre em código de alto risco e não se perca em tangentes. E você quer ter certeza de que sua equipe entenda o que o revisor encontrou, quão importante é cada bug e como eles devem ser corrigidos. Assim, você não só precisa ter pessoas ajudando o revisor – eles devem ser suas melhores pessoas.
Propriedade Intelectual e Confidencialidade e outras questões legais são importantes, especialmente para as revisões de código – você está deixando um estranho olhar o código, e ao mesmo tempo você quer ser transparente, a fim de garantir que a análise é abrangente, você também pode estar arriscando seu segredo. Sólida contratação e trabalhar com empresas de renome irão minimizar algumas dessas preocupações, mas você também pode precisar limitar estritamente o código que o revisor começará a ver.
Outros fatores na escolha entre pen tests e revisões de código
O tipo de sistema e sua arquitetura também podem afetar sua decisão.
É fácil encontrar pen testers que têm muita experiência em testar portais e lojas online – eles estarão familiarizados com a arquitetura geral e reconhecerão funções e fluxos de trabalho comuns, e podem contar com verificação externa, e ferramentas de fuzzing para ajudar nos testes. Isso se tornou um serviço baseado em commodities, em que você pode esperar um bom trabalho feito por um preço razoável.
Mas se você está construindo um aplicativo com proprietário de APIs do sistema-a-sistema ou clientes proprietários, ou você está trabalhando em um domínio técnico altamente especializado, é mais difícil de encontrar pen testers qualificados, e eles custarão muito mais. Eles precisarão de mais tempo para compreenderem a arquitetura e o app, como tudo se encaixa e no que eles devem focar nos testes. E eles não serão capazes de aproveitar as ferramentas padrão, então eles vão ter que bolar algo por conta própria, o que vai demorar mais tempo e pode não funcionar tão bem.
Uma revisão de código poderia te dizer mais nesses casos. Mas o revisor tem que ser competente na (s) linguagem (s) em que seu aplicativo está escrito – e, para fazer um trabalho completo, ele também deve estar familiarizado com os frameworks e as bibliotecas que você está usando. Como nem sempre é possível encontrar alguém com o conhecimento e a experiência certa, você pode acabar pagando-os para aprenderem sobre o trabalho – e confiando muito em quão rápido eles aprendem. E, claro, se você estiver usando um monte de código de terceiros para os quais você não tem fonte, então um pen test é realmente a sua única opção.
Você está em um estágio avançado de desenvolvimento, preparando-se para lançar? Nesse momento, o mais importante é validar a segurança do sistema em execução, incluindo a configuração de tempo de execução e, se você está realmente atrasado em desenvolvimento, encontrar quaisquer vulnerabilidades exploráveis de alto risco, porque isso é tudo que você vai ter tempo para corrigir. Esse é o lugar onde vários pen tests são feitos.
Se você está nos estágios iniciais de desenvolvimento, é melhor escolher uma revisão de código. Pen test não faz muito sentido (você não tem o suficiente do sistema para fazer testar o sistema real), e uma revisão de código pode ajudar a definir o caminho certo para o time do resto do código que eles têm de escrever.
Aprendendo e utilizando os resultados
Além de encontrar vulnerabilidades e ajudá-lo a avaliar o risco, a revisão de código ou um pen test proporcionam oportunidades de aprendizagem – uma chance para a equipe de desenvolvimento entender e melhorar a forma como eles escrevem e testam o software.
Pen tests diz o que está quebrado e é explorável – os desenvolvedores não podem argumentar que o problema não é real, porque um invasor o encontrou, e que o atacante pode explicar quão fácil ou difícil é para eles encontrarem o erro, que o risco é real. Os desenvolvedores sabem que eles têm que corrigir alguma coisa – mas não está claro onde e como corrigi-lo. E não é clara a forma como eles podem verificar que eles consertaram. Diferentemente da maioria dos bugs, não existem passos simples para o desenvolvedor reproduzir o bug em si: eles têm que contar com o pen tester para voltar e fazer um “re-teste”. É ineficiente e não é um bom feedback para reforçar a compreensão.
Outra desvantagem com os pen tests é que eles são feitos no final do desenvolvimento, muitas vezes, muito tarde. A equipe pode não ter tempo para fazer nada além da triagem dos resultados e corrigir o que tem de ser corrigido antes de o sistema entrar no ar. Não há tempo para os desenvolvedores refletirem, aprenderem e incorporarem o que aprenderam.
Também pode haver um déficit de comunicação entre testadores e desenvolvedores pen. A maioria dos pen testers pensa e fala como hackers, em termos de exploits e ataques. Ou eles falam como auditores, focados na conformidade, mapeando suas descobertas para taxonomias de vulnerabilidades e frameworks de gestão de risco, que não significam nada para os desenvolvedores.
Revisores de código pensam e falam como os programadores, o que torna as revisões de código muito mais fáceis de aprender – desde que o revisor e os desenvolvedores de sua equipe gastem tempo trabalhando juntos e compreendendo os resultados. Um revisor de código pode explicar ao desenvolvedor o que há de errado, explicar por que e como corrigi-lo e responder às perguntas do desenvolvedor imediatamente, em termos que um desenvolvedor vá entender, o que significa que os problemas podem ser consertados mais rápido e direito.
Você não vai encontrar todas as vulnerabilidades de segurança em um aplicativo por meio de uma revisão de código ou um pen test – mesmo fazendo os dois (embora você tenha uma chance melhor). Se eu pudesse fazer apenas uma coisa ou outra, com todos os outros fatores de lado, eu escolheria uma revisão de código. A revisão dará mais trabalho e provavelmente vai custar mais, e pode até não encontrar tantos bugs de segurança. Mas você vai ter mais valor no longo prazo a partir de uma revisão de código. Desenvolvedores vão aprender mais e mais rápido, espero que o suficiente para entender como procurar e corrigir problemas de segurança por conta própria e, ainda mais importante, para evitá-los em primeiro lugar.
Recentemente li um artigo muito interessante e didático publicado por Simon Liew no site SQL Server Central com o título “How efficient is your covered index?”.
O autor mostra um exemplo onde um índice com várias colunas não se comporta da forma esperada, o que afeta sua eficiência. Isso ocorre porque o uso dos índices varia de acordo com ‘n’ fatores, tais como a forma como se escreve as consultas, a estruturas de índices e de tabelas, etc.
Por exemplo, quando se usa critérios de pesquisa com funções (ISNULL, DATEADD, etc) ou operadores especiais (LIKE..), o otimizador de consultas não consegue usar os índices. Costuma-se classificar estes critérios de “SARGable” ou “non-SARGable”, conforme eles permitam ou não o uso dos índices. SARG é uma abreviatura pra Search Argument, que, neste caso, se refere a “argumento de pesquisa” de índices; eu não encontrei uma tradução adequada para este termo, por isso uso a palavra em inglês.
Exemplo:
WHERE date >= ’01-01-2012′ AND date < ’01-01-2013′ permite uso de indices (SARGable)
WHERE Year(date) = 2012 usa uma função e é non-SARGable
(Para mais informações sobre critérios SARGable, clique aqui).
Eu pretendo estender este estudo com algumas adaptações, mas recomendo ao leitor que antes leia o artigo original (clique aqui).
No meu ambiente eu uso o SQL Server 2008 R2, que é atualmente a versão padrão usada na empresa onde trabalho. E base demo ADVENTUREWORKS, que uso aqui também, é idêntica à original.
Meus testes visam observar a influência e a interação de três fatores:
estrutura da tabela, com campos que aceitam nulos (1) ou não (2);
sequência de campos do índice: original (A) ou com campo nulo na última posição(B);
critério da consulta SARGable (SARG) ou non-SARGable (NSARG).
Os links para os scripts destes objetos são mostrados a seguir:
Tabela 1
Tabela 2
Índice A
Índice B
Consulta NSARG
Consulta SARG
Clique aqui para fazer o download dos arquivos zipados.
Com base nestas definições, os testes são feitos para oito cenários:
Tabela 1 com Índice A e Consulta NSARG, configuração idêntica à usada no artigo do Simon Liew;
Tabela 1 com Índice A e Consulta SARG;
Tabela 1 com Índice B e Consulta NSARG;
Tabela 1 com Índice B e Consulta SARG;
Tabela 2 com Índice A e Consulta NSARG;
Tabela 2 com Índice A e Consulta SARG;
Tabela 2 com Índice B e Consulta NSARG;
Tabela 2 com Índice B e Consulta SARG.
A opção por tabelas físicas, em teoria, não influencia os resultados dos testes.
OBS: neste mundo dos SGBDs, é preciso sempre ter cuidado com as afirmações que fazemos J.
A ideia é consultar os planos de execução das consultas, avaliando o Custo Total de Execução e se a pesquisa dos índices (Index Seek) usa todos os critérios de pesquisa.
A primeira coisa que se observa é que o custo total é praticamente o mesmo em todos os cenários (variação de apenas 3,6%). Mas isso não é surpresa, já que todos os cenários envolvem consulta com o uso de índices.
(Por curiosidade, eu fiz um teste rodando consultas sem índice algum e o custo total dispara: sobe para 0,871546, ou seja, mais que 250 vezes maior do que o custo de qualquer outro cenário).
A segunda coisa que chama a atenção é que os testes com mudança de estrutura da tabela (usando campo CarrierTrackingNumber como NULL ou NOT NULL) não teve nenhum efeito nos resultados. Todos os testes dão exatamente o mesmo resultado (veja os cenários 1 e 5, 2 e 6, 3 e 7, 4 e 8). Portanto alterar definição de campos não é um fator que possa ajudar significativamente na otimização do uso de índices.
Já a influência da mudança da sequência dos campos do índice tem impacto, mas apenas nos testes que usam consultas non-SARGable. Pode-se notar isso comparando os testes 1 e 3, 2 e 4, 5 e 7 e 6 e 8. Veja que os testes 2, 4, 6 e 8 tiveram exatamente o mesmo o custo de execução das consultas.
Finalmente, chegamos aos critérios de consulta (SARGable e non-SARGable). Este parece ser o fator de maior impacto. Veja os testes 1 e 2, 3 e 4, 5 e 6, 7 e 8. Em todos os casos, houve redução do custo total de execução das consultas, mesmo que a diferença fosse pequena. A diferença quando usamos o índice original chegou a 3,6% e apenas 0,1% para o índice com nova sequência.
O fato é que este rápido estudo nos dá algumas dicas sobre o que podemos fazer para otimizar o uso de um índice:
Mudar a definição de campos em relação aos valores nulos provavelmente terá pouco ou nenhum impacto;
Redefinir a sequência de campos do seu índice pode ajudar em algumas situações, mas provavelmente não será efetiva se for uma ação isolada;
Escrever consultas SQL que usem apenas critérios SARGable parece ser a alternativa que tem melhor resultado quando aplicada isoladamente. Mas os resultados tendem a ser melhores quando usada em conjunto com outras ações.
É difícil extrapolar os resultados deste teste e dizer que eles se aplicam a qualquer cenário. Na realidade, eu não conheço nenhuma bala de prata para ser usada na área de bancos de dados, se é que existe alguma.
É importante destacar também que os três fatores estudados neste artigo não são os únicos possíveis. Mas estes resultados, por mais limitados que sejam, mostram uma tendência que eu acredito que todo mundo previa: o cenário de melhor performance considera uma combinação de fatores. Em outras palavras, a ação de melhor resultado é, na verdade, uma combinação de ações.
Mesmo mostrando um resultado de aplicação limitada, acho que o ponto fundamental deste artigo é mostrar para o leitor um método simples e rápido de definir e testar os cenários que pareçam viáveis para solução de um problema de otimização.
Por um tempo, eu estive pensando sobre a melhor forma de lidar com a exibição de caixas de diálogo modais para os meus aplicativos ao utilizar views Backbone. Um monte de ideias interessantes passou pela minha cabeça, mas nenhum delas parecia exatamente certa. Então eu vi um artigo de Derick Bailey, no qual ele descreveu como usa Regions do Marionette para lidar com o trabalho. Seu artigo é um pouco sobre o lado antigo, e Regions mudaram um pouco, então eu decidi ver como poderia fazê-lo sozinho.
As questões
Há várias questões que envolvem a criação de diálogos modais apenas com uma view. Como Derick fala em seu artigo, a maioria dos plugins para criar esses diálogos irá remover (ou apenas mover) o elemento real do DOM, então todos os eventos que você configurou na view serão perdidos.
Junto com isso, perdemos a capacidade de reutilização. Ao fazer a view lidar com o trabalho de controlar o modal, isso não pode ser usado em um lugar onde você não queira uma janela modal. Ao mover a funcionalidade modal para fora da view, ela pode ser utilizada em qualquer lugar da aplicação.
A solução
Agora que sabemos o que precisamos para extrair a funcionalidade modal em views, é apenas uma questão de descobrir onde colocá-la. Regions do Marionette são perfeitos para isso. Regions são objetos que representam um lugar que já existe no DOM, e eles lidam com adição e remoção de views de/para aquele lugar no DOM. Basta chamar`show` em um Region para adicionar um views lá e chamar `close` para removê-lo.
Tudo o que precisamos fazer é aumentar um Region para chamar o método do plugin para mostrar o modal quando a view é exibida. Certifique-se de esconder o modal quando a view é fechada, e feche a view quando o modal estiver escondido.
Aqui eu desenvolvi um `ModalRegion` que utiliza um plugin modal do Twitter Bootstrap:
- Criei uma função ‘construtor` em vez de `initialize`. Isso porque se alguém estende esse Region, substituirá essa função `initialize` com a dele. Dessa forma, não será substituída.
- Eu chamo `this.ensureEl ()`. Esse é o método de um Region para armazenar em cache o objeto jQuery do elemento Regions para `this.$el`. Normalmente, esse método é chamado em seu método `show`, mas precisávamos que `this.$el` fosse configurado antes disso.
- Há algumas coisas estranhas acontecendo nesse manipulador de eventos:
◦ O evento ‘hidden’ é disparado pelo Twitter Bootstrap depois que ele esconde o modal.
◦ O objeto que é passado como o segundo parâmetro para `on` é um objeto de dados que está ligado a `event` como sua propriedade `data`. Isso não é comumente usado, por isso muitas pessoas não sabem que ela existe. Se isso é novidade para você, confira a API. Passei por esse objeto como uma forma de me certificar de que o método `close ‘foi chamado para o Region com o contexto certo. Existem várias formas alternativas para fazer isso, e esta provavelmente não é a melhor maneira, mas evita um encerramento, e isso me ajuda a ensinar algo que você não sabia antes.
◦ `onShow` e `onClose` são chamados imediatamente após os métodos `show` e `close` terem terminado. Essa é uma maneira simples para que possamos aumentar os métodos `show` e `close`, para fazerem mais do que fazem normalmente, sem substituir as funções.
Agora é simples de usar:
var modal = new ModalRegion({el:'#modal'});
var view = new SomeView();
modal.show(view);
Conclusão
Simples assim. Isso pode ser alterado muito facilmente para usar outros plugins, como jQueryUI, KendoUI, Wijmo etc. Além disso, agora que a funcionalidade modal está em um lugar, se você acabar trocando o plugin que você usa, você só tem que mudar o código nesse Region, e tudo deve funcionar bem. A única coisa que você precisa para trabalhar é receber todo o markup específio do modal lá.
Olá, pessoal! Continuando a nossa série de post sobre o Spring, vamos ver hoje como fazer um simples CRUD com Spring + Hibernate + JSF. O objetivo maior é que quem esteja chegando possa ver como juntar as tecnologias de maneira simples. E o melhor de tudo: veremos como Spring é uma mão na roda de verdade.
A nossa aplicação será super simples. Vamos fazer o cadastramento de carros, e exercitar o CRUD. Para exibição e cadastro dos veículos, vamos fazer uma página JSF da maneira mais simples possível e que seja funcional. A seguir os requisitos:
Requisitos:
MysqlSQL 5.x
Hibernate 3.x
Spring 3.x
Tomcat 7.x
JSF 2.x
Jboss tools
Vou considerar que você vem acompanhando a série de posts Spring e alguns pontos que já foram tratados aqui, não vou explicar novamente. Têm alguma dúvida a respeito? E sobre JSF 2.x também irei considerar que você já brincou com o framework, mesmo que seja na versão anterior. Estou ressaltando isso, para que não ter que entrar nos detalhes de cada ponto, fazendo com que o artigo fique mais objetivo.
Configuração
O primeiro ponto é criar um projeto JSF Project (é preciso ter o jboss tools instalado).
Escolha a opção JSF 2 na tela do assistente e em seguida crie os packages conforme mostrado a seguir. Adicione os .jars na pasta lib do projeto:
Criando o source unit/test
Observe que eu criei um source para os unit tests:
Agora, vamos criar o arquivo de configuração do Spring. Na verdade, teremos dois: um para os unit tests e outro para aplicação. Apenas dupliquei, mas poderíamos otimizar o de unit tests importando apenas o que precisamos a partir do arquivo principal, mas não quis fazer isso por agora; vamos focar no CRUD.
Crie um arquivo springconfiguration.xml dentro de WEB-INF. O nome pode ser qualquer um (normalmente utiliza-se applicaiton-context.xml, mas quis fazer diferente para que você veja que o nome não importa).
Não irei adicionar ao cabeçalho apenas o código, que não tem nada de diferente do que já vimos nos artigos quando vimos hibernate com Spring.
Agora precisamos fazer umas configurações no arquivo web.xml.
<!– dizendo onde está meu arquivo de configuração –>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springconfiguration.xml,</param-value>
</context-param>
<!– configurando o context loader do Spring, esse cara permite carregar N arquivos de configuração –>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!– esse cara permite dizer ao JSF que os beans serão gerenciados pelo Spring é requerido ter essa
configuração –>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
Testando
Se você subir aplicação agora, não há nada de especial, apenas vai subir o exemplo que temos no JSF Project, que é criado pelo Jboss tols. Mas é bom testar para garantir que as mudanças que fizemos no web.xml não afetaram nada da aplicação.
Arquivo de configuração do spring para unit tests
Agora vamos criar um arquivo de configuração para executar os unit tests. Mas por que? Simplesmente porque a partir de uma classe de teste você não consegue ler um arquivo que está em web-inf, então você precisa ter o arquivo em source.
Noticia boa: o código é o mesmo que do arquivo anterior, então apenas duplique o XML, o meu chamei de springconfiguration-test.xml e coloquei no JavaSource:
Agora vamos começar a brincadeira, ou seja, desenvolver!
Começaremos pelos testes, claro. Para isso, criaremos uma classe que testará o serviço que implicitamente testa as classes DAO.
Antes, criaremos os testes, pois é importante entender que:
Após os testes terem sido executados, este deve dar rollback, para que os dados não fiquem no banco;
Os testes não podem ser dependentes de outro test, ou seja, um @Test não pode depender da execução de outro @Test
Por enquanto, ao criar os unit test é esperado que nem compile, já que não temos nenhuma classe pronta. Mas é isso que queremos: que os testes nos ensinem a desenvolver as regras de negócio que precisamos.
O primeiro teste vai nos permitir testar o salvarOrUpdate da nossa aplicação. Eu poderia ter separado em dois testes: um para save e outro para update, mas não quis entrar em detalhes sobre testes aqui, senão o artigo ficaria duas vezes maior.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={“classpath:springconfiguration-test.xml”})
@TransactionConfiguration(transactionManager=”transactionManager”,defaultRollback=true)
@Transactional
public class CarServiceImplTest {
@Autowired
private CarServices carServices;
private Car car;
@Before
public void setUp() throws Exception {
car = new Car();
car.setDescription(“ferrari”);
car.setPriceSale(BigDecimal.ZERO);
car.setYear(“2010″);
}
@Test
public void testSaveOrUpdate() {
try{
carServices.saveOrUpdate(car);
}catch (Exception e) {
fail(“not expected result”);
}
Observe que configuramos o rollback igual a true para tudo, então sempre será feito rollback e nada é inserido no banco. Mas, se você quer inserir no banco para um método especifico, basta anotarmos no método assim: @Rollback(false) //isso faz inserir no banco.
Note: nesse exemplo não estou usando um banco em memória, então é preciso que o Mysql esteja rodando para que os testes possam ser executados. O ideal era fazer os testes rodarem em um banco em memória assim, aí não teríamos essa dependência, pois o banco seria iniciado sempre que os testes fossem executados.
Agora vamos criar as classes que o Eclipse está reclamando. Vamos começar pela entidade Car:
@Entity(name=”SALE_CARS”)
public class Car implements Serializable{
private static final long serialVersionUID = 2792374994901518817L;
@Id
@GeneratedValue
private Long id;
private String description;
private BigDecimal priceSale;
private String year;
//getters/setters omitidos
Services
Vamos criar os serviços, mas antes precisamos ter uma interface para os nossos serviços CRUD, então:
@Service
public class CarServiceImpl implements CarServices {
@Autowired
private CarDAO carDAO;
public void saveOrUpdate(Car car) {
carDAO.addCar(car);
}
//setters CarDAO omitido. Não é preciso criar get.
DAO Interface
Também criaremos uma interface para o DAO e em seguida a implementação do método save:
public interface CarDAO {
void addCar(Car car);
List<Car> readAll();
void deleteCar(Long id);
}
@Repository
public class CarDAOImpl implements CarDAO {
@Autowired
private SessionFactory sessionFactory;
private Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
public void addCar(Car car) {
getCurrentSession().saveOrUpdate(car);
}
//setters sessionFactory ommitido, não é preciso cria get.
Claro que os demais métodos da interface estarão sem código por enquanto, pois o objetivo agora é testar ocreate do CRUD.
Testando via Unit Tests
Agora vamos rodar o nosso teste e ver o resultado. Se você não adicionou o JUNIT4 ao seu projeto, o Eclipse vai fazer a sugestão.
Note: Ao rodar o unit tests e você ter a exceção “org.springframework.aop.framework.AopConfigException: Cannot proxy target class because CGLIB2 is not available”, faça o download e adicione a lib ao seu projeto na pasta web-inf/lib.
O resultado
Agora precisamos desenvolver o RUD. A seguir, os testes que foram criados e depois, o código com a resolução:
@Test
public void testDelete() {
try{carServices.saveOrUpdate(car);
carServices.delete(car);
}catch (Exception e) {
String notExpectedResult = “Not expected result “+ e.getMessage();
fail(notExpectedResult);
}
}
@Test
public void testListAll() {
carServices.saveOrUpdate(car);
assertFalse(carServices.listAll().isEmpty());
}
Classe DAO
public List<Car> readAll() {
return getCurrentSession().createCriteria(Car.class).list();
}
public void deleteCar(Long id) {
Criteria criteria = getCurrentSession().createCriteria(Car.class);
criteria.add(Restrictions.eq(“id”, id));
Car car = (Car) criteria.uniqueResult();
getCurrentSession().delete(car);
}
Classe de Serviço:
<strong></strong>
public void delete(Car car) {
carDAO.deleteCar(car.getId());
}
public List<Car> listAll() {
return carDAO.readAll();
}
public void setCarDAO(CarDAO carDAO) {
this.carDAO = carDAO;
}
Rodando todos os testes:
Há um detalhe que fiz de propósito. Observe que não tem um teste para validar o update, deixarei esse como motivação para você brincar mais com unit test!
Observe que nosso banco está vazio, ou seja, nada foi adicionado.
JSF
Agora que já sabemos que o nosso CRUD está funcionando, vamos trazer isso para o nosso front-end com JSF.
Crie uma classe controller:
@Controller
public class CarController {
@Autowired
private CarServices carServices;
private Car car;
private List<Car> listCars;
public CarController() {
car = new Car();
}
public void save(){
carServices.saveOrUpdate(car);
car = new Car();
}
public void delete(){
carServices.delete(car);
car = new Car();
}
public List<Car> getListCars() {
listCars = carServices.listAll();
return listCars;
}
//setters/getters omitidos
Observe que nosso controller conecta com o nosso service. É só isso que precisamos. Se você já brinca com JSF, nada de especial por aqui.
Criando .xhtml
Na verdade, vamos alterar o index.xhtml que foi criado por default pelo jboss tools:
Pronto! Esse é o nosso front-end para testarmos o CRUD. Vamos subir aplicação. Clique com o botão direito no projeto e escolha Run as >> Run on Server
Note: Caso não tenha um servidor selecionado com o projeto, o Eclipse vai solicitar que escolha um (no meu caso escolhi o tomcat e informei o local de instalação). Se der tudo certo você terá a tela a seguir:
Vamos para tela de cadastro clicando no botão “Cadastro”:
Verificando no bd:
Editando:
Resultado da edição:
E no banco:
Ufa! Artigo longo, heim?! E olha que reduzi! Mas vou ficando por aqui. E espero que tenham gostado.
O Brasil ainda é um país em desenvolvimento, mas mesmo assim, quando se trata de tecnologia para uso pessoal, principalmente mobile, os números chamam a atenção. Uma pesquisa divulgada recentemente pela Nielsen mostra que 84% dos brasileiros possuem celular, sendo que 36% deste total são smartphones, ou seja, de cada dez brasileiros três utilizam smartphones. As empresas não podem ignorar esse cenário. Se uma pessoa possui um aparelho que lhe oferece diversos recursos, certamente o utilizará no ambiente de trabalho e na maioria dos casos para realizar alguma tarefa que envolve dados da empresa.
Apesar de estar sendo muito comentado nos últimos tempos, ainda há muito que evoluir quando se fala em Bring Your Own Device (BYOD). A produtividade dos funcionários ainda é um ponto muito questionado, mas, certamente, a segurança das informações deve ser a principal preocupação das empresas. E para obter segurança existem diversos pontos que precisam ser observados.
Quando se fala em dispositivo móvel, este pode ser um smartphone, um tablet ou um notebook. O uso mais comum do smartphone, por exemplo, é para o acesso a e-mails. Se esse aparelho é roubado ou perdido, como garantir que informações confidenciais da empresa não caiam em mãos erradas? Outra questão é se o funcionário usa um notebook pessoal para trabalhar e tem acesso a rede corporativa. É importante garantir que o computador pessoal não tenha um código malicioso para que a rede não seja afetada. Por essas e outras, o BYOD não pode ser negligenciado. Todos esses detalhes são importantes na hora de garantir a segurança da rede e, por consequência, dos dados.
As companhias que já se deram conta de que é importante cuidar das informações que são transmitidas via dispositivos móveis estão adotando as soluções de Mobile Device Management (MDM), que asseguram que os dados estejam protegidos em qualquer situação, até mesmo nos casos de perda do aparelho. E os investimentos nesses softwares de gerenciamento só aumentam. A expectativa é que o MDM tenha um crescimento de 27,3% ao ano, segundo a consultoria Analysys Mason. Nesse ritmo, os investimentos que neste ano são de US$ 574,8 milhões passarão para US$ 1,5 bilhão em 2018.
Mas, além de investir em tecnologia – identificando, monitorando e controlando os usuários –, é importante apostar na conscientização dos colaboradores, para que entendam a complexidade da rede e todos os riscos e consequências que podem ocorrer em decorrência do mau uso. Uma boa alternativa é criar políticas de segurança, por exemplo.
Vale salientar que os cuidados com o BYOD devem ser tomados por qualquer empresa, seja de grande, médio ou pequeno porte. Certamente, em uma empresa menor há uma relação mais estreita e é mais fácil de observar a forma como está sendo feito o uso das redes. Mas as equipes de TI de organizações menores são mais enxutas e nem sempre estão preparadas para a rapidez com que a tecnologia corporativa está se transformando. Por isso, todo o cuidado é pouco, independentemente do tamanho da empresa. Afinal, todas as organizações possuem informações importantes que precisam ser preservadas.
Há um ditado que diz que se você não pode combater o inimigo deve se unir a ele. É certo que cada empresa possui um perfil e deve acompanhar a evolução tecnológica de acordo com a sua cultura. Mas não há como fugir da rapidez com que os avanços estão acontecendo. É fundamental acompanhar os fatos de perto, sem preconceitos e, principalmente, sem acreditar que está longe da realidade da empresa. Existem soluções para companhias de todos os perfis, e o melhor é se precaver. Afinal, o seguro morreu de velho.
Se eu fosse definir o Brasil do ponto de vista de negócios, diria que é o país da demanda. Isso é o que observo consumindo produtos e serviços todos os dias e enfrentando o que aqui, mais que em outros países, parece ser uma lei: o bom tem fila!
A escassez de oferta em relação à demanda é comum em muitos setores do País, e parece que não será diferente no mobile. Aqui, o desenvolvimento é mais lento em relação aos países ricos. Segundo o Ibope, a participação de smartphones é de apenas 18%, longe dos Estados Unidos e Europa, que têm índice acima dos 40%. Mas o que mais impressiona é o atraso em relação ao uso de dados: um smartphone no Brasil consome 155 MB por mês, ante 577 MB dos norte-americanos[1]. O fenômeno é fácil de explicar, e podemos resumi-lo em três pontos:
Infraestrutura: a velocidade de download de dados para um smartphone no Brasil é menos de um terço da existente nos Estados Unidos (897 kbps ante 3,010 kbps);
Tarifas: até há pouco tempo, não havia planos maiores que 1 GB, enquanto nos EUA ou Europa são comuns planos de 3 GB, e os preços, que chegam a ser de 30% a 50% mais caros;
Custo dos aparelhos: mais de 50% em relação a aparelhos similares vendidos nos Estados Unidos.
Com todas essas limitações, a pergunta é: por que as empresas deveriam investir seus recursos limitados no mobile, quando há tantas oportunidades nos canais convencionais?
O varejo tradicional cresceu 8,1% em 2012, e o e-commerce, 20%[2]. Então, por que não esperar o mobile decolar para subir a bordo?
Vou dar duas razões para não fazer isso: a primeira é de senso comum. Seu cliente já está ou estará nesse canal e vai querer falar com sua empresa. Se ela não estiver lá, seus concorrentes não perderão tempo em atendê-los. A segunda é que a multicanalidade incrementa o client lifetime value (CLV). Ou seja, adicionar um canal na relação com os clientes é uma receita infalível para incrementar o fluxo de caixa esperado de cada um deles enquanto dure a relação com a empresa. Por um lado, o mobile está presente com o cliente 24 x 7, o que induz a uma maior frequência de acessos. Por outro lado, os obstáculos da democratização dos smartphones no Brasil fazem com que o público mobile seja o de maior poder aquisitivo e com tíquete médio mais alto que em outros canais. Surge então a oportunidade, não apenas de incrementar o CLV daqueles que já são clientes, mas de captar novos clientes com CLV superior.
Nesse ponto, alguém poderá insinuar que os clientes que estão no canal mobile já eram de mais alto valor e não passaram a ser de mais alto valor porque estão nele. E depois vão falar de canibalização e argumentar que a conversão mobile é menor que no desktop, portanto, é um canal pior. De qualquer forma, a oportunidade continua aí, tanto para aqueles que têm o instinto de segui-la, como para outros que precisam medir o impacto incremental em seu negócio.
Hoje em dia, no mundo digital, é possível medir esse impacto. É possível medir todas as conversões geradas pelo mobile que são concluídas no desktop. É possível saber a conversão por usuário e não por visita, que prejudica este canal por ter sessões mais curtas e frequentes. Enfim, é possível analisar o antes e o depois da entrada de um cliente no mobile, e entender a relação de causa e efeito que termina por incrementar seu valor.
É importante ter claro que apostar no mobile não é apenas desenvolver um aplicativo ou adaptar seu site, que também são condições necessárias para operar nesse canal. Trata-se então de construir uma proposta de valor diferente e exclusiva, oferecendo motivos válidos para que os clientes adquiram novos hábitos.
No aplicativo da Privalia, por exemplo, utilizamos promoções com vendas exclusivas ou antecipadas e estratégias de promoções específicas. Os clientes que nos acompanham no mobile sabem que serão recompensados com vantagens e benefícios diferentes. Graças à nossa aposta contínua em mobile desde 2010, hoje em dia um terço de nossas vendas são realizadas nesse canal.
É preciso que o Brasil, que aponta ser o quarto país em número de smartphones conectados, desenvolva sua oferta nesse canal. É necessário que mais empresas despertem para isso e apostem energicamente, pois não podemos deixar o desenvolvimento do canal mobile unicamente nas mãos do consumidor.
A oferta tem de fazer sua parte. Com isso, e também quando a infraestrutura, as tarifas e os custos forem competitivos, o Brasil dará o salto que, sem dúvida, o fará ocupar a posição que merece no m-commerce internacional.
[1] Fonte: Cisco – VNI Mobile Forecast Highlights, 2012-2017
O GIMP é um software incrível pra edição de imagens, e que muitas vezes acaba sendo melhor que o próprio Photoshop. Eu particularmente tenho exemplos de clientes meus e de amigos nossos que não acreditam quando mostramos algum serviço feito exclusivamente usando o GIMP, bem como em outros Softwares Livres, pois pensam que “trabalho profissional” só é possível me ambiente Windows.
No artigo de hoje vamos abordar a instalação de um pacote de filtros de imagem bastante interessantes e funcionais. Mas primeiro vamos a pergunta básica: o que são filtros de imagem? Com certeza você já usou ou usa filtros em suas imagens, sabia? Você tem Instagram? Então, aqueles efeitos que podemos colocar nas imagens são chamados de “filtros”.
No GIMP existem alguns pacotes que fornecem alguns filtros interessantes, porém nenhum se compara ao G’MIC. Ele é um ambiente completo para processamento de imagem, proporcionando diferentes interfaces gráficas para converter, manipular, filtrar e exibir imagens. E ainda te dá a opção de criar seus próprios filtros!
O tamanho médio de uma página é hoje de 1200kB, e 60% disso são imagens. Com todo esse foco em desempenho e velocidade na indústria da web, seria de se pensar que inovação em novos formatos de imagem estivesse no topo das prioridades. Nem tanto. Na verdade, estamos vivendo em uma escassez autoimposta de formatos, o que efetivamente nos limita a gifs, pngs e jpegs.
Na prática, fazer uso de novos formatos tem sido algo penoso – recorde apenas a saga do png. Mas esperávamos que o png não fosse o último. De fato, se quisermos realmente impactar o desempenho da web, o formato das imagens é o lugar em que você deve atuar. Não há razão alguma para não termos dúzias de formatos especializados, cada um otimizado para um caso e um tipo específico de imagem. Mas antes de chegarmos lá, precisamos acertar alguns ponteiros…
Fazendo uso do novo “Magic Image Format”
Como exemplo prático, vamos imaginar que inventamos um novo “magic image format” (mif). Como utilizá-lo? Conforme um bug recente da W3C aponta, nosso markup não fornece nenhuma facilidade para especificar diferentes formatos para uma única imagem. Vamos assumir que acrescentamos tal mecanismo. A sintaxe não importa. Vou apenas inventá-la para fins de exemplificação:
1
<imgsrcset="awesome.jpeg 1x, awesome.mif 2x"alt="Use awesome MIF for retina screens!">
Até agora, tudo bem. O navegador lê a página e decide carregar o arquivo .mifenquanto renderiza a página. O usuário ama a nossa imagem e decide compartilhá-la em sua rede social favorita: clica com o botão direito, copia a URL ou simplesmente arrasta a imagem para um bookmark ou extensão. Nesse ponto, temos um problema. Nossos amigos podem utilizar um navegador diferente, o qual pode não suportar arquivos .mif. Em vez de uma imagem maravilhosa, eles veem um elemento quebrado.
Por que isso aconteceu? Ao ser apresentado a todas as opções de imagens disponíveis, o navegador foi capaz de negociar o formato para o usuário, mas quando o usuário fez a escolha para todos os seus amigos, quebrou o processo para eles. Negociação no lado do cliente deixa de funcionar no momento em que os recursos deixam a página onde todas as representações estão disponíveis.
Agora, na teoria, os navegadores poderiam resolver isso ao assegurar que toda vez que eles pedissem a URL da imagem, fosse através de clicar e arrastar, de um clique com o botão direito ou mesmo por interação Javascript, uma URL “segura” fosse então retornada. Entretanto, no longo prazo, acho que essa não é a solução certa.
Humanos não são escaláveis
Rápido, qual é o formato da imagem acima? Pergunta difícil. O arquivo é salvo como.png no meu servidor original, mas dependendo do seu navegador ela pode estar vindo tanto como .jpeg como quanto .webp ao tentar visualizar a página. O PageSpeed Service proxy testou vários formatos e decidiu recodificar a imagem para atingir uma melhor compressão (veja as configurações).
De fato, já sabemos, baseado em resultados empíricos, que nós humanos somos terríveis para otimizar imagens: nos esquecemos de redimensioná-las, escolhemos o formato errado, e trata-se de um trabalho entediante. Você poderia pensar que com três formatos de imagens a serem escolhidos, tal problema não existiria. Não é verdade. Há muito tempo parei de me preocupar com otimização manual de imagens. Computadores são muito melhores para essa tarefa do que nós, e eles não ligam para trabalho chato.
O formato não importa
Por conta do argumento, vamos dizer que otimizamos manualmente cada imagem. Em seguida, enumeramos cada variação:
Isso é idiota, chato e um desperdício de bytes no markup. Precisamos de ferramentas de automatização que simplifiquem a tarefa de gerar o modelo repetido – não é improvável que o CSS existente cause tristeza. Por conta disso, a extensão do arquivo de imagem não importa – podemos viver sem ela.
Imagine que em vez disso inventamos um formato .img abstrato que atua como um formato substituto para o formato ideal. O que seria ideal? Seria uma função de conteúdo de imagem e de preferências e capacidades de user agent no momento da requisição. Quem realiza a otimização? O servidor, é claro. Dada uma única imagem, ele é capaz de recodificar, recomprimir, redimensionar e retirar metadados desnecessários… e entregar o formato ideal.
Em um mundo com dúzias de formatos de imagens, a solução humana não escala – leia-se: markup não escala. Ao passo que computadores são fantásticos para fazer exatamente o tipo de trabalho de otimização necessário para resolver esse problema.
Negociação de tipo de conteúdo
Boas notícias, o HTTP 1.1 já antecipou todos os mecanismos para fazer com que isso funcione a partir de negociação por servidor (server-driven negociation):
O user agent indica quais tipos de arquivo que ele suporta ou está disposto a aceitar, através do cabeçalho de requisição Accept.
O servidor seleciona o formato e indica o tipo retornado através do cabeçalho de resposta Content-Type.
Perceba que a extensão no arquivo de imagem na URL não importa. A mesma URL pode retornar diferentes representações baseada em negociação de preferências cliente-servidor, e o navegador usa então o Content-Type para interpretar de forma apropriada a resposta. Não é necessário markup extra, nem ajustar manualmente cada imagem. Além disso, usuários não se importam com qual formato é feita a negociação, desde que a imagem funcione e seja recebida rapidamente.
Solução em linhas gerais
Se a negociação de conteúdo já está por aí, por que estamos tendo essa discussão? No papel, temos tudo que precisamos, mas, na prática, há alguns pontos de implementação para serem resolvidos. Primeiro, vamos ver o cabeçalho Accept enviado pelos navegadores modernos ao fazer uma requisição para uma imagem:
Os cabeçalhos do Chrome e do Safari são absolutamente inúteis: aceitamos qualquer coisa! Firefox e IE não fazem muito melhor. O Opera é o único que explicitamente enumera os tipos de arquivos suportados, que é o comportamento que queremos, apesar de também acrescentar alguns tipos desnecessários no início. Se queremos que a negociação acionada pelo servidor funcione, então a primeira tarefa é fazer com que os navegadores enviem cabeçalhos Accept que sejam úteis – um cabeçalho que realmente enumere os tipos suportados.
Entretanto, consertar o cabeçalho Accept é apenas metade do problema. O fato que a mesma URL pode ter múltiplas representações significa que todos os caches intermediários precisam ter uma forma de diferenciar as várias respostas. Felizmente, o HTTP 1.1 também possui um mecanismo para isso, o cabeçalho Vary.
O servidor indica para os clientes que os recursos devem variar baseados nos valores do cabeçalho Accept do cliente ao retornar Vary: Accept. No exemplo acima, dadas as três opções de formatos, o servidor escolheu mif como a mais otimizada. Qualquer cache upstream pode armazenar de forma segura o arquivo em cache e servir o objeto mif para qualquer usuário que forneça o mesmo cabeçalho Accept. Se outro user agent enviar um cabeçalho com valor diferente, sem o image/mif, por exemplo, então um formato diferente será servido e armazenado em cache. Para o usuário, essa negociação é transparente.
Mas, mas, mas…
Mas isso coloca mais bytes de cabeçalho nas redes! Sim, coloca. Se enumerarmos explicitamente todos os formatos de imagens, então o cabeçalho será 50-100 bytes para envio. Metade das requisições (aproximadamente 40) em uma página padrão são requisições de imagens, o que significa aproximadamente de 2 a 4kB no total. Para download, essas imagens custam aproximadamente 600kB. Assumindo que podemos conseguir uma compressão de 10% a 30% melhor e um retorno do investimento da ordem de 30 a 45 vezes. E espero sei que poderemos fazer ainda melhor no futuro.
Além disso, para o HTTP 2.0, teremos compressão de cabeçalhos, o que amortizaria o custo de envio de cabeçalhos de imagem para uma única transferência de 50-100 bytes, em vez dos 2-4kb de hoje. Assim como no HTTP 1.1, podemos ser espertos e providenciar um mecanismo de escolha por parte do site, de forma que apenas sites que suportam a nova negociação receberão dos navegadores o cabeçalho atualizado – um mecanismo desse tipo merece sua própria discussão em separado.
Mas isso fragmentaria o cache! Pequenas mudanças no cabeçalho Accept têm o potencial de criar entradas duplicadas no cache. Isso não é nada de novo, e a especificação documenta formas de normalizar os cabeçalhos: letra minúscula, a ordem não importa etc. A proposta de usar “Key” foi projetada para resolver essa questão de uma forma genérica e que beneficie os caches.
Mas não há suporte para Vary por parte dos caches! Acontece que a maioria dos CDN (sistemas de entrega de conteúdo, em tradução do inglês) não colocarão em cache nada que use Vary: Accept. Isso é um triste estado das coisas e deveria ser considerado um bug. A boa notícia é que não se trata de algo extremamente complexo; na verdade, é uma questão de incentivos comerciais. Melhor otimização de imagens se traduz em menos bytes nas redes e melhor desempenho – o que está alinhado com o que qualquer fornecedor de conteúdo está tentando vender para você. Se o suporte do lado do cliente já existe, então suportar Vary: Accept é uma vantagem competitiva para todo cache e provedor de conteúdo.
Mas clientes antigos não funcionarão com Vary: Accept! O suporte para Vary tem sido instável em clientes antigos: o IE6 não colocará em cache nenhum elemento com Vary, o IE 7 coloca em cache mas faz uma requisição condicional, e assim por diante. Uma abordagem prática é fazer com que isso seja um a otimização futura que é ativada apenas em clientes que aceitem esse novo comportamento.
Mas agora eu preciso de um novo software no servidor para otimizar as imagens! Sim, você precisa. Essa é, mais uma vez, uma questão de incentivos para provedores de hosting e conteúdo. De fato, muitos provedores de conteúdo já realizam otimização de imagem experimentalmente. De forma parecida, projetos de software livre como mod_pagespeed e ngx_pagespeed são módulos que realizarão todo o trabalho necessário para que isso funcione.
Mas isso significa mais carga no servidor! Otimização dinâmica de imagens não existe sem uma contrapartida, mas seu tempo é mais valioso do que isso. O servidor pode otimizar o elemento, colocá-lo em cache, e esquecê-lo. Não há uma escassez global de ciclos de CPU e uma vez que haja incentivo, esse fluxo de otimização de imagens cairá no esquecimento.
Exemplo prático com WebP
O WebP é um novo formato de imagem que fornece compressão com e sem perdas para imagens na web. Imagens WebP sem perda são 26% menores quando comparadas com PNGs. Imagens WebP com perdas são de 25-34% menores quando comparadas com imagens JPEG com índice SSIM equivalentes. WebP suporta transparência sem perdas (também conhecidas como canal alfa) com apenas 22% de bytes a mais. Transparências também estão disponíveis com compressões lossy (com perdas) e normalmente resultam em arquivos 3x menores do que PNGs quando compressões do tipo lossy são aceitáveis para os canais de cores vermelho/verde/azul.
Economia de 25-35% para PNG e JPEG e até 60% para PNGs com canais alfa (transparência). Isso significa algumas centenas de kilobytes de economia para a maior parte das páginas. Algo que vale a briga.
Tanto o Chrome quanto o Opera suportam o WebP, assim como alguns proxies de otimização, tal como o mod_pagespeed, o serviço PageSpeed, Torbit, e alguns outros. Entretanto, por falta de contexto nos cabeçalhos Accept existentes, cada um deles é forçado a marcar recursos .webp com cabeçalhos Cache-Control: private. Isso faz com que cada requisição seja forçada a rotear para o servidor de otimização, que realiza a detecção do user agent e serve o tipo de conteúdo apropriado.
Como muitos apontaram, este método não é escalável: cada requisição é roteada para o servidor de origem, e marcar o recurso como privado “dribla” todos os caches intermediários. Apenas isso é suficiente para explicar por que não vemos uma adoção significativa do WebP ou de qualquer outro formato experimental na web moderna.
O plano de ação
Como primeiro passo, precisamos consertar e normatizar os cabeçalhos Accept. O cabeçalho do Opera é o mais próximo do que nós queremos, apesar de podermos restringir o content-type para image, ao requisitar imagens. Talvez algo como:
Com isso no lugar certo, haverá um incentivo para que provedores de conteúdo, proxies e servidores realizem a negociação para entregar o formato de imagem ideal. Finalmente, uma vez que o servidor puder escolher o formato, o cache do cliente terá um incentivo para fazer com que o Vary funcione. No final, cortamos centenas de kilobytes de dados de imagens, automatizamos a tarefa manual de selecionar os formatos ideias e teremos mecanismos de negociação preparados para um futuro no qual eles poderão escalar para dúzias de formatos de imagens. Todos ganham… até compensarmos os ganhos ao inserir mais imagens de gatos em nossas páginas…
Estamos vivenciando uma revolução na Tecnologia da Informação. Essa revolução muda não somente os departamentos de TI das organizações, como também gera grandes mudanças socioculturais em nosso planeta. Essa revolução está baseada em algumas inovações de ruptura: smartphones, tablets, redes sociais, e-commerce, Big Data e a computação em nuvem. Podemos dizer que a computação em nuvem é a fundação que permitiu o crescimento e a inovação presente nas outras tecnologias de ruptura.
A computação em nuvem (que começou a ser oferecida de forma pioneira pela Amazon através da Amazon Web Services em 2006) permitiu o surgimento de centenas de milhares de apps para smartphones e tablets, apps para redes sociais, aplicações web e até mesmo novas redes sociais de cunho específico, como o Pinterest, uma rede social que está 100% na nuvem da Amazon. Empresas como Netflix, Peixe Urbano, Foursquare, Instagram, Dropbox, portal Terra, Chaordic, entre tantas outras, nasceram já suportadas pela computação em nuvem da AWS.
Grandes corporações e agências de governo já notaram os benefícios da computação em nuvem e estabeleceram estratégias – empresas como Shell, Unilever, Nasdaq, Samsung, NASA, SEGA, Amazon.com, The New York Times, Grupo Pão de Açúcar, Gol Linhas Aéreas, Sul América Seguros e muitas outras. Segundo o Instituto de Pesquisas Gartner, a computação em nuvem já representa um mercado de 109 bilhões de dólares em 2012 e estima-se que chegará a 206 bilhões de dólares em 2016. Já está crescendo a um ritmo de praticamente 20% ao ano.
Mas por que a computação em nuvem cresce tanto no mundo? Quais são os seus benefícios? Por que ela revolucionou nossas estruturas sociais, culturais e o modo como trabalhamos com os recursos computacionais? Podemos responder essas perguntas a partir do entendimento das suas características básicas: elasticidade, pagamento com base no consumo, infraestrutura de autosserviço e APIs/automação. Elas permitem que as organizações tenham DATACENTERS automatizados e gastem somente aquilo que utilizam, diminuindo os custos de capital em TI e os transformam em investimentos operacionais.
Outro ponto fundamental é que a computação em nuvem facilita a inversão de uma tendência descrita pelo Gartner: a maioria das organizações está gastando 80% do seu investimento e tempo de TI com manutenção, sustentação de projetos e DATACENTERS em vez de investirem em inovação. E a computação em nuvem permite às organizações diminuírem os custos totais de propriedade com infraestrutura de TI, aumentando a agilidade e diminui os riscos de desenvolvimento de novos projetos.
Com a nuvem é possível mudar o foco dos investimentos de TI para pesquisa e desenvolvimento e focar no negócio (o ‘core business’) da organização. Assim é possível transformar o departamento de Tecnologia em um centro de resultados e inovação, e não simplesmente ser mais um centro de custos. Segundo um estudo da IDC, em cinco anos de utilização da nuvem o TCO pode chegar a 70% de economia em comparação a um ambiente dentro de casa. Além disso, o estudo concluiu que a TI aumenta a agilidade e a produtividade em 52% e reduz o tempo de parada de sistemas em até 72%.
No Brasil e na América Latina estamos dando os primeiros passos na nuvem. A vinda da Amazon Web Services ao Brasil com a região São Paulo – South America acelerou muito a adoção da tecnologia. A AWS é atualmente considerada líder em serviços de cloud na modalidade IaaS pelo Gartner, por conta de seus preços baixos e do número de soluções, funcionalidades, recursos e segurança que oferece. Com o lançamento em 2011, notamos o crescimento acelerado com que a América Latina está aproveitando os benefícios da nuvem.
Acredito que a TI e a computação em nuvem podem revolucionar mercados e indústrias no Brasil, ajudando na sua competitividade. E a Amazon Web Services está presente no país para ajudar empresas, órgãos de governo e pessoas a crescerem e a prosperarem através da tecnologia. O mercado de computação em nuvem precisa de pessoas altamente qualificadas para acelerar a adoção de Tecnologia da Informação.
Para saber mais sobre a tecnologia da AWS, além dos canais institucionais que oferecemos, indico o recém-lançado livro”Arquitetura de Nuvem – Amazon Web Services“, do professor Doutor Manoel Veras, uma obra prática que detalha a nuvem da Amazon por meio de uma análise de aspectos básicos e avançados dos serviços oferecidos. Com seu livro, o professor Veras ajuda na divulgação do conhecimento sobre a computação em nuvem em português e presta, desse modo, uma contribuição valiosa para a Tecnologia da Informação em nosso país.
Eu mantenho um repositório de confiuração Varnish aberto a commits de todos. Era para ser uma boa base para iniciar qualquer tipo de implementação Varnish. Eu a uso como meu Varnish Boilerplate pessoal. E eu, pessoalmente, nunca gostei de configurações como estas:
1
2
3
4
5
# Remove all cookies for static files
if (req.url ~ "^[^?]*\.(css|jpg|js|gif|png|xml|flv|gz|txt|...)(\?.*)?$") {
unset req.http.Cookie;
return (lookup);
}
O trecho acima vai retirar todos os cookies de arquivos estáticos que fazem com que sejam armazenados em cache por padrão. Por que eu não gosto disso? Porque parece que isso está fazendo com que as pessoas comemorem ao terem um hitrate de 99% em cache, mas na verdade está causando mais um despejo de cache, porque a memória é bastante limitada. O único momento em que você nunca deve armazenar arquivos estáticos em cache é quando você tem memória de sobra.
Arquivos estáticos não provocam “carga”
Claro, eles causam acesso ao disco. Não apenas para a leitura do arquivo, mas também para registrar a sua entrada nos logs do servidor web (se você não tiver excluído o cache de conteúdo estático dos logs do seu servidor). E seu servidor precisa enviar o arquivo de volta para o cliente.
Mas, em toda probabilidade, o seu sistema operacional provavelmente tem seus arquivos estáticos mais frequentes em seu buffer, ou seja, o acesso ao disco não é necessário para fornecer o arquivo. E o seu servidor web deve simplesmente ser bom em fornecer arquivos estáticos, ou você deve considerar a mudança (para, digamos, lighttpd ou nginx).
Você está olhando cegamente para a taxa de cache
Se a configuração padrão armazena em cache todos os arquivos estáticos sem exceção, você vai ver altas hitrates em cache. Você (e seu cliente) serão felizes. E você vai se enganar pensando que os gargalos de desempenho foram resolvidos. Não são os arquivos estáticos que causam sua carga de servidor web, é PHP/Ruby/Perl/ … scripts que estão atingindo o seu banco de dados e estão chamando APIs externas. Esses são os pedidos que precisam ser armazenados em cache, e não um arquivo estático, que não está causando qualquer carga de CPU.
Prossiga com a memória sobressalente
Portanto, o foco deve ser dado às páginas dinâmicas que causam a carga do servidor. Essas solicitações em cache realmente fazem a diferença. Então, se você tem memória livre, abra algumas solicitações de arquivos estáticos para serem armazenados em cache. E continue a acompanhar o tamanho do cache, pois se os arquivos estáticos estão fazendo com que páginas PHP pesadas sejam despejadas da memória, você só irá prejudicar o seu desempenho ainda mais.
Praticamente ninguém consegue armazenar todo o seu conteúdo estático em cache no Varnish. Não sacrifique um pedido de arquivo estático de 5ms para um processamento de script PHP de 500ms. Eu comentei todas as entradas que fazem com que arquivos estáticos sejam armazenados em cache explicando isso, e espero que as pessoas não copiem cegamente os templates de configuração, mas que os leiam e pensem sobre isso.
Storytelling nada mais é do que contar histórias. Este contar de histórias, quando empregado corretamente em ações de marketing, é uma poderosa ferramenta de comunicação que conquista a atenção plena do público e, assim, te permite transmitir a mensagem que deseja.
No mercado, muitos não usam corretamente este ‘artifício’, mas é possível, sim, integrar a narrativa à sua marca e ao seu design. Veja algumas dicas:
Não diga, mostre
Um dos princípios da redação criativa é este: não dizer, mostrar. Leve isso para o seu projeto de storytelling e procure maneiras de mostrar a história de sua marca, sua personalidade e posicionamento. Contar histórias é uma maneira de levar o cliente para o universo da marca de forma pessoal, de acordo com suas interpretações.
Descubra quem você é
Tudo que estará em sua história deve ser uma realidade de sua empresa, ou seja, suas características desempenham importantes papéis na narrativa. Quem é você? O que você está vendendo? Por que isso importa? Que tipo de ambiente você quer representar com o seu negócio? Quem são seus principais clientes?
Parta para a ação
Com a história já em mente, você pode utilizar diversos elementos do design para contá-la. Exemplos:
Palavras e tipografia
É preciso compreender que as palavras fazem parte do web design. As palavras emprestam sua definição e atribui “atmosfera” à sua história. Estilo de escrita, tom e vocabulário desempenham um importante papel na narrativa online. Lembre-se que é a partir do texto que se chama para a ação. A mesma atenção deve ser dada à tipografia; o que ela te faz sentir está de acordo com a marca e a mensagem?
Fotografia, ilustração, vídeo
As fotografias mostram simultaneamente a definição, caráter e ação de uma história. Mostrar alguém usando um produto conta uma história, assim como o seu fundo que mostra o ambiente. As ilustrações estão no mesmo teor, porém são melhores para ‘explicar’ conceitos mais abstratos. E o vídeo é o que traz a história para a vida, veja este exemplo de narrativa ilustrada.
Mascotes
Mascotes são ótimos elementos que podem guiar o visitante através do site, ou mesmo demonstrar o papel que você quer que seu visitante desempenhe na história da marca. Não importa o que ou quem é seu mascote desde que ele esteja encaixado em sua história e ajudando a passar o que você tem a dizer. Aposte em um destes ‘representantes’.
Você ainda pode apostar em ícones e símbolos que agregam bons valores à sua marca e, ainda, apostar na interatividade com botões, fundo e rolagem personalizados.
Tudo pode ser utilizado para contar uma história, aposte nisso e estenda a história para suas mídias sociais, ações de marketing, atendimento ao cliente, transporte e até a embalagem do produto. Fortaleça sua marca de forma inteligente!
Passo um, monitorar todas as coisas. Passo dois, dedicar 90% do seu tempo e recursos para analisar os dados, derivando ideias e iterando as métricas que estão sendo monitoradas e otimizadas. No entanto, existe um pequeno problema. As possibilidades são que a quantidade de dados produzidos pela instrumentação supere sua capacidade de analisar, monitorar e correlacionar todas as variações das variáveis em jogo.
Este é o lugar onde um algoritmo de detecção de anomalia, apoiado por um bom motor de estatística com acesso aos dados, pode ser inestimável: não precisa ser perfeito, mas deve ser capaz de alertá-lo para os valores atípicos dos dados. Com o alerta em mãos, ou na sua caixa de entrada, você pode colocar as mãos na massa e determinar se mais investigações são necessárias.
Eventos de inteligência do Google Analytics
Boa noticia, se você estiver usando o Google Analytics, então você já tem um poderoso mecanismo de detecção de anomalia à sua disposição: Eventos de Inteligência. O melhor de tudo é que ele pode aproveitar todos os dados existentes, os segmentos configurados e outras personalizações. E o preço é justo, já que é grátis.
Analytics monitora o tráfego do seu site para detectar variações estatisticamente significativas, e gera alertas automaticamente, ou Eventos de Inteligência, quando essas variações ocorrem. Dando uma olhada mais de perto, essas anomalias podem fornecer informações que você pode ter perdido, por exemplo, um aumento no tráfego de uma determinada cidade ou site de referência.
Na verdade, com um pouco de trabalho e personalização, os Eventos de Inteligênciapodem ser facilmente configurados para ajudar a monitorar o desempenho do seu site! Visitantes da Índia vendo um aumento repentino no tempo de carregamento de página? Agora você tem uma ferramenta automatizada que irá ajudá-lo a identificar o problema.
Melhor ainda, o relatório de exemplo acima correlacionou os prováveis colaboradores para o alerta gerado e visitantes identificados a partir de Chennai, na Índia, como experimentar um aumento significativo em seu tempo de carregamento da página. Com essas informações em mãos, você pode cavar mais fundo para identificar a causa raiz.
Detecção de anomalias de desempenho Web
A página de exemplo de tempo de carregamento de desempenho de dados do Google Analytics para navegadores que suportam as APIs do W3C Navigation Timing, que inclui: redirecionamentos e tempos de DNS, estabelecimento TCP, tempos de resposta do servidor, bem como métricas de nível DOM e tempo onload. Há mais de meia dúzia de métricas no total, cada uma gravada a partir de um usuário real acessando o seu site – em outras palavras, trata-se de Real User Measurement (RUM), não dados sintéticos.
Se você não estiver familiarizado com relatórios Site Speed, então é um bom lugar para começar – confira este episódio GDL para um olhar mais aprofundado. No entanto, estamos indo para um nível mais profundo: cada uma das métricas de Navigation Timing pode ser monitorada com Eventos de Inteligência! Tudo o que você precisa fazer é criar um alerta personalizado e definir alguns critérios de limites que serão acionados no futuro.
Algumas ideias de alerta de desempenho para o seu site:
Fazer track de DNS resolve a questão de fuso horário pelo mundo ou em regiões específicas
Acompanhe os tempos de resposta do servidor em todos os visitantes ou personalize para diferentes versões do site
Controle o tempo de onload para detectar algum comportamento estranho no CSS, nos scripts e em outros recursos
Dica importante: use segmentos avançados!
Criar um alerta para uma variável especifica, dentro de um contexto global, é um bom lugar para começar. No entanto, se você não tiver criado um segmento avançado personalizado no Google Analytics, então você apenas arranhou a superfície do que é possível. Precisa monitorar o tempo de carregamento da página (PTL), ou tempos de DNS para todos os visitantes móveis na Ásia, ou talvez em Tóquio, especificamente? Não tem problema, basta criar um novo segmento personalizado:
Você pode aplicar o segmento em qualquer relatório no Google Analytics e, uma vez criado, você também pode selecioná-lo ao configurar o Alerta de Inteligência! Se existem mercados específicos, ou tipos de usuários ou de tráfego com que você está preocupado, então criar um segmento avançado vai lhe permitir adaptar bem os alertas.
Medir, otimizar, iterar
Detecção de anomalias é uma ferramenta, e uma poderosa. No entanto, ainda é para ser definida por você, e de forma iterativa melhorar os segmentos e os limiares para adequar os alertas para a sua aplicação. Espere falsos positivos, mas também espere ser alertado para as questões que você nunca teria pego na torrente do monitoramento de dados.
SPDY é um protocolo experimental desenvolvido pelo Google e projetado para reduzir a latência das páginas web. Especificamente, o objetivo é abordar as limitações do HTTP 1.1 e para remover os gargalos existentes: cabeçalho do bloqueio de linha, uso ineficiente de conexões TCP subjacentes e cabeçalho lotado, entre outros. No entanto, enquanto tudo isso soa muito bem por escrito, a implementatação de um novo protocolo na web, na prática, é repleta de dificuldades.
Especificamente, as muitas camadas de roteadores HTTP, caches e ferramentas personalizadas, que muitas vezes se comportam de maneiras imprevisíveis quando apresentadas com um protocolo alternativo, levando à queda de conexão aleatória e a usuários frustrados. Para resolver isso, o SPDY é entregue via SSL: do início ao fim, o túnel criptografado permite que o cliente e o servidor troquem de frames SPDY sem a intervenção de nós intermediários. É importante notar que o SPDY não requer SSL, mas, na prática, o SSL é uma escolha pragmática para se chegar a uma solução de trabalho.
Next Protocol Negotiation (NPN)
Mas como é que o cliente e o servidor sabem usar SPDY quando o túnel SSL é aberto? Este é o lugar onde o Next Protocol Negotiation (NPN) entra em cena. O NPN é uma extensão SSL que permite que o cliente e o servidor negociem o protocolo de aplicação, como parte do handshake do SSL.
O NPN elimina a viagem extra para negociar o protocolo de aplicação – compare isso com o handshake do WebSocket atual, que impõe uma outra ida e volta de latência em cima da negociação SSL. Se você está contando, isso é um RTT para o handshake do TCP, dois para a negociação SSL, e um para a atualização do WebSocket – quatro RTTs antes de quaisquer dados de aplicativos podem ser trocados!
Suporte para NPN foi adicionado no OpenSSL (1.0.0d +), NSS, e TLSLite. Chrome, Firefox, Opera suportam o SPDY (e, portanto, também NPN), bem como muitos dos servidores populares: Apache, nginx, Jetty, e Node.js, entre outros. Dito isso, a necessidade de suporte para NPN tem sido, no entanto, uma barreira para a adoção mais ampla. Isto é, até o HAProxy decidiu tornar a vida de todos muito mais fácil! Vamos dar uma olhada em um exemplo.
Construindo HAProxy com suporte para NPN
O suporte para SSL chegou aos builds de desenvolvimento do HAProxy no início de setembro e foi melhorando rapidamente desde então. Uma adição notável é, você adivinhou, o suporte para negociação NPN! Para fazê-la funcionar, baixe o último pacote de desenvolvimento, ou faça um check-out de git e construa o binário contra uma versão mais recente do OpenSSL:
$> wget http://www.openssl.org/source/openssl-1.0.1c.tar.gz
$> tar -zxvf openssl* && cd openssl*
$> ./configure --prefix=/tmp/openssl --openssldir=/tmp/openssl darwin64-x86_64-cc
$> make && make install
$> git clone http://git.1wt.eu/git/haproxy.git/ && cd haproxy
$> make =generic =1 =-I/tmp/openssl/include =-L/tmp/openssl/lib -lssl
$> ./haproxy -vv
# Built with OpenSSL version : OpenSSL 1.0.1c 10 May 2012
# OpenSSL library supports TLS extensions : yes
As instruções acima devem construir a última versão do HAProxy em OSX. Se você já tem OpenSSL 1.0.0d +, então você pode pular o primeiro passo, assim como o ADDINC extra e directivas ADDLIB. Com isso, estamos quase prontos para lidar com SPDY ao lado de nosso HTTP regular e tráfego HTTPS!
Configurando HAProxy para HTTP, HTTPS e SPDY
O que queremos fazer é configurar nosso HAProxy como um proxy de terminação SSL. Ou seja, o HAProxy servirá o nosso certificado SSL para o cliente, e todo o tráfego encaminhado para nossos servidores internos fluirá sem criptografia. Isso também significa que o HAProxy terá de lidar com o handshake do NPN. Na verdade, de modo ideal, ele deve tratar e rotear todos os tipos de tráfego: HTTP, HTTPS, e SPDY.
defaults
log 127.0.0.1 local0
# accept connections on port 80, forward requests to "http_cluster"
frontend http
mode http
bind :80
default_backend http_cluster
# accept connections on port 443 (SSL)
# - forward SPDY connections to spdy_cluster
# - forward regular HTTPS connections to http_cluster
# - ha.pem should be a concatenated file: certificate first then the key
frontend secure
mode tcp
# advertise spdy/2 support via NPN
bind :443 ssl crt ./certs/ha.pem npn spdy/2
use_backend spdy_cluster if { ssl_npn -i spdy/2 }
default_backend http_cluster
# SPDY server running on port 10000
backend spdy_cluster
server srv01 127.0.0.1:10000
# HTTP server running on port 8000
backend http_cluster
mode http
server srv01 127.0.0.1:8000
Simples assim. HAProxy aceita a conexão SSL, realiza o handshake do SSL e anuncia suporte para spdy/2 se o cliente suporta NPN. Se o cliente seleciona spdy/, então o pedido é encaminhado para o servidor SPDY. Caso contrário, a solicitação HTTPS é encaminhada para o cluster HTTP. A parte importante é que, em ambos os casos, o tráfego encaminhado já não é criptografado – o servidor SPDY não precisa executar o handshake do SSL ou se preocupar com NPN; em vez disso, simplesmente tem que analisar e devolver os frames SPDY de volta para o proxy.
NPN não é apenas para SPDY
Para ver o SPDY em ação, você pode baixar e executar a demonstração do servidor SPDY escrito em Ruby atrás do HAProxy. Com suporte nativo para NPN no HAProxy, adicionar suporte SPDY nunca foi tão fácil – você está literalmente a apenas algumas regras de distância para rotear as solicitações SPDY recebidas com o resto do seu tráfego. Não há necessidade de reduzir sua conexão SPDY para HTTP, furar sua infraestrutura para expor servidores NPN capazes, ou implementar infraestrutura paralela.
O melhor de tudo, enquanto que o exemplo acima é específico para o SPDY, é que o mecanismo do NPN funciona para qualquer protocolo novo, ou já existente. Como exemplo, podemos eliminar a viagem extra em na negociação do WebSocket com NPN, ou podemos implementar outros, protocolos novos e experimentais, sem impor quaisquer penalidades de latência adicionais.