Conteúdo Mundo API

Como aperfeiçoar os erros na API com um design defensivo

Erros na API podem ocorrer com alguma frequência. Acontece. Mas o problema realmente vem à tona quando os provedores de API não dão aos erros a atenção que eles merecem, causando problemas aos desenvolvedores que estão tentando utilizar a API.

O design defensivo é uma prática comum quando se constrói uma aplicação front-end, reforçando a certeza de que os usuários não são abandonados, perdidos e confusos quando (e não se) ocorrerem erros. Contudo, nas APIs, os erros nem sempre são tratados com a atenção que eles merecem.

Quais códigos de erros posso esperar? O que eles significam? Como posso resolvê-los? Os desenvolvedores são continuamente forçados a efetuarem os testes de tentativa-e-erro para descobrir como lidar com os erros ou a generalizarem uma resposta quando a API retornar com algo diferente de “ok” (código 200) informando aos seus usuários de que “houve um erro interno desconhecido”.

Documentação

Erros são uma parte importante da documentação de qualquer API. As respostas devem sempre seguir um formato documentado com clareza, preferencialmente incluindo um código, mensagem e potencialmente alguma informação adicional. Toda possibilidade de erro deve ser claramente exibida de forma que os desenvolvedores possam facilmente entender o que aconteceu e decidir se eles precisam responder ao usuário com um erro ou contornar com algum valor padrão, dependendo da situação.

Se você não documentar os erros adequadamente, os desenvolvedores provavelmente só irão descobrir como eles funcionam quando um deles ocorrer. Não importa o quão excepcionais os desenvolvedores sejam, eles só querem o problema resolvido.

20160511_img01

Quando encontramos um erro inesperado, é muito comum simplesmente buscar o código do erro ou a mensagem no Google e esperar por uma solução vinda da documentação do serviço ou de sites relacionados. Um bom teste é buscar por docs API Error Codes – o Facebook se sai terrivelmente nessa. A maioria das suas APIs possuem os erros documentados, mas estão todos separados e são difíceis de se encontrar. O primeiro resultado é da API deles de Marketing e Propagandas, que iremos analisar.

Códigos de Erro

Muitas APIs retornam um código numérico, mas isso é complicado para um desenvolvedor entender. Mesmo que a mensagem sobre o erro esteja lá, no código sendo produzido provavelmente teremos apenas if (err.code === 1001), o que dificulta a compreensão do significado, além da manutenção se não houver qualquer comentário a respeito – if (err.code === 'missing_param') é bem mais sugestivo.

20160511_img02

O Facebook utiliza códigos numéricos e entre eles há o código de erro 200 que pode ser confundido com o código padrão de sucesso de uma requisição (código 200). E qual a diferença entre o código 1: Erro desconhecido encontrado e o 5000: Código de erro desconhecido? Como eu consigo consertar ou diagnosticar esses problemas?

Respostas

Se um desenvolvedor conseguir resolver o problema apenas olhando para a mensagem, isso é substancialmente melhor do que precisar procurar na documentação. Todos os erros devem ter um código interpretável por máquinas e uma mensagem interpretável por humanos, explicando o seu significado.

{
  "code": "missing_param",
  "message": "A required parameter is missing from the request",
}

A mensagem de erro acima ainda não é boa o bastante, infelizmente, é como muitas APIs retornam seus erros. O desenvolvedor não consegue saber qual parâmetro está faltando, então, ele precisará encontrar a documentação e buscar pelos parâmetros requeridos, ou desistir. Precisamos de informações adicionais, por isso é interessante um objeto “extra”.

Também pode-se adicionar uma descrição longa para explicar de forma mais detalhada o que aconteceu e informar ao consumidor da API como resolver a questão, mas algumas vezes isso é desnecessário se a mensagem for expressiva. Adicionar um status pode também facilitar, porque os cabeçalhos são difíceis de ler, ou impossíveis se estiver utilizando JSONP.

{
  "code": "missing_param",
  "message": "Está faltando um parâmetro requerido.",
  "description": "Há um parâmetro requerido faltando, por favor adicione-o e tente novamente.",
  "status": 409,
  "extra": {
    "param": "key",
    "in": "query",
    "type": "string",
    "allow_null": false,
    "format": "hex",
    "description": "Uma chave válida da API é necessária para efetuar o acesso. Visite / para maiores informações sobre a autenticação."
  }
}

A propriedade "in": geralmente é esquecida quando se está retornando o erro de um parâmetro. Esse parâmetro estava em um envio (body), em uma requisição (query) ou na URL? Não há razão para se esforçar em deixar os erros curtos – se for para ajudar os usuários, inclua todas as informações possíveis.

Configuração

Consideramos que todos concordam que os erros devem possuir mensagens excelentes e bem documentadas, mas como isso pode ser alcançado? Ter um arquivo de configuração que define todos os tipos possíveis de erros é perfeito.

{
  "unknown": {
    "status": 500,
    "message": "Erro desconhecido no sistema",
    "description": "Ocorreu um erro desconhecido em nosso(s) sistema(s). Nos contate caso continue ocorrendo.",
  },
  "missing_param": {
    "status": 409,
    "message": "Está faltando um parâmetro requerido.",
    "description": "Há um parâmetro requerido faltando, por favor adicione-o e tente novamente.",
  }
}

Depois, você pode implementar uma função que cuide dos detalhes do erro, por exemplo:

// esse pode ser o seu arquivo de erros
var erros = {};

function error(code, extra) {
  var e = errors[code];

  if (!e) {
    // devolve um erro padrão ou retorna erro desconhecido
    e = errors[500];
    throw new ReferenceError('Unknown error code: ' + code);
  }

  var err = new Error(e.message);
  err.code = code;
  err.description = e.description;
  err.status = e.status;
  if (extra) err.extra = extra;

  // err.formatted é um objeto que pode ser enviado para o usuário
  err.formatted = {
    code: err.code,
    message: err.message,
    description: err.description,
    status: err.status,
    extra: err.extra
  };

  return err;
};

// quando houver um erro...
var err = error('missing_param', {
  param: 'key',
  in: 'query',
  type: 'string',
  allow_null: false,
  format: 'hex',
  description: 'Uma chave válida da API é necessária para efetuar o acesso. Visite / para maiores informações sobre a autenticação.',
});

Configurar os erros dessa forma torna-os mais fácil de utilizar e efetuar a manutenção. Se você estiver utilizando um framework como o Node.js você pode efetuar uma chamada next(error('missing_param', {})) e depois enviar o objeto err.formatted para o usuário.

O arquivo de configuração de erros pode também ser utilizado para gerar a sua referência de erros na documentação. APIs que exportam as suas configurações e rotas são formidáveis porque isso previne que suas documentações fiquem desatualizadas e o mesmo se aplica aos erros.

Considerações Finais

O design defensivo destaca que oferecer aos usuários uma experiência agradável durante os erros pode reduzir suas frustrações e até fazer com que eles expressem publicamente sua gratidão. Faça as pessoas felizes e elas te recompensarão por isso.

Em comparação ao tempo empregado para construir uma API inteira, ter certeza de que os erros são úteis e claros demanda pouquíssimo tempo. E vale muito a pena!

Adaptado de Luno.

Comentários

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *