Blog logotrial and stderr

Figuring out what went wrong


I was working on a large refactor with hundreds of changed files, with six commits on the branch past main. But somehow something had broken that seemed totally unrelated to my work! When I would git checkout main everything was fine, and when I would git checkout mybranch it had broken. Specifically, some Typescript types for a third-party library were no longer resolving—and my changes involved neither the third party library nor the Typescript setup.

First thing I did was see if I had changed any of the build config files, anything at the top level that could make strange things happen. git checkout main yarn.lock and things like that. No dice.

How I ended up resolving the issue was the following:

  1. Create a new branch, git checkout -b mybranch-debug
  2. Do an interactive rebase with git rebase -i and fixup everything into a single commit
  3. Split that commit into one commit per file using this script, resulting in hundreds of commits.
  4. git bisect to find the guilty file
  5. Delete stuff from that file until it's evident what caused the problem

Hope that helps someone else figure their way out of a baffling problem!

React Hook debugging hack

 •  Filed under react

Want a quick, no-lib hack to make it easier to track which instances of a hook are doing what? Use useRef.

function useMyHook() {
  const id = useRef(Math.floor(Math.random() * 1000));`useMyHook ${id.current}`);

Linting uninitialized field dereference in Java

 •  Filed under java

Am I out of my mind for thinking that some linter or other should be able to recognize the following code as buggy?


public class Main {

    public String greeting = "hey";

    private Main anotherMain;

    public Main getAnotherMain() {
        return anotherMain;

    public void setAnotherMain(Main main) {
        this.anotherMain = main;

    public static void main(String[] args) {
        Main main = new Main();

I expect a linter to tell me that the unchecked dereference main.getAnotherMain().greeting could produce an NPE because anotherMain defaults to null.

I tried SpotBugs, NullAway, and the IntelliJ static checker, and none of them can catch this. Bug filed against SpotBugs. We'll see how it goes.

Choose unique names for test fixtures

 •  Filed under testing, art

In a library I maintain, I wrote all the tests using names from the foo-bar-baz family in fixtures.

I realized that having 50 tests where all the test data is foo-bar-baz makes it harder to debug. Adding a console.log to a line that gets hit 50 times might yield variations on { foo: "bar" } for all 50 tests, but only one corresponds to the error I'm interested.

That's why I've switched to pulling random names from a less limited domain. For this library, I'm using Japanese food names. It doesn't matter what they are or what they mean. What matters is that I can search my test logs for tamago and, if I've only used tamago in one test, immediately find the data corresonding to the test I'm interested in, { tamago: { kake: "gohan" } }. Delicious.


 •  Filed under java

Type safety without null safety is like a biohazard suit with an open face.

This is very typical Java code. It exhibits obnoxiously verbose typing, and yet can crash your application with NullPointerExceptions at runtime.

SuperDatabaseConnector superDatabaseConnector = SuperDatabaseConnector.getInstance();


public static SomethingElseButMaybeNull getResultsFrom(SuperDatabaseConnector superDatabaseConnector) {
  return superDatabaseConnector.getResults();

Java is a bad language and you shouldn't use it.

Merging packages and their histories into a Lerna monorepo

 •  Filed under git

Update: You probably just want to use lerna import. But if you'd like a manual technique, or aren't actually using Lerna, read on. Let's say you have a Lerna monorepo called acme. You have a package called app, presently in its own repository, that you want to move into repo. read on...

My git workflow

 •  Filed under git

This is my main workflow when using git.

(master)$ git pull
(master)$ git checkout -b my-feature
# make changes
(my-feature)$ git add [changed files]
(my-feature)$ git commit
# repeat the above as needed
(my-feature)$ git fetch origin
(my-feature)$ git rebase origin/master  # try --interactive some time, it's fun
(my-feature)$ git push [origin|upstream|fork] my-feature
# open a PR
# make some fixes
(my-feature)$ git add [changed files]
(my-feature)$ git commit
(my-feature)$ git push [origin|upstream|fork] my-feature
# uh-oh, need to resolve conflicts with master!
(my-feature)$ git fetch
(my-feature)$ git rebase origin/master --interactive
# fix conflicts
(my-feature)$ git rebase --continue
(my-feature)$ git push [origin|upstream|fork] my-feature --force  # never force master
# ok, my PR's merged, all done with that branch!
(my-feature)$ git checkout master
(master)$ git pull
# and we begin again

I've regularly been seeing help threads from people who somehow wind up hundreds of commits "ahead" of master. I don't know how you get git into such a mess, but hopefully the workflow above helps more people avoid it.