Well hello there!

Coming up with new salutations every week is going to be the hardest part of this whole endeavor...

Welcome to DevChat #6! Last week was a grind, but in a good way. Let's get into it. We've got:

  • The social life of aphantasiacs
  • The Typescript transformation continues
  • Don't believe the lies of Mocha.js

This is an archive of a DevChat newsletter. To get the next one early, and in your inbox, sign up! To read past issues, head to the archives!

What having aphantasia is like

Sometime in or around 2017 I read about a thing called "aphantasia". It's the inability to picture things in your mind; the absence of a "mind's eye." As with every medical thing, the human ability to conjure mental images exists on a spectrum. Some people are on the very-deficient end of that spectrum.

When people learn about aphantasia it often blows their minds. It blew mine, but for the less common reason: I didn't know that people could create and recall images in their minds! I always thought that stuff was metaphorical.

For me this was a discovery that other people could do something, and that this specific thing explained an enormous fraction of the parts of my life that had been hard or led to interpersonal conflict. Here's what I learned about other people, and therefore myself:

Experiential memory -- memories wherein you can essentially "replay" a version of past events -- are rooted in visual memory for most people. Experiential memory is powerful -- it allows for re-feeling of emotions, and more strongly anchors memory in general than, say, isolated facts or emotion-free information and events. It's also a lot of what anchors interpersonal relationships: you have a version of other people in your mind, and that version in your mind is associated with all these experiences and feelings that you can recall and re-feel. Plus, shared experiences give you something to talk about with those with whom you shared those experiences, providing an easy mechanism to deepen and maintain those relationships.

Or at least that's how I understand it. I don't know because I don't have any of that. My memory is informational and largely non-emotional. Because it isn't experiential, I remember things very poorly. It's caused people to think I don't care, because if I cared how could I possibly forget those moments we all shared? It means I can't socialize effectively by reminiscing about past experiences. And it means that I have to make completely conscious efforts to keep in touch with people because I don't miss them and therefore have no emotional triggers (out of sight, literally out of mind). My social-emotional life is almost entirely in-the-moment.

(Experiential memory isn't just about imagery. After all, presumably people who cannot see can usually create experiential memories. So it somehow goes deeper than that. I also can't associate any other senses, or any emotions, to memories. Is that all part of aphantasia, or is my form of aphantasia a subset of something even more far-reaching? I have no idea!)

To drive the point home: if I close my eyes and try to imagine my spouse, a person who I see literally every day and who I like more than anyone in the world, I cannot conjure up an image. I can't see her in my mind. I cannot recall the experience of our wedding, or any other events in our 15 years together. I can recall facts about many of those events, and even some vague image-like information representing them, but I cannot recall nor re-experience how I felt on those occasions.

I imagine some people are horrified by that idea. It's just how things are for me, so it's neither good nor bad. It does, however, make me feel extremely guilty because it feels like I'm doing something wrong.

The good news is that now that I have a name for this, and an understanding that how I work is not even close to how most other people do (along these particular dimensions), I've been able to slowly feel less guilty about it while simultaneously opening up dialogs with everyone in my life impacted by this difference.

It's probably harder for other people than it is for me to feel okay about these deficiencies of mine. Sure, I can explain to my family members that it isn't possible for me to remember things the same way they can and, more importantly, that I can't have emotional attachments to our shared experiences. I can explain that the reason I haven't called isn't because I don't like them, but because I don't think about them -- wait, no, that doesn't mean what you think it means -- uh -- I mean I don't think about anyone when they're not around because that's not how my brain works... crap that probably sounds bad too, doesn't it?

Fortunately the people around me are great and have allowed me to be open about the realities of how my brain works, so that we can collectively figure out how to meet somewhere in the middle with relationships that work well enough for everyone. It requires careful and sometimes-difficult conversations, but we're all better for it.

Here's my takeaway from this:

It took me 32 years of life to discover that my experience differed so fundamentally from other people's along this experiential-memory dimension. And then another three more years to discover that I have ADHD on top of that (discussed in the last couple DevChats), again informing an enormous difference between my and others' experiences.

If it can take that long to discover something that makes a person so deeply and intensely different from others, it's a fair bet that you are not explicitly aware of almost any of the infinite things that make you different from those around you. No action, inaction, or belief means exactly the same thing to any two people. It's likely that you could pick any aspect of how you experience the world and, if you began talking through that with someone else (and with yourself!), you'd begin to learn important differences that will help you understand how you and each other see the world differently.

It's also important to note that just because there is a label I can attach to this ("aphantasia"), that doesn't imply that my experience matches anyone else's even if they use the same label.

Do you have aphantasia? Or know someone who does? What was the discovery process like for you? How does your experience match or differ from mine? Let me know!

Rumpus → Typescript

Last week I talked about the first phase of converting Rumpus (our core webtech) from plain Node.js to Typescript. That first week was all about automated, bulk transformations. As I stated then, the whole thing left me feeling quite stressed and worried about actually getting the thing done. Now we're one more week in, so how'd it go?

In short: mixed bag.

After the batch migration I found myself chasing seemingly endless Typescript errors in my approach to getting things compiling. I'd fix one thing, and five more errors would be uncovered. It started to seem like a hopeless endeavor, and then I just decided SCREW IT I'll literally go from top to bottom in the project and do a quick pass on every single file to make fixes and essential refactors. I couldn't refactor everything, no matter how much I wanted to, because that would take forever and the whole project would be broken the entire time. That's very much the opposite of the small-batch-delivery mantra of DevOps.

It turned out to only take three days, even with substantial refactors. By Thursday morning I was getting no more compile errors!

However, that certainly didn't mean things were working. Not having a compiler error doesn't mean I don't have application errors.

That left me with a conundrum: the project is compiling but probably doesn't run. I need to get it running before making too many more changes so that I can get back to test-driven small-batch changes. But without refactoring into more project references compile times are slow (5-10 seconds). Slow compile times means slow iteration, which is also the enemy of small-batch and rapid changes. So what am I to do?

I decided to take a little more time to reorganize the project to speed up compile times before the true debugging.

That whole "project references" concept is intended to allow the compiler to identify which subsets of files need re-compiling after a code change. That way compiles can be fast because most of the project does not have to be recompiled. To get all this working you have to, in essence, break a project up into multiple, pseudo-independent subprojects (a subproject being a largely-independent collection of files/modules). This is hard to do, period, but particularly hard to do after the fact with a pre-existing large project.

But, whatever, fine, what's a little more hard work? I started to move files around only to uncover the real nightmare: Visual Studio Code was unable to detect that I'd moved those files to automatically update all references to them!

To explain: in Node.js each file is treated as an independent "module", and modules can import components from each other. So a Node project is just a jillion files all importing/exporting functions and data structures for each other to use. When you import one module into another, you need to use its filepath. That means you're screwed if you want to move that file later -- now all those other files that import it are pointing to something that doesn't exist!

VSCode and other good code editors solve this for you by keeping track of which files are imported where, and then automatically updating those filepaths when you move or rename something. This feature is essential when reorganizing large projects. I have several utility modules imported by no fewer than 50 other files -- having to go manually update every one of those files by hand would be horrible.

And it turns out that VSCode stops being able to perform this task once project references are being used.

My workaround was to rename all of my Typescript config files so that they wouldn't be auto-detected by VSCode, and then make a new config file that doesn't use project references. The original configs are now used for compiling, while VSCode uses the new one for Intellisense (auto-complete) and auto-updates when files are moved.

While this gets the job done, it's horribly frustrating because all of the speed gains are lost with Intellisense. It takes actual minutes for the Typescript language server in VSCode to fully start, and Intellisense is also very slow (sometimes taking 5+ seconds to auto-complete!).

I've filed a bug report, so hopefully I've just done something stupid or this is something that can get fixed. In the interim, my plan is to use this bad workaround while reorganizing the project and then switch back before beginning the debug process. I'll lose the auto-update-on-file-rename thing at that point, but that'll matter less when I'm not constantly re-organizing files!

Have you run into similar problems? Or found solutions? Or was this information helpful for you? Tell me about it!

The lies of Mocha.js

I'm a big believer in test-driven development, though I admit I'm a recent convert. Rumpus has something like 380 tests, largely functional tests for HTTP API endpoints. It could definitely have better coverage, and a lot more unit tests, but overall it's not in a bad state.

All these tests are going to be essential when it comes to debugging Rumpus after this Typescript switch. And it's likely that there are enough changes that a lot more needed tests will reveal themselves, so I'm assuming that a lot of the work is going to go into adding tests for each failure I discover.

I use the Mocha.js test framework. At the time I was choosing frameworks it was consistently at or near the top for popularity and user-friendliness. JavaScript frameworks are a dime a dozen and there's a new fad every 3 months, so who knows what the kids are into these days.

There are a couple of gotchas when it comes to using Mocha that can lead you to not realizing you're skipping tests. This is the "lie" I'm referencing here: Mocha may be telling you that your tests have passed, when in fact some of your tests weren't run at all! Here are the two causes of this that have hit me particularly hard:

  1. Async errors thrown outside of test definitions are silently swallowed by Mocha.js.
  2. If you frequently use the .only flag to limit test runs to a subset of tests, you might forget to remove that flag.

Silent, swallowed errors.

A while ago there was a CLI tool I was building that had been merrily passing all of its tests for weeks. When I finally made it available for the team, it was completely broken. Weirdly, it was broken in ways that I knew I had test coverage for. How could this be?

It turns out that errors thrown in async contexts can cause Mocha to exit early without registering all tests, all while swallowing the error that that caused it to happen! Observe:

describe('My test suite', async function(){

  // throw new Error("Bwahahaha! Tricked you!");

  it('can pass this test', async function(){
    // This will "pass", even without any code,
    // since Mocha tests pass unless an error is thrown.
  });

  it('cannot pass this test', async function(){
    throw new Error("OH NOOOOOO!");
  });
});

This test runs as expected, informing us that one test passed and one failed:

Screenshot of the expected output of a failed Mocha test.

But what happens if we uncomment out that extra thrown error? Despite there now being two explicitly thrown errors in that little Mocha snippet, we get this:

Screenshot of Mocha output falsely showing that a test didn't fail.

Yeah, sure it says zero passed, which sounds like a failure. But that's in green, because Mocha saw zero tests! This is a success state, because Mocha doesn't care about things that pass (or that nothing passed), only things that fail. And nothing failed, according to Mocha. When something fails, Mocha exits with a non-zero status. That non-zero exit would be used to inform downstream tools that something has gone wrong, preventing your automated pipelines from continuing when tests fail. But here we got a 0 status despite obvious test failures!

Even without the automation problem, this same bug can be hard to spot when doing things manually. Yeah, in this case "0 tests passed" is quite obviously wrong. But this problem can cause a subset of tests to get skipped, so you might see "321 tests passed" when there should have been "351", and if you hadn't memorized how many tests you had there'd be no way to realize you were skipping tests!

As a workaround, you can tell the Node process to catch such errors and force a non-zero exit status:

function onUncaught(err){
  console.log(err);
  process.exit(1);
}

process.on('unhandledRejection', onUncaught);

describe('My test suite', async function(){

  throw new Error("Bwahahaha! Tricked you!");
  // ...

And now we get:

Screenshot showing the error no longer being silently swallowed.

Forgotten .only()

When you're actively working on a new feature, or debugging an existing one, the test-driven approach is to first write the tests, ensure they're failing where they should be, and then code until all tests are passing.

If you're doing this in the context of a large project, you probably don't want to be running all tests just to see if the current thing is working. Mocha provides a few mechanisms for dealing with that, the easiest one being to use .only to indicate that only that test (and any others similarly flagged) should be run:

describe('My test', function(){
  it.only('will run this test', function(){});
  it('will not run this test', function(){});
});

But what happens when you inevitably forget to remove that .only to ensure your other tests run again? You'll be bypassing tests that might be failing! I've done this countless times myself.

Mocha has a great solution for this: the --forbid-only flag.

When you add this flag to your CLI call, Mocha treats the mere existence of .only in any part of your test code as a test failure, and exits with a non-zero status.

For my build pipelines I always use this flag. It pairs nicely with --bail, which aborts as soon as a single test fails so that you don't waste time running other tests on a bad build.

Did you find this helpful, or WRONG, or have your own tips? Hit me up!

Next time

That wraps DevChat #6!

Say hi, give feedback, and share your own stuff with me!

Know someone who would be interested in any of these topics? Share by forwarding this email, or link them directly to the archived post: https://www.bscotch.net/post/devchat-6

Have a great week!

❤ Adam