E aí Bruno,
Vamos entender o seu código, vou colocar comentários nas linhas
//adiciona um evento de escuta para quando o usuário clicar no botão
botaoAdicionar.addEventListener("click", function(event){
//previne da ação padrão do botão
event.preventDefault();
//seleciona o formulário
var form = document.querySelector("#form-adiciona");
//seleciona o paciente
var paciente = obtemPacienteDoFormulario(form);
//seleciona TR
var pacienteTr = montaTr(paciente);
//recebe os erros do paciente
var erros = validaPaciente(paciente);
//SE tiver erro, chama a função e para de executar o código que vem depois do if
if(erros.length > 0){
exibeMensagensDeErro(erros);
return;
}
//só vem aqui se não gerou erro, ou seja não teve erro e não entrou na função, então se já tiver um erro na tela, o erro continuará lá, pois a função não foi chamada e o innerHTML = "" não foi acionado.
var tabela = document.querySelector("#tabela-pacientes");
tabela.appendChild(pacienteTr);
form.reset();
});
Uma maneira de você saber por onde o seu código está passando é colocar um break-point no devTools do Chrome na linha em que ele limpa a UL, você verá que quando não tem erros, ele não irá passar por essa linha o/
Ou um jeito de corrigir isso também é colocando o código de limpar antes do if
var ul = document.querySelector("#mensagens-erro");
ul.innerHTML = "";
if(erros.length > 0){
exibeMensagensDeErro(erros);
return;
}