Se você não vai fazer nada, ao menos faça nada do jeito certo
(devblogs.microsoft.com)- Mesmo que não seja possível oferecer funcionalidade real por causa de uma plataforma específica ou de um estado de aposentadoria, para que apps existentes não quebrem é preciso projetar o comportamento de “não fazer nada” dentro do contrato de API documentado
- Em ambientes sem infraestrutura de impressão, como o Xbox, uma resposta bem-sucedida com 0 impressoras é mais segura para apps escritos com base no PC do que uma
NotSupportedException - Essa abordagem é uma API inerte: um design que mantém a superfície e a especificação da API, mas não realiza nenhuma operação realmente útil
- Em uma API de widgets sendo aposentada, se
CreateWidgettiver sucesso e retornarnullptr, o chamador passa por um estado contraditório; portanto, o caminho de falha documentado comERROR_CANCELLEDé mais consistente - Uma API inerte correta faz apenas a funcionalidade desaparecer e usa caminhos de falha para os quais o código existente já estava preparado, reduzindo exceções, handles inválidos e crashes
Mesmo sem funcionalidade, o contrato da API permanece
- Há casos em que uma API precisa ser feita para não fazer nada, mas essa inatividade também deve estar de acordo com o código existente e com o comportamento documentado
- Uma inatividade mal projetada pode gerar exceções, valores de retorno contraditórios e estados inválidos, levando a crashes de apps ou a erros desnecessários para o usuário
Como neutralizar APIs de impressão no Xbox
- O Windows tem uma ampla infraestrutura de impressão, mas o Xbox não tem essa infraestrutura
- Se, quando um app tentar imprimir no Xbox, a função de impressão lançar uma
NotSupportedException, o problema pode ficar maior- É bem provável que os apps instalados no Xbox tenham sido testados principalmente no PC
- No PC, a impressão está sempre disponível, então uma exceção não tratada pode levar ao crash do app
- Mesmo que a exceção seja capturada, o usuário pode ver uma mensagem de erro como “forneça o incident code ao suporte”
- Um design melhor é a função de impressão ter sucesso, mas informar que não há impressoras instaladas
- O app exibe a UI de seleção de impressora e mostra uma lista vazia
- O usuário vê que não há impressoras e pode cancelar a solicitação de impressão
- Para apps que tentam ajudar a instalar uma impressora, a função de instalação pode retornar imediatamente com um código de resultado que significa que o usuário cancelou a operação
Condições de uma API inerte
- Esse comportamento de “não fazer nada” é chamado de inerte
- Uma API inerte mantém a superfície da API existente e se comporta conforme a especificação, mas, na prática, não faz nenhuma operação útil
- O ponto central é não fazer nada de uma forma consistente com a documentação e com a menor probabilidade de causar problemas ao código existente
- A API de impressão também poderia adicionar uma função para verificar se a impressão em si é possível
- O app pode usar essa função para ocultar o botão Print em sistemas que não dão suporte algum à impressão
- Apps que simplesmente assumem que a impressão é possível também passam a se comportar como em “um sistema sem impressoras e em que tentativas de instalação não têm efeito”
Design a evitar em uma API de widgets sendo aposentada
- A API a ser aposentada tem uma função que cria um handle de widget, uma função que recebe um handle de widget e uma função que fecha um handle de widget
- A proposta inicial era fazer
CreateWidgetretornarS_OKe definir*widget = nullptr- A chamada teria sucesso, mas não receberia um handle válido
- Entre o código de teste real, havia código que julgava o sucesso não pelo valor de retorno, mas por verificar se o handle era
null
- Um design em que
EnableWidgetretorna “handle inválido” também confunde o chamador- O app passa para
EnableWidgeto handle recebido após o sucesso deCreateWidget - A API responde que esse handle não é válido
- O chamador passa por um estado contraditório: “pedi um widget, recebi um, mas quando o mostro de novo dizem que não é um widget”
- O app passa para
Design corrigido com um caminho de falha documentado
- A documentação existente tem o valor de retorno
ERROR_CANCELLED, que significa que o usuário cancelou a criação do widget - Como o app já precisa lidar com a possibilidade de o widget não ser criado por condições externas, a criação do widget pode ser feita para sempre falhar como cancelamento pelo usuário
- O fluxo do design revisado é simples
CreateWidgetdeixa*widget = nullptre retornaHRESULT_FROM_WIN32(ERROR_CANCELLED)- Como a criação do widget sempre falha, não existe handle de widget válido
GetWidgetAliases,EnableWidgeteCloseretornamE_HANDLE, indicando que o handle não é válido
- Nesse método, como não há widget, também não é preciso criar um alias fictício
- Como não existe widget válido, também não há situação em que o app possa solicitar aliases normalmente
Critério ao levar APIs de desktop para outro ambiente
- No desktop, a API de impressão sempre existiu, e a função que “obtém a lista de impressoras” é documentada para não lançar exceções
- Ao levar essa API para o Xbox, para que apps de desktop existentes continuem executando, o comportamento inerte correto é retornar honestamente que “não há impressoras no Xbox”
- Lançar uma exceção para a pergunta “quantas impressoras há?” não está de acordo com o contrato de API documentado
- O mesmo princípio se aplica ao aposentar APIs existentes
- Mesmo que não façam trabalho útil, o comportamento da API deve ser consistente com o contrato
- É importante usar valores de retorno e estados que o código existente já consiga tratar
2 comentários
Comentários no Lobste.rs
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
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
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
Fica impossível saber se a função foi removida ou se acabou escondida em outra tela nesse meio-tempo
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”
ifpara checar null. Sempre que vejo isso, lembro deste textoEntã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
Opiniões no Hacker News
Aprendi isso como uma forma de engolir erros, e vejo como uma prática ruim
Além de não resolver o problema real de não ser possível imprimir no Xbox, isso esconde o quanto o software está quebrado, tornando a descoberta de bugs e os testes muito mais difíceis
O
panicdo Go é bom porque não foi feito para ser abusado ou recuperado em tempo de execução, mas para soar um alarme bem alto, durante os testes, dizendo que o programador fez algo erradoQuando os atores dentro de um sistema escondem ativamente seus próprios defeitos ou erros, encontrar e corrigir a causa fica exponencialmente mais difícil
Nos sistemas B2B, de microsserviços e centrados em APIs com os quais lido, o correto é falhar rapidamente em requisições fora da especificação
Mas o Windows fez enormes concessões em nome da compatibilidade, chegando a salvar softwares famosos quebrados como SimCity até com patches de memória: https://arstechnica.com/gadgets/2022/10/windows-95-went-the-...
Em um ambiente desktop em que o usuário não consegue corrigir o código do sistema, muitas vezes a experiência do usuário final é mais importante que a correção
Não importa por que ocorreu uma BSOD; o que a Microsoft aprendeu é que software ou hardware ruim prejudica a reputação da Microsoft
Pessoalmente, prefiro projetos que falham rápido ou interrompem a execução diante de entradas ou condições inválidas, mas entendo o valor dessa abordagem nesse ambiente
O importante é o contexto, não aplicar dogmaticamente um lado ou outro
Por exemplo, não se deve levar a abordagem de Chen para o projeto de um reator nuclear ou de trading de alta frequência, e ela pode ser uma ótima abordagem para um motor de jogo
Decidir se um componente do runtime ficará “inofensivamente inativo” é uma decisão de projeto que vem antes de a implementação dar
panicou nãoNeste caso, cabe à Microsoft definir o significado de “imprimir no Xbox”
Isso pode ser definido como um erro, ou, em teoria, como um recurso que pode ser suportado no futuro, mas que hoje não tem uma forma real de ser executado
Se alguém conectar uma impressora USB e o “jogo” souber como imprimir, como pela porta de expansão de jogos de Game Boy que davam suporte ao GB Printer, em teoria seria possível imprimir até no Xbox
Um jogo de Xbox tentar imprimir não é necessariamente um erro intrínseco; defini-lo como erro também é apenas uma escolha com trade-offs, como portabilidade de aplicações
Você poderia exibir uma tela de seleção sem impressoras disponíveis, como no Xbox, fazer a API mentir dizendo que imprimiu, ou dar suporte à impressão real
Só depois de definir o significado de “imprimir no Xbox” como erro é que o fato de esse erro não ser lançado e ser engolido se torna um problema de implementação ou depuração
Há dois tratamentos possíveis: como a máquina nunca terá uma impressora, retornar um erro; ou, como a máquina nunca terá uma impressora, retornar uma lista vazia de impressoras
Ambos são válidos, mas só um deles não quebra o espaço de usuário
“Soar um alarme bem alto” é bom para testadores ou desenvolvedores, mas não é muito bom para o usuário final
Do ponto de vista do usuário final, engolir o erro é melhor do que o app morrer
Não dá para saber o que falhou: o software do meu computador, o hardware, o cabo Ethernet ou o Wi-Fi, o switch, o roteador, o modem a cabo, o cabo de internet que chega até a minha casa, o ISP, ou o sistema remoto que estou tentando acessar; nada disso melhorou
É esperado que haja uma reação negativa, mas esse tipo de coisa bagunçada é justamente o motivo de um documento do Word ’97 ou de um jogo compilado há 30 anos para MS-DOS abrir como esperado quando você clica nele
Compatibilidade retroativa é sempre messy, e ou ela é feita de forma imperfeita, ou simplesmente não é feita
No fim acabo usando o GOG, e o GOG também acaba virtualizando o ambiente
Por exemplo, não acho que alguém vá conseguir instalar o Sim City original no Windows 11 com sucesso
A formatação dependia do driver da impressora instalado
Por outro lado, um arquivo LaTeX de 35 anos atrás ainda funciona bem hoje
O Word não conseguia garantir que a formatação não mudaria nem ao reabrir no mesmo computador e na mesma versão, e foi por isso que aprendi TeX
Nos jogos também, a Microsoft quebrou inúmeros jogos ao encerrar o GFWL
O Dark Souls original não pôde ser jogado até ser relançado no Steam, então era preciso usar uma cópia pirata
Aparece uma caixa de diálogo dizendo “não suportado”, e você pode baixar o dosbox, mas isso não é “exatamente como esperado”
Quase sempre desisti ou comprei de novo no GOG; na prática, paguei pelo trabalho de alguém que já tinha resolvido os problemas de compatibilidade
Nada é tão frustrante quanto uma UI de dispositivo que sugere que algo poderia existir de fato, mas agora diz que não há nada
No fim, só depois de gastar tempo é que você descobre que aqueles dispositivos não são compatíveis e que aquela tela era apenas um mockup feito por alguém
Se o app não previu a situação de impressão sem suporte e a impressão lança uma exceção, o app provavelmente morre ali mesmo
Um app que previu a falta de suporte à impressão deveria primeiro verificar, por uma API explícita, se a impressão é suportada e, se não for, não deveria mostrar a UI de impressão
O texto não fala desse tipo de app, mas de como lidar com apps que não fazem essa verificação antes
O bug está no app, mas uma boa plataforma deve tentar fazer até apps ruins funcionarem o máximo possível, em vez de simplesmente deixá-los morrer
O papel da Microsoft é impedir que o app morra só porque o fornecedor do software não levou impressoras em consideração
Quando um app tenta imprimir, pedir ao usuário que escolha uma impressora e mostrar uma lista vazia — parece que a Microsoft não aprendeu em 30 anos: https://www.reddit.com/r/hacking/comments/djvzd/windows_nt_l...
A direção concreta proposta no texto — isto é, que o componente deve sofrer antes do usuário sofrer — está certa, mas o enquadramento incomoda bastante
Coisas como “há momentos em que é preciso fazer a API não fazer nada” ou “é errado uma função de impressão lançar
NotSupportedException” definitivamente não são conselhos geraisIsso é um hack para dar suporte a clientes péssimos e, embora às vezes seja necessário, não é um conselho normal nem generalizável
Esse tipo de formulação mostra sinais de ter internalizado o sofrimento de ser desenvolvedor da Microsoft
Se o cliente não mudou e a API o quebra, então quem é péssima não é o cliente, mas a API
Em qualquer plataforma amplamente adotada e que leve compatibilidade retroativa a sério, isso é algo completamente normal e generalizável
O Windows é o exemplo clássico, mas o mesmo acontece no ABI do kernel Linux, na glibc, na plataforma web, no Java etc.
Não dá para voltar no tempo e recompilar ou reescrever esses clientes
Se o cliente já está verificando
NotSupportedException, você deve retorná-la; se não estiver, precisa de outra abordagemIsto é Windows
Você não pode quebrar aplicativos dos quais os usuários dependem
Se há um app cliente “péssimo” ou que se comporta incorretamente, você precisa usar todos os contornos, shims e hacks de compatibilidade necessários para fazê-lo funcionar como esperado
Mesmo que isso signifique colocar código especial de gerenciamento de memória para fazer SimCity funcionar
Seu cliente depende dele e, se ele parar por causa de algo que você mudou, vai culpar você
Aqui todo mundo fica preso em “não fazer nada” e “tratamento de erros”, mas parece que o texto não explicou bem que não está propondo engolir todos os problemas e cantar kumbaya
O contexto real é emulação e compatibilidade para software que não vai mudar, algo muito diferente da maioria das situações
A própria Microsoft costuma borrar a fronteira entre as duas coisas, o que aumenta a confusão, mas o assunto aqui não é tratamento de erros em geral
Emulação é, por natureza, mentir
Não há como conectar uma impressora de verdade a um Xbox, você está rodando um jogo de Windows no Linux, e o iPhone falso pode na verdade ser um servidor ARM da AWS
Se você vai mentir, precisa mentir de forma convincente, para que os aplicativos existentes continuem funcionando
Quando a Microsoft diz que é possível portar apps Windows existentes para o Xbox, havia nos apps Windows a promessa de que era possível imprimir; portanto, o Xbox precisa mentir para fazer isso parecer possível
Por outro lado, se um programa está fazendo use-after-free e você contorna silenciosamente fingindo que não há bug, isso não é o tratamento correto
Afinal, ninguém prometeu que até use-after-free existente teria compatibilidade bug a bug na nova plataforma
Essa interpretação foi apenas assumida porque sustenta a posição que você quer defender
Na prática, o conteúdo do texto também se aplica exatamente igual se eu escrever amanhã um programa para Xbox e tentar imprimir
Não há nada distinguindo que esse comportamento se aplique apenas a software antigo e sem manutenção
Gosto e não gosto dessa abordagem
Instintivamente, não gosto de lidar com problemas por meio de cumprimento malicioso
Por outro lado, se o objetivo é fazer com que mais usuários rodem mais software na plataforma, mesmo que a impressão esteja quebrada, concordo totalmente que é uma boa decisão
Este fluxo de comentários foi estranho
Claramente há gente que odeia qualquer coisa que a Microsoft faça
O ponto central do blog é tornar sem atrito o processo de levar apps (UWP) para o XBOX
Fazer o app “simplesmente funcionar”, sem recompilação, sem
ifdefsujo, sem trabalho adicional, e então o desenvolvedor pode ajustar o app à plataforma XBOXO importante é que o desenvolvedor conseguiu levar o app para o XBOX sem esforço nenhum, e isso é bom tanto para a plataforma quanto para os desenvolvedores
Para o usuário, a lista de impressoras vazia é um sinal claro de que não é possível imprimir
Acima de tudo, o app não morre nem mostra um erro péssimo como “Ocorreu um problema. Tente novamente mais tarde”
Essa mensagem não é uma experiência melhor para o usuário
O desenvolvedor pode recompilar o app e inserir uma mensagem mais explicativa, mas aí voltamos à estaca zero
Por exemplo, a acusação de que ele engole erros não tem nada a ver com o ponto central
O ponto central que quem critica este texto deixa passar é que aqui não há uma situação excepcional que exija lançar uma exceção
Não há uma impressora conectada a este dispositivo e, neste caso, isso é verdade por definição; portanto, o app deve tratar essa situação de forma limpa, não morrer
Não se lançaria uma exceção só porque um notebook não consegue acessar uma impressora
Depois de adicionar uma impressora nas configurações, se você mudar para a aba “Color Profiles” durante a instalação, no timing certo o gnome-control-center morre
Olhando mais de perto, ele tentou enumerar as impressoras disponíveis; sabe que deveria haver uma impressora, mas as informações ainda não existem, então simplesmente morre
Felizmente, basta esperar alguns segundos até o GNOME terminar de instalar a impressora e abrir as configurações de novo
Mesmo assim, se é um app essencial que permite a usuários finais de diferentes perfis usar o desktop corretamente, em um mundo ideal ele nunca deveria morrer
Pensar em condições de contorno é difícil, e imaginar que o próprio tratamento de condições de contorno também tem outras condições de contorno é ainda mais difícil
Certo ou errado, pelo menos o autor está pensando mais a fundo do que “é um app ruim, então precisa ser reescrito para dar suporte ao Wayland”
Houve uma época em que era considerado uma ótima estratégia o navegador fazer o melhor possível para exibir uma página mesmo quando havia erros no código HTML
Era algo como seguir em frente tentando adivinhar ao máximo a intenção do autor, partindo da ideia de que erros são ruins e usuários não querem erros
Eu achava que tínhamos aprendido com essa experiência, mas aparentemente não
A tentativa do XHTML de substituir isso por validação estrita fracassou; em vez disso, o HTML5, que teve sucesso, voltou atrás e definiu explicitamente como interpretar todas as sequências inválidas possíveis, para que os navegadores ao menos se comportassem de forma consistente
Se os primeiros navegadores exibissem um erro assim que encontrassem uma tag desconhecida, novos recursos dos navegadores teriam sido sufocados no nascimento
HelloeWorld!, dá para ver se, graças à sabedoria que adquirimos, agora aparece um erroMas, se ainda renderiza bem, isso pode significar que ainda há muito a aprender, ou que também faz sentido tratar certos tipos de erro com elegância e morrer em situações excepcionais
Acho que, se eu viajasse no tempo para 1999, não sentiria tanta falta assim