Как правильно обрабатывать исключения при работе с файлами в С#

Я прочитал много блогов/статей/глав книги о правильной обработке исключений, и эта тема для меня пока не ясна. Я попытаюсь проиллюстрировать свой вопрос следующим примером.

Рассмотрим метод класса, который имеет следующие требования:

  • получить список путей файла как параметр
  • прочитать содержимое файла для каждого файла или пропустить, если есть какие-либо проблемы с попыткой сделать это
  • возвращает список объектов, представляющих содержимое файла

Итак, спецификации просты и вот как я могу начать кодирование:

    public class FileContent
    {
        public string FilePath { get; set; }
        public byte[] Content { get; set; }

        public FileContent(string filePath, byte[] content)
        {
            this.FilePath = filePath;
            this.Content = content;
        }
    }

    static List<FileContent> GetFileContents(List<string> paths)
    {
        var resultList = new List<FileContent>();

        foreach (var path in paths)
        {
            // open file pointed by "path"
            // read file to FileContent object
            // add FileContent to resultList
            // close file
        }

        return resultList;
    }

Теперь обратите внимание, что 2. из спецификаций говорит, что метод должен "пропускать любой файл, содержимое которого не может быть прочитано по какой-либо причине". Поэтому может возникнуть много разных причин (например, файл не существует, доступ к файлам запрещен из-за отсутствия разрешений безопасности, блокировка файла и использование другим приложением и т.д.), Но дело в том, что я должен неважно, в чем причина, я просто хочу прочитать содержимое файла, если это возможно, или пропустить файл, если нет. Мне все равно, что ошибка...

Итак, как правильно реализовать этот метод?

ОК, первое правило правильной обработки исключений никогда не будет ловить общее исключение. Поэтому этот код не подходит:

    static List<FileContent> GetFileContents(List<string> paths)
    {
        var resultList = new List<FileContent>();

        foreach (var path in paths)
        {
            try
            {
                using (FileStream stream = File.Open(path, FileMode.Open))
                using (BinaryReader reader = new BinaryReader(stream))
                {
                    int fileLength = (int)stream.Length;
                    byte[] buffer = new byte[fileLength];
                    reader.Read(buffer, 0, fileLength);

                    resultList.Add(new FileContent(path, buffer));
                }
            }
            catch (Exception ex)
            {
                // this file can't be read, do nothing... just skip the file
            }
        }

        return resultList;
    }

Следующее правило правильной команды исключений говорит: улавливайте только определенные исключения, которые вы можете обрабатывать. Ну, я не забочусь о том, чтобы обрабатывать какие-либо исключения, которые могут быть выброшены, я просто хочу проверить, можно ли читать файл или нет. Как я могу сделать это правильно, наилучшим образом?

Ответ 1

Хотя обычно не считается хорошей практикой ловить и проглатывать неспецифические исключения, риски часто завышаются.

В конце концов, ASP.NET поймает неспецифическое исключение, которое бросается во время обработки запроса, и после его переноса в исключение HttpUnhandledException будет перенаправлено на страницу с ошибкой и продолжит счастливо на этом пути.

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

UnauthorizedAccessException IOException FileNotFoundException DirectoryNotFoundException PathTooLongException NotSupportedException (путь не в допустимом формате). SecurityException ArgumentException

Вероятно, вы не захотите поймать SecurityException или ArgumentException, а некоторые из других - из IOException, поэтому вы, вероятно, захотите поймать IOException, NotSupportedException и UnauthorizedAccessException.

Ответ 2

Ваши требования понятны - пропустите файлы, которые невозможно прочитать. Итак, в чем проблема с общим обработчиком исключений? Это позволяет выполнять вашу задачу таким образом, чтобы она была легкой, чистой, удобочитаемой, масштабируемой и поддерживаемой.

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

Значит, вы лучше увидите приведенный ниже код? Обратите внимание, что если вы добавите больше кода для обработки чтения файлов, вы должны добавить новые исключения в этот список. Все это ничего не значит?

try
{
    // find, open, read files
}
catch(FileNotFoundException) { }
catch(AccessViolation) { }
catch(...) { }
catch(...) { }
catch(...) { }
catch(...) { }
catch(...) { }
catch(...) { }

Условные обозначения - это рекомендации и великолепно пытаться придерживаться того, чтобы создать хороший код, но не слишком усложняйте код, чтобы поддерживать нечетное чувство правильного этикета.

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


Изменить: Поэтому я рекомендую следующее

try
{
    // find, open, read files
}
catch { } // Ignore any and all exceptions

Приведенное выше говорит мне, не заботясь о том, какое исключение выбрано. Не указывая исключение, даже просто System.Exception, я разрешил .NET по умолчанию. Таким образом, приведенный ниже точный код.

try
{
    // find, open, read files
}
catch(Exception) { } // Ignore any and all exceptions

Или если вы запишете его как минимум:

try
{
    // find, open, read files
}
catch(Exception ex) { Logger.Log(ex); }  // Log any and all exceptions

Ответ 3

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

Ответ 4

Что-то, что вы можете рассмотреть в этом случае, заключается в том, что между FileNotFoundException, который вы не можете поймать, потому что их слишком много, и наиболее общий Exception, все еще существует слой IOException.

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

Ответ 5

Вы смешиваете разные действия в одном методе, изменение вашего кода упростит вам вопрос:

static List<FileContent> GetFileContents(List<string> paths)
{
    var resultList = new List<FileContent>();

    foreach (var path in paths)
    {
          if (CanReadFile(path){
                resultList.Add(new FileContent(path, buffer));
          }
    return resultList;
}

static bool CanReadFile(string Path){
     try{
         using (FileStream stream = File.Open(path, FileMode.Open))
            using (BinaryReader reader = new BinaryReader(stream))
            {
                int fileLength = (int)stream.Length;
                byte[] buffer = new byte[fileLength];
                reader.Read(buffer, 0, fileLength);
            }
     }catch(Exception){ //I do not care what when wrong, error when reading from file
         return false;
     }
     return true;
}

Таким образом CanReadFile скрывает реализацию для вашей проверки. Единственное, о чем вы должны подумать, - это правильный метод CanReadFile, или если ему нужна обработка ошибок.

Ответ 6

Это повторяет сказанное, но, надеюсь, поможет вам лучше понять.

У вас есть логическая ошибка в "пропустить любой файл, содержимое которого не может быть прочитано по какой-либо причине".

Если эта причина является ошибкой в ​​коде, вы не хотите пропустить ее.
Вы просто хотите пропустить файлы с ошибками, связанными с файлами.
Что делать, если ctor в FileContent выдавал ошибку?

И исключения дороги.
Я бы проверил для FileExists (и все еще поймал исключения)
И я согласен с исключениями, перечисленными Джо Приходите на MSDN имеет ясные примеры того, как поймать различные исключения

Ответ 7

По моему мнению, разделите исключения на три типа. Во-первых, вы ожидаете исключения и знаете, как восстановить. Во-вторых, исключения, которые, как вы знаете, можно избежать во время выполнения. В-третьих, это те, которые вы не ожидаете встречаться во время выполнения, но не можете избежать или не можете реально справиться.

Обрабатывать первый тип, это класс исключений, которые действительны для вашего конкретного уровня абстракции и которые представляют собой действительные бизнес-кейсы для восстановления на этом уровне (в вашем случае игнорирование.)

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

Как говорили другие, вы можете обрабатывать множество исключений, добавляя больше блоков catch в существующий блок try, они оценивают в том порядке, в котором они появляются, поэтому, если вам приходится обрабатывать исключения, которые вытекают из других исключений, которые вы также обрабатываете, сначала используйте более конкретный.