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 Error
s. 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:
- From
Exception
:
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.
- From
Error
:
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 assert
s 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.
- When to test: before implementation --- How PBT helps TDD.
- When to test: after implementation --- How PBT can increase your confidence in existing code.
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