Hacker News new | past | comments | ask | show | jobs | submit login
Swift Asserts (mikeash.com)
73 points by ingve on Mar 4, 2016 | hide | past | web | favorite | 21 comments

I'm sooo happy to see prominent developers advocating for leaving sanity checks (via precondition) on in released builds! Love the article, but love that recommendation more.

The common wisdom of only having them be active in debug builds baffles me.

Sometimes the argument is from performance. I can buy that.

But the most frequent argument I see is basically, "maybe it'll still work." Basically, it's rude to crash the app on purpose and lose the user's work when maybe, maybe, if you keep on going from an assertion failure the program will still work somehow.

Sure. And maybe you'll crash strangely. And maybe you'll corrupt the user's data, ruining all of their work, not just whatever you didn't save.

What really drives me nuts is that, on the Mac, Apple codifies this crazy idea. The NSAssert macro throws an exception on failure, and uncaught exceptions on the main thread get caught by a handler in the event loop which logs the exception and continues running the app!

But then, throwing exceptions instead of just crashing the app can also let tools like crashlytics get them and send them to a web service before the app really terminates.

Throwing an exception isn't really the problem, the problem is the catch-all exception handler in the event loop that just logs them and continues running the app. If that was gone, and NSAssert threw exceptions that by default crashed the app, that would be pretty much fine.

Yeap, but that was also a point against using abort or exit functions in production code.

Cleanly exiting is bad, but your crash reporting should catch abort().

You can always catch regular crashes in your app through Mach exceptions, or by scanning the crash logs directory for your app.

Observation: JavaScript runtime errors don't crash my browser tab, just halt the current call stack. I wonder if that's a consequence of bad/adhoc architecture, or if browsers represent a class of apps where silently swallowing errors is the right thing to do?

It's probably a historical oddity. Remember, Javascript started out as just an optional piece to copy-and-paste nifty effects in the beginning. You wouldn't want one script tag to kill the whole page.

For a web app this behavior is horrible, and I say that as a big fan of Javascript and the web platform in general. I would love to be able to specify a setting and just have exceptions kill the app with a nice error message. So many times have I had users report weird problems in my single-page-app, and my only possible explanation is that they've had the app open in a tab for days on end, run into a random unrelated silent exception at one point, and that has later affected the behavior of a different feature later. Sigh...

> What really drives me nuts is that, on the Mac, Apple codifies this crazy idea. The NSAssert macro throws an exception on failure, and uncaught exceptions on the main thread get caught by a handler in the event loop which logs the exception and continues running the app!

There's a defaults setting you can use that will pop up a window with crash/continue options if it catches your NSException.

But it is inconsistent - some old stuff like NSInvocation also catches and throws out exceptions, but dispatch queues will kill your process if they catch one.

Yes, but that is just the default behavior, does this not work?

    NSSetUncaughtExceptionHandler({exception in

Nope. These exceptions aren't actually uncaught, they're caught by an exception handler in the event loop. NSSetUncaughtExceptionHandler only catches exceptions thrown when there's no handler in place at all, all the way up to the top of the current thread's stack. In practice, this means it really only works for exceptions thrown on secondary threads.

Even if you could modify the behavior, having the default work this way would still be terrible.

Interesting. Just tried another approach, and you can get the behavior by overriding reportException: in a custom NSApplication subclass.

edit: Also I'm not suggesting to just naively exit(1). edit2: Strangely, exception generating code in [[NSOperationQueue mainQueue] addOperationWithBlock:] does actually hit the NSUncaughtExceptionHandler, which was surprising to me.

The mainQueue thing is because the catch-all exception handler is in the event loop, not the runloop. So if you throw during event handling (mouse down, key down) you'll hit it, but if it's something else (GCD, timers) you won't. It's extremely weird.

Good call about reportException:. Although I personally prefer to just avoid NSAssert. The good old C assert() gets the job done well for me.

Good to note, I must have logically mapped addOperationWithBlock: to a typical NSResponder event in my head, but what you're saying makes more sense.

Reminds me of Objective C happily calling a nil ptr, Apple wants to be generous and not pester you by terminating your app :-)

That's not really the same. Messaging nil is not necessarily an error. Making it an error is just an arbitrary language choice. Messaging nil in Objective-C isn't an uncaught error, but an explicitly allowed action with fully defined results.

One could certainly argue that making nil messaging an error is better, but either way it's a different sort of reasoning.

Apple didn't invent Objective C. It has been this way since 1983, before Steve Jobs left Apple the first time:


Swift will happily message nil also, for example:

delegate?.someOptionalMessage?(param: param)

It's a great language feature I appreciated in Obj-C, and not lost to Swift!

This is also something that Rust encourages. The default `assert!()` macro remains in even in release builds, and you may use the `debug_assert!()` macro to have the assertion present only in debug builds.

Super helpful. I hadn't thought of that use case for @autoclosure but that makes a ton of sense.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact