TS Debugging that doesn’t suck

Unfortunately, at some point or another you are going to create a bug in your system. The larger it grows the more likely it is that you will create that bug.

Or an exception! Lean more on how to deal with those here.

So what do we do about it?

Well, we need to arm ourselves with the proper tools to ensure that when we need to debug it, that our debugging experience doesn’t suck.

Generally speaking, I would argue there are two categories of debugging:

  1. Proactively: the things we can do to (try to) prevent things from going wrong in the first place
  2. Reactively: the tools we reach for when things to go wrong and we need to fix it.

Let’s take a look at each category, and dive into it!

Proactive debugging

At the end of the day, as developers we get paid to ship working code. Yes, if you have a fulltime job with a steady paycheck you get paid to ship any code, but eventually you’ll have to come back and fix it, so the goal is clearly working code.

I don’t think this comes as a surprise, but it is an important premise to understand, because it means we have a relationship between our code and ‘confidence that it works’. We’ll just refer to that as ‘confidence’ as is most common in the industry.

Proactive debugging then, is all about raising confidence, ideally to a point where confidence is so high that we don’t produce unexpected bugs.

TS in itself is actually found within this category!
We write Typescript because it provides a much higher level of confidence in our code, ensuring we write higher level code from the get-go.

Another common yet very powerful tool at our disposal is tests. It could be unit tests, end to end tests (often called ‘e2e’), mutation testing, acceptance testing or a myriad of other types of testing.

Typically you will use a combination of these two (and more) tools, such as static type checking, but for now lets focus on TS and testing.

Typescript is an amazing tool in our toolbelt, because it effectively reduces the amount of tests we have to write.

When working with plain Javascript you run the risk of misspelling function parameters, inputs or producing unexpected outcomes.

This just doesn’t happen in Typescript, so we have already raised the bar for what can go wrong, just by using Typescript.

Adding in tests

To put even more eggs in the proactivity bucket, we can write tests on top of using Typescript. This should further increase confidence, not just by making sure everything is written out in TS, but ensuring that everything works as expected.

In some cases “working as expected” could simply refer to error messages being thrown correctly when an endpoint is given a wrongful input, and that it fails at all.

We want to ensure the best possible experience for anyone using our product, which is what tests are for. They also help bolster collaboration because tests can provide some context that isn’t clear otherwise.

Generally when testing we should try to strive to write tests for:

  • Test that it fails when it should
  • Test that it doesn’t fail when it shouldn’t
  • Test the preconditions
  • Test the postconditions

Effectively ensuring that it behaves how we intended.

Refactoring

Tests give an often overlooked benefit for anyone working on the codebase. On top of ensure expected behaviour for the end user, tests also serves as a sort of recipe for how things should work.

This can make life a lot easier when refactoring. Simply perform your refactor and run your tests. Did they pass successfully? Great! Did they break? Time to debug!

This is because if we follow proper Test-Driven Development (TDD, for short) we should, ideally, write tests once and then very rarely have to re-write them. That also means we can rely on tests to set the tone for how the expected behaviour of a method, integration or whatnot is supposed to act, and the actual code to just be the implementation of that expectation.

I try to include tests in my codebase and almost always use TS. Emphasis on try to include tests.
With testing I believe less is more as Typescript already greatly increases confidence, but I still find it important for critical parts of the application(s).

I don’t yet have many or strong opinions on testing, I think it’s a tool to be used and different use-cases have different needs, but that opinion may change in the future as it is an opinionated topic 😄

Reactive debugging

Shit broke, your application isn’t behaving how you expected and it’s time to fix an issue.

Even with high confidence, and perfectly good tests, at some point the time comes to fix an issue. When it does, we want to make sure we reach for tools that help us in a fast, elegant and effective way.

As Developer Experience is often talked about in the Javascript ecosystem, it is becoming increasingly clear that when working in the industry we want the best tools to help us solve issues that we commonly face, such as debugging.

For ages, Chrome has tried to help us with the builtin Chrome Debugger, which often works fantastically.

Chrome Debugger supports source maps, which even makes it possible to debug directly from TS!

Additionally, we have great tools such as Jest that has an extension for VS code, which Chrome even easier to work with if you’re using jest as your testing library.

When working with the chrome debugger it is important that you verify your build tool chain and ensure that you can properly generate source maps, otherwise the debugger may provide very unexpected behaviours.

Quick tip
A good way to ensure the most reliable behaviour possible is to set your tsconfig.jsons target attribute to something new like es2022 or esnext.

Another option is to create a custom launch command, as it will be executable directly from your editor and do something that you expect it to do.

Often this will launch a chrome debugger under certain conditions, with special parameters.

Console.log()

As much as I want to improve and get more used to using the chrome debugger, as it is a great tool, I often find myself debugging with console.log() here and there.

I personally believe that many TS (and JS!) developers are in that boat.

So for us, it is even more important to have great proactive tools, because it is clear that when we reach for the reactive tools they are just not quite as sharp.

One thing I do, that I can highly recommend, is instead of transpiling your TS then running your JS using nodemon or a similar tool, find a tool that can integrate both so you get a seamless experience when developing, as I have outlined in this article.

Having near-native execution of TS will make it quite a bit more bearable to use console.log() as a mechanism for debugging.

As you can tell, I learn more towards proactive tools and actually don’t have a great suite of tools for when things do go wrong.

So far I have found my tooling of console.log() adequate, but I’d love to hear thoughts on your favorite tools and methods for reactive debugging. Drop a comment below and let me know 💪

Leave a Reply