Организуйте свои ошибки | Кодементор
23 апр 2017
(эта статья изначально была написана для моего блога vojtastavik.com)
Есть два типа программистов: те, кто правильно обрабатывает ошибки, и те, с которыми вы не хотите работать.
Это происходит снова и снова. Что-то перестало работать, и мне нужно это исправить. После десятков минут отладки и более глубокого погружения в кодовую базу я наконец нашел его. Кто-то (включая меня несколько лет назад) проигнорировал ошибку и оставил ее необработанной.
Причина, по которой эта ситуация так раздражает, проста. Информация об ошибке, обычно включая причину возникновения ошибки, была тут же! Кто-то просто решил не обращать на это внимания.
Правильная обработка ошибок: старые (Objective-C) времена
API-интерфейсы Objective-C имеют довольно неясный способ, как сигнализировать об ошибках. Брать NSData
и это writeToFile
функция в качестве примера:
- (BOOL)writeToFile:(NSString *)path options:(NSDataWritingOptions)writeOptionsMask error:(NSError **)errorPtr;
Функция возвращает BOOL
значение, указывающее, была ли операция успешной. Когда значение NO
вы можете найти дополнительную информацию об ошибке внутри объекта, на который ссылается errorPtr
. Заметь erorrPtr
это указатель на указатель на NSError
объект.
NSData* data = [NSData new]; // some data
NSString* path = @"path"; // some path // ⚠️ DO NOT DO THIS ⚠️ 👎
// This code completely ignores the error and it will, sooner or later,
// bite you back (or ever worse: bite your colleagues).
[data writeToFile:path options:0 error:nil]; // YOU SHOULDN'T EVEN DO THIS 👎
// This is definitely better than the previous. However, you need
// to check the returning value, not just the error pointer.
// Some APIs could return non-nil error even for non-error states.
NSError* error1;
[data writeToFile:path options:0 error:&error1];
if (error1) { // Do something with the error. If nothing, at least log it // to the console.
} // THE PROPER WAY 👍
NSError* error2;
if ([data writeToFile:path options:0 error:&error2]) { // Happy path
} else { // Here we're sure something went wrong if (error2) { // We even have more info about it }
}
Правильная обработка ошибок: Swift
Несмотря на то, что в Swift 2 обработка ошибок стала проще и понятнее, все еще есть много способов сделать это неправильно.
let data = Data() // Some data
let url = URL(string: "url")! // Some URL // ⚠️ DO NOT DO THIS ⚠️ 👎
// Using try! will crash your app
// when an error occurs. Like implicitly
// unwrapped optionals - use try!
// only if you really know what you
// are doing (i.e. in your tests).
try! data.write(to: url) // ⚠️ DO NOT DO THIS ⚠️ 👎
// This particular usage of try?
// is also not good. When the write
// function fails, the error is
// silently consumed and no one
// will ever know about it.
try? data.write(to: url) // YOU CAN DO THIS 👍
// If the return value of the throwing
// function is what matters, it might be
// a good idea to use try?. You won't
// have any further information about
// the error, though.
guard let data = try? Data(contentsOf: url) else { // Something went wrong, but we don't why return
}
// Happy path: Everything is OK and we can use data
print(data.description) // BASIC ERROR HANDLING 👍
do { try data.write(to: url)
} catch { // You can access variable 'error' of // the type 'Error' within this scope.
}
Пользовательские типы ошибок в Swift
Здесь начинается самое интересное. Вы можете определить свои собственные типы ошибок и сделать так, чтобы ваши функции их выдавали:
enum Error: Swift.Error { case network(NetworkError) enum NetworkError { case notReachable case unknown } case device(DeviceError) enum DeviceError { case notEnoughSpace case unsupportedSystemVersion }
}
Затем вы можете обрабатывать определенные ошибки иначе, чем другие:
// COMPLETE ERROR HANDLING 👍
// (WITH CUSTOM ERRORS) do { try doSomething() } catch let Error.network(error) { // The returned error is a network error. print(type(of: error)) // prints `Error.NetworkError` // ... do something with the error (log it, etc.) } catch let Error.device(error) { // The returned error is a device error. print(type(of: error)) // prints `Error.DeviceError` // ... do something different with the error (show alert etc.) } catch { // Some other error without specific handling.
}