Usabilidade na prática

Quando algo dá errado

Dr. Charles Kreitzberg e Ambrose Little

Código disponível para download na MSDN Code Gallery
Navegue pelo código online

Sumário

O desafio da usabilidade
As mensagens de erro são importantes
Perguntas importantes a serem feitas na fase de design
Formatando mensagens de erro
Técnicas de programação
Manipulação consistente
Uma mensagem de erro bem-construída
Frameworks
Personalizando exceções (com a Enterprise Library)

kreitzberg.gif

Charles Kreitzberg

O DESAFIO DA USABILIDADE

Do ponto de vista da usabilidade, muitas vezes as mensagens de erro podem se tornar um pesadelo. Algo deu errado com o programa e o usuário deve decidir o que fazer em seguida. Em condições ideais, o programa geraria uma mensagem de erro para informar o usuário sobre o que deu errado e como corrigir o problema. Infelizmente, muitas mensagens de erro ficam muito distantes desse objetivo.

Considere a mensagem na Figura 1, que foi exibida no meu PC logo após a sequência de inicialização. Imagine o impacto dessa mensagem sobre um usuário não técnico, que não tem ideia de qual é o problema. A mensagem indica que ocorreu uma violação de segurança e que a situação é deseperadora. Na verdade, a situação não era tão terrível. Posteriormente, eu descobri que a mensagem de erro vinha de meu software de edição de vídeo e que tudo continuava funcionando bem. Mas é surpreendente o número de falhas de design contidas nessa mensagem:

  1. Não há qualquer indicação de qual programa gerou a mensagem de erro.
  2. A mensagem não explica o motivo pelo qual o programa está sendo encerrado.
  3. A mensagem é muito geral. Ela menciona "as informações de segurança" sem fazer qualquer referência a quais informações se refere.
  4. A mensagem não indica a gravidade do problema e se o computador do usuário está em risco.
  5. O usuário não tem ideia de como corrigir o problema ou de onde obter mais ajuda. Tudo o que ele pode fazer é pressionar OK (e certamente não está tudo OK).

fig01.gif

Figura 1 Nossa, um programa encerrado é terrível

fig02.gif

Figura 2 E se eu não estiver em casa quando o Windows entrar em contato?

Ninguém está imune a mensagens de erro confusas. Até mesmo a Microsoft, que elaborou padrões sugerindo como criar mensagens úteis, pode acabar com uma falha ocasional, como a observada na Figura 2, que eu recebo de tempos em tempos.

O Guia de Experiência do Usuário do Windows Vista fornece diretrizes para mensagens de erro que colocam que uma boa mensagem de erro é criada com base em três componentes:

  • A informação de que ocorreu um problema.
  • A explicação da causa.
  • O fornecimento de uma solução para que os usuários possam corrigir o problema.

As diretrizes também sugerem que uma boa mensagem de erro deve ser apresentada de forma:

  • Relevante: a mensagem apresenta um problema importante para os usuários.
  • Passível de ação: os usuários devem executar uma ação ou alterar seu comportamento em decorrência da mensagem.
  • Direcionada ao usuário: a mensagem descreve o problema em termos de ações ou objetivos do usuário-alvo, não em termos das dificuldades do código.
  • Breve: a mensagem é tão curta quanto possível, mas não curta demais.
  • Clara: a mensagem usa uma linguagem simples, de forma que os usuários-alvo possam entender facilmente o problema e a solução.
  • Específica: a mensagem descreve o problema usando uma linguagem específica, fornecendo nomes, locais e valores específicos dos objetos envolvidos.
  • Educada: não se deve culpar os usuários ou fazê-los se sentir tolos.
  • Rara: exibidas ocasionalmente. Mensagens de erro exibidas com frequência são sinal de um design ruim.

A mensagem de erro na Figura 2 não atende à maioria desses critérios.

As mensagens de erro são importantes

Devido à grande importância da eficiência do usuário final, a qualidade das mensagens de erro não é simplesmente uma sutileza. Os erros interrompem o processamento, prejudicam a experiência do usuário e envolvem um custo. O custo do suporte aos usuários pode se tornar significativo. Boas mensagens de erro, que permitem ao usuário identificar e corrigir problemas podem economizar bastante tempo e dinheiro, além de minimizar o impacto sobre a experiência do usuário. Mas, nem sempre é fácil gerar boas mensagens de erro.

Por definição, as mensagens de erro são produzidas quando o programa encontra uma situação inesperada. A origem do problema pode já ter sido removida no momento em que o erro é detectado e talvez seja impossível descobrir sua causa original. Nem sempre você pode controlar o ambiente, mas é possível melhorar a situação criando uma mensagem útil sem pressa e traduzindo o problema técnico em termos direcionados ao usuário e passíveis de ação.

Perguntas importantes a serem feitas na fase de design

Estas são algumas perguntas importantes direcionadas ao usuário que você pode considerar ao pensar sobre como vai gerenciar exceções:

Quem é a sua audiência? São desenvolvedores, usuários treinados, usuários casuais ou o público em geral?

Quais tipos de exceções serão relatadas? Como você deseja apresentá-las?

Que suporte será oferecido ao usuário? Por exemplo, haverá suporte da Assistência Técnica e da Base de Dados de Conhecimento?

Como você direcionará o usuário para o caminho correto? Simplesmente notificar o usuário de que ocorreu um problema sem oferecer uma forma passível de ação para solucioná-lo levará, invariavelmente, a uma experiência negativa do usuário.

É necessário registrar em log e notificar a ocorrência de exceções? Se for o caso, talvez você deva considerar como os usuários ou a equipe de TI irão exibir, pesquisar, classificar, filtrar e manter os logs e qual será o formato e o conteúdo das notificações. Tudo isso deve ter uma usabilidade grande.

Você deve personalizar a exibição para audiências diferentes? Por exemplo, seria útil ter uma exibição para desenvolvedores e uma exibição para o público? O uso de arquivos de diretiva externos para controlar a exibição geram falhas de segurança?

Você pode melhorar a usabilidade de suas mensagens? E também é possível preservar o vínculo com a informação técnica básica através do encapsulamento da exceção em uma nova exceção criada por você? O fornecimento de informações técnicas além dos limites de um aplicativo representa possíveis riscos de segurança?

Essas perguntas devem ser consideradas no início e não no final do projeto.

Formatando mensagens de erro

Os objetivos das mensagens de erro são ser tão compreensíveis, precisas e passíveis de ação quanto possível. Empenhe-se em fornecer:

  • O cenário mais completo possível.
  • O máximo de contexto possível em termos do que é significativo para o usuário.
  • As ações do usuário sugeridas, em termos claros. Ajude o usuário a sentir-se o mais confortável possível com a decisão, certificando-se de que ele entende as consequências de cada opção.

Estas são algumas sugestões para ajudá-lo a criar boas mensagens de erro:

Identifique a causa Indique o site ou o aplicativo que gerou a mensagem de erro. Também pode ser útil indicar informações mais aprofundadas que podem ajudar a localizar a área do código na qual ocorreu o erro, mas cuidado ao expor detalhes internos aos usuários, pois essas informações podem ser usadas em ataques mal-intencionados. Separe as informações adicionais de diagnóstico e pesquisa da mensagem principal, de forma que ela não pareça técnica demais para o usuário final. Considere mostrar detalhes por demanda (por exemplo, em uma região recolhível), de forma que o usuário não técnico não precise lidar com uma apresentação técnica.

Use uma linguagem simples Explique o que aconteceu usando a linguagem menos técnica possível. Isso pode ser difícil, porque talvez você não saiba o que o usuário estava tentando fazer quando o erro ocorreu. Entretanto, quanto mais você puder relacionar o problema com o que o usuário estava tentando realizar e os dados envolvidos, melhor ele poderá compreender e possivelmente agir para resolver a situação. Novamente, cuidado aqui para não expor informações que poderiam ser usadas em um ataque ou para violar a privacidade. Explique a gravidade do erro e, se possível, explique as consequências do problema.

Forneça detalhes e orientações Para exceções de alto nível, como "Erro de HTML - 500 - Erro interno do servidor", tente fornecer mais detalhes e orientações específicas, quando possível. Explique as ações que o usuário pode executar para resolver o erro e certifique-se de que as opções e suas consequências estejam claras. Se possível, forneça um link para mais informações que possam ajudar o usuário a tomar a decisão apropriada. Se a única opção for sugerir que o usuário entre em contato com o suporte, certifique-se de que as informações de contato sejam precisas e facilmente atualizáveis.

Oriente o usuário Caso você use uma página de erro personalizada no ASP.NET, integre-a visualmente ao seu site e permita a navegação nas páginas relacionadas (ou, no mínimo, na home page). Além disso, se você direcionar os usuários para um site com informações adicionais, não os abandone na home page, onde precisarão localizar as informações. Leve-os diretamente para a página apropriada.

little.gif

Ambrose Little

TÉCNICAS DE PROGRAMAÇÃO

Concordo que pode ser difícil lidar com mensagens de erro, e todas as sugestões fornecidas pelo Charles são bastante úteis. Então, como você realmente as implementa no seu código?

Para fins de programação, é útil dividir as mensagens de erro em duas categorias. Os erros de programação são exceções que ocorrem no código devido a situações imprevisíveis. Quando ocorre um erro de programação, pode não haver outra opção além de encerrar o programa. Exceções de validação ou de entrada do usuário são situações nas quais talvez o usuário possa corrigir o problema; uma mensagem de erro bem-elaborada pode permitir a recuperação desse tipo de erro. Nesta coluna, vou me concentrar no primeiro tipo, os erros de programação, e deixarei os erros de validação e de entrada do usuário para uma outra ocasião.

Manipulação consistente

Os erros ocorrerão até nos programas mais bem-escritos. A melhor maneira de lidar com eles é através da criação de classes de mensagens de erro que possam ser usadas em todo o programa. Ao direcionar todo o processamento de mensagens de erro para um único ponto, você pode:

  1. Assegurar que todas as mensagens sejam apresentadas em um formato completo e consistente.
  2. Usar tipos de exceções personalizados e atribuir códigos de erro exclusivos para cada mensagem, de forma que seja fácil produzir um dicionário de mensagens de erro, ampliá-lo com informações adicionais e localizá-lo, se apropriado.
  3. Se aplicável, vincular as mensagens de erro a um site que forneça suporte ao usuário ou pelo menos forneça maneiras para o usuário entrar em contato com o suporte do aplicativo com a mensagem de erro.
  4. Criar logs que possam ser enviados automaticamente ou com a permissão do usuário para um servidor e usados para analisar problemas e aprimorar o software.

Uma mensagem de erro bem-construída

As caixas de diálogo nas Figuras 3 e 4 mostram elementos de uma mensagem de erro bem-estruturada. Se a mensagem de erro for simples, você poderá usar uma caixa como a exibida na Figura 3, adaptada das Diretrizes do Windows Vista. Essa mensagem de erro será mais útil para um erro que pode ser corrigido pelo usuário. Um erro de programação pode exigir uma mensagem mais abrangente, como a mostrada na Figura 4.

fig03.gif

Figura 3 Formato de uma mensagem de erro simples

fig04.gif

Figura 4 Mensagem de um erro mais complexo

Uma boa fonte de informações sobre como manipular exceções é o artigo da Biblioteca MSDN "Guia do Desenvolvedor do .NET Framework: Diretrizes de design para exceções". Ao pesquisar a Biblioteca MSDN, examine o artigo "Bloqueio de aplicativos de manipulação de exceções". Ele tem a capacidade de encapsular uma exceção, aninhando-a dentro de uma exceção criada por você com mais informações. Isso pode ser usado para adicionar mais informações que capturam o contexto no qual a exceção ocorreu. Você pode usar essas informações para criar mensagens de erros mais direcionadas ao usuário.

Se você estiver preocupado com a segurança e quiser evitar fornecer informações técnicas, poderá substituir a exceção por outra mais direcionada ao usuário, em vez de encapsulá-la. Por fim, você pode usar o Bloqueio de Manipulação de Exceções para registrar o erro em log e notificar os participantes por email, através da Instrumentação de Gerenciamento do Windows (WMI) ou de algum outro mecanismo personalizado.

Se estiver procurando um recurso leve de relatórios e log de erros para o ASP.NET, examine o Manipuladores e módulos de log de erros (ELMAH). Ele oferece logs, diversas opções de armazenamento em log, o recurso de exibir os logs em uma página da Web, notificações por email e RSS. E você pode criar um manipulador de exceções do ELMAH para EntLib, como a única disponível gratuitamente no dotNetTemplar. Dessa forma, você obtém a infraestrutura fornecida pelo EntLib e os serviços de relatório fornecidos pelo ELMAH.

Se estiver criando aplicativos para um ambiente Windows XP ou Windows Vista, talvez queira considerar o Serviço de Relatórios de Erro do Windows (Dr. Watson). Quando um usuário envia dados de falha, o Serviço de Relatórios de Erro do Windows verifica se há uma mensagem do usuário associada a eles e apresenta a mensagem no momento da falha.

Essas estruturas são soluções excelentes para gerenciar a manipulação de exceções, mas sua utilização não garante a usabilidade. Você ainda precisará ver a situação da perspectiva do usuário, tentando entender a compreensão técnica do usuário e criar a apresentação mais clara, precisa e passível de ação.

Agora, vamos examinar um exemplo e alguns exemplos de código que demonstram os conceitos discutidos até o momento.

Personalizando exceções (com a Enterprise Library)

Segue a descrição de um exemplo de como aproveitar o Bloqueio de Manipulação de Exceções da Enterprise Library para personalizar exceções para um aplicativo do Windows Forms; uma técnica semelhante poderia ser utilizada para outras tecnologias de interface do usuário do Microsoft .NET Framework.

Primeiro, centralize a manipulação de exceções por meio de algum tipo de classe ExceptionHandling, como mostrado na Figura 5. Essa é uma classe estática com métodos que são mapeados diretamente para diretivas de exceções definidas na sua configuração da Enterprise Library.

Figura 5 A classe ExceptionHandling

public static class ExceptionHandling
{
    public static Exception UiUnknown(Exception exception)
    {
        try
        {
            if (Microsoft.Practices.EnterpriseLibrary.
                ExceptionHandling.ExceptionPolicy
                  .HandleException(exception, "UiUnknown"))
                return exception;
        }
        catch (Exception ex)
        {
            return ex;
        }
        return null;
    }
}

Configure essa diretiva na ferramenta de configuração da Enterprise Library, como mostrado na Figura 6. Especifique manipuladores de dois tipos, de forma semelhante ao que faria em uma instrução try/catch. O manipulador mais específico é escolhido. Para ambos, você pode primeiro registrar em log a exceção gerada usando o formatador interno de exceções XML com um ouvinte de rastreamento de log XML. Para a exceção geral, você pode encapsular a exceção de origem com uma nova exceção que possui uma mensagem direcionada ao usuário. Alternativamente, você poderia substituí-la, mas caso seja necessário que a interface do usuário tenha os detalhes da exceção real, talvez seja melhor encapsulá-la.

fig06.gif

Figura 6 A ferramenta de configuração da Enterprise Library

Um importante e pequeno truque que você precisa fazer ao encapsular ou substituir é definir PostHandlingAction nos tipos de exceção a serem ThrowNewException; assim, ele irá gerar a nova exceção encapsulada.

Se desejar que o código de chamada faça mais com a exceção, a outra opção é escolher NotifyRethrow, que faz EntLib ExceptionPolicy.HandleException retornar true. Como você verá, para DatabaseConnectivityException, você pode manipular o material direcionado ao usuário em seu tipo de exceção personalizado, de forma que a diretiva anterior apenas o registra em log e notifica que ele deve ser gerado novamente.

A partir da chamada para ExceptionHandling.UiUnknown, você recebe a nova exceção encapsulada, a exceção original ou nenhuma, caso a diretiva opte por não gerar uma nova ou notificar para a nova geração; essa é uma vantagem importante do bloqueio de manipulação de exceções: você pode configurar suas diretivas fora do código do aplicativo propriamente dito (essa é uma boa forma de manter a preocupação abrangente fora do código do aplicativo). Em seguida, forneça isso para o método ErrorMessage.Show, que é definido no formulário ErrorMessage mostrado na Figura 7.

Figura 7 O formulário ErrorMessage

public static void Show(Exception relatedException)
{
    ErrorMessage em = new ErrorMessage();
    if (relatedException == null)
    {
        em.problemDetailsContainer.Visible = false;
    }
    else
    {
        em.problemDescription.Text = relatedException.Message;
        IUIExceptionDetail detail = 
          relatedException as IUIExceptionDetail;
        if (detail == null)
        {
            em.errorCode.Text = "500";
            em.problemDetails.Visible = false;
        }
        else
        {
            em.errorCode.Text = detail.ErrorCode.ToString();
            em.problemDetails.Text = detail.DetailMessage;
        }
        em.problemType.Text = 
          em.GetMeaningfulExceptionType(relatedException).Name;
        em._SearchText = em.errorCode.Text + " " + em.problemType.Text;
    }
    em.ShowDialog();
}

O formulário ErrorMessage é uma tela amigável que possui a marca da empresa, informações de contato e permite relatar o problema para a empresa, além de pesquisar uma solução online.

Se nenhuma exceção for fornecida, ela oculta o contêiner de detalhes; não é necessário fazer os usuários pensarem que há detalhes quando não há. (Se for o caso, talvez você nem queira mostrar uma mensagem.) Se você tiver uma exceção, defina a descrição do problema para a mensagem da exceção. Em seguida, verifique se o tipo da exceção implementa a interface IUIExceptionDetail personalizada.

Essa interface permite fornecer de forma inteligente informações de exceções mais significativas aos usuários, quando possível. A interface tem esta aparência:

public interface IUIExceptionDetail
{
    int ErrorCode { get; }
    string DetailMessage { get; }
}

Usando isso, você pode criar exceções direcionadas ao usuário significativas e personalizadas que podem ajudar os usuários a possivelmente solucionar seus problemas sozinhos ou, no mínimo, fornecer um código de erro útil que sirva como uma chave de referência fácil para pesquisa e suporte. A implementação de exemplo desta interface é o tipo DataConnectivityException mostrado na Figura 8.

Figura 8 DataConnectivityException

public class DatabaseConnectivityException 
  : Exception, IUIExceptionDetail
{
    public DatabaseConnectivityException(Exception innerException)
     : base(Resources.Exceptions.DatabaseConnectivityException_Message, 
                innerException)
    {
        int.TryParse(
          Resources.Exceptions.DatabaseConnectivityException_Code,
          out _ErrorCode);
        _DetailMessage = 
           Resources.Exceptions
             .DatabaseConnectivityException_DetailMessage;
    }

    #region IUIExceptionDetail Members
    int _ErrorCode;
    public int ErrorCode
    {
        get { return _ErrorCode; }
    }

    string _DetailMessage;
    public string DetailMessage
    {
        get { return _DetailMessage; }
    }
    #endregion
}

O ponto mais interessante aqui é a utilização de um arquivo RESX para armazenar a mensagem, o código de erro e os detalhes. Isso permite a localização, mas também serve como um prático código-fonte XML para a documentação de mensagens de erro; você poderia aplicar facilmente o XSLT para o formato RESX a fim de produzir HTML ou outra marcação rich text.

Talvez você prefira essa abordagem aos recursos de encapsulamento/substituição mais básicos fornecidos por EntLib e tudo bem. Não é necessário usar EntLib para fornecer mensagens direcionadas ao usuário, mas é uma boa ideia externalizar a manipulação de exceções em diretivas para logs e, pelo menos, para permitir que você personalize a manipulação de exceções fora do próprio aplicativo.

Voltando ao método ErrorMessage.Show, você pode ver a verificação para a interface IUIExceptionDetail. Se ela for encontrada, usará as informações extra fornecidas para mostrar ao usuário. Caso contrário, ela ocultará a caixa de detalhes e mostrará um código de erro genérico 500 (semelhante ao HTTP 500).

O resultado para um erro genérico/desconhecido é mostrado na Figura 9. Para um erro mais específico, como DataConnectivityException, você obtém uma mensagem como a mostrada na Figura 10.

fig09.gif

Figura 9 Mensagem de erro genérica

fig10.gif

Figura 10 Mensagem de erro mais específica

Há algumas coisas a serem observadas aqui. Primeiro, estamos seguindo as diretrizes do Windows Vista para mensagens de erro bem de perto. A janela é uma caixa de diálogo modal. O título da caixa de diálogo informa claramente aos usuários o que está gerando o erro, de forma que não possam, por exemplo, confundi-lo com erros do sistema; esse também poderia ser um recurso específico do aplicativo. Existe um indicador em destaque de que se trata de um erro, com o ícone grande sobreposto ao logotipo do aplicativo, com a dupla finalidade de reforçar a origem do problema e comunicar claramente que se trata de um erro. O texto do título também informa em destaque ao usuário que há um problema com o aplicativo.

Logo abaixo do título é mostrada a mensagem da exceção. Ele deve ser conciso, mas educado e escrito em termos que o usuário possa entender. Se possível, ele também deve oferecer soluções. No caso do erro geral, talvez você não conheça as soluções, então a melhor abordagem seria instruir o usuário a relatar o erro ou pesquisar online. No caso da conectividade, você pode fornecer algumas outras sugestões, se souber soluções prováveis. O ideal é oferecer ao usuário condições de resolver o problema sozinho; por isso, mensagens bastante específicas são preferíveis a mensagens gerais.

fig11.gif

Figure 11 Tell Us about the Problem (Informar sobre o problema) é o botão apresentado Ambrose Little

O link "Search for Solutions Online" (Pesquisar soluções online) iniciará uma pesquisa usando "MSDN Magazine" mais o erro e o tipo de exceção. Esse é um ótimo recurso, pois os usuários podem não saber o que colocar na caixa de pesquisa. Se você tiver uma seção de suporte ao usuário no site, também poderá limitar a pesquisa ao seu site. E, claro, se tiver um serviço de pesquisa específico (baseado em código), deverá preferi-lo a uma pesquisa geral de palavras-chave.

O botão "Tell Us About the Problem" deve enviar o log em XML para um Web service. Talvez você queira dar ao usuário a oportunidade de fornecer observações contextuais e/ou informações de contato, e o serviço deve retornar algumas informações de acompanhamento que o usuário poderia usar para referência, se optar por fazer uma chamada.

Uma exibição alternativa que você poderia usar é mostrada na Figura 11. O importante aqui é que o botão Tell Us About the Problem é o comando com mais destaque na tela. Um ponto a ser considerado no design da interface do usuário é a orientação dos usuários com base na hierarquia visual; em outras palavras, se algo é mais importante ou, nesse caso, é o comando que você deseja incentivar que os usuários utilizem, assegure que ele seja mais realçado óbvio e fácil de usar. Você deve decidir, de acordo com o seu contexto, quais ações são mais importantes e, se houver uma hierarquia, comunicá-la visualmente.

Depois de ter aplicado isso, você pode adicionar um manipulador em nível de aplicativo no método Main de Program.cs:

Application.ThreadException += Application_ThreadException;

Esse método tem esta aparência:

static void Application_ThreadException(object sender, 
  System.Threading.ThreadExceptionEventArgs e)
{
    ErrorMessage.Show(ExceptionHandling.UiUnknown(e.Exception));
}

Ele captura todas as exceções inesperadas da interface do usuário e as roteia pela diretiva "UiUnknown" na sua classe de manipulador de exceções centralizadas que, neste exemplo, delega a manipulação à Enterprise Library, apesar de que poderia ser qualquer estrutura de manipulação de exceções que você possui. Possivelmente, esse método retornará uma exceção para ser mostrada através do formulário ErrorMessage mais amigável que você criou. E, claro, você pode usar uma abordagem semelhante para diretivas de exceções mais específicas por todo o aplicativo.

Como se vê, não é fácil criar boas mensagens de erro, mas ao estruturá-las com cuidado é possível evitar bastante esforço do usuário e reduzir sua frustração.

Dr. Charles Kreitzberg é CEO da Cognetics Corporation, que oferece serviços de consultoria em usabilidade e design de experiência do usuário. Sua paixão é a criação de interfaces intuitivas que envolvam e encantem os usuários e, ao mesmo tempo, apoiem os objetivos comerciais do produto. Charles mora na parte central de Nova Jersey, onde também se apresenta como músico.

Ambrose Little mora com sua esposa e seus quatro filhos na parte central de Nova Jersey. Ele trabalha com projeto e desenvolvimento de software há mais de 10 anos e tem a honra de ser um palestrante da INETA e MVP da Microsoft. Recentemente ele mudou do design técnico para o projeto para pessoas e agora é designer de experiência de usuário da Infragistics.