Обработка ошибок/исключений в методе, который возвращает 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 должно быть принято решение вызывающего абонента. Как я вижу, возвращающая ошибка не очень радует пользователя от имени метода.