2 pontos por GN⁺ 2024-02-17 | 2 comentários | Compartilhar no WhatsApp

Quando uma API não faz nada, faça esse nada do jeito certo

  • Quando uma API não deve fazer nada, é importante garantir que ela não faça nada da maneira correta.
  • Por exemplo, o Windows tem uma vasta infraestrutura de impressão, mas o Xbox não tem essa infraestrutura.
  • Quando um aplicativo tenta imprimir no Xbox, lançar NotSupportedException é a maneira errada de lidar com isso.
  • Como o aplicativo provavelmente foi testado principalmente no PC, a exceção pode não ser tratada ao rodar no Xbox e o aplicativo pode travar.
  • Um design melhor para "suportar" impressão no Xbox é a função de impressão ter sucesso, mas informar que não há impressoras instaladas.
  • Quando o usuário tenta imprimir, o sistema pede a seleção de uma impressora, mas a lista está vazia, então o usuário percebe que "não há impressoras" e cancela a solicitação de impressão.
  • Para aplicativos que tentam instalar uma impressora, a função de instalação pode retornar imediatamente com um código de resultado dizendo que "o usuário cancelou a operação".
  • O objetivo é se comportar como se o recurso de impressão fosse totalmente suportado, mas na prática agir como se não houvesse impressoras.
  • Em sistemas onde impressão simplesmente não funciona, pode-se adicionar uma função que verifique se a impressão está disponível para ocultar o botão de imprimir na UI.
  • Esse comportamento é chamado de "inerte".
  • A superfície da API continua existindo e funcionando conforme a especificação, mas na prática não faz nada.
  • O importante é não fazer nada de forma consistente com o que está documentado, minimizando problemas com código existente.

Exemplo de desativação de API

  • Há um exemplo de desativação de uma API que inclui várias funções para criar handles de widget, funções que recebem handles de widget e uma função para fechar esses handles.
  • A equipe inicialmente propôs desativar a API fazendo com que CreateWidget tivesse sucesso, mas retornasse um ponteiro nulo.
  • Porém, essa abordagem pode confundir o aplicativo. "A chamada teve sucesso, mas eu não recebi um handle válido?"
  • Fazer EnableWidget retornar "handle inválido" também pode causar confusão.
  • Na documentação existente, encontraram um valor de retorno chamado ERROR_CANCELLED, que significa que a criação do widget foi cancelada pelo usuário.
  • Portanto, sempre que o aplicativo tentar criar um widget, é possível responder: "não, o usuário cancelou".

Opinião do GN⁺

  • O ponto mais importante deste texto é que, quando uma API não faz nada, ela deve não fazer nada de um jeito que não prejudique a experiência do usuário e mantenha a compatibilidade com o código existente.
  • Essa abordagem reforça para os desenvolvedores a importância do design de APIs e contribui para oferecer uma experiência de software mais amigável ao usuário.
  • O texto mostra um aspecto cuidadoso da engenharia de software e oferece um caso interessante sobre como aplicativos podem falhar de forma elegante até mesmo em ambientes inesperados.

2 comentários

 
GN⁺ 2024-02-17
Comentários do Hacker News
  • Opinião sobre “engolir erros”:

    • Ocultar erros é uma prática ruim.
    • Isso esconde defeitos do software sem resolver o problema, tornando mais difícil encontrar bugs e testar.
    • O panic da linguagem Go é uma boa forma de sinalizar em alto e bom som erros do programador durante os testes.
    • Quando os atores dentro do sistema tentam esconder seus próprios defeitos, fica muito mais difícil identificar e resolver o problema.
  • Opinião sobre retrocompatibilidade:

    • Retrocompatibilidade é sempre um trabalho confuso, e a escolha é entre fazê-la de forma imperfeita ou simplesmente não fazê-la.
    • É por isso que, ao clicar em um arquivo do Word '97 ou de um jogo para MS-DOS, ele ainda abre como esperado nos computadores de hoje.
  • Reclamação sobre design de UI:

    • Uma UI que sugere dispositivos que talvez existam é extremamente frustrante.
    • Isso faz você perder tempo descobrindo dispositivos que na prática não são suportados.
  • Crítica à falta de aprendizado da Microsoft:

    • Mesmo depois de 30 anos, a Microsoft continua repetindo os mesmos erros.
    • Mostrar uma lista vazia quando um app tenta encontrar uma impressora é repetir um problema antigo.
  • Opinião sobre a falta de suporte a impressão no Xbox:

    • O Xbox não suporta impressão porque a Microsoft definiu que seria assim.
    • Em termos de hardware, ele tem a mesma capacidade de impressão que outros dispositivos Windows.
    • Essa “solução” é um problema causado por uma decisão irracional da Microsoft.
  • Conselho sobre uso de API:

    • É verdade que o componente deve encontrar o problema antes do usuário, mas não concordo com a forma como o autor expressa isso.
    • Não há nada de errado em uma função de impressão lançar NotSupported­Exception.
    • O que o autor descreve é um hack para dar suporte a clientes ruins.
  • Sentimento sobre “conformidade maliciosa”:

    • Ao mesmo tempo gosto e não gosto da forma como isso lida com o problema.
    • Ainda assim, se o objetivo é permitir que mais usuários executem mais software na plataforma, esse método é bom.
  • Opinião positiva sobre segurança:

    • Ignorar corretamente chamadas de API impede que o programa tenha comportamentos mais prejudiciais.
  • Reflexão sobre a estratégia dos navegadores:

    • A estratégia de tentar exibir a página da melhor forma possível, mesmo quando há erros no código HTML, já foi considerada uma boa estratégia.
    • Os usuários não querem erros, e deveríamos ter aprendido com essa experiência.
  • O mal-entendido dos críticos sobre tratamento de exceções:

    • Há um ponto que os críticos estão deixando passar em situações em que não é necessário lançar exceções.
    • Se o dispositivo (impressora) não estiver conectado, o app deve lidar com essa situação de forma limpa, e não travar.
 
GN⁺ 4 일 전
Comentários no Lobste.rs
  • O que está sendo dito aqui é que as funções de impressão se comportam de forma consistente como se a impressão fosse totalmente suportada, mas, estranhamente, nunca haverá de fato uma impressora para imprimir, o que explica muita coisa
    Brincadeiras à parte, não concordo com esse tipo de programação excessivamente defensiva e com essa experiência do usuário. Desse jeito, o software simplesmente deixa de fazer o que deveria sem que ninguém saiba por quê, e não há como descobrir o motivo. O app deveria capturar o erro e, se possível, gerar uma mensagem amigável para o usuário; caso contrário, ao menos mostrar a mensagem de erro original. Se for uma tarefa em segundo plano, deveria haver um log de erros
    Reconheço que este texto foi escrito da perspectiva de um desenvolvedor de API, não de um desenvolvedor de aplicativo. Então, os erros da API devem ser documentados, e devem ser fornecidas mensagens de erro sobre as quais quem chama a API possa agir
    Também não gosto de esconder botões na UI só porque o usuário não tem permissão. Se houver espaço, acho melhor mostrar o botão desativado e, quando o usuário passar o mouse por cima, exibir uma mensagem explicando como ativá-lo
    • Também é preciso levar em conta que este texto não está na perspectiva de um simples desenvolvedor de API, mas de um desenvolvedor da API do Windows. Há muito tempo a posição da Microsoft é que a API do Windows não deve quebrar compatibilidade mesmo quando a versão muda
    • Esta é uma discussão clássica sobre a Lei de Postel / princípio da robustez. Hoje todos já sabem que o princípio da robustez envelheceu mal e gerou monstros como HTML e protocolos e formatos de arquivo enormes nos quais é difícil escrever parsers corretos
      No geral, é melhor exigir correção. Mas, se você tem 1 bilhão de usuários existentes, é muito sensato evitar quebrar as coisas sempre que possível, e isso também gera valor real no nível do sistema porque, do ponto de vista do usuário, simplesmente funciona. No fim, a atitude deveria ser: falhe rápido, mas não faça com que muita coisa falhe
    • Neste caso, uma das APIs existentes provavelmente não tinha um erro documentado, então pode muito bem ter sido criada sem nenhum tratamento de erro desde o início. Se você não quer quebrar todo o software existente, precisa contar uma mentira piedosa
      Isso está mais próximo de estabilidade de ABI do que de API. No Windows, até software compilado há 15 anos deve continuar funcionando em sistemas operacionais novos sempre que possível. Como não dá para mudar a assinatura da função, se você quiser que APIs que já não fazem mais sentido continuem funcionando, precisa contar mentiras piedosas
      Por exemplo, a API ainda finge que o Active Desktop existe, porque a alternativa seria quebrar em massa softwares antigos já existentes
    • Concordo totalmente. Há poucas coisas tão frustrantes quanto ficar procurando um botão que não aparece na minha tela, mas aparece para a pessoa ao lado que está explicando ou no tutorial
      Fica impossível saber se a função foi removida ou se acabou escondida em outra tela nesse meio-tempo
    • Está certo que o app deveria capturar o erro e mostrá-lo ao usuário.
      Mas, se o app não fizer isso, as pessoas que usam esse app vão culpar o Windows. Vão culpar o Windows, não o app, e isso vale até quando o app trava
      É por isso que a Microsoft cria soluções alternativas. É muito mais fácil deixar o trabalho de impressão simplesmente desaparecer, e aí o usuário pensa por um instante e aceita: “ah, é verdade, não tem impressora”
  • Se a função de instalação da impressora puder retornar imediatamente com um código de resultado indicando o usuário cancelou a operação, do ponto de vista de suporte a aplicações isso quase certamente leva a um comportamento indesejado muito mais difícil de lidar do que se a API de impressão simplesmente lançasse uma exceção desde o início
  • Tenho feito bastante programação assistida por AI ultimamente e vejo agentes inserindo com frequência verificações if para checar null. Sempre que vejo isso, lembro deste texto
    Então passei a instruir o agente a não repetir verificações de null, a usar funções inofensivas e a verificar uma única vez, no momento da declaração, que o valor nunca é null