Использование обработки исключений по сравнению с NSError в приложениях Cocoa

Всем привет. Я читал предложения Apple о том, когда/где/как использовать NSError по сравнению с @try/@catch/@finally. По сути, у меня сложилось впечатление, что Apple считает, что лучше избегать использования языковых конструкций обработки исключений, кроме как в качестве механизма для остановки выполнения программы в неожиданных ситуациях с ошибками (может быть, кто-то может привести пример такой ситуации?)

Я пришел из Java, где исключения — это путь, когда нужно обрабатывать ошибки. По общему признанию, я все еще нахожусь в пространстве мыслей Java, но я постепенно осваиваю все, что может предложить NSError.

Одна вещь, на которой я зациклился, - это задача очистки памяти при возникновении ошибки. Во многих ситуациях (например, при использовании библиотек C, C++, CoreFoundation и т. д.) вам необходимо выполнить много операций по очистке памяти, прежде чем функция выйдет из строя из-за ошибки.

Вот пример, который я придумал, который точно отражает ситуации, с которыми я сталкивался. Используя некоторые воображаемые структуры данных, функция открывает дескриптор файла и создает объект MyFileRefInfo, который содержит информацию о том, что делать с файлом. Кое-что делается с файлом до закрытия дескриптора файла и освобождения памяти для структуры. Используя предложения Apple, у меня есть этот метод:

- (BOOL)doSomeThingsWithFile:(NSURL *)filePath error:(NSError **)error
{
  MyFileReference inFile; // Lets say this is a CF struct that opens a file reference
  MyFileRefInfo *fileInfo = new MyFileRefInfo(...some init parameters...);

  OSStatus err = OpenFileReference((CFURLRef)filePath ,&inFile);

  if(err != NoErr)
  {
    *error = [NSError errorWithDomain:@"myDomain" code:99 userInfo:nil];
    delete fileInfo;
    return NO;
  }

  err = DoSomeStuffWithTheFileAndInfo(inFile,fileInfo);

  if(err != NoErr)
  {
    *error = [NSError errorWithDomain:@"myDomain" code:100 userInfo:nil];
    CloseFileHandle(inFile); // if we don't do this bad things happen
    delete fileInfo;
    return NO;
  }      

  err = DoSomeOtherStuffWithTheFile(inFile,fileInfo);

  if(err != NoErr)
  {
    *error = [NSError errorWithDomain:@"myDomain" code:101 userInfo:nil];
    CloseFileHandle(inFile); // if we don't do this bad things happen
    delete fileInfo;
    return NO;
  }      

  CloseFileHandle(inFile);
  delete fileInfo;
  return YES;

}

Теперь... моя логика Java подсказывает мне, что было бы лучше настроить это как структуру try/catch/finally и поместить все вызовы для закрытия дескриптора файла и освобождения памяти в блок finally.

Вот так..

    ...

    @try
    {
      OSStatus err = OpenFileReference((CFURLRef)filePath ,&inFile);
      if(err != NoErr)
      {
        ... throw some exception complete with error code and description ...
      }

      err = DoSomeStuffWithTheFileAndInfo(inFile,fileInfo);

      if(err != NoErr)
      {
         ... throw some exception ...
      }

      ... etc ...        
}
@catch(MyException *ex)
{
        *error = [NSError errorWithDomain:@"myDomain" code:[ex errorCode] userInfo:nil];
        return NO;
}
@finally
{
        CloseFileHandle(inFile); // if we don't do this bad things happen
        delete fileInfo;
}
return YES;

Я схожу с ума, думая, что это гораздо более элегантное решение с менее избыточным кодом? Я что-то пропустил?


person Community    schedule 05.01.2010    source источник
comment
Пример может быть немного упрощен, но не забудьте проверить, что ошибка не равна NULL, прежде чем присваивать ей значение, так как соглашение состоит в том, чтобы отправлять NULL вместо переменной ошибки, когда вы не заботитесь об ошибках. Если вы разыменуете NULL в этом случае, вы вызовете ошибку шины (это не похоже на обмен сообщениями с нулевым объектом).   -  person Jason Coco    schedule 06.01.2010


Ответы (4)


Ответ Даниэля правильный, но этот вопрос заслуживает более резкого ответа.

Генерировать исключение только при обнаружении неисправимой ошибки.

Используйте NSError при сообщении об ошибках, которые могут быть устранены.

Любое исключение, выбрасываемое через фрейм в платформах Apple, может привести к неопределенному поведению.

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

person bbum    schedule 05.01.2010
comment
Я рад, что вы поместили это в ответ, если бы я мог +2 :) - person Jason Coco; 06.01.2010

По сути, у меня сложилось впечатление, что Apple считает, что лучше избегать использования языковых конструкций обработки исключений, кроме как в качестве механизма для остановки выполнения программы в неожиданных ситуациях с ошибками (может быть, кто-то может привести пример такой ситуации?)

Это не совсем мое впечатление. Я думал, что Apple предлагает использовать исключения для действительно исключительных условий и NSError для ожидаемых сбоев. Поскольку вы пришли с Java, я думаю, что NSError -> java.lang.Exception и исключения Obj-C -> java.lang.RuntimeException. Используйте исключение Obj-C, когда программист сделал что-то не так (например, неправильно использовал API), и используйте NSError, когда произошел ожидаемый сбой (например, удаленный сервер не может быть найден).

Конечно, это только моя интерпретация позиции Apple. А я наоборот люблю исключения!

person Daniel Yankowsky    schedule 05.01.2010
comment
Вы правильно уловили позицию Apple. Ошибки — это предсказуемые проблемы, которые вы, вероятно, захотите представить пользователю. Исключения должны либо никогда не происходить, либо всегда перехватываться. Cocoa разработан с учетом этого, о чем свидетельствует новая система представления ошибок AppKit. developer.apple.com/mac/library/ документация/Cocoa/Conceptual/ разработчик. apple.com/mac/library/documentation/Cocoa/Conceptual/ - person Peter Hosey; 05.01.2010
comment
Ладно, думаю, я понял. Похоже, что Apple предлагает нам два способа сообщения об ошибках вверх по стеку вызовов функций, и из этих двух способов NSError лучше поддерживается Cocoa. Я бы хотел использовать метод, который лучше поддерживается ... но функции потока управления исключениями, похоже, делают код менее избыточным. Есть ли шанс, что в Obj-C/Cocoa есть что-то, что решает эту проблему? - person ; 05.01.2010
comment
JC Reus: Нет, вы ошибаетесь. Поддерживаются как ошибки, так и исключения, но они предназначены для разных целей. - person Peter Hosey; 07.01.2010
comment
JC Reus: Я нашел эту часть вашего предложения проясняющей: «…два способа сообщить об ошибках…» Нет. В Cocoa ошибки и исключения — это разные вещи. Cocoa предоставляет способ представления ошибок пользователю, потому что это то, для чего они предназначены: вещи, которые пользователь должен знать и, возможно, должен обрабатывать (хотя вы можете обрабатывать ошибки самостоятельно, если можете). Он не предоставляет такого механизма для исключений, поскольку обработка исключений является исключительно вашей работой. Они не соревнуются друг с другом за выполнение одной и той же задачи; они для совершенно разных задач. - person Peter Hosey; 07.01.2010

Исключения в Objective-C исторически были «тяжелыми», с затратами производительности на вход в блок try, затратами на выбрасывание, затратами на использование finally и т. д. В результате разработчики Cocoa обычно избегали исключений за пределами «о нет, небо падает» — если файл отсутствует, используйте NSError, но если нет файловой системы и отрицательное количество свободной памяти, это исключение.

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

person Skirwan    schedule 05.01.2010
comment
Тот факт, что механизм исключений в Obj-C для 64-битной среды выполнения не требует больших затрат, не означает, что его безопасно использовать для Cocoa. Если вы пишете свои собственные приложения и свои собственные фреймворки, это не проблема, но AppKit, UIKit и Foundation НЕ защищены от исключений, что означает, что вы не можете создавать исключения для них и безопасно перехватывать их позже, как в Java. - person Jason Coco; 06.01.2010
comment
Есть абсолютно все причины не использовать исключения. А именно, что системные фреймворки не защищены от исключений. Таким образом, даже если вы выполняете все свои броски и перехваты исключений изолированно от системных фреймворков, это будет излишне отличаться, и вам придется заплатить ад, если вы захотите позже провести рефакторинг, чтобы где-то использовать системный API. - person bbum; 06.01.2010
comment
Можете ли вы привести пример того, как вам придется адски платить? Если вы не находитесь в ситуации, когда вы пытаетесь генерировать исключения через системный API, я не вижу, как использование исключений в вашем собственном коде может иметь какие-либо негативные взаимодействия. - person Skirwan; 06.01.2010
comment
Тривиально легко запустить исключение через системный API. Например, если вы создаете исключение из обратного вызова делегата. - person Chris Hanson; 07.01.2010
comment
Как правило, когда я хочу создать исключение, это происходит в том случае, когда я также планировал его поймать. Разве это не уменьшит вероятность того, что он будет запущен через системный API? Мне кажется, что вопрос связан с удобством: один блок catch с исключениями может быть гораздо менее утомительным для кодирования, чтения, рефакторинга и обслуживания, чем десятки условных и избыточных действий. Не для этого ли были придуманы исключения? - person Hari Honor; 02.02.2012
comment
И вся цель исключения состоит в том, чтобы создать второй, исключительный путь кода, который перескакивает через любые кадры, существующие между throw и catch. Таким образом, хотя код может быть удобочитаемым, фактическое поведение чрезвычайно трудно правильно реализовать в модульной системе с большим количеством повторного использования сложного кода. Таким образом, было принято решение не использовать исключения для исправимых ошибок в Cocoa/iOS. И, таким образом, фреймворки его не поддерживают. Это означает, что вы не должны этого делать, потому что это просто усложнит поддержку вашего кода. - person bbum; 20.09.2012

Согласно More iPhone 3 Development Дэйва Марка и Джеффа ЛеМарша, исключения в используются только в действительно исключительных ситуациях и обычно указывают на проблему в вашем коде. Никогда не следует использовать исключения для сообщения о заурядных ошибках. состояние ошибки. Исключения в Objective-C используются гораздо реже, чем во многих других языках, таких как Java и C++.

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

Вот пример, где вы могли бы использовать исключение:

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

NSException *ex = [NSException exceptionWithName:@"Abstract Method Not Overridden" reason:NSLocalizedString(@"You MUST override the save method", @"You MUST override the save method") userInfo:nil];
[ex raise];

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

person Rose Perrone    schedule 03.07.2010
comment
Если бы это был единственный случай использования исключения, то какой смысл было бы вообще иметь исключения? Разве вы не могли бы просто напечатать ошибку и выйти (), как в старые дни до того, как существовали исключения? - person Hari Honor; 02.02.2012
comment
Спасибо, что задали этот вопрос. Преимущества использования исключений описаны здесь: разработчик .apple.com/library/mac/#documentation/Cocoa/Conceptual/. Вы можете использовать NSExceptions для вывода описательного журнала и трассировки стека на консоль при возникновении такой ошибки (непроверенное NSException) и обрабатывать ошибку таким образом, чтобы предотвратить результирующее внезапное завершение. - person Rose Perrone; 04.02.2012
comment
да. Но вы, как правило, не хотели бы обрабатывать ошибку программирования таким образом, чтобы предотвратить... внезапное завершение. - person Hari Honor; 04.02.2012
comment
@HariKaramSingh Вы все еще можете прекратить, но не «внезапно». Представление приятного диалогового окна с сообщением об ошибке и т. Д. Xcode (само приложение) даже доходит до того, что дает возможность игнорировать исключение и продолжать выполнение, что я всегда использую, и был бы гораздо более раздражен, если бы Xcode каждый раз давал сбой. - person Andrey Tarantsov; 11.07.2013