Hacker News new | past | comments | ask | show | jobs | submit login
JavaScript debuggers are broken (samdesota.com)
95 points by mistersys on Apr 22, 2019 | hide | past | favorite | 118 comments

Some of the comments here are surreal, I tell you. Surreal.

I use chrome's built-in debugger to debug compiled down typescript code. I go to the original TS files, mark a line by conditional breakpoints, it works perfectly. It stops the execution of the compiled JS files, it uses the source map to find the original line (which I placed the breakpoints in) AND it has a perfectly fine, working stack trace as well at pause. I can also blackbox at will.

Timetravel debugging is going to happen soon (with its proper costs), and thats about it. Variable watching is the only pain point, but that is only because TC39 axed the native observable implementations a few years ago.

I would not respect anyone who calls this level of debugging support "bad". It can be improved, but it is satisfactory.

I have had a similar experience... perfect debugging, breakpoints working, everything fine, up until yesterday. I think something in my stack got upgraded, and ever since, exceptions have broken callstacks. Trying to click a breakpoint takes me somewhere completely else in the sources viewer, and sometimes, exceptions don't break at all. No, I don't know why. Something broke, I still need to investigate, but it's another annoyance that reminds me of how much of my time is going to debugging and fixing platform issues.

I think the big issue is just how fragile and brittle source maps are. They feel like a joke, compared to DWARF and native debuginfo.

But who knows, maybe they'll start working again tomorrow.

That is frustrating, I concede on that point. (If there is one thing that is bad about the JS ecosystem, its the brittleness and complexities of setting up build tools. If you get together one that suits your need, you do not look at it again unless an update breaks it. Then the process of looking it up how exactly it works comes again, and this repeats yearly :P)

It is bad compared to more developed debugging experiences (Eg visual studio and C# or even eclipse and Java). It is better than nothing or GDB of course.

I personally think the Chrome debugger interface is horrible, but that is easily fixed by using Visual Studio Code instead. Even then, things feel off...but more like unpolished.

> GDB of course

Every time I end up using a GUI debugger, I end up disappointed. GDB has spoiled me. Best feature that GDB has, for me? The ability to script what happens when a breakpoint is hit. A recent example: I have a state machine on an embedded system that is somewhat timing dependent (delaying a couple milliseconds is fine, delaying >1 sec is not). I stuck a breakpoint on the entry point and told GDB to dump a state structure every time it hit and then automatically continue. I get output describing exactly which events are coming in and what the state is at each step. Magic!

This is relevant in JS land when you have a bunch of callbacks/promises/whatever. HTTP requests continue executing while you’re paused on a breakpoint, so debugging things like out-of-expected-order promise results gets really hard.

GDB is definitely a power tool and requires some learning to use it effectively, but don’t write it off just because of that.

> Best feature that GDB has, for me? The ability to script what happens when a breakpoint is hit.

Conditional breakpoints in chrome could do this for a while by having your conditional be a call to a logging function, and it was recently made more discoverable and user-friendly as "logpoints".

You could just write that code temporarily inline

Sure, if I had access to some kind of console output interface! I could have set up a UART, and a bunch of complex code to walk through the whole data structure, and hoped that formatting and streaming it all out didn't blow my timing.

Or I could do:

    break fsm_entry
    p/x event
    p/x state

Ya, that’s what I would do. More to the point, the interface for more more advanced debugger features generally don’t make them worth it (eg intellitrace). Debugging is a heavily constrained usability experience.

I love typescript. I use it basically as much as I can. Some projects have builds that break breakpoint support. I’m not sure of the exact cause, but it is frustrating. I often have to disable source maps when this happens,

An interesting trend I've seen is that interpreted languages often lack a debugger, as most "prototyping" ends up happening in a REPL, which act as sort of an interactive debugger with each prompt serving as a sort of "barrier" where each value is guaranteed to be reified and execution is not in some awkward place. Whereas with compiled languages you have debuggers that "work" because there's a consistent model of how execution proceeds and getting things like a stack trace is easy, allowing for things like placing breakpoints on lines of code or "executing" arbitrary code in essentially any context. Starting out with compiled languages myself, I find it really interesting to watch how other programmers structure their code in Python or JavaScript code to make it introspectable in the face of arbitrary reflection, asynchronous callbacks, etc.

"Interpreted" is probably the wrong word, "interactive" would be my choice. But I think I agree with your overall point, that when you have a high level of interactivity, you don't need to rely as much on automated debuggers which is why you see them missing or barely mentioned -- at least if you are making use of such interactivity and aren't just trying to write code the same way you would older Java. I've done a lot of Python without ever feeling a need for pdb, and didn't even learn about its existence until quite a bit later in my Python studies, but doing C and C++ necessitated learning about gdb (and valgrind) very early on. For Java, it's a mixed bag for me -- I can get by fine with pure reason (and some logging and test code) and vim up until a certain project size or contributing coders size threshold is reached.

Meanwhile you have old dogs like Lisp that newer languages supposedly cribbed a lot from, yet these newer languages have all seemed to miss things like having what we think of as IDE features built into the language spec itself. The Lisp standard requires built-ins like 'COMPILE, 'DISASSEMBLE, 'BREAK, 'STEP, 'TRACE, 'INSPECT, 'INVOKE-DEBUGGER that end users can use on their own or as building blocks for more sophisticated tools (like Slime -- https://malisper.me/debugging-lisp-part-1-recompilation/) and implementation-defined extensions. I appreciate the advances browsers have made in dev tools (Firebug was a godsend) but it's still rather primitive compared to languages where debugging has long been a staple or was even defined as part of the language...

Compiled functional langs tend to have a repl and not a debugger.

In Lisp usually a debugger is built into the REPL.

I think JavaScript debuggers in browsers are AMAZING, it is fantastic to have such powerful programming tools on almost every computer! Just like BASIC in DOS computers and similar computers from that era, this is a programming language available by default almost everywhere. And the debugger tools are powerful too with inspecting and changing variables, ...

But what did the author expect, when using a programming language that is not JavaScript, that the JS debugger would show code in that language instead? Maybe the framework needs to compile to JS that looks closer to the code you typed then (or else have its own debugger for its language). That's not the JS debugger's fault but the framework.

Disclaimer: I didn't read past the part where it showed React code and then showed that the debugger stops in different looking Javascript code instead

Par for the course for any language and development environment is interactive debugger in the code you are writing. As the author I don’t care much about how many translation steps there are from “my” syntax to machine code, all I care about is that I can debug my own code and watch my own variables.

The grandparents point is (I guess) that if choosing a reasonable language (such as TS) instead forces you to give up another must-have (e.g interactive debugging) then something is wrong with the tooling. I read others that claim they have working ts debugging so it’s certainly possible, but I’ll add one more par-for-the-course bullet point: it should work out of the box.

Well so it's the title of the article that ticks me off, "JavaScript debuggers are broken"

Isn't instead the language the author is using (JSX?) that's broken by not providing a debugger for itself or compiling to an easier to debug form?

I made a fairly interesting JS introspection tool about 8 years ago ... I used to use it all the time: https://github.com/kristopolous/_inject

The idea is that you want to run arbitrary code at some random deep scope in the code or perhaps run some map/reduce/filter function over every time it goes in there and find the anomaly ... it was really useful in characterizing things like itemizing a frequency of failure and other things I guess I stopped dealing with.

I think it can be expanded upon but never really seemed to go anywhere. (sorry if the code walk-through article doesn't work well on mobile and is not complete - I never finished it)

Its interesting to see developers talking about and building debuggers whilst only apparently having experience of one.

EDIT: apparently the following is no longer true

From gdb, going to Chrome dev tools was super painful. I had been used to writing high quality watch statements. watch x, and break if it is <criteria> is a very powerful debugging mechanism.

Chrome has "pin this variable and tell me its value every time I pause". This is overly manual. Its bad enough trying to get the erronous middle value when looping over a 10k array, never mind a more complex data structure where the error condition will depend on a parent/less obvious value.

Im not familiar with many debuggers, and I know I'm not a true expert in any. Id be keen to know what people think are awesome debugger features.

That’s actually not true anymore, Chrome supports conditional breaks and expressions.

Edit and continue/resume

    if(x) throw x

The reason this doesn't work in the same way is because it requires you to know where the error occurs.

You tend to operate on a data structure and know that at some point x becomes y. By placing a watch on x you find out where that point is.

Editing the code does not get you this (bar modern proxies in js).

Aha. I sometimes use ES5 defineProperty where you can define get and set functions. I built an ORM using defineProperty. Where changing a value triggers an database update. But the best way is to avoid globals or only update it at one place.

if(x) { debugger }

Play with conditional watchpoints in gdb to understand why this does not make sense as a reply to the comment explaining why this is not a substitute.

Brian W. Kernighan and Rob Pike wrote that stepping through a program less productive than thinking harder and adding output statements and self-checking code at critical places. Kernighan once wrote that the most effective debugging tool is still careful thought, coupled with judiciously placed print statements.

Clearly the guy didn't have to pull out a working PoC in one week with a forced uppon JS framework that just changed its public API and not its doc and that need magic lines just for webpack to not crash.

Or that has to pull data from a manually written csv our client uploads at 3 pm on their managed ftp and an untested REST api designed last month by a one man start up, using a legacy client lib that only god and a fired engineer knew how to get to work.

Or that has to be done with a trainee designer that knows photoshop but will "learn this css thing on the way" , a remote turkish consulting firm and your boss dog barking next to you because animals are good for moral and we are agile right ?

Those theories are good in a lab while writting carefully crafted c for the board you designed the spec for.

This says much more about the work environment front-end developers are subjected to than anything else.

Or work with a geographer client that wants to integrate his first terrible, terrible plugin into postgis.

Or contribute to a mess of an open source project that your microservice happen to depend on.

Or debug your student exercice that found yet a brand new way to crash that you never heard of.

Or find what are the side effects of your 3am emergency fix during the last red teaming.

Programming is a vast land.

True, I am big fan of them but there are some legitimate cases where a debugger is so practical.

There is a lot of code out there that has very little logical sense per se other than elusive business requirements..

Debuggers implement this but better. You can place a conditional breakpoint containing your code checks, and when execution stops you have access automatically to all the scope you could possibly use to print your output statements without having to write any. You are also free to still think carefully while using a debugger.

No. You're misrepresentating or misunderstanding the claim.

It is not about debugging. It is about program design. If you need to step through execution in order to understand and reason about iit then your design is too complex.

It is not a claim that print and conditionals are better debugging techniques. It is a claim that having those critical points in execution making assertions and logging warnings is a more productive alternative which precludes debugging in the first place.

> have access automatically to all the scope you could possibly use to print your output statements without having to write any.

If youre in a context where you need this level of access to the state scope you have a ball of mud that needs to be decomposed. You should know what state you have by the failure mode or the failing test at hand.

Using a debugger only makes sense if you're dealing with a mess. Fix the mess don't build tools to work with it.

> You are also free to still think carefully while using a debugger.

Yes but again, if you need a debugger it goes to show you don't think carefully very often so maybe telling me you can with a debugger is kind of aoot point when I know you won't.

I'm sorry but this reasoning sounds like elitist rubbish to me, like trying to turn a completely subjective workflow step into a universal law ;)

It's not about a "need to step through because your code is too complex", more about validating the code you just wrote, whether it still "feels right" after it has been dumped from your mental model into actual code.

You may have carefully thought about your program for hours before typing the first line of code, but usually such a splendid plan doesn't survive the first written line. Maybe the super-intuitive API you thought about doesn't quite feel that great in practice, maybe in the end you still didn't think "hard enough" about some problem. Spending time in the debugger to think about solutions is usually better than "just thinking hard" about solving the problem, because you have more data, and can tinker with potential solutions, and finally figure out which of the potential solutions feels best.

It's the same reason you're using a profiler for performance optimization, not just thinking about how to make the code faster (of course that needs to happen too, but it's not enough).

This sort of "validation debugging" in a tight edit-debug loop requires that the debugger runs in the same environment as the source code editor. It must be possible to switch between editing and debugging (and running to the location you edited) in under a second.

You've repeated AP's failing this isn't a workflow nor a replacement replacement debugging concept. It is a design paradigm that lessens the need for heavy weight debugging.

Debugging still has its usecases. I, like others, just see it as an infrequently required tool when most systems when properly composed simply lack the complexity required to make them useful.

> It's not about a "need to step through because your code is too complex", more about validating the code you just wrote, whether it still "feels right" after it has been dumped from your mental model into actual code.

Debuggers are for debugging. Not development.

It's not a rule without excception but in the general sense I would be very concerned to see a developer using the debugger on a daily basis especially if they're using it for state validation.

Like what are you validating that your tests aren't? Why aren't your tests validating it?

The need for a debugger is a symptom of a sick system. I have never required a debugger on a healthy system.

I've used them on healthy systems for more complex problem sets but by and large I only reach for them when someone has done something appalling.

> Like what are you validating that your tests aren't? Why aren't your tests validating it?

Your tests are probing your code on a narrow range of inputs (or if you're using something like property testing, potentially a larger but still finite range) drawn from the valid state space that is almost always too large to actually verify. They're also themselves pieces of code that itself can have bugs. Formal methods can prove properties about code, but usually w/ a much larger investment of time.

> The need for a debugger is a symptom of a sick system. I have never required a debugger on a healthy system.

I think people who say stuff like this are probably a little in denial about how many bugs they've actually written and how difficult it is to get any kind of certainty about the correctness of their code. Programmers write bugs, why are we policing the tools they use to fix them?

> Your tests are probing your code on a narrow range of inputs drawn from the valid state space that is almost always too large to actually verify.

You don't need to verify all inputs with mathematical certainty. You're writing the code some reasonable shortcuts can be made, some basic understanding of what bugs happen and what kinds of tests provide the best ROI gets you > 80% of the way there.

> They're also themselves pieces of code that itself can have bugs.

No, they are tests, bugs in test code are incredibly hard to produce if you are practicing test first development.

Most real bugs that make it into production are from conditional flows that are not being covered by tests.

> I think people who say stuff like this are probably a little in denial about how many bugs they've actually written

No denial, I know I've written a bug before.

> and how difficult it is to get any kind of certainty about the correctness of their code

Nope, not in denial about this either, I find it relatively easy. Maybe I just don't do a lot of rocket science or something.

> Programmers write bugs, why are we policing the tools they use to fix them?

Nobody is policing tool usage here..

> Nobody is policing tool usage here..

> I would be very concerned to see a developer using the debugger on a daily basis especially if they're using it for state validation.

Those two statements seem to be in conflict.

Or maybe I should say that latter is not "policing" so much as "patronizing" in a condescending manner.

> Like what are you validating that your tests aren't? Why aren't your tests validating it?

I use use a debugger fairly regularly when I'm writing tests. Debuggers are for moments of incredulity. When a test doesn't behave as I expect, I go back and look at my code. I'll look back and forth, and if I still don't see the root cause, then I'll fire go up gdb instead of adding print statements. Debugging a unit test is often way faster than recompiling.

> Using a debugger only makes sense if you're dealing with a mess. Fix the mess don't build tools to work with it.

I have long thought if I was unable to use a good IDE debugger I would just change careers. Why? Because often I make the wrong assumption about the data that library or 3rd party functions will return. It is not until I can actually see the data that I realize my assumptions were wrong.

Using a debugger is all about learning, and debuggers make learning faster.

I am often able to code rings around my peers on projects. And I believe the reason is not because I am better or smarter but because I use a good IDE and debugger. And they don't.

Just because two famous people didn't like step-debugging doesn't mean that there shouldn't be proper debugging tools beyond printf() ;)

We need to be careful not falling into the cargo-culting trap (same as "Goto considered harmful").

Sure, blindly accepting what famous people say is problematic.

But then again, Kernighan, Pike and Dijkstra became famous because of merit and not because of whatever made Kim Kardashian famous. So it seems reasonable to at least entertain the ideas people like them put forward, instead of dismissing the ideas outright, or dismissing the idea after only the most superficial thinking.

Dijkstra make an argument that proper control structures (if-then-else blocks, for-while loops) are better than simulating those with goto. Not that every last use of goto was bad. And if you look at what code looks like these days, seems he was right, or at least most people thought he was. People do not use goto anymore, except for a tiny set of special use cases where it makes sense. E.g. jumping into the "cleanup" end of a function in C, because there is no better way in C to do it, really (no RAII, no python-style with/C# style using, no go-style defer). But you'll hard pressed to find good code, or any code really, that uses goto to implement looping when there are language level loop structures available.

The idea here is not that "all and every use of side-stepping debuggers is considered harmful", and not even "just sprinkle some printf()", but to use a combination of good self-checking design (aka defensive programming) that allows to sprinkle printfs()/tracepoints/breakpoints at interesting places instead of blindly stepping through huge blobs of code in a debugger.

I like to do that myself, add some error checks and some "output" and it usually works great and is very productive, and having the additional error checks is useful not just then but also in the future... But when it does not work out (legacy code, code other people wrote and I am unfamiliar with) then a step debugger might be a viable alternative especially if the other remaining alternative is "nothing/ask magic eight ball".

Sorry for moving the discussion a little bit off-topic, but I like the "Goto considered harmful" as example of cargo-culting, because Dijkstra wrote this when structured-programming wasn't common, and a goto at that time literally meant "jump anywhere in the entire program", like GOTO works in BASIC.

Meanwhile, goto has been tamed and has become a part of the structured-programming-toolset. In C you cannot jump out of the current function with a goto, it's just a slightly more flexible break/continue/return. The meaning of goto has changed completely, yet people still use the "Goto considered harmful" meme as if nothing had changed since Dijkstra wrote that ;)

Well, even today it's not entirely useless as a mantra: I have seen a lot of "hardware" and "signals" people in university who when they first had to do something in C or C++ used goto instead of loops and if blocks, coming from an assembler mindset probably where you have to (conditionally) jump (aka goto) around. I had people come to me and ask me "how do you do goto in java" in a java class where I was kinda a TA-A. "Goto considered harmful* (*most of the time, in higher level languages)" still served as a good enough rule of thumb.

But the actual argument Dijkstra makes against goto applies to any use of goto (and would also apply to break and continue and to multiple returns).

What would they know? This was written twenty years ago, and as far as I'm aware neither of them had any relevant experience with languages that allow for debugger-driven development, production quality selective tracing or anything of any sophistication.

A lot of programming these days involves sending and interrogating complex data via awkward and ill-documented APIs and no amount of abstract a-priori thought or judicious print statements (and good luck printing anything with complex structure with C or Go) is gonna come anywhere close to being able to do an API call, drop into the debugger to see what's going wrong, pretty print deeply nested data sanely and effortlessly and fix up some code or value in the debugger and press on.

Sounds like you could benefit from a REPL!


> awkward and ill-documented APIs

As someone else mentioned, that sounds more like a work environment problem. If using a debugger allows crap like that to persist, maybe it's better for everyone if we tone down how much we use debuggers.

> As someone else mentioned, that sounds more like a work environment problem.

Have you ever used any API by, say, a major cloud provider? For example biquery and google docs are both a buggy mess despite being high profile, long established products. Using libraries like pandas is an exercise in figuring out by trial and error how to get it not to corrupt your data and which of the recommended ways of doing things don't involve a 2 orders of magnitude performance defect.

I'm in fact mostly using "repl"-driven development for these types of problems, but of course it's inferior to what I mean by debugger driven, a crippled subset to be precise. The scare quotes are because most languages don't have real read-eval-print-loops either, but something less powerful.

Unfortunately to the best of my knowledge there are basically only two languages which support this: Common Lisp and Smalltalk (maybe factor or something else really fringe also does) and I haven't used either in years. What I mean by debugger-driven is that you can literally write your whole program without ever restarting it, from the debugger. By contrast python can't even properly reload a changed module definition.

Interestingly it's hard to google good explanatory links, but the point is that you can write some code, run into a problem, and fix the problem right there, in the debugger without aborting the current execution or unwinding the stack.

Common Lisp and Smalltalk both have a bunch of unique features that make this type of thing very powerful, for example some function calls another function that's not defined. In python you'd be screwed at this point and restart your program from scratch. In Common lisp you can just write the missing function, compile it and continue the current execution frame as if nothing ever happened. This is because Common Lisp, unlike any remotely popular language allows for resumable exceptions where you can continue from just before the error happened (it also has the usual stack unwinding exceptions).

Here's two examples I found :

https://www.reddit.com/r/programming/comments/65ct5j/a_pytho... https://malisper.me/debugging-lisp-part-1-recompilation/

Concerning low-overhead, selective tracing (that you can run in production without fear of bringing the system down): erlang for example has nice support for this, see e.g. https://github.com/massemanet/redbug (although again unfortunately it will be difficult to grok without any prior erlang exposure).

We are on the same page friend!

I’m glad to know that I’m not the only one who thinks a bunch of the cloud APIs are insane. I haven’t had to deal with any of that for about a year now, and I’m quite happy about that.

Re: Python and pandas, I haven’t used pandas, but did quite a bit of numpy/scipy/matplotlib stuff in the past. Jupyter doesn’t quite get where you’re talking about, but it does handle the “only have to repeat one step” things very well.

I only recently feel like I truly grokked the magic of the Common Lisp restart/repl/debugger stuff. Just the other day I used quicklisp to install a package, and at runtime it failed to open a C library (libsdl2-image.so or something like that). While it was paused asking what to do, I used apt to install the missing libraries and told it to retry. Boom. Program was running, and I hadn’t had to restart it even though a shared library was missing when I started it. Hacking on the little game I was fiddling with, I could C-c C-c as I added features to the game and keep playing it with the changes. Un-friggin-believable.

Edit: and yes, if I have to make a backend service these days, it’s generally in Elixir. Not quite the same as the beautiful Lisp stuff, but when your processes are expected to die all the time, it’s really easy to iterate quickly while keeping the system up. I feel like mistakes I make while writing Elixir code help make my overall system stronger.

Yup, your apt-get example is a great one. It's very frustrating that the ability to do stuff like this completely died with common lisp/smalltalk . I don't see the appeal of exceptions at all if you don't have restarts (I'd rather have Either).

I agree, if I get to write my own code from scratch.

Working in a pre-existing codebase with millions upon millions of lines of code written by others over years, having absolutely no idea in what context the function I'm looking at is even called, I need be able to to set breakpoints.

Hi. I'm the author of the post.

I actually agree here. As soon as I realize it's going to be non-trivial for me to trace the issue with the debugger, more and more instead of setting up complex conditional breakpoints I just step back from the computer and have a think, and white board out my current understanding of how things are working. Usually, I get an "aha!" moment pretty quickly.

However, sometimes I don't get that "aha!" moment quickly, it random issues endup taking 30 minutes to hours to trace.

What I've come to understand is that's only true because debuggers are so limited. For example, white-boarding out the problem is incredibly useful.. why can't my debugger do that for me, by presenting a number of ways to layout the program and some filters so I can just view my code as graph and watch execution slowed down? Today's debuggers even in their best form are like looking through a tiny peep hole at a picture, I can only see this tiny section... but I want to see the whole picture.

I agree. And isn't programming about automation and repeatability? A debugger session is manual and one-off. Early in my career I spend a lot more time in debuggers. Now I go months without using a debugger.

I try not to get into situations where I need a debugger, but when that happens, having a debugger makes a huge difference compared to using print.

Absolutely. But I think that years of programming has resulted in sort of an internal/mental debugger :)

Debugger's have uses beyond just debugging.

The article mentions a strategy of defining an empty function with a breakpoint when writing code for new functionality -- 'I'll write that code when I hit the breakpoint' can be an extremely fast path to productivity boost. Having the program state in front of you to inspect can really make it easier to write the code for a new piece of functionality.

it's odd that debuggers aren't helpful.. aiding in problem space reduction should yield improvements, but maybe depth first tracing is not the right tool here

I would argue that both Brian W. Kernighan and Rob Pike are exceptionally bright individuals, and likely able to hold considerable more abstract complex thought in their heads than the average person.

One of the bigger blindspots I find with exceptionally bright people is they have no concept of how their brains are capable of things that far exceed the capabilities of the average person.

Or said another way, of course Brian W. Kernighan and Rob Pike think that thinking harder is better than experiencing and seeing. Because they can.


Totally agreed: proper thinking (not hard thinking but proper) on how to design programs makes most debuggers tools useless. Kernighan and Pike, but also Thompson, Ritchie, etc. perfectly understood this 20 years ago. I would not be surprised to learn that they never had the needs to use something like gdb in their nontrivial C programs.

I think JavaScript is a special case because it's too much complex by design.

It is much more efficient to place a breakpoint at the spot where you would have put the print statement and then inspect the values of interest in the debugger.

It is a rather false dichotomy to assume that using better tools precludes you from "careful thought".

A debugger is very useful when you trace a piece of code that 1) you understand well and 2) executes relatively linearly. JavaScript is pretty terrible at both.

First of all, most JavaScript apps are just a small chunk of code that sit on frontend frameworks (React etc). This means that using a debugger, you mostly step through framework's internal code base, of which you have a vague understanding at best.

Also, an average JavaScript code hops around -- to put it mildly -- crazily, due to the heavy usage of anonymous functions, callbacks and other very cleaver abstractions.

This is why placing a bunch of console.log beats tracing with a debugger.

What? It jumps around because it’s running in an event loop. Not because JS devs try to do too many clever things. You hope it jumps around often or you might be blocking the loop for too long.

Now that you brought it up this way, I realized that I rarely step through the code. Instead I use break points + inspect like I would a print statement.

I find this a much more efficient workflow.

I actually really like to debug Ember.js code. Because of the source map, I always see my code as I wrote it and I know when I'm in the framework's code.

Clean, well written and modern framework, like Ember.js, which supports the developer and help to implement features much faster are great choice and you won't have those problems what were mentioned in the article.

Use great tool not the hyped one...


I have to admit I find it rather puzzling and a little amusing that randomly promoting Ember.js, of all things, is apparently your thing.

I mean, I sort of get the intercooler.js dude (if anything, I kind of came around to his perspective and am now all in on Phoenix' LiveView), and I can see how people would be all into Go and whatnot, but Ember is one of those things that I just don't understand anyone being evangelical about..

Then why create new languages at all? Just 'think harder' about the assembly language someone else wrote.

Not a big REPL user I guess.

I have an error reporter in my app and it is completely useless. Stacktraces are usually just starting within some library and never touch app code. I rarely know where the error started, I just have to guess. It sucks.

This is excessively common. Somehow modern debugging is essentially as nuanced as "shit broke". Debugging was far better 20+ years ago under C++ in win32 then it is today - there was working code navigation as well.

I don't know how we have fallen so far back in the tooling.

PHP debugging is fine. Conditional breakpoints, walking up the stack trace, interactive and in-place code execution at run-time meaning I can run code or change variables and it applies to the current execution. It's great.

Javascript is a nightmare to debug, but it's probably just as much to do with architecture than debugging tools. Of course it's hard to debug a dozen tiny libraries written in ES6, transpiled down to ES5, packed into a module loader and flung around asyncronously.

JS debugging is fine in native, dependency free code I write. My sense is that debugging was something a lot of newer developers found out about later in their education and as such they never noticed debuggability and tooling wasn't up to par with older tooling.

Python and perl are good too. I've heard marvelous things about emacs consistent uniform debugging interface that takes care of the magic behind the scenes. In so much as my friend just gets debugging as a built-in feature when he transits around languages without any knowledge of the underlying invocation and implementation nuances. He has the same keystrokes and interface in all the common languages. It sounds magnificent. One day I hope to actually take the time to learn it.

I found it extremely hard to learn Emacs and memorize all the keystrokes. They made no sense. After some time I gave up. Learning Vim was a breeze compared to it.

Debugging Java/Scala works really nice in IntelliJ. It also has support for asynchronous stack traces which means to say that stack traces are carried over asynchronous boundaries. It even works for callbacks.

Edit: Having said this, I still rely more on creating test cases to capture what I think should happen instead of stepping through the code in a white box way.

I'm also writing a C app as a hobby. Somehow LLDB is an excellent debugger in my hobby app.

Same with Swift (iOS) development.

Stack traces for async code are always borderline useless in Javascript. Instead of telling me who called the offending code it just informs me that the code is executed by an scheduler in the event loop.

Are you transpiling your async functions away? IIRC there's been a lot of work on async function stack traces in V8 but it's obviously useless if they are compiled to some state machine monstrosoty before getting executed.

You create the error before doing the async operation, then throw/callback that error with additional info from the async error.

That sounds tedious in a web server where like half the functions are async. Also why is that my responsibility? A stack trace is supposed to show the call chain, not language implementation details. At a minimum `async x()` should just be equivalent to `try { async x() } catch(err) { throw new Error(err) }` (with the logger printing all nested exceptions)

Most of the time you will get the correct call chain, so you only have to do this rarely.

One feature of CUDA that is nice is that while you can use streams to do stuff asynchronously, you can make it synchronous for easier debugging by setting a flag.

Unfortunately, I don’t think something like this is possible with JS promises since new Promise() is guaranteed to execute in a different iteration of the event loop.

Yep, unfortunately you have to consider any NPM (or whatever) libraries as your code, your responsibility & your problem. You might need to get/build source maps for those and debug them to see why it is falling over.

Notable is that such a debugger, that records the entire exrcution and allows you to go back in time, already exists for compiled languages in the form of a gdb plugin: https://github.com/mozilla/rr

Record and replay is coming to Mozilla’s JS debugger engine: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/We...

I don't think the author knows the full set of features available in DevTools. Sourcemaps make the code look familiar. Blackboxing hides the framework stack. DevTools supports conditional breakpoints so in the article's example you could put a breakpoint in the onAddWidget line looking for widget.position.x < 0 or whatever. Voila. Reproduce the bug and you have a stack of your own code that you can inspect and find the issue.

The author is aware of source maps, and has had issues with them

> To add to our list of feedback for Javascript debuggers here’s a couple more frustrations you may be familiar with:

> Buggy source maps resulting from bundling and transpilation tools like Webpack and Babel that cause placing break points to be unreliable, and defined names declared undefined

In my experience source maps usually sort of work in Chrome, but don't really work very well in Firefox (which TFA mentioned).

It's not the debugger that is broken, it's the entire JS ecosystem.

It's such a terrible language we spent the last 2 decades fixing its warts, adding one layer on top of the other. And now you have es7 + typescript + jsx + some magic introspection + code splitting + weird non standard imports, all that turned into regular JS and source maps, in a complex concurrent and async env that deals with graphics AND the network, 2 of the hardest things to get right. All that with zero stdlib, multiple incompatible clients, specific extensions for each store/router/event system you use and hot reload live pushing code.

And you wonder why it's hard to debug ?

I get that tooling is supposed to make our life easier, but let's be nice with the poor guys that have to work on the monster that must be usable with that stack.

I agree with your points.

You can do great things without all that tooling if you're able to only target modern browsers (>90% of the market):

- https://www.npmjs.com/package/htm#example

- https://www.npmjs.com/package/preactz#example-app-with-unpkg

The problems you describe are in most languages. Networking and graphics are quite easy in JavaScript. Nothing fancy or complex about that. Debuggers are super light weight. Build into most browsers. Async programming is just another way. Again nothing fancy and complex about it. You just need to understand it.

E = MC2 is just a regular equation. Nothing complex about it. You just need to understand it.

Meanwhile vanilla JS is a pleasure to work with. Sure, you can't make a monkey write good JavaScript but you cant do that in any other language either.


There's no need to be snarky towards the person you are replying to. Just because you don't like using plain Javascript, that doesn't mean others aren't allowed to, or that their feelings are worth less than yours.


This isn’t that hypothetical forum. Here, just because someone has a controversial option, that doesn’t mean your allowed to be snarky towards them.


> Be civil. Don't say things you wouldn't say face-to-face. Don't be snarky.


Could you please not post like you did in this thread? There's no need for nasty discussion and we don't want it here.


You can use Object's as name-spaces, it's somewhat common to use var global = {}; and then put all global variables in it. But I'm a big fan of scoped imports aka. require in Node.JS. Scoped import is like a global variable, but with the complexity of a local variable. I prefer function scope as it lets me declare variables where ever I want, but others prefer block scope, so either choose var or const and stick to it.

JavaScript was not the only contender for the browser, Microsoft tried with vbScript, and Google tried with Dart. Then there was ActionScript (flash) and Java (applets). And also ActiveX. Then there are various compile to JS languages, like CoffeeScript, yet plain old JavaScript still seem to be the most popular.

My own pain point with JavaScript is that it's getting more and more complicated. Personally I like to use the bare minimum when it comes to language features. And work with the language instead of against it, eg. I make use of null==undefined, I almost never use === unless I need to be explicit. Learn the Equality-Table! Also learn regular expressions! (but don't over-use them). Learn how to use closures, learn how to use callbacks. Then it becomes enjoyable. Bonus: Learn how to program without global variables, then use them when it's convenient. Learn how to program with only pure functions, then use impurity for convenience. Learn test-driven-development, but only write tests where they're needed. Learn how to program without an IDE (just plain text editor and command prompt), then go back to the IDE. All the great tools that exist today make you not feel the pain when writing unmaintainable code.

A more apt metaphor might be hand tools vs power tools, in case you someday arrive at a place where you're actually trying to explore the point, but I suppose the intentionally stacked metaphor choice is a convenient signal to onlookers.

Given that:

There are two types of code: Code that implements the “domain logic,” and code that implements various abstractions used by the do an logic.

I suggest that:

We need to be able to both debug the domain logic and the abstractions implementation independently of each other.


In simpler days, the above was accomplished with feature like “step over,” or, “run until.” We simply skipped over the lines of code in functions that were “beneath” the level of abstraction that interested us.

Today, there are many deep layers of abstractions, and sometimes important things happen within them. Similarly, our data itself is wrapped up in bundles of abstractions, and sometimes we need a way to view that data as an abstraction, at other times we need to “drill down.”

We have sophisticated tools for building abstractions in code, and we have a way of bolting some of that onto the debugger by means of source code mapping.

But that is not enough. We need more sophisticated ways of stepping through our domain code that understands the abstractions it is hiding. We need to be able to write a framework or library, and write debugger extensions to go with it, just as we write extensions for our compiler toolchain.

That way, we can build abstractions that can be “stepped over” in the debugger as easily as we can write our code in abstractions in the editor.


I think everyone realizes this and is rushing to implement point solutions here and there, like special cases for async stack traces when you use JS’ built-in async/await. And the author is hinting at an important “special case” for React.

I suspect that we need a more general facility for meta-debugging, much as macros are a more general facility for meta-programming.

Blackboxing in Chrome has a major issue, it's impossible to blackbox these "anonymous" tabs that keep on popping up, which sometimes makes debugging a nightmare.

I don't have huge problems stepping through code in browsers (source maps, async/await, blackboxing helps a lot), and when I do, I just resort to console.debug logging, but what I would like to know is why Firefox' source map support (at least when using Webpack) still is so terrible? And why is it OK in the Developer Edition of the browser? Why have two different implementations? Is Mozilla intentionally hobbling the regular edition of the browser to justify the existence of the Developer version (because other than the source map support, I don't see the point of the Developer version)?

And then people complain about developers only testing in Chrome, well...

Javascript is the only language I've worked with that doesn't offer a good debugger/breakpoint system, and I agree with the reasons/issues stated in this article.

It makes working with JS (and Node) a real pain in the butt. IDEs such as VSCode offer integrated debuggers etc. and I wonder if anyone ever was able to use it for anything more than a few lines of vanilla JS. I can't seem to understand how those would work.

I compare this to my experience with Ruby, Python, Go and Elixir, and all those languages have either a great debugger or an alternative that makes debugging much easier than going through a "guess based feedback cycles" with JS.

I guess I must not know what I'm missing. I've used Visual Studio for 15 years. gdb for 10. I have zero issues using the Chrome's debugger. I don't no what's different about the breakpoints in Chrome. The seem to offer the same features.

Well my main issue is that most of the time when I put a breakpoint somewhere it just doesn't work, or if it works it's pretty much impossible to move forward or backward from line to line because, as stated in OP's article, going to next line in a React app for example, you end up in the actual React implementation code instead of being able to separate framework and written code.

To me, that makes it pretty much unusable enough that I just don't try to use anymore.

You can "blackbox" certain code in DevTools so it doesn't pollute your debugging sessions, made a gif of how it works & what it results in: https://umaar.com/dev-tips/128-blackboxing/

I like this article -- its accurate and describes real pain points in the existing debugger experience which _do_ have technical causes that might ultimately derive from language/ecosystem design flaws.

I'm curious to see what the author's team is building. I'm quite excited to see language-level adoption of debugger features. It's unfortunate to me that debugger's are mainly bolted onto the side of language's with (at best) convention-based 'semantics' baked into the compiler/interpreter implementation. With the interface to 'debugger semantics' completely outside the code and with at most a language-level 'debugger' statement that is basically equivalent to adding a non-conditional breakpoint.

How about a language with a debugger() {} block so that the full logic for logpoints, breakpoints, conditional breakpoints can be persisted in the source rather than in inherently non-scalable ide gui -- and specified as part of the language design?

How about something like debugger() { setRewindTarget("point1") } -- for capturing a particular program state in a returnable way -- with perhaps some semantics/heuristics around the behavior of common abstractions which interface with out-of-process contexts (sockets and files) -- and/or the ability to specify what you want to happen to those kinds of resources inside the debugger {} block so that you can quickly create exactly the rewind behavior you want for a particular task.

How about more dynamic and easily queryable debugger contexts: 'break on first execution of each line in each function in project (for reading a new codebase)', 'break on first execution of code changed in last n commits', 'break on code written by author according to git blame' ...

How about features for augmenting print() based debugging. Baked the semantics needed to support a value-history inspector into the language implementation. A program should be able to describe 'debuglog(matrix as image)', 'debuglog(object as tree view)' and the _language_ should ensure the program is compatible with a debugger protocol able to communicate with an appropriate rendering context capable of displaying these in a useful way ...

I remember IE5-6 ? Where it only told you there was an error, and you had to figure out where yourself via alert("this is line 500") Being used to that I think Chrome dev tools are amazing. I love that you get a call stack on every console.warn. All those console.log's can be removed in production via overloading or marking them as pure functions when minifying, as they will otherwise slow down the app. There's otherwise no cost to having extra info available in the dev console, besides Firefox debugger being terrible slow 100x slower then the Chrome console.

I wonder if this is the reason that js tends to fail silently or convert types whenever possible because debugging errors was just too hard.

This was Netscapes design. I would guess it was designed like that because it was the simplest way to handle error conditions. Initially JavaScript was very simple and didn't even have exception, so there was no way to report errors.

I think Microsoft just didnt want you to develop in JS, but they where forced to support it.

It's great to know that you're working on this. The tools are definitely lacking on that regard.

Certainly not 'broken' but definitely room for much improvement.

s/Javascript/React (e.g. debugging works fine with stimulus/coffeescript for instance)

You're not wrong, as far as I can tell.

I'm a rails dev who has some godawful vanilla JS and JQuery in my legacy apps, that I maintain to some extent. We've largely avoided the quagmire of component-ized frameworks like React, Vue, keeping our JS footprint as minimal as possible while other teams in our area have moved ahead timidly, doing Vue without TypeScript or any build tools.

The one place where I have started moving my ball ahead is by adding Stimulus. It coaxes me toward Object-oriented (or at least Controller-oriented) JavaScript with ES6 classes. It has been a major success story for our small dev group which doesn't have the capacity to go crazy with a new language or framework, after deciding on Ruby+Rails some time ago. We can use it without major upheaval, it works with our legacy apps too without build tools, without much icky feeling, and without even webpack, just plain asset pipeline, while our internal-only application customer base is able to guarantee we are serving pages to a baseline of "current supported version of Chrome" that has given us the guarantee of needed support for ES6 and no concern about legacy browser support.

And one problem I also don't have, is any issue with the JS debugging support that is provided natively in Chrome, plain and simply put. I just write a "debugger" statement where it is needed in the code, ensure I have the JS console open when it reaches that line of code, and my debugging environment is roughly as powerful as pry-rails, no problem.

You shouldn't be using debugging in dev tools to help write your code. Do TDD instead. This should help https://www.youtube.com/watch?v=B93QezwTQpI&t=2581s

Money shot: "In my next post I want to take a step back and imagine programming nirvana… what if we could design a better debugger? I’ll be talking about the work me and my team at Raft have been doing to build a new type of debugger and experimental programming experience for building reactive GUI applications, without the mess."

When I first learned to code in TurboC some 30 years ago I used the IDE's step debugger for a while. Although a great tool for learning how your code executes line after line, I stopped using it after a short while because I found it to tedious and distracting to go through the code this way. Being able to set a breakpoint and see the actual value of something I can do in an instant with logging. I never use the browsers 'debug' tool, tried it once and got bored already..

IMAO the code I'm working on should live inside my head, that's the place where I construct and debug. Even when I'm new on a +100Kloc project, I'd still prefer to read through the code and figure out what's actually going on.

I see so many developers in the JS world spending soo much time and focus on all those tools; types, eslint, debuggers, testing, etc.. it makes me really suspicious about whether they have the actual skill to write a good codebase. I can only be convinced when my code would be buggy and theirs flawless, but I never see that, it's for some reason always the other way around! Just take a look at this project (a random pick) with all those (virtual) safeguards, result: over 3600 issues!!! https://github.com/Microsoft/TypeScript

I mean 3600+ issues, WTF!! And I should be using this tool to prevent issues in my software? Sometimes it feels like this entire industry is broken.

I agree.

Some “safeguards,” eminently type systems (with algebraic types and inference if possible) I've slowly come to value a lot, especially when maintaining a codebase for an extended period of time.

But otherwise, I still prefer to reason over my code, with the aid of at most a few debug prints / logs. Breakpoints I only use very rarely. Other debugger features not at all.

But I remember it taking many years before I could build a complete working picture of the code I was writing (or reading) in my head. Before that, I was basically horsing around. Thankfully, that was around high school time. By the time I got into the industry, I already had that skill finely developed.

Nowadays, I don't think many fresh graduates have it, and yet the industry prefers hiring them, rather than more experienced (older) people. Go figure.

Hopefully there are many brilliant alternatives to TypeScript, eg. Reason, PureScript, Elm.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact