Рекурсия при обработке ошибок?

#java #exception-handling #recursion

#java #исключение #рекурсия

Вопрос:

Я пишу программу чтения файлов, которая возвращает объект, и я бы хотел, чтобы она предупреждала об ошибках синтаксического анализа и переходила к следующей записи.

Приведенный ниже код является очевидной реализацией этого, но включает рекурсию изнутри блока catch. Есть ли какая-либо техническая или стилистическая причина не делать этого?

 public RecordType nextRecord() throws IOException{
    if (reader == null){
        throw new IllegalStateException("Reader closed.");
    }
    String line = reader.readLine();
    if (line == null){
        return null;
    }else{
        try {
            return parseRecord(line);
        }catch (ParseException pex){
            logger.warn("Record ignored due to parse error: " 
                  pex.getMessage());
            //Note the recursion here
            return nextRecord();
        }
    }
}
  

Комментарии:

1. Проблема в том, что ваша программа будет накапливать пространство стека с каждой ошибочной записью, что делает ее уязвимой. Например, когда этот фрагмент кода будет частью веб-службы, злоумышленник может передавать только много-много записей об ошибках, пока вся служба не умрет из-за переполнения стека. Это была бы успешная атака типа «отказ в обслуживании».

2. @Ingo спасибо, это должен быть ответ :-).

Ответ №1:

Я бы предпочел использовать цикл. С рекурсией вы никогда не знаете, насколько глубоко вы можете безопасно зайти.

 String line;
while((line = reader.readLine()) != null) {
    try {
        return parseRecord(line);
    }catch (ParseException pex){
        logger.warn("Record ignored due to parse error: "   pex);
    }
}
return null;
  

Комментарии:

1. Да, и отладка этого StackOverflowError была бы затруднительной, не так ли?

2. По крайней мере, перед ошибкой вы бы увидели множество предупреждений. 😉

Ответ №2:

Почему бы не заменить рекурсию циклом:

 public RecordType nextRecord() throws IOException {
    if (reader == null) {
        throw new IllegalStateException("Reader closed.");
    }
    for (;;) {
        String line = reader.readLine();
        if (line == null) {
            return null;
        } else {
            try {
                return parseRecord(line);
            } catch (ParseException pex) {
                logger.warn("Record ignored due to parse error: "
                          pex.getMessage());
                // continue to the next record
            }
        }
    }
}
  

Стилистически я нахожу это предпочтительным.

Комментарии:

1. Это немного менее понятно (спорно), и вы не ответили на вопрос: есть ли какая-либо причина не использовать рекурсию.

2. @C. Ross: Последнее предложение — мой (субъективный) аргумент в пользу того, почему я считаю это предпочтительным.

Ответ №3:

Было бы чище позволить исключению ParseException распространяться обратно на вызывающий объект? Затем вызывающий может решить, что с этим делать.

Ответ №4:

Мне кажется, что независимо от того, что вызывает ваш метод, он будет продолжать вызывать его до тех пор, пока метод не вернет null.

Я бы, вероятно, последовал совету предыдущих постеров и использовал цикл, однако я бы посмотрел на то, что вызывает метод (поскольку он, вероятно, уже использует цикл), пропустил строку, ища исключение, которое будет сгенерировано.