Обработка ошибок/исключений в методе, который возвращает bool
В моей пользовательской структуре у меня есть метод, подобный показанному ниже, который извлекает значение из словаря и преобразует его в BOOL и возвращает логическое значение.
- (BOOL)getBoolValueForKey:(NSString *)key;
Что делать, если вызывающий объект этого метода передает ключ, который не существует. Должен ли я бросать пользовательский ключ NSException, который не существует (но исключение бросания не рекомендуется в объективе c) или добавить параметр NSError к этому методу, как показано ниже?
- (BOOL)getBoolValueForKey:(NSString *)key error:(NSError **)error;
Если я использую NSError, мне придется возвращать "НЕТ", что будет вводить в заблуждение, поскольку "НЕТ" может быть допустимым значением любого действительного ключа.
Ответы
Ответ 1
API для этого давно установлен NSUserDefaults
и должен стать отправной точкой для разработки вашего API:
- (BOOL)boolForKey:(NSString *)defaultName;
Если логическое значение связано с именем по умолчанию в значениях по умолчанию пользователя, это значение возвращается. В противном случае возвращается NO.
Вам следует избегать создания другого API для извлечения bools из хранилища ключей, если у вас нет веской причины. В большинстве интерфейсов ObjC выбор не-exixtant-ключа возвращает nil
и nil
интерпретируется как NO
в булевом контексте.
Традиционно, если вы хотите различать NO
и nil
, затем вызовите objectForKey
для извлечения NSNumber
и проверьте nil
. Опять же, это поведение для многих хранилищ ключей Cocoa и не должно быть легко изменено.
Однако возможно, что существует серьезная причина для нарушения этого ожидаемого шаблона (в этом случае вы должны обязательно внимательно его отметить в документах, потому что это удивительно). В этом случае существует несколько хорошо установленных шаблонов.
Во-первых, вы можете считать, что неизвестный ключ является ошибкой программирования, и вы должны выбросить исключение с ожиданием, что программа скоро выйдет из строя из-за этого. Очень необычно (и неожиданно) создавать новые виды исключений для этого. Вы должны поднять NSInvalidArgumentException
, который существует именно для этой проблемы.
Во-вторых, вы можете различать nil
и NO
, используя метод get
. Ваш метод начинается с get
, но это не должно. get
означает "возвращает по ссылке" в Cocoa, и вы можете использовать его таким образом. Что-то вроде этого:
- (BOOL)getBool:(BOOL *)value forKey:(NSString *)key {
id result = self.values[key];
if (result) {
if (value) {
// NOTE: This throws an exception if result exists, but does not respond to
// boolValue. That intentional, but you could also check for that and return
// NO in that case instead.
*value = [result boolValue];
}
return YES;
}
return NO;
}
Это принимает указатель на bool и заполняет его, если это значение доступно, и возвращает YES
. Если значение недоступно, оно возвращает NO
.
Нет причин привлекать NSError
. Это добавляет сложности, не придавая при этом никакой ценности. Даже если вы рассматриваете Swift-мост, я бы не использовал NSError
здесь, чтобы получить throws
. Вместо этого вы должны написать простую Swift-оболочку вокруг этого метода, которая возвращает Bool?
. Это гораздо более мощный подход и проще использовать на стороне Swift.
Ответ 2
Если вы хотите передать передачу несуществующего ключа в качестве ошибки программиста, то есть чего-то, что на самом деле никогда не должно происходить во время выполнения, потому что, например, что-то вверху должно было позаботиться об этой возможности, тогда ошибка утверждения или NSException - это способ сделать это. Процитировать документацию Apple из Руководство по программированию исключений:
Вы должны зарезервировать использование исключений для программирования или непредвиденных ошибок времени выполнения, таких как доступ к коллекции вне пределов, попытки мутации неизменяемых объектов, отправка недопустимого сообщения и потеря соединения с оконным сервером. Обычно вы заботитесь об этих типах ошибок с исключениями, когда приложение создается, а не во время выполнения.
Если вы хотите сообщить ошибку времени выполнения, из которой программа может восстановить/продолжить выполнение, добавление указателя ошибки - это способ сделать это.
В принципе, использовать BOOL
в качестве возвращаемого типа хорошо, даже если есть некритический случай ошибки. Есть, однако, угловые случаи с этим, если вы собираетесь взаимодействовать с этим кодом из Swift:
- Если вы получаете доступ к этому API через Swift,
NO
всегда подразумевает, что возникает ошибка, даже если в реализации вашего метода Objective-C вы не заполнили указатель ошибки, то есть вам понадобится do/catch и обрабатывать специфически ошибку nil.
- Противоположная действительность также действительна, т.е. можно исключить ошибку в случае успеха (например, NSXMLDocument делает это для передачи некритических ошибок проверки). Насколько я знаю, нет способа передать эту некритическую информацию об ошибках в Swift.
Если вы намереваетесь использовать этот API из Swift, я бы, возможно, поставил BOOL в NULL-номер NSNumber (в этом случае ошибка будет равна нулю, и успешное событие NO будет NSNumber с NO, завернутым в него).
Следует отметить, что для конкретного случая потенциально неудачного сеттера существуют сильные соглашения, которые вы должны следовать, как указано в одном из других ответов.
Ответ 3
Вы определяете основную слабость подхода к обработке ошибок Apples.
Мы имеем дело с этими ситуациями, гарантируя, что NSError
nil
в случаях успеха, поэтому вы действительно проверяете ошибку:
if (error) {
// ... problem
// handle error and/ or return
}
Так как это противоречит дескриптору ошибки Apples, где Error
никогда не гарантируется nil
, но гарантированно не будет nil
в случаях сбоев, затронутые методы должны быть хорошо документированы для клиентов, которые знают об этом особое поведение.
Это нехорошее решение, но лучшее, что я знаю.
(Это одна из неприятных вещей, с которыми нам не приходится иметь дело быстрее)
Ответ 4
Если вы хотите все эти
- Различать случаи отказа и успеха
- Работа с значением bool только в случае успеха
- В случае сбоя вызывающий абонент ошибочно не считает, что возвращаемое значение является значением ключа
Я предлагаю сделать реализацию на основе блоков. У вас будет successBlock
и errorBlock
, чтобы четко разделить.
Caller вызовет метод, подобный этому
[self getBoolValueForKey:@"key" withSuccessBlock:^(BOOL value) {
[self workWithKeyValue:value];
} andFailureBlock:^(NSError *error) {
NSLog(@"error: %@", error.localizedFailureReason);
}];
и реализация:
- (void)getBoolValueForKey:(NSString *)key withSuccessBlock:(void (^)(BOOL value))success andFailureBlock:(void (^)(NSError *error))failure {
BOOL errorOccurred = ...
if (errorOccurred) {
// userInfo will change
// if there are multiple failure conditions to distinguish between
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: NSLocalizedString(@"Operation was unsuccessful.", nil),
NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"The operation timed out.", nil),
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Have you tried turning it off and on again?", nil)
};
NSError *error = [NSError errorWithDomain:@"domain" code:999 userInfo:userInfo];
failure(error);
return;
}
BOOL boolValue = ...
success(boolValue);
}
Ответ 5
Мы используем этот
- (id) safeObjectForKey:(NSString*)key {
id retVal = nil;
if ([self objectForKey:key] != nil) {
retVal = [self objectForKey:key];
} else {
ALog(@"*** Missing key exception prevented by safeObjectForKey");
}
return retVal;
}
Заголовочный файл NSDictionary + OurExtensions.h
#import <Foundation/Foundation.h>
@interface NSDictionary (OurExtensions)
- (id) safeObjectForKey:(NSString*)key;
@end
Ответ 6
В этом случае я бы предпочел вернуть NSInteger с возвратом 0
, 1
и NSNotFound
, если вызывающий абонент передает ключ, который не существует.
Из характера этого метода для обработки NSNorFound
должно быть принято решение вызывающего абонента. Как я вижу, возвращающая ошибка не очень радует пользователя от имени метода.