Dart explicitly makes a distinction between Error, that signals a problem in your code's logic and should never happen and should never be caught and Exceptions that signal a problem based on run-time data.
I really like this distinction but I wonder when should I then use assert() functions?
Asserts are ways to perform code useful in development only, without hindering the performances of release mode – usually to prevent bad states caused by a missing feature in the type system.
For example, only asserts can be used to do defensive programming and offer a const constructor.
We can do:
class Foo {
const Foo(): assert(false);
}
but can't do:
class Foo {
const Foo() { throw 42; }
}
Similarly, some sanity checks are relatively expensive.
In the context of Flutter, for example, you may want to traverse the widget tree to check something on the ancestors of a widget. But that's costly, for something only useful to a developer.
Doing that check inside an assert allows both performance in release, and utility in development.
assert(someVeryExpensiveCheck());
Background
In Dart an Exception is for an expected bad state that may happen at runtime. Because these exceptions are expected, you should catch them and handle them appropriately.
An Error, on the other hand, is for developers who are using your code. You throw an Error to let them know that they are using your code wrong. As the developer using an API, you shouldn't catch errors. You should let them crash your app. Let the crash be a message to you that you need to go find out what you're doing wrong.
An assert is similar to an Error in that it is for reporting bad states that should never happen. The difference is that asserts are only checked in debug mode. They are completely ignored in production mode.
Read more on the difference between Exception and Error here.
Next, here are a few examples to see how each is used in the Flutter source code.
Example of throwing an Exception
This comes from platform_channel.dart in the Flutter repo:
#optionalTypeArgs
Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
assert(method != null);
final ByteData? result = await binaryMessenger.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null) {
if (missingOk) {
return null;
}
throw MissingPluginException('No implementation found for method $method on channel $name');
}
return codec.decodeEnvelope(result) as T;
}
The MissingPluginException here is a planned bad state that might occur. If it happens, users of the platform channel API need to be ready to handle that.
Example of throwing an Error
This comes from artifacts.dart in the flutter_tools repo.
TargetPlatform _currentHostPlatform(Platform platform) {
if (platform.isMacOS) {
return TargetPlatform.darwin_x64;
}
if (platform.isLinux) {
return TargetPlatform.linux_x64;
}
if (platform.isWindows) {
return TargetPlatform.windows_x64;
}
throw UnimplementedError('Host OS not supported.');
}
First every possibility is exhausted and then the error is thrown. This should be theoretically impossible. But if it is thrown, then it is either a sign to the API user that you're using it wrong, or a sign to the API maintainer that they need to handle another case.
Example of using asserts
This comes from overlay.dart in the Flutter repo:
OverlayEntry({
required this.builder,
bool opaque = false,
bool maintainState = false,
}) : assert(builder != null),
assert(opaque != null),
assert(maintainState != null),
_opaque = opaque,
_maintainState = maintainState;
The pattern in the Flutter source code is to use asserts liberally in the initializer list in constructors. They are far more common than Errors.
Summary
As I read the Flutter source code, use asserts as preliminary checks in the constructor initializer list and throw errors as a last resort check in the body of methods. Of course this isn't a hard and fast rule as far as I can see, but it seems to fit the pattern I've seen so far.
As asserts are ignored in production mode, you should use them as way to do initial tests to your code logic in debug mode:
In production code, assertions are ignored, and the arguments to assert aren’t evaluated.
When exactly do assertions work? That depends on the tools and framework you’re using:
Flutter enables assertions in debug mode.
Development-only tools such as dartdevc typically enable assertions by default.
Some tools, such as dart and dart2js, support assertions through a command-line flag: --enable-asserts.
In production code, assertions are ignored, and the arguments to assert aren’t evaluated.
Refer:https://dart.dev/guides/language/language-tour#assert
Related
I am trying to create a repository for use cases of FirebaseAuth and can see warnings!
Should I ignore the warning, as it not impacting the app?
OR should I handle it now, and How?
Dart has analyzed your program and found out that you have not handled all cases how the method can exit. This is a potentially a problem since your method signature specifies that you method are returning a Future<User> object.
In your case you have not handled the case where an exception are throw from the createUserWithEmailAndPassword method. If an exception are catch, you ends up with a execution path without any return statement.
In this case, Dart will at runtime just return null so it is not an error. But it is a potential sign of a code error since it it not unlikely that you have forgotten the handling of the exception by not have any return statement.
If it is your intention to just return null in case of an exception, you should therefore have insert return null; in both of your catch blocks (or just at the bottom of your method outside the catch-blocks).
Alternative, you can rethrow the exception if you just want to add some logging but still let the caller to also handle the exception. You can read more about this in the language tour: https://dart.dev/guides/language/language-tour#catch
In either cases, you should describe the behavior in the documentation of the method.
I am getting an "Expensive Method Invocation" AND a "Null comparison expensive" warnings and I would like to know how to fix these.
void Update()
{
CheckCollision();
}
and in CheckCollison where the errors are happening, shown in comments below,.
void CheckCollision()
{
var topHit = Physics2D.OverlapCircle(topCollision.position, 0.2f, playerLayer);
if (topHit != null) // ***<<<< NULL COMPARISON EXPENIVE!***
{
if (topHit.gameObject.CompareTag("Player"))
{
if (!stunned)
{
var rigidBodyTopHit = topHit.gameObject
.GetComponent<Rigidbody2D>(); // ***<<<< EXPENSIVE METHOD INVOCATION!***
rigidBodyTopHit.velocity = new Vector2(rigidBodyTopHit.velocity.x, 7f);
canMove = false;
_myBody.velocity = new Vector2(0,0);
_animator.Play("SnailStunned");
stunned = true;
}
}
}
if (!Physics2D.Raycast(downCollision.position, Vector2.down, 0.1f))
{
ChangeDirection();
}
}
These are not warnings, but informational highlights. They are intended to inform you that you are doing something that is known to be expensive, inside a performance sensitive context (the Update method) and you can re-evaluate your approach if necessary.
The "if necessary" bit is important - only you can decide if the performance characteristics of this Update method are appropriate for your game/application. If this happens in a single game object in an options screen that's rarely used, then it's most likely not a problem. But if it's in an object that is instantiated many times and used in core gameplay, maybe these expensive methods will add up and reduce performance (or affect battery life).
Generally speaking, it's probably going to be very hard (and unnecessary) to write code that does not have any of these informational highlights, but if you find that you have a lot of code that has a lot of highlights, then it's a good indication that you should profile and see if you're happy with the results.
You can find out more about these highlights by using Alt+Enter, expanding the inspection options item and selecting the "Why is Rider/ReSharper suggesting this?" item. This will open a web page with more details about that particular highlight.
This page also has an overview and more details, and the inspections can also be disabled in the Preferences | Editor | Inspection Settings | Inspection Severity | C# settings page (under Unity | Performance indicators).
Looking at the items marked in the code sample, the equality comparison is expensive because it also checks to see if the underlying engine object has been destroyed, which means a transition into native code. If you know that the object hasn't been destroyed (and seeing that it's returned from a Unity API, I think that's a safe bet), then you can replace it with:
if (!Object.ReferenceEquals(topHit, null)) {
// …
}
But remember that this is a micro-optimisation that is potentially unnecessary, in your use case.
(Calling the implicit bool operator with if (!topHit) is exactly the same as if (topHit != null), both operators call the private Object.CompareBaseObjects method)
The second item is a call to GetComponent, which is known to be expensive, but is necessary in this context. That's ok - this is just informational, not telling you that you've done something wrong.
I am following MyFancordionRunner example from Fancordion v1.0.4 official documentation to test a BedSheet application, but the suiteSetup method (see below) is not being called and the server remains null, causing the fixture tests to fail with a NullPointerException.
override Void suiteSetup() {
super.suiteSetup
server = BedServer(AppModule#.pod).addModule(WebTestModule#).startup
}
Looking at FancordionRunner source code, the runFixture(Obj fixtureInstance) method should be invoking suiteSetup() the first time a Fixture is run as per this piece of code...
FixtureResult runFixture(Obj fixtureInstance) {
...
locals := Locals.instance
firstFixture := (locals.originalRunner == null)
if (firstFixture) {
locals.originalRunner = this
suiteSetup()
...
}
But for some reason in my case the condition (locals.originalRunner == null) must be returning false, causing the suiteSetup() invocation to be skipped. It seems that this piece of code uses Fantom Actors which I'm not familiar with.
I am manually invoking the suiteSetup within MyFancordionRunner like this:
override Void fixtureSetup(Obj fixtureInstance) {
if (server == null) suiteSetup
...
This workaround solves the NullPointerException issue and allows the fixtures to run successfully but I don't know if this workaround is defeating the purpose of the Actor logic, which I presume is meant to invoke suiteSetup only once.
Can anyone explain what could be going on here that is preventing the suiteSetup method from being called within runFixture(...), please?
I don't know what's going on here without seeing a lot more code.
The only part of Actor being used is Actor.locals() which is really just a pot to hold thread local variables - as it is assumed that all tests are run in the same thread.
As you shown, the logic in runFixture() is pretty simple, are you sure it is being called?
I would like to throw an exception from some "deep" function, so it bubbles up to another function, where I want to catch it.
f1 calls f2 calls f3 calls ... fN which may throw an error
I would like to catch the error from f1.
I've read that in Swift I have to declare all methods with throws, and also call them using try.
But that's quite annoying:
enum MyErrorType : ErrorType {
case SomeError
}
func f1() {
do {
try f2()
} catch {
print("recovered")
}
}
func f2() throws {
try f3()
}
func f3() throws {
try f4()
}
...
func fN() throws {
if (someCondition) {
throw MyErrorType.SomeError
}
}
Isn't there a similar concept to the RuntimeException in Java, where throws doesn't leak all the way up the call chain?
Yes, it is possible!
Use: fatalError("your message here") to throw runtime exception
To elaborate on Максим Мартынов's answer, Swift has 3 ways to do throw undeclared, uncatchable errors (but other approaches are possible if you want to venture outside Swift's standard library). These are based on the 3 levels of optimization:
-Onone: No optimization; debug build
-O: Normal optimization; release build
-O SWIFT_DISABLE_SAFETY_CHECKS: Unchecked optimization; extremely optimized build
1. assertionFailure(_:)
Write this line when you're doing debugging tests and hit a line you don't think should ever be hit. These are removed in non-debug builds, so you must assume they will never be hit in the production app.
This has a sister function called assert(_:_:), which lets you assert at runtime whether a condition is true. assertionFailure(_:) is what you write when you know the situation is always bad, but don't think that'll harm the production code very much.
Usage:
if color.red > 0 {
assertionFailure("The UI should have guaranteed the red level stays at 0")
color = NSColor(red: 0, green: color.green, blue: color.blue)
}
2. preconditionFailure(_:)
Write this line when you're sure some condition you've described (in documentation, etc.) was not met. This works like assertionFailure(_:), but in release builds as well as debug ones.
Like assertionFailure(_:), this one's got a sister function called precondition(_:_:), which lets you decide at runtime whether a precondition was met. preconditionFailure(_:) is essentially that, but assuming the precondition is never met once the program gets to that line.
Usage:
guard index >= 0 else {
preconditionFailure("You passed a negative number as an array index")
return nil
}
Note that, in extremely optimized builds, it is not defined what happens if this line is hit! So if you don't want your app to wig out if it might ever hit this, then make sure the error state is handleable.
3. fatalError(_:)
Used as a last resort. When every other attempt to save the day has failed, here is your nuke. After printing the message you pass to it (along with the file and line number), the program stops dead in its tracks.
Once the program gets to this line, this line always runs, and the program never continues. This is true even in extremely optimized builds.
Usage:
#if arch(arm) || arch(arm64)
fatalError("This app cannot run on this processor")
#endif
Further reading: Swift Assertions by Andy Bargh
The error handling mechanism in Swift does not involve raising unchecked (runtime) exceptions. Instead, explicit error handling is required. Swift is certainly not the only recently designed language to go for this design – for instance Rust and Go also in their own ways also require explicitly describing the error paths in your code. In Objective-C the unchecked exception feature exists, but is largely used only for communicating programmer errors, with the notable exception of a few key Cocoa classes such as NSFileHandle which tends to catch people out.
Technically you do have the ability to raise Objective-C exceptions in Swift with the use of NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise() as is explained in this excellent answer to this question, arguably a duplicate of your question. You really shouldn't raise NSExceptions though (not least because you have no Objective-C exception catching language feature available to you in Swift).
Why did they go with this design? Apple's "Error Handling in Swift 2.0" document explains the rationale clearly. Quoting from there:
This approach […] is very similar to the error handling model manually
implemented in Objective-C with the NSError convention. Notably, the
approach preserves these advantages of this convention:
Whether a method produces an error (or not) is an explicit part of its API contract.
Methods default to not producing errors unless they are explicitly marked.
The control flow within a function is still mostly explicit: a maintainer can tell exactly which statements can produce an error, and
a simple inspection reveals how the function reacts to the error.
Throwing an error provides similar performance to allocating an error and returning it – it isn’t an expensive, table-based stack
unwinding process. Cocoa APIs using standard NSError patterns can be
imported into this world automatically. Other common patterns (e.g.
CFError, errno) can be added to the model in future versions of Swift.
[…]
As to basic syntax, we decided to stick with the familiar language of
exception handling. […] by and large, error propagation in this
proposal works like it does in exception handling, and people are
inevitably going to make the connection.
Isn't there a similar concept to the RuntimeException in Java, where throws doesn't leak all the way up the call chain?
Swift does indeed have error handling which doesn't propegate at compile time.
However, before I discuss those, I must say that the one you point out, where you use the language's do...catch, try, throw, and throws keywords/features to handle errors, is by far the safest and most preferred. This ensures that every single time an error might be thrown or caught, it's handled correctly. This completely eliminates surprise errors, making all code more safe and predictable. Because of that inherent compile- and run-time safety, you should use this wherever you can.
func loadPreferences() throws -> Data {
return try Data(contentsOf: preferencesResourceUrl, options: [.mappedIfSafe, .uncached])
}
func start() {
do {
self.preferences = try loadPreferences()
}
catch {
print("Failed to load preferences", error)
assertionFailure()
}
}
guard let fileSizeInBytes = try? FileManager.default.attributesOfItem(atPath: path)[.size] as? Int64 else {
assertionFailure("Couldn't get file size")
return false
}
Probably the easiest way to silence Swift's compiler is with try! - this will allow you to use native Swift errors, but also ignore them.
Here's what your example code would look like with that:
enum MyErrorType : ErrorType {
case SomeError
}
func f1() {
f2()
}
func f2() {
f3()
}
func f3() {
try! f4()
}
...
func fN() throws {
if (someCondition) {
throw MyErrorType.SomeError
}
}
Obviously, this has the problem of not allowing you to ever catch these, so if you want a silent error you can catch, read on.
There are also assertions, preconditions, and fatalErrors, which I described in detail in my answer from October of 2017. The compiler provides reasonable handling of these, such as ensuring that return statements and other control flow are placed and omitted when appropriate. Like try!, however, these cannot be caught.
exit is in this family if your goal is to stop the program immediately.
If you venture outside Swift into the wider Apple ecosystem (that is, if you are writing Swift on an Apple platform), you also see Objective-C's NSException. As you desire, this can be thrown by Swift without using any language features guarding against that. Make sure you document that! However, this cannot be caught by Swift alone! You can write a thin Objective-C wrapper that lets you interact with it in the Swift world.
func silentButDeadly() {
// ... some operations ...
guard !shouldThrow else {
NSException.raise(NSExceptionName("Deadly and silent", format: "Could not handle %#", arguments: withVaList([problematicValue], {$0}))
return
}
// ... some operations ...
}
func devilMayCare() {
// ... some operations ...
silentButDeadly()
// ... some operations ...
}
func moreCautious() {
do {
try ObjC.catchException {
devilMayCare()
}
}
catch {
print("An NSException was thrown:", error)
assertionFailure()
}
}
Of course, if you're writing Swift in a Unix environment, you still have access to the terrifying world of Unix interrupts. You can use Grand Central Dispatch to both throw and catch these. And, as you desire, there's no way for the compiler to guard against them being thrown.
import Dispatch // or Foundation
signal(SIGINT, SIG_IGN) // // Make sure the signal does not terminate the application.
let sigintSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: .main)
sigintSource.setEventHandler {
print("Got SIGINT")
// ...
exit(0)
}
sigintSource.resume()
exit is in this family if your goal is to trap it and read its code.
I have a question about error/exception handling on the iPhone.
I've read the documentation about Exception, and it seems exceptions can be used only for exceptional situations.
Does it mean you can't use them like in java ?
for example, I'm trying to write a use case controller for my application. I've got a few examples in Java from previous projects in that language, that use exceptions in case of errors.
the question put simply is: can I follow the example I've got in Java, and "translate" it in Objective-C (and use Objective-C exceptions) or is there a better way to do that ?
here is the code I would like to make objective-c friendly:
public void addPerformance(Perfomance perf) {
//do some preparation
...
//execute the usecase
executor(new AddPerformance(perf));
}
private void executor(Usecase usecase) {
try {
UnitOfWorkServices.INSTANCE.bizTransactionStart();
usecase.execute();
UnitOfWorkServices.INSTANCE.bizTransactionCommit();
} catch (RealException re) {
UnitOfWorkServices.INSTANCE.bizTransactionEscape();
throw re;
} catch (Exception e) {
UnitOfWorkServices.INSTANCE.bizTransactionEscape();
throw new FatalException(this.getClass().getName() + " / executor("
+ usecase.getClass().getSimpleName() + ")", e,
"APPXCP_006_UNEXPECTED_EXCEPTION",
"\n\t |*| : Unexpected exception translated into FatalException");
} finally {
UnitOfWorkServices.INSTANCE.bizTransactionEnd();
}
}
All the exceptions are meant to be caught by the UI to display an error message.
thanks for your help,
Michael
In general, yes, you can translate your try/catch logic into a comparable construct in Objective-C and get comparable results. Though do be careful with that throw re; line, as an uncaught exception on the iPhone will crash your app.
More to the point however, the standard pattern used by the iPhone SDK and other commonly used libraries is that instead of throwing exceptions, API methods that may fail for a variety of reasons return a boolean value indicating whether or not the operation was successful, and accept as a parameter a reference to an NSError pointer that is used in the event of an error to provide the caller with specific details about what went wrong. So in this case, your code might translate to something more like:
NSError* error = nil;
[[UnitOfWorkServices sharedInstance] bizTransactionStart];
bool success = [usecase execute:&error];
if (success) {
[[UnitOfWorkServices sharedInstance] bizTransactionCommit];
}
else if (error) {
int code = [error code];
[[UnitOfWorkServices sharedInstance] bizTransactionEscape];
if (code == MY_MINOR_ERROR_CODE) {
//do something
}
else if (code == MY_FATAL_ERROR_CODE) {
//do something else
}
else {
//handle unexpected error type(s)
}
}
else {
//operation failed with no specific error details
[[UnitOfWorkServices sharedInstance] bizTransactionEscape];
//handle generic error
}
[[UnitOfWorkServices sharedInstance] bizTransactionEnd];
You can use exceptions just as you would use exceptions in Java, but it is not the recomended way to do it. Translating Java to Objective-C that way works, and most people will understand your intentions. But the result will be just as bad as if you tried to translate English to German using a dictionary; the spelling is perfect, but the grammar and context is completely wrong.
Understanding the "Cocoa way" to handle errors and exceptions will help you create more robust iOS applications, and give you a better understanding of how to use the official and third party API:s.
I have written a longer blog post on the topic here: http://blog.jayway.com/2010/10/13/exceptions-and-errors-on-ios/
Lets recap the important parts:
Exceptions should only be used for programming errors, and fatal stuff that you can not recover from. For example out of bounds exception, if you called with the wrong argument the first time your app is in error, retrying 100 times will not fix your app, only keep failing. The exceptions are exceptions to normal workflow and there to help you fix all bugs before you ship the app to end users.
NSError instances is what you use for errors caused by the users, and stuff that your application can recover from. For example IO-errors, the user might want to re-connect to the network, try a new password, and try again. This is for errors that you can predict, and that the app or the user can do something about.