PurelyFunctional.tv Newsletter 345: Tip: know your exception hierarchy

Issue 345 - September 23, 2019 ยท Archives ยท Subscribe

Clojure Tip ๐Ÿ’ก

know your exception hierarchy

The Java Virtual Machine handles errors in a relatively consistent way. We inherit most of that system in Clojure, so it's useful to know a bit about how it works.

One thing that has bitten me is not remembering that Exception is not the top of the hierarchy of things that can be thrown. There's a class called Throwable above it. Instances of Throwable are the only things that can be thrown with the throw expression in Clojure.

Underneath Throwable are two branches: Exception and Error. If you want to catch everything, you have to (catch Throwable t ...). If you only (catch Exception e ...), you miss all of the Errors. It's worth taking some time reading the information-dense Javadocs for this hierarchy. Start at the Javadocs for Throwable and read down through Exception, RuntimeException, and Error.

Some important excerpts:

The class Exception and its subclasses are a form of Throwable that indicates conditions that a reasonable application might want to catch.

Common exceptions include FileNotFoundException. The idea is you code the happy path and handle the sad fact of a file not existing by catching this exception.

An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.

Common errors include StackOverflowError and OutOfMemoryError. These are serious flaws in your program, the JVM configuration, or the environment.

This distinction between situations you're supposed to handle and situations you shouldn't handle has led to a lot of debate. Luckily, the JVM does not prohibit catching and throwing any kind of Throwable. So when you need to break the rules for practical reasons, you can. For instance, if you want to catch a StackOverflowError and return a default value, you can. You don't have to let your program crash. And if you want to ignore a FileNotFoundException even though "reasonable" programs should catch it, go right ahead.

There's one wrinkle in Clojure that has bitten me before about all of this. I've often used Clojure's :pre and :post conditions to check the arguments and return value. Likewise, I've used (assert ...) expressions. And guess what they throw! java.lang.AssertionError. Yes, they're an Error. So if you thought you'd catch that with a (catch Exception e ...), think again.

Because asserts and :pre and :post conditions are so common, I always start with (catch Throwable t ...) in my mind and work my way down the hierarchy as needed.

Follow-up ๐Ÿ™ƒ

Kind reader Jakub Holรฝ reminded me of How to ns, which is Stuart Sierra's guide to Clojure ns forms. Generally great suggestions in there. If you followed this style, you will look professional.

Jakub has also written up common beginner mistakes in Clojure. These will get you really far.

Book discount ๐Ÿ“–

Buy the print book for $25.

Manning has informed me that today, Monday, September 23, is a special sale. They are offering all print books for $25.

You can buy my print book now. You will get a PDF of the chapters that are ready now, plus updated PDFs as the book progresses. And then, one day, when the book is done, you will get a pleasant surprise in the mail. And if you see me at a conference, I will sign it for you (if you're into that kind of thing).

No code is needed. Just head to Grokking Simplicity and buy it. Just remember: it's today only.

Feel free to buy my book and anything else that suits your fancy. I'll get a small affiliate fee :)

If you don't want the print book, you can use code PUREFUNC40 for 40% off. But I don't know why you'd do that. It's practically the same price.

Buy the print book for $25.

Currently recording ๐ŸŽฅ

Property-based Testing course has two new lessons this week. It's getting close to the end, so I'm going to close the Early Access Program before the next newsletter comes out. The price will never be as low as it is now.

This week's lessons are about exploring the different processes for testing at different times in the life of software.

If you want access to the course and you're not a member, you should buy it this week. It will go off the market until the course is done. And after that, the price will be higher. Buy it now.

The lessons I've planned to wrap up this course include:

  • Testing distributed systems --- test for things like eventual consistency and data loss
  • Testing non-Clojure web frontends --- introduce Clojure where it's not allowed in production
  • Spec integration --- double the usefulness of all those specs you have
  • Using PBT to help design a system --- catch bugs in the specification of a system
  • Using PBT to reproduce known bugs --- bugs your users report but you can't reproduce on your local machine
  • Fuzz testing --- put your software through its paces and check that it's okay

Brain skill ๐Ÿ˜Ž

build recall into your learning

Reading about a system is great for learning. You get a ton of e

xplanation and you can see how something is done well. You'll remember a lot of that when you go to use the knowledge.

But there are limits to how much you can learn this way. Sure, you do learn by reading explanations. But what you really want to do is get better at using the knowledge. In the case of programming, that means programming. You should find a way to use what you've learned, ideally without looking at the source material again.

Our brains get better at the situations we put them in. If we put our brains in situations of absorbing a lot of information quickly, like we do when we read, they're get better at that. The information is in there. But without practicing recalling it, our brains won't get better at recalling it when it's needed. The neurons for retrieving helpful information at the right time won't have enough practice.

Recall is not important, but it's not enough. Take, for instance, learning keystrokes in Emacs. Sure, you read a blog on how to do something. Then you even quizzed yourself on it later (which is recall). But did you also practice connecting that recall to the neurons that control your fingers? Or what about the visual part of your brain that decides what the text should look like and plans changes to the current text? Ugh! There's so much involved in any one skill, there's a reason real-world experience is so highly valued.

In your learning, you've got to balance time between keeping the task small enough to practice one skill (deliberate practice) and keeping it realistic enough to get all the systems involved.

Bottom line: absorbing information is good, but get out and practice it in a realistic scenario, like a side project.

Clojure Challenge ๐Ÿค”

Last week's challenge

The challenge in Issue 344 was to make a higher-order function that retries another function three times.

You can check out the submissions here.

This week's challenge

rate limiting

The reality of the distributed, service-oriented world we live in is that we have to contend with the consequences of not only our requests, but the requests of all of the other clients of that service. For instance, many services have maximum request rates to protect the availability of a service from overambitious clients. An API might say "make no more than 10 requests per second".

How do you enforce that? My server has 24 cores, and I run as many threads. How do those threads coordinate so they don't go over the limit? It gets more complicated when you scale to multiple machines. Let's keep it simple and just talk about the shared-memory case.

There's a nice algorithm for implementing rate limiting called token bucket. If you can't make more than 10 requests per second, you make a rule: no one can make a request unless they have a token. Now dole out the tokens at no more than 10 requests per second. It's a great way to centralize the control of the rate.

Your mission, should you choose to accept it, is to implement token bucket. Make sure it works well with multiple threads. It's surprisingly easy in Clojure with a thread and an atom, but there are other ways to do it. Core.async makes it a cinch.

Extra credit for then making a higher-order function that rate limits a function.

As usual, please send me your implementations. I'll share them all in next week's issue. If you send me one, but you don't want me to share it publicly, please let me know.

Rock on!
Eric Normand