Como os compiladores devem relatar erros e avisos?

11

Não pretendo escrever um compilador no futuro próximo; Ainda assim, estou bastante interessado nas tecnologias de compilador e em como essas coisas podem ser melhoradas.

Começando com linguagens compiladas, a maioria dos compiladores possui dois níveis de erro: avisos e erros, o primeiro sendo coisas não fatais que você deve corrigir e erros indicando na maioria das vezes que é impossível produzir máquina (ou byte) código da entrada.

Porém, esta é uma definição bastante fraca. Em algumas linguagens como Java, alguns avisos são simplesmente impossíveis de serem eliminados sem o uso da @SuppressWarningdiretiva. Além disso, o Java trata certos problemas não fatais como erros (por exemplo, código inacessível em Java aciona um erro por um motivo que eu gostaria de saber).

O C # não tem os mesmos problemas, mas possui alguns. Parece que a compilação ocorre em várias passagens, e uma falha na passagem impedirá a execução de outras passagens. Por esse motivo, a contagem de erros que você obtém quando sua compilação falha geralmente é subestimada. Em uma corrida, pode-se dizer que você tem dois erros, mas depois de corrigi-los, você poderá obter 26 novos.

Escavar em C e C ++ simplesmente mostra uma combinação ruim das deficiências de diagnóstico da compilação de Java e C # (embora possa ser mais preciso dizer que Java e C # apenas seguiram seu caminho com metade dos problemas cada). Alguns avisos realmente deveriam ser erros (por exemplo, quando nem todos os caminhos de código retornam um valor) e ainda são avisos porque, suponho, no momento em que eles escreveram o padrão, a tecnologia do compilador não era boa o suficiente para fazer esse tipo de coisa. verificações obrigatórias. Na mesma linha, os compiladores geralmente buscam mais do que o padrão diz, mas ainda usam o nível de erro de aviso "padrão" para obter descobertas adicionais. E, frequentemente, os compiladores não relatam todos os erros que podem encontrar imediatamente; pode levar algumas compilações para se livrar de todas elas. Sem mencionar os erros enigmáticos que os compiladores C ++ gostam de cuspir,

Agora, adicionando que muitos sistemas de compilação são configuráveis ​​para relatar falhas quando os compiladores emitem avisos, obtemos uma mistura estranha: nem todos os erros são fatais, mas alguns avisos devem; nem todos os avisos são merecidos, mas alguns são explicitamente suprimidos sem menção adicional de sua existência; e algumas vezes todos os avisos se tornam erros.

Os idiomas não compilados ainda têm sua parcela de relatórios de erros ruins. Erros de digitação no Python não serão relatados até que o código seja realmente executado, e você nunca poderá realmente chutar mais de um erro de cada vez, porque o script interromperá a execução depois de encontrar um.

O PHP, por seu lado, possui vários níveis de erro mais ou menos significativos e exceções. Os erros de análise são relatados um de cada vez, os avisos geralmente são tão ruins que devem abortar seu script (mas não por padrão), os avisos geralmente mostram problemas sérios de lógica, alguns erros não são ruins o suficiente para interromper o script, mas ainda assim e, como de costume no PHP, existem algumas coisas realmente estranhas lá embaixo (por que diabos precisamos de um nível de erro para erros fatais que não são realmente fatais?, E_RECOVERABLE_E_ERRORestou falando com você).

Parece-me que todas as implementações de relatórios de erro do compilador que consigo pensar estão quebradas. O que é uma pena, pois todos os bons programadores insistem em como é importante lidar corretamente com os erros e, no entanto, não conseguem ter suas próprias ferramentas para fazê-lo.

Qual você acha que deve ser o caminho certo para relatar erros do compilador?

zneak
fonte
-1: "Os idiomas não compilados ainda têm sua parcela de relatórios de erros ruins" Subjetivo e argumentativo. Realmente inútil. Isso é uma pergunta ou reclamação?
precisa saber é o seguinte
2
@ S.Lott Acho que você está um pouco no limite aqui. Acho que fui muito mais exigente em idiomas compilados e isso não pareceu incomodá-lo.
zneak
@ zneak: As outras declarações estão mais próximas de serem factuais e mais difíceis de analisar. Essa afirmação mostrou-se mais facilmente subjetiva e argumentativa.
precisa saber é o seguinte
1
@ S.Lott Estou errado ao afirmar que Python indica um erro de cada vez?
zneak
1
@ S.Lott Então as coisas devem ter mudado, porque da última vez que tentei, qualquer erro de sintaxe faria o Python parar de tentar "compilar" e um erro de nome geraria uma exceção e não verificaria o restante da função (embora isso tenha deixado espaço para relatar um erro por unidade testável). Minha afirmação subjetiva e argumentativa foi uma introdução ao que eu acreditava ser um fato, mas se não for mais verdade, editarei minha pergunta. Como isso funciona agora?
zneak

Respostas:

6

Sua pergunta não parece ser sobre como relatamos erros do compilador - é sobre a classificação dos problemas e o que fazer com eles.

Se começarmos assumindo, por enquanto, que a dicotomia de aviso / erro está correta, vamos ver o quão bem podemos construir em cima disso. Algumas ideias:

  1. Diferentes "níveis" de aviso. Muitos compiladores implementam isso (por exemplo, o GCC tem muitas opções para configurar exatamente o que será avisado), mas precisa de trabalho - por exemplo, relatando a gravidade de um aviso relatado e a capacidade de definir "avisos" são erros "apenas para avisos acima de uma gravidade especificada.

  2. Classificação sã de erros e avisos. Um erro só deve ser relatado se o código não atender à especificação e, portanto, não puder ser compilado. Instruções inacessíveis, embora provavelmente um erro de codificação, devam ser um aviso , não um erro - o código ainda é "válido" e há instâncias legítimas em que alguém gostaria de compilar com código inacessível (modificações rápidas para depuração, por exemplo) .

Agora, eu discordo de você sobre:

  1. Fazendo um esforço extra para relatar todos os problemas. Se houver um erro, isso interrompe a compilação. A compilação está quebrada. A compilação não funcionará até que esse erro seja corrigido. Portanto, é melhor relatar esse erro imediatamente, em vez de "continuar", a fim de tentar identificar tudo o mais "errado" no código. Especialmente quando muitas dessas coisas provavelmente são causadas pelo erro inicial de qualquer maneira.

  2. Seu exemplo específico de um aviso de que deve ser um erro. Sim, provavelmente é um erro de programador. Não, não deve quebrar a compilação. Se eu souber que a entrada da função é tal que ela sempre retornará um valor, devo poder executar a compilação e fazer alguns testes sem precisar adicionar essas verificações extras. Sim, deve ser um aviso. E uma maldita alta gravidade nisso. Mas não deve quebrar a compilação por si só, a menos que seja compilado com avisos de erros.

Pensamentos?

Anon.
fonte
Eu concordo com você, exceto pelos pontos em que discordamos (duh), então isso é +1 de mim. Eu acho que é fácil o suficiente fazer com que cada caminho de código retorne um valor ou aborte seu programa, considerando o quão ruim é quando você realmente cai no caso do comportamento indefinido.
zneak
7

Um problema que você levantou foi o relatório incompleto de erros - por exemplo, o relatório de 2 erros e, quando você os corrige, recebe muito mais.

Isso é (em grande parte) um compromisso por parte do escritor do compilador. Dependendo do que erro que você fez, é muito fácil para o compilador para começar a interpretar mal o que você faz tem mal o suficiente para que ele começa a relatar erros que têm muito pouco a ver com a realidade. Apenas por exemplo, considere um erro de digitação simples em que você tem algo parecido em itn x;vez de int x;. A menos que você tenha feito algo que itnsignifique algo, isso será relatado como um erro. Tudo bem, mas agora considere o que acontece a seguir - o compilador analisa muito código que tenta usar x como variável. A) deve parar e permitir que você conserte isso; ou B) vomitar 2000 erros error: "x": undeclared identifierou algo nessa ordem? Considere outra possibilidade:

int main()[

Este é outro erro de digitação bastante óbvio - obviamente deve ser um em {vez de um [. O compilador pode lhe dizer essa parte com bastante facilidade - mas deve reportar um erro por algo como x=1;dizer algo como error: statement only allowed inside a function?

Observe que esses são problemas bastante triviais - problemas muito piores são fáceis de encontrar (especialmente, como a maioria de nós sabe, quando você entra em modelos C ++). O ponto principal é que o gravador do compilador geralmente fica impedido de tentar comprometer entre relatar erros falsos (ou seja, relatar algo como um erro, mesmo que esteja bom) e deixar de relatar erros reais. Existem algumas regras práticas que a maioria segue para tentar evitar erros excessivos em qualquer direção, mas quase nenhuma delas está perto da perfeição.

Um outro problema que você mencionou foi Java e @SupressWarning. Isso é bem diferente do acima - seria bastante trivial de corrigir. A única razão pela qual não foi corrigido é que isso não se encaixa no "caráter" básico do Java - ou seja, na opinião deles, "isso não é um bug, é um recurso". Mesmo que seja geralmente uma piada, nesse caso as pessoas envolvidas são tão equivocadas que realmente acreditam que é verdade.

O problema que você menciona em C e C ++ com caminhos de código que não retornam um valor não é realmente permitir compiladores primitivos. É para permitir décadas de código existente , alguns dos quais ninguém quer consertar, tocar ou mesmo ler. É antigo e feio, mas funciona, e ninguém quer nada além de continuar trabalhando. Para melhor ou pior, os comitês de idiomas estão praticamente presos em manter essa compatibilidade com versões anteriores, então eles continuam permitindo coisas que ninguém realmente gosta - mas algumas pessoas (pelo menos pensam que precisam).

Jerry Coffin
fonte
3
Além do seu argumento sobre erros antigos que causam muitos outros, também há o fato de que passes posteriores geralmente são criados para exigir que os passes anteriores sejam concluídos com êxito. Por exemplo, uma das primeiras passagens no compilador C # verifica se não há ciclos no gráfico de herança - você não tem A herdar de B que herda de A. Se você quisesse continuar e gerar uma lista de todos os erros depois disso, cada passo posterior teria que ser capaz de lidar com ciclos - tornando-o significativamente mais lento mesmo em compilações "boas".
Anon.
@Anon. O compilador Java faz esforços muito melhores para sobreviver a passes iniciais, e eu não o acho significativamente mais lento. Para mim, é um pouco irritante a rapidez com que cscdesiste.
zneak
@zneak: Como Jerry diz, é um compromisso por parte dos desenvolvedores dos compiladores. Escrever bons diagnósticos de erros é realmente um problema muito difícil (veja clang para obter um exemplo de quão longe você realmente pode levá-lo). Veja aqui uma boa discussão sobre as fases e passes do compilador C #.
Dean Harding