Primeiro, uma definição: um erro difícil de encontrar, pelo que entendi, é um bug que pode ser reproduzido, mas a causa é difícil de encontrar.
Provavelmente, o aspecto mais importante é o que eu chamaria de estreiteza , ou seja, até onde um bug pode escapar, quão grande é o escopo que um bug pode potencialmente influenciar. Em idiomas como C, um bug, por exemplo um índice de array negativo ou ponteiro não inicializado pode afetar literalmente tudo em todo o programa, então, no pior dos casos, você precisa verificar tudo em qualquer lugar para encontrar a origem do problema.
Bons idiomas a esse respeito suportam modificadores de acesso e os aplicam de uma forma que torna difícil ou impossível contorná-los. Boas linguagens encorajam você a limitar o escopo de suas variáveis, em vez de tornar muito fácil ter variáveis globais (por exemplo, "tudo que não é explicitamente declarado é uma variável global com um tipo e valor padrão").
O segundo aspecto importante é a simultaneidade . As condições de corrida são geralmente difíceis de reproduzir e, portanto, difíceis de encontrar. Boas linguagens oferecem mecanismos de sincronização fáceis de usar e suas bibliotecas padrão são seguras para threads sempre que necessário.
Isso já completa minha lista; outras coisas, como digitar strong, ajudam a capturar bugs em tempo de compilação, mas esses bugs provavelmente não seriam difíceis de encontrar depois.
Considerando tudo isso, eu diria que Java e C #, e muitas outras linguagens no mundo da JVM e do .net, são adequadas para evitar erros difíceis de localizar.