Hacker News new | ask | show | jobs
by nicoburns 1891 days ago
When you're trying to debug a JavaScript error do you use Chrome's built in JavaScript debugger, or do run Chrome in GDB? Or perhaps you get out your oscilloscope and try attaching it to your CPU? Debugging at multiple levels of abstraction is nothing new.

The fact that there are debugging tools at the React level does reduce complexity. It means you don't need to understand the details of how React is implemented. You can just think in terms of React concepts (which are quite straightforward and have excellent documentation).

4 comments

It's a bit unfair to expect a framework not to provide new abstractions. That's required almost by definition.

In React's defense, it's far from "impenetrable." In fact, its API exposes less than a dozen attachment points (considering render + lifecycle methods or their hook equivalents). It's a small and stable interface on top of a simple tree structure and an intuitive reconciliation algorithm. Most of the time you don't even need to reason about its logic, but when you do, it's understandable and debuggable.

I'm also skeptical that we should consider framework-specific dev tools as evidence of an impenetrable abstraction. As long as the tools strictly enhance the debugging experience, they are a feature, not a smell. They are evidence of a problem when they only exist because it's impossible to debug the API without them.

For example, Apollo Dev Tools "strictly enhances" the debugging experience. It provides a nice UI for GraphQL specific debugging sessions, but it doesn't break the "network" tab of Dev Tools. If you want to use apollo-client without its dev tools, you're no worse off than you were before adopting apollo-client. But you do gain something if you choose to use them.

React used to be simple, but now its indeed pretty much "impenetrable". Hooks are a strange API. Timeslicing and suspense add a lot of complexety for the benefit of very few. The processes for hooks-like funcitonality (algebraic effects) are supposed to be happening at TC39 (ESNext) level, but I can't see anything going on on that front; instead we have workaround hacks to make the API "look" like algebraic effects.

Things just need to improve there, one way or another...

Is React language or a library? Does it have its own virtual machine? If I'm debugging something that compiles down to assembly, sure, I can use GDB. Why shouldn't I be able to use the JavaScript debugger? Why does React go though so much effort to obfuscate its internals to such an extreme?

There are a million other JavaScript libraries that do not have this problem, for what it's worth.

> It means you don't need to understand the details of how React is implemented.

Isn't this the topic of discussion? Why shouldn't it be as simple as "React stores state in this variable" and I can just poke at it like any other variable?

Abstractions are great, but they all leak eventually. Being able to see through the abstraction is immensely valuable, regardless of where you are in the stack.

Don't confuse simplicity for its pernicious cousin convenience, which usually does not actually reduce complexity, but heralds it.

> Is React language or a library? Does it have its own virtual machine?

It's a library, but it does provide a runtime. As such it IMO makes sense that it would have it's own debugging tools. This is pretty common for complex frameworks. Other frontend frameworks like Angular and Vue do it. As do backend frameworks like Rails and Laravel. Or as another example you could look at Unity (the game framework).

You can still use the JavaScript debugger, and I do all the time with React code. But for React specific concepts the React specific tools provide a better experience.

> Why does React go though so much effort to obfuscate its internals to such an extreme? There are a million other JavaScript libraries that do not have this problem, for what it's worth.

It doesn't do it intentionally. It's internals are somewhat complex for performance reasons. Which other JavaScript libraries allow you to inspect their current state with the JavaScript debugger? That's what the React dev tools give you: state inspection. IME, lot's libraries make inspecting that quite difficult (unless they're using global variables).

> Why shouldn't it be as simple as "React stores state in this variable" and I can just poke at it like any other variable?

The problem is that you can't typically poke variables using the JavaScript debugger. To do so you need to get a reference to the variable. And you can only reasonably do that for globals.

> Abstractions are great, but they all leak eventually. Being able to see through the abstraction is immensely valuable, regardless of where you are in the stack.

I guess I've just never hit into the limitations of the React abstraction (and I've done pretty complex things). Nor have I ever hit a bug in the implementation. To me it's like my database or my filesystem: sure it's great to be able to peak under the hood if I really need to, but 99.9% of the time I'm super glad that I don't. I've had much worse experiences with Angular (1 and 2) where I frequently felt like the abstraction was leaking a lot.

As someone who works on and fixes bugs in databases and filesystems, perhaps we don't look at things the same way.
When you have to debug something in postgres, are you going to communicate with it by using netcat and writing your own TCP messages by hand, or use psql? Both should work, but you wouldn't say postgres is obfuscating things intentionally so that you have to use psql (or some other postgres client). The fact of the matter is that complex tools are much more useful if they also come with tools to pave over (some of) that complexity and provide a more useful interface for developer experience.

You can debug React apps just fine without the React developer extension, but in some situations it will be much harder. And to elaborate, I work on React full-time and just use the javascript console and debug messages for 99% of my debugging.

The Postgres protocol is documented extremely well such that it is actually trivial to write messages by hand, and yes, I have used netcat instead of psql.

I honestly couldn't find anything remotely similar in my search for to unearth React runtime internals. Maybe I'm missing something but literally every answer was "just use the extension."

If you want to debug a performance issue in postgres, then the answer will be similar: "just use EXPLAIN ANALYZE". You could get out a C debugger and step through the source code, and there are cases where that is appropriate (e.g if you're working on Postgres itself, or you're trying to diagnose a bug in postgres), but if all you want to do is debug your query then you should use the postgres-specific debugging tool (EXPLAIN ANALYZE).

Same for React. If you are working on React itself, or you need diagnose an actual bug in the runtime then you'll want to use the JavaScript debugger and step through. If all you need to do is work out why your component isn't updating then you can use the React-specific tool (the extension) to make your life much easier.

If you really wanted to, you could write your own renderer. there’s already several: react-dom, react-native, react-test-renderer. Also, I believe there’s at least one conference talk where a renderer was live coded
Maybe you're just not familiar enough with React? The core of it is actually quite simple, it's just a giant while loop tree traversal that gets triggered either imperatively (by calling one of the React.render* functions) or in response to a state change. The VDOM stuff is mostly orthogonal to this, it just improves performance in the browser, but you could technically skip it without losing anything.

Your argument is basically the same as saying you'd rather view HTML as a giant string rather than in a tree view... the devtools basically present something that's linear (the tree traversal) in a tree view, it's not that bad.

Well, parent can find an error in a React app with a standard JS debugger, you can find an error in databases and filesystems with gdb or some such, but not vice versa.

Can be as simple as that: familiarity.

I don't think that's quite it, at least not entirely. They were complaining about how everything they found on how to diagnose and inspect the problem started with downloading a third party tool.

I read this as a complaint that React doesn't provide information on what's going on that you can see and inspect (whether true or not). The equivalent for Postgres would be if they didn't document their protocol and instead referred you to a special debugger they provided, and you had to rely on that instead, making use of gdb or the equivalent much more complicated.

Except the React devtools extension is a first-party tool maintained by the React team [0]. They don't distribute with the library, but given that it's a browser extension there's not really any way that they could.

Also, the react dev tools aren't really a debugger. There's no way to step through code or similar. It just provides an insight into React's internal state (the component tree, and any state contained within it). The equivalent would be Postgres providing a tool to inspect the contents of its caches or indexes, and the OP complaining that they can't read them when using GDB.

[0]: https://reactjs.org/blog/2019/08/15/new-react-devtools.html

The difference between a language and a library is arbitrary: it’s all an artifact of programming ecosystems that place a hard division between the “language implementors” and the “programmers”: every library of sufficient complexity is an embedded language with annoying syntax.
I got a good chuckle at the silliness of the mental image of hooking up a scope to diagnose a JS error. Thanks for the laugh.

(tl;dr: lol)

Debugging a popular library is not at all equivalent to those layers of abstraction. This argument is so flawed it almost feels malicious.