No Exceptions

I’m a big fan of runtime exceptions. The best Ruby programs, in my opinion, will throw an exception at the slightest provocation.

Exceptions do two important things: they halt your program, preventing the fallout from unexpected/undefined behaviour and they notify you, the developer, that something went wrong.

I maintain two Ruby gems whose main purpose is to raise an exception:

Literal helps you write precise descriptions of the types of properties each object expects. It raises a TypeError unless these expectations are met.

Strict Ivars ensures you read only defined instance variables. If you read an instance variable that’s undefined, it raises a NameError.

And yet, I have absolutely no tolerance for exceptions in production. Your error monitoring system should hit Inbox Zero every day. No exceptions.

There’s no acceptable level of runtime exceptions any of us should be putting up with. If an exception is caused by a bug, the bug should be fixed. If it’s not caused by a bug, the exception is the bug. Stop the code from raising that exception, that’s not what exceptions are for.

No Exceptions is entirely achievable but the way to do it is a little unintuitive. Here’s some advice:

Raise more exceptions

It seems backward but the more exceptions your code can raise, the fewer you’ll see in production.

Make a habit of validating your assumptions at runtime. You can do this by hand or use tools like Literal and AyeVar.

Think about your database constraints, too. What fields can be non-nullable or unique? Use check constraints, foreign key constraints, exclusive arcs.

Validating your assumptions allows you to write confident code, safe in the knowledge that if something does go wrong, you’ll know about it.

Stop rescuing exceptions

If you rescue an exception for anything other than to re-raise another exception, you’re doing something wrong. The only exception I would make to this rule is if you need to work with a third-party library that raises when it shouldn’t.

If you want the control flow ergonomics of an exception for things that aren’t broken, use throw / catch instead.

The most appropriate use for rescue is to raise a more useful exception.

Exercise everything

The unfortunate fact is most production code is tested by users, in production.

Testing is hard to begin with and in most Rails codebases I’ve worked on, it’s incredibly difficult to do well due to years systemic and architectural issues.

Most Rails test suits are slow, flaky and unreliable — it takes ten to fifteen seconds to run one test and whole minutes to run the entire suite.

If you’re in this situation, my advice is to focus on coverage. Your number one priority is to exercise every endpoint with one test, and all it needs to do is assert_response :success. Don’t sweat the details. Precise assertions are a lower priority; simply exercising the code will often trigger exceptions when there are real bugs.

Use TypeScript

If you’re writing JavaScript, learn TypeScript. No, no. I’ve heard all your excuses and they’re pathetic. Read a book, talk to AI, spend a couple of weeks learning about the type system and then stop shipping bugs.

Frontend code is incredibly easy to break and incredibly difficult to test. TypeScript isn’t a substitute for testing, but it eliminates a whole class of structural bugs, leaving you to focus on the logic.

Don’t tell anyone, but if your app is pretty light on frontend JavaScript anyway, you can get by with a combination of TypeScript and manual testing. 🤫

Stop ignoring exceptions

An exception in production is a “fix today” kind of issue.

When deploying code, you should first log in to your exception monitor and see nothing. Inbox Zero.

Then keep an eye on it for a few minuets after your code is deployed. If you can, go and use the feature you just shipped to confirm it still works.

Keep monitoring your exceptions for a few minutes. If something comes up, it was probably you and you should fix it without delay.

It’s easy to think that “that’s just the way it is” means “that’s the way hit has to be”. But we should be careful to validate our assumptions in life as in code. It doesn’t in fact have to be that way. It doesn’t have to be “normal” to have dozens of unresolved exceptions.

Inbox Zero may seem a long way off, but you can embrace the mindset today. If you have 500 production issues in your exception tracker today, don’t let it go to 501 tomorrow. Create a filter with “first seen” set to today as an absolute date. Draw a line. No exceptions.