Dev Consoles Considered Harmful (…for learning)

TL;DR

The developer console appears to be a standard kind of JS environment in the traditional and familiar REPL style of workflow interaction, which lends itself naturally to learning JS in line-by-line progressive coding.

However, there are a number of troubling issues where the console environment isn’t exactly the same as what JS devs can expect of a real JS program. Traditionally these rough edges have been considered just quirks of implementation that are at least eligible for possible correction.

I now believe these things are not just accidents but are intentional designs intended to dissuade precisely the kind of coding that “learning JS” approaches have always encouraged. That means we’re fighting against the current rather than going with it as I’ve always felt.

The developer console is designed in such a way that presents several active hostilities to the process of accurately learning JS line-by-line. We can’t change the design intentions of the console, so we need to find/build a different tool for aiding interactive JS learning.

At a minimum, this tool needs to look, feel, and behave almost identically to the dev console — that is, like a familiar REPL — but it needs to be preferred over using the regular console, specifically when you’re trying to teach or learn how JS really works. Don’t use the dev console for these purposes anymore.

Pains

This post pains me to write, on a number of levels.

First, years ago I worked for 8 months on the Developer Tools team at Firefox. So I hold the concept of devtools near to my heart. I believed then, and still believe even as I write this sharp critique, that devtools are the most important tool for outreach to and between developers. I remember a time in my dev career when not all browsers took devtools seriously, and it’s encouraging in 2015 that all the major browsers (yes, IE included!) take it very seriously. We need these tools, and the tools the browsers provide us are amazing!

Secondly, it pains me because my primary “job” for the last almost-3 years has been dedicated to teaching JavaScript to developers, not only through my YDKJS book series but mostly through JS workshops, both publicly and corporately.

I take this job very seriously, and consider it a solemn privilege to be trusted to do so for so many developers. Over these last few years, I’ve taught well over 100 live workshops, with on average 20 attendees each. That’s perhaps over 2,000 developers I’ve personally taught and guided through understanding the ins and outs of JS. I’ve also had thousands of developers view my online training courses via Frontend Masters. The YDKJS books have sold more than 10k copies, and the repo has over 13k stars (of which I hope many have read some or all of the content).

In all of that teaching in my various capacities, I’ve always felt that there’s no substitute for actual hands-on coding experience. No amount of lecturing or slides on my part will mean anything for your learning unless you actually type code yourself and see it work on your screen.

That’s why this post is so hard to stomach, because the developer tools console has been such a natural and integrated part of all that teaching. And that’s certainly not unique to me. Nearly every JS teacher and workshop that I’ve ever heard about or attended uses the same technique.

Learning Via Console

A great illustration of this approach is the fantastic JSforCats.com site, which is one of the best and most approachable “Intro to JS” resources available and I recommend regularly. As you can see, they use and endorse others to use the dev console to get hands-on learning experience with JS:

jsforcats

I do the same thing in my YDKJS: Up & Going book:

upgoing

Why do so many of us recommend and demonstrate learning of JS with the dev console?

Because when you’re teaching a complicated concept, most people will struggle to grasp the important parts if you just throw an entire program of several dozen lines at them. Instead, teaching is usually done on a line-by-line, statement-by-statement, concept-by-concept basis.

I teach you how to declare a variable first, then I teach you how to assign a value to it, then I tell you how to… That’s just the natural way to piece-by-piece build up learning of a bigger concept.

The thing is, the concept of learning and experimenting with coding on a statement-by-statement basis is not new to me or even to JS. REPL based coding, either in separate tools or integrated into IDE’s, has been a standard way of piece-by-piece coding for 50 years of programming history, across nearly every language ever designed.

I’ll call upon the authority of that precedent to back up my style of teaching based on interactive line-by-line coding.

So it is only natural that dev consoles which represent themselves very much in the spirit of a progressive REPL would be the go-to tool for such JS coding/teaching. It’s natural, and it’s easy (don’t need a separate tool!), and it’s helpful (the console can interact with a browser page for more complex output/interaction).

Let me be clear: not all learning is done at the console. The more complete and complex your coding becomes, the more likely you are to start turning to a full JS program in its own file(s). Of course, I also teach with full-scale program exercises, too.

It’s just that the initial steps in learning a new concept piece-by-piece seem to fit naturally into the dev console “REPL” environment.

Console Illusion

The natural nature of running code in a line-by-line fashion using this console “REPL” is reinforced by how the console intentionally tries to mimic running code in a normal global scope environment of your program.

console1

Anyone who’s ever worked with the console knows this to be true. If you open up the console on an existing page, and you start trying to type code, you know you’re, at least in effect, running in the global scope of that page’s program, meaning you can access its globals, etc.

If you declare (or redefine) a variable or function in the console, it’s added to (or updated in) the global scope of the program, such that other code running in the page would see your changes.

I won’t belabor this point anymore. But it’s totally self-obvious that the console appears, at least to any but the most careful eye, to be roughly indistinguishable from the global scope of the program.

That’s the starting point observation that any new-to-JS learner immediately picks up on. All the nuances under the covers of how that really works are totally irrelevant (to them) implementation details. Or, at least, I would say it should be that way. That’s the natural with-the-current path, anyway.

Console Disillusion

There are a number of problems with the console “REPL”. Thus far, everything I’m about to list here I’ve taken as various quirks and annoyances — caveats to tell devs about only if they run into them. I’ve reported many of these as bugs to various browsers, and fought (by way of long discussions in those bug threads) to convince the devtools teams of the importance of addressing them.

I’ve always represented that my perspective was how these things harm the learning experience of JS. And I’ve been frustrated time and time again that this perspective doesn’t seem to resonate with those teams. This morning, I realized the source of this long-held dissonance.

A developer on one of those teams actually said that they intentionally design the console to discourage this kind of coding. Let that sink in. More on it later.

Problems With Console

Let’s start by talking about how developer consoles represent values when evaluated. You might expect that there’s a precedent to represent it the same way that you’d interact with the value in your program, but that’s not at all true. There’s no standard that controls how devtools should make such representations, and they tend to pick representations of values that match whatever their mental model is for the value, regardless of how accurate or inaccurate that might appear, especially to a new JS learner.

Here’s an illustration of how the consoles represent a String(..) object and if that’s different than how they represent a primitive string value. These are very different values, and should be represented differently.

console2

As you can see, older FF used to represent a string object almost identically to a string primitive (subtle italics were the only difference — look closely). Newer FF, at my behest, at least represents it much better. Credit there, where it’s due!

Chrome unfortunately does this bizarre thing with showing the [[PrimitiveValue]] property, which isn’t a property you can access and is the notation only used by the specification to indicate a hidden internal property. Why on earth would they show that to a developer if there’s no direct way to access it? This trips up students in my classes regularly.

Newer FF is the best so far, but it’s still not how I think this value should be represented. I’d represent it this way:

var s = new String("abc");
s;
// String { "foo" }

That representation makes it clear it’s an object (via the { .. }) and that it’s inherited from String, but doesn’t confuse the developer with an implementation detail like suggesting the internal representation of the string is some sort of pseudo-array-like value. It’s not a real array, so any suggestion that it is, is nothing short of misleading to a learner. Moreover, JS devs don’t typically treat strings as arrays, so it also doesn’t fit with canonical JS development.

And that same pattern would work as Number { 42 } and Boolean { false } representations. Those cases are still much worse in both FF and Chrome. Try it and you’ll see.

What about arrays with empty slots (which btw are terrible as a feature in JS — never do this on purpose!)?

console3

Here things are similarly ugly. Chrome is horrible here, in saying [undefined x 3] to represent 3 empty slots, which as you can see is a very different value from a [undefined,undefined,undefined] array of undefined values in 3 slots. Nearly every developer, especially new learners, looks at “undefined x 3″ as basically the same as “undefined,undefined,undefined”. But they’re not! Why on earth would Chrome represent it this way? It’s awfully misleading/confusing.

FF also used to be pretty bad, by representing the value along with the trailing slash (the one that’s dropped/ignored by ES5+), which when representing “[ , , , ]” implies there are four elements, not three! WTF!?

Their reasoning was they wanted to make it easy to copy-paste a value like this from the console output into console input or a program, in which case it needed the trailing slash. WTF!?!?

Thankfully they’ve improved it in newer FF, again after my exhaustive complaints in a bug, to say “[ <3 empty slots> ]” which honestly is about the best/most accurate I could suggest. Chrome needs to get on the ball here.

OK. Get the point? The consoles are actually not very good at representing values accurately in a way that’s not confusing to new JS learners (or even seasoned devs). Ugh.

Blank Tab Isn’t An Empty JS Environment

I actually wrote about this problem with ‘blank tabs’ back in July of last year, so I won’t belabor this post by dredging all those details up again.

Bottom line: typically when a dev is learning and playing around with JS, they want a fresh clean unaltered JS environment to start with in their console. I, and many devs like me, are accustomed to using the “about:blank” page to do, or in some cases, the “new tab” page.

However, this environment is not at all the same thing as an empty fresh JS environment in all cases. There’s several ways it can be not fresh at all, including what browser extensions you may or may not have installed, as well as how you actually opened the tab, as well as browser settings, as well as intrinsic browser behaviors.

You have to try a lot harder than I think you should to actually get a fresh blank empty tab and console environment. This should be as simple as a click of a button or a keystroke, but it’s definitely not unless you pay close attention. See my blog post for more info.

Misc

There are several other miscellaneous differences/divergences. For example, certain types of static errors that you can expect from normal JS programs do not happen in the console.

Because of entering your program line-by-line, “hoisting” is not really a thing (at the top level of scope). That’s not really the fault of the console “REPL”, but it’s something that can trip people up.

There have in the past been inconsistencies with how implicit global declarations were handled, where they didn’t really create a global in the same way that an explicit var .. declaration at the top-level scope would.

If you run delete window.x in the console “REPL”, it deletes not only the window property but also throws away the lexical variable x. In real JS programs, this doesn’t happen. This problem is exposing an implementation detail, in that your code is actually always run by wrapping it with with (window) { ... } so as to make your global window properties available as lexical global variables. That’s why delete window.x makes the faked lexical x disappear.

Sigh. This rabbit hole keeps going deeper. You get the point.

Non-strict Mode

This issue specifically is the one that has spurred my presently declared war on dev consoles.

It started innocently as a request to fix an annoyance I’ve long had, and many, many, many other devs and learners have had, which is the frustration that you cannot make your JS environment in your console operate in strict mode. Everyone seems to naturally try this on the first line of a fresh empty console:

"use strict";

They get no feedback that this has actually failed to apply strict mode. So they think it worked, naturally. But it didn’t. And they get bitten when they later try things like checking how this binding with functions is supposed to be different in strict mode, but isn’t.

Every single road bump you throw in front of new JS learners hampers an already difficult task of understanding how the language works in all its idiosyncratic ways.

Eventually, they figure out they have to do something hacky like wrapping the whole code in an IIFE:

(function(){ "use strict";

  // .. type your code here

// don't forget this line
})();

Ugh. That sucks.

So my filed bug was asking Chrome to either support the "use strict"; pragma on the first line of a fresh console session, OR to give a devtools config setting like “Always force strict mode” which could just force the console to always be running in strict mode (if you so choose). You could toggle the setting and refresh the console if you wanted to flip between the modes.

Seems reasonable enough, I think. But nope. Big pushback.

Snippets

It is strongly asserted in that thread that to do strict mode, the only proper way is to use the Snippets tool in Chrome’s devtools (Firefox has a similar feature called “Scratchpad”). If you’re not aware of these features, go spend a few minutes googling and trying it out yourself.

But the bottom line takeaway is that in this workflow, you don’t consider your program being entered line-by-line in a REPL like fashion. You write the whole program at once, and then you run it. You do that by entering it into a snippet (or scratchpad session), and the telling the tool to run the whole contents. Or, you can highlight a specific line manually, and tell it to run only that one line.

When Chrome runs your snippet, it outputs … guess where? Into the console. But beware! You don’t want to go start interacting with your program from the console “REPL” then, even though it’s there and so tempting. No, because despite the fact that your console “REPL” and the snippet will have shared the global scope, the “programs” are separate, so things like strict mode will not apply now to your console “REPL”. Gotcha!

Also, once you now make a change to your snippet, if you try to run it again, but you forget to do CMD+R refresh to reset the console and global scope, now your program will be overwriting itself. So it’s not actually like a whole program, it’s like a way to write an entire program in a single REPL step. Or some weird bastardization thereof.

Or, you can write your first step in the snippet, run it, then highlight everything in the snippet (EXCEPT the "use strict"; line, because that needs to remain to stay in effect)… and erase the contents, and then write your second line of code, and rinse and repeat. Ugh, try that yourself, see how clunky that really is.

That’s why below the errors are reported. The first error comes from me trying to run the snippet a second time without a refresh. Even though my snippet run should be a separate program, for some reason it’s still taken as if I’m doing a duplicate declaration of let x, which is in fact disallowed.

snippets

But I’m not really redeclaring x am I? I’m running a whole new program in the same scope. How do I know that? Because if I remove “use strict” from the snippet, and try to re-run, I get told I’m not in strict mode and need to be to do let declarations. So, it’s partially the same program and partially a different program entirely. This is all kinds of weird.

This workflow sucks: having to switch between snippet authoring pane and the console output pane, and remember to do the refresh there before going back to the authoring snippet pane, and back and forth, back and forth.

It also really sucks that I can’t mix-n-match coding between the snippet and the console “REPL”, because if I do, I get weird unexpected results! Gotchas and footguns abound.

More road bumps that make learning JS interactively way, way harder than it needs to be.

Won’t Fix

That Chrome bug thread has, at the moment, been closed as “WONTFIX” and the developer who’s pushing back is saying that not only won’t they do this, but that it’s totally invalid to want to run strict mode code in a console.

This is what I was referring to earlier when I said “more on it later”, where the new revelation is:

This implies you code in console which it is not optimized for. We don’t want to encourage implementing complex functions / snippets in console atm.

You see, these problems I’m finding with the console behavior, and the mismatch between that and using the console as a way to progressively teach and explore coding with real and accurate JS behavior… that’s against what the team who builds the tool wants you to do.

It took me several years of frustration and banging my head against this wall to finally realize this simple fact: I will never succeed in getting the console “REPL” to be the learning-friendly tool I think it should be.

No, it’s clear now.

Different Tool

We need a different tool. IMO, at a bare minimum, this tool would need to:

  • Either be built into the browser dev tools, or a plugin you can optionally install.
  • Look like the console currently does, in that it would have a progressive step-by-step “REPL” workflow look and feel. I’d get immediate output after entering each line of code.
  • Each fresh session of this tool would be like a brand new program, just one that is potentially entered one line at a time. If on the first line of my new program I put in "use strict";, then my program becomes strict mode. The rest of what I type line-by-line would behave exactly as if those lines had been processed from a file holding the JS program.
  • The top-level scope would really and truly be the global scope.
  • The tool would in fact share the global scope with a main page program, if one exists. Or you could invoke the tool in entirely separate sandbox context, kinda like a “new tab” or “about:blank”, but without all the crazy there.

If such a tool existed, and it was in the browser, would there be any compelling reason to use the classic console “REPL” instead? I can’t think of any major reason you’d want the weird broken console “REPL” if you had a real and true JS environment REPL tool.

Well, that might mean you give up some of the console extensions, like auto-complete, or aliases like print(..), clear(), etc. Since those things are console only, they wouldn’t exist in a pure and fresh JS environment REPL.

Which is more important? Real and reliable JS behavior, or helpful extensions?

OK, so yeah, maybe both tools would need to coexist. But at least we’d have the option of choosing to write code line-by-line in a true JS REPL that doesn’t have all these crazy gotchas.

Not Node

Many people will ask, “Why not just use the REPL in node/iojs?”

  1. Not everyone has node/iojs installed locally. A lot of people don’t have the freedom to run those sorts of tools on their machines. It’s really myopic to think that everyone can easily and universally do such.

    And that goes a thousand times more for a new JS learner, for whom installing and running such a tool could be incredibly intimidating.

  2. A tool away from the browser means that someone can’t use this tool in the context of some web page, which is still a big part of what people learn with.
  3. The node/iojs REPL doesn’t actually support doing "use strict"; on the first line to switch into strict mode. You can do it per line, but it doesn’t stick around for the next line. WTF!? Ugh.

    node-repl

  4. The node global environment is even more “non-standard” than the browser dev console “REPL”.

    For example, the setTimeout(..) provided by node doesn’t behave the same with its this binding behavior as the browser version does. So when people try to test out async coding, where setTimeout(..) is often used to force asynchrony, now all of a sudden they’re getting different behavior than their browser programs would get.

  5. Yeah, so… nope.

    Summary

    Sigh.

    I didn’t wake up this morning planning to ditch my usage of the dev console. But it’s clear now that the problems are not just quirks but flat out incompatibilities in goal and vision.

    We need a new real-JavaScript-REPL tool, and it needs to land in a browser soon.

    I have no idea how to build it, but I guess this is the new thing I have to occupy my time with if I want to preserve/improve the interactive learnability of JS.

    Posted in: JavaScript, Misc by getify 8 Comments ,