Antes de mais nada, discordo dessa afirmação:
Favour exceptions over error codes
Este não é sempre o caso: por exemplo, dê uma olhada no Objective-C (com o framework Foundation). Lá, o NSError é a maneira preferida de lidar com erros, apesar da existência do que um desenvolvedor Java chamaria de verdadeiras exceções: @try, @catch, @throw, NSException class, etc.
No entanto, é verdade que muitas interfaces vazam suas abstrações com as exceções lançadas. Acredito que isso não seja culpa do estilo "exceção" de propagação / manipulação de erros. Em geral, acredito que o melhor conselho sobre o tratamento de erros é este:
Lide com o erro / exceção no nível mais baixo possível, período
Eu acho que se alguém mantiver essa regra, a quantidade de "vazamento" das abstrações pode ser muito limitada e contida.
Se as exceções lançadas por um método devem fazer parte de sua declaração, acredito que elas devem: elas são parte do contrato definido por essa interface: Esse método faz A ou falha com B ou C.
Por exemplo, se uma classe for um XML Parser, uma parte de seu design deve ser para indicar que o arquivo XML fornecido está simplesmente errado. Em Java, você normalmente faz isso declarando as exceções que espera encontrar e adicionando-as à parte throws
da declaração do método. Por outro lado, se um dos algoritmos de análise falhar, não há razão para passar essa exceção acima do não tratado.
Tudo se resume a uma coisa: Bom design de interface. Se você projetar sua interface bem o suficiente, nenhuma quantidade de exceções deve assombrá-lo. Caso contrário, não são apenas exceções que o incomodariam.
Além disso, acho que os criadores de Java tinham motivos de segurança muito strongs para incluir exceções a uma declaração / definição de método.
Uma última coisa: Algumas linguagens, Eiffel, por exemplo, têm outros mecanismos para o tratamento de erros e simplesmente não incluem recursos de lançamento. Lá, uma 'exceção' do tipo é automaticamente levantada quando uma pós-condição para uma rotina não é satisfeita.