Gemini doesn’t understand exceptions. Then again neither do you.

Some days I feel like code review is an exercise in educating developers. Nowhere is that clearer than in Java exception handling. Almost nobody understands this. When I’m interviewing software engineers, I pretty much never ask about exceptions because it’s an almost 100% guaranteed fail. If I’m being interviewed and I’m asked about exceptions, I have to play the meta game where I assume that my interviewer believes things that aren’t true, and I tailor my response to fit their misconceptions. Mostly I do that by focusing on the syntax and behavior without covering the semantics and proper use of exceptions. Effective Java comes closer than most to correctly explaining which exceptions you should use when, but even Bloch doesn’t quite stick the landing.

Today after yet another round of back-and-forth code review comments where I once again had to explain to an otherwise experienced developer how exception handling is supposed to work, I got the bright idea to ask Gemini, Google’s machine learning chatbot, what it thought. It gave me a better answer to the question than most programmers do; but it still made two fundamental mistakes, one of commission (saying something that isn’t true) and one of omission (leaving out an absolutely critical point). See if you can spot the mistakes.

Read the rest of this entry »

Stack Traces Considered Harmful

I’m trying to build an open source project in a language I’m unfamiliar with and hit this problem:

(base) $ ruby bin/setup
== Installing dependencies ==

A new release of RubyGems is available: 3.6.2 ? 3.6.3!
Run `gem update --system 3.6.3` to update your installation.

Bundler 2.6.2 is running, but your lockfile was generated with 2.3.9. Installing Bundler 2.3.9 and restarting using that version.
Fetching gem metadata from https://rubygems.org/.
Fetching bundler 2.3.9
Installing bundler 2.3.9
Your Ruby version is 3.4.1, but your Gemfile specified ~> 3.0.4
bin/setup:16:in 'Kernel#system': Command failed with exit 18: bundle (RuntimeError)
	from bin/setup:16:in 'block in 
' from /opt/homebrew/Cellar/ruby/3.4.1/lib/ruby/3.4.0/fileutils.rb:241:in 'Dir.chdir' from /opt/homebrew/Cellar/ruby/3.4.1/lib/ruby/3.4.0/fileutils.rb:241:in 'FileUtils#cd' from bin/setup:10:in '<main>'

This illustrates a common antipattern in error handling. This is a Ruby program, but I’ve encountered it often in Python programs too, including the Google Cloud SDK. It also happens in Java, though less frequently. The most common place it appears in Java is when JUnit tests fail. Do you see it?

Read the rest of this entry »

Different Styles of Dependency Management

Recently I was hacking out a quick Python script. Everything was fine until I needed to import one common third party library and then Boom! I was dropped head first into the messy chain of Python dependencies, weak typing, virtual environments, and conflicts. I couldn’t even install the necessary library with pip. I’d just get an unintelligible hash of error messages and stack traces.

So off to DuckDuckGo I went to remind myself how one actually builds and packages real world Python programs that do more than print Hello World! It wasn’t pretty, and it took me quite a while to understand it. This was actually harder for me than it might have been for a newbie because I was so invested in the way Java manages dependencies. I’ve spent years working with dependency management in Java at a depth most developers never sink to. I’m an Apache Maven committer. I’ve rewritten most of their documentation about dependencies. I’ve written tools to analyze Java jars, dependency trees, and classpaths. I wrote or edited most of Google’s Java Library Best Practices. I’ve debugged many problems caused by Java class loaders. So I’ve got a pretty good understanding of how Java dynamically links third party dependencies.

But Python? Python doesn’t work like that. And neither does everything else.

Read the rest of this entry »

Assume vs. Assert

Assumptions are an underused feature of modern testing frameworks that should be more widely known. Briefly, an assumption verifies that conditions are right to execute the test. An assertion verifies that the test passes. If an assertion fails, the test failed, and we know the code is broken. If an assumption fails, the test was not run and the code may or may not be broken.

Read the rest of this entry »

“Is A” vs. “Represents A”

We’ve all had the distinction between “Is A” and “Has A” in object oriented programming drilled into us. “Is A” relationships call for inheritance. “Has A” relationships call for delegation. Some theorists will even bother to distinguish between “Is A” and “Is a Role Played By“. However, recently I’ve been wondering about the distinction between real “Is A” and “Represents A” relationships, and I wonder if it may help explain some otherwise confusing points.

Read the rest of this entry »