segunda-feira, 15 de novembro de 2010

Design centrado no usuário

Atualmente o design tem recebido uma atenção especial, visto que, além de ser o conjunto que é percebido pelo usuário, ele também pode significar redução de custos de treinamentos e manuais.

Pesquisando na internet sobre o assunto encontramos uma video aula de um curso de iPhone da Stanford sobre "How to Build an iPhone App that Doesn’t Suck! (In 10 Easy Steps)" do Steve Marmon (@marmon). Ele menciona 10 passos para se desenvolver aplicativos que não sejam “um lixo”. Porém, estes passos podem ser aplicados para quase qualquer projeto. Os passos comentados, estão a seguir:


1. Decida o que construir (Decide what to build)

Muitas vezes pensamos em uma solução levando em conta apenas necessidades secundárias, ou seja, aquelas que nos guiam a atender desejos e não o real problema que estamos nos propondo a resolver. Dessa forma, corremos o risco de não solucionar nenhum problema, ou ainda, de desenvolvermos uma solução ineficaz.

Para não cair nessa armadilha, procure enteder qual problema estamos nos propondo a resolver, pra quem estamos projetando e os tenhamos em mente ao desenvolver a solução. Focar em um grupo específico aumenta a chance de sucesso, pois o feedback desses usuários será de útil para melhorarmos o produto e conquistarmos os clientes.

Se o seu software for um produto interno, envolva os usuários finais (ou pessoas chave que representem e entendam as necessidades desses usuários), agora, caso seja um produto para o mercado, ou seja, você não tem como envolver o usuário, procure fazer pesquisas (faixa etária, classe social, nível de escolaridade, etc) para identificar bem as necessidades e guiar o desenvolvimento da interface do produto.

Conheça as personas.


2. Visite a app store (Visit the app store)

No mundo globalizado um produto inovador pode ser a composição de idéias brilhantes de produtos similares ou não, portanto, observe como aplicativos similares resolveram problemas que você está se propondo a resolver.

Eles já devem ter tido feedback de usuários e alterado o produto em função disso, e você, estará "recebendo" estes feedbacks gratuitamente.

Se existirem concorrentes, faça um benckmarking e compare as vantes e desvantagens em relação ao seu produto.


3. Explore as possibilidades de soluções (Explore possible solutions)

A primeira solução não é a única e tampouco a melhor!

Trabalhe com as limitações e aproveite os padrões para conseguir, com mais algumas tentativas, encontrar a solução ideal.


4. Rabisque (Sketch)

Para ter uma boa idéia, tenha várias, procure designs alternativos para o mesmo problema, faça várias propostas de solução!

Com 10 propostas, por exemplo, sua chance de ter algo realmente bom é muito maior. É assim que a Apple funciona. É assim que a IDEO funciona.

Rabisque e comunique suas idéias.


5. Construa um protótipo de papel (Build a paper prototype)

Um vício de longa data da área de desenvolvimento de softare e sair para a programação ou o design sem antes criar um protótipo, e quando ele é feito, a única preocupação é com relação aos campos que tem que ter.

Hoje há mais itens a ser levado em consideração, e aproveite que nesta fase, o custo de alteração é muito baixo. Pense, quanto tempo você gasta para alterar uma página no protótipo? E para reprogramar uma tela? Apresente o protótipo para pessoas chaves, analise suas reações, colha feedbacks e veja se o caminho que você está tomando é o correto.

"Fail early to succeed sooner".

6. Abra o omnigraffle (Fire up omnigraffle)

Aqui ele fala do omnigraffe para o caso específico do iPhone, porém, a mensagem é para que use uma ferramenta que permita criar um protótipo (Sketchflow?). Este protótipo será esteticamente parecido com a versão final, mas sem precisar gastar tempo programando.

A idéia é a mesma da etapa anterior, permitir alterações sem elevar o custo.

7. Faça tudo denovo (Do it all again)

Já ouviu a frase "A melhor época de começar uma faculdade é quando se termina"? Isso quer dizer que, quando terminamos os estudos é que descobrimos o que precisavamos aprender.

Sendo assim, como todo design centrado no usuário, a parte mais importante do processo é iterar. Veja onde está com problemas, volte, questione se a solução dada é a melhor para aquele caso e pense em várias alternativas de correção para o problema encontrado.

Prototipe antes de alterar, teste e siga assim até que o produto esteja estável.

8. Agora você pode programar (Okay, you can code finally)

Agora pode partir para a programação do seu produto.

Use padrões de design e programação como MVC, que possibilitem alterações futuras sem muito trabalho, desvinculando o código da parte visual/apresentação.

9. Abra seu aplicativo para beta testers (Beta test your app)

Utilize os testes para levantar os bugs que passaram desapercebidos pelos testes em protótipos e pelos programadores, como bugs relativos à satisfação dos usuários.

Um exemplo, no iPhone, pode ser a rolagem da tela estar muito "lerda". Os programadores vão achar que a tela está excelente, porque está rolando, porém os usuários, após algum tempo de uso, se sentirão incomodados com isso.

O Tweetie foi o primeiro app de twitter a conseguir uma rolagem rápida, e teve grande destaque por conta disso.

10. Lance (Release)

Se você fez tudo direito, este será um momento feliz e de boas noites de sono, senão, prepare-se para os problemas que virão (e-mails de bugs, reclamações, comentários negativos sobre o aplicativo, etc.).

Bem vindo ao mundo moderno do design!

Compartilhe suas idéias conosco e até a próxima!

 

terça-feira, 9 de novembro de 2010

Reflection de Alta de Performance

Pesquisando no Google vemos alguns bons artigos explicando que Reflection é lento. Está certo, alguns métodos do Reflection, como GetXXX (do MethodInfo, PropertyInfo, FieldInfo, etc.), são cerca de 100 vezes mais lento que acessar diretamente uma propriedade ou executar um método.

O quanto o Reflection é lento?

Vamos criar um pequeno exemplo para analisarmos a performance do Reflection. Esse exemplo terá uma classe simples que contém apenas 1 método:

namespace CustomNamespace
    public interface ICustom
    {
        void CallMe(int x);
    }

    public class CustomClass : ICustom
    {
        public void CallMe(int x)
        {
            x = x + 10;
            return;
        }
    }
}

Simples, não? Bom, vamos criar uma classe para consumir e checar a performance em situações de extress. Vamos criar objetos do tipo CustomClass em um loop e executar o método CallMe com valores randomicos de duas formas: 1. Sem Reflection, 2. Com Reflection

Para o teste com Reflection, vamos criar uma classe (ReflectionCalculator) com alguns métodos:

public partial class ReflectionCalculator
{
private Type dummyType;
public Type DummyType
{
get
{
dummyType = dummyType ??
CurrentAssembly.GetType("CustomNamespace.CustomClass",
true,
true);
                return dummyType;
}
}

private MethodInfo method;
public MethodInfo Method
{
get
{
this.method = this.method ??
this.DummyType.GetMethod("CallMe", BindingFlags.Instance |
BindingFlags.Public, null, new Type[] { typeof(int) },
null);
return this.method;
}
}

private object dummyObject;
public object DummyObject
{
get
{
if (this.dummyObject == null)
{
dummyObject = Activator.CreateInstance(this.DummyType);
}
return dummyObject;
}
}
       
public void ReflectionBasedCall(int value)
{
this.Method.Invoke(this.DummyObject, new object[] { value });
}

public void NormalCall(int value)
{
this.MyClass = this.MyClass ?? new CustomClass();
this.MyClass.CallMe(20);
        }
}

Então, se olhar para as duas chamadas ReflectionBasedCall e NormalCall verá que ambos fazem a mesma coisa. A abordagem de reflexão deve primeiro obter um Type usando Assembly.GetType (que evetualmente vasculha toda a hierarquia do objeto inteiro) e depois o GetMethod no tipo encontrado. Portanto quanto maior o tamanho do Assembly mais lento será. Por fim, usamos o Activator.CreateInstance para criar a instância do objeto.

Agora, e se executassemos os métodos 1.000.000 de vezes usando esse código e imprimissemos o tempo em segundos, ele irá se parecer com:

static void Main(string[] args)
{
var watcher = new System.Diagnostics.Stopwatch();
var calculator = new ReflectionCalculator();

watcher.Start();
for (int i = 0; i < 1000000; i++)
{
calculator.NormalCall(i);
}
watcher.Stop();

Console.WriteLine("Time Elapsed for Normal call : {0}",
watcher.Elapsed.TotalSeconds);
watcher.Reset();

watcher.Start();
for (int i = 0; i < 1000000; i++)
{
calculator.ReflectionBasedCall(i);
}
watcher.Stop();
Console.WriteLine("Time Elapsed for Reflection call : {0}",
watcher.Elapsed.TotalSeconds);

Console.ReadLine();
}

Abaixo, a saída da execução:

 

Reflection + Lambda Expression = Dynamic Delegates


Na versão 3.0 do Microsoft .Net Framework, juntamente com o Linq, foi introduzido o Lambda Expression, que é uma função anonima que pode conter expressões e funções e pode ser usada para criar Delegates ou tipos de árvores de expressão.

Criar delegates em runtime é atualmente a melhor maneira de fazer o código Reflection ser executado muito rápido. A idéia básica é criar delegates de cada MethodInfo que precisamos executar e eventualmente fazer cache dele em memória.

Para isso, precisamos construir um Action<> (no caso de método VOID) ou Func<> (no caso de método que retorne algo) para cada método:

public static Action<object, T> CallMethod(this MethodInfo methodInfo)
{
if (!methodInfo.IsPublic) return null;

var returnParameter = Expression.Parameter(typeof(object), "method");
var valueArgument = Expression.Parameter(typeof(T), "argument");

var setterCall = Expression.Call(Expression.ConvertChecked(returnParameter,
methodInfo.DeclaringType),
                             methodInfo,
                             Expression.Convert(valueArgument, typeof(T)));
return Expression.Lambda<Action<object, T>>(setterCall, returnParameter,
valueArgument).Compile();
}

A expressão lambda acima cria um delegate do tipo Action onde “object” representa a instância da classe no qual executaremos o método e “T” representa o tipo do argumento do método. O código acima cria uma expressão: 

(x, y) = return x.[method](y)


Com isso, podemos guardar na memória a referência construída dinamicamente:


private MethodInfo methodInfo;
public MethodInfo CachedMethodInfo
{
get
{
this.methodInfo = this.methodInfo ?? this.GetMethodInfo();
return this.methodInfo;
}
}

private MethodInfo GetMethodInfo()
{
Type myClass = this.CurrentAssembly.GetType("CustomNamespace.CustomClass",
true,
true);
return myClass.GetMethod("CallMe", BindingFlags.Instance |
BindingFlags.Public, null, new Type[] { typeof(int) }, null);
}

private Action<object, int> _methodcallDelegate = null;
public Action<object, int> MethodCallDelegate
{
get
{
this._methodcallDelegate = this._methodcallDelegate ??
this.CachedMethodInfo.CallMethod<int>();

return this._methodcallDelegate;
}
}

Vamos executar o código de teste mais uma vez para ver o quão eficiente é essa técnica:


Veja que a diferença em relação a uma abordagem hard-coded caiu drasticamente expandindo as possibilidades de criação de algoritmos genéricos e de alta performance.

Você tem dicas e truques? Compartilhe! Envie para social@apolineo.com.br que colocaremos no blog.

Fique a vontade também de enviar suas sugestões e críticas.

Download: Código Fonte

Até a próxima!