Edit: Just to be clear, all of the lessons shown on the course page are available, but you have to start at the beginning for now.
2,312 karma · joined August 30, 2013
Edit: Just to be clear, all of the lessons shown on the course page are available, but you have to start at the beginning for now.
English is not a programming language. I'm sure that we can both name revered authors who have used far "worse" grammar consistently throughout their careers, whereas the word choice that has so ruffled you here involves a single word.
As to "flexing knowledge": I had some fun in a couple sentences in that article. I aim to be at least slightly more than a machine that extrudes gray paste of unit density. Fabricating an ulterior motivate and attributing it to me is offensive.
- Visit a lesson page with a code example that returns a string.
- Enter the correct answer to that code example, but without the quotes around it.
- Assert that the text "Almost, but the answer is a string so it needs quotes." now exists on the page.
No implementation knowledge, nothing faked, real UI behavior tested. You can test most app behavior like this. E.g, we test our entire billing system like this, making these kinds of real assertions.(To make sure that we know when actual rendered pixels change unexpectedly, we use Percy, which is a separate topic.)
I switched to Dvorak on regular staggered keyboards and got to 80-90 WPM or so, but pretty much lost my ability to QWERTY.
Then I switched back to QWERTY full time for 5+ years.
Then I switched to Norman on an ErgoDox, getting to 80-90 WPM or so again. This time around, I could still QWERTY on a staggered keyboard just fine. Night and day vs. the first attempt.
(Then I switched back to QWERTY again. Now I can't Dvorak or Norman or ErgoDox!)
But, a warning from experience: when you fly one-way to another country where you'll go through immigration, always pre-book travel OUT of that country as well, and have printed (on paper!) proof to show border control. Fortunately this doesn't apply between most European countries due to the Shengen area. Immigration is not the part of the trip where you want to wing it. Always make sure you understand the visa and border crossing rules in advance.
If you do that stuff, great, but here's what I do if you don't want to use any specialized tools:
My home directory is just a git repo. There's a regular old ~/.git directory. All of the management I do is regular git stuff, like any other repo. Everything I don't want to commit is listed in .gitignore, like any repo.
The only wrinkle is setting up a new machine. You can't clone into an existing directory (AFAIK), and your home directory already exists, so you need a workaround. An easy solution: clone the remote repo to ~/dotfiles, then `mv ~/dotfiles/.git ~`, then `rm -r ~/dotfiles`. Now your home directory is a git repo where the working copy is exactly as the home directory was before, and HEAD points to your repo's default branch. It will be dirty because all of your config files are missing. Use regular `git status` to take a look. It's always looked fine to me, so I `git checkout .`, which effectively installs the dotfiles. Then I'm done until I need to do that copy trick again when I buy my next computer. In the meantime I just commit and push as normal, and occasionally add a new file to .gitignore.
I initially had low expectations because it's such a weird use case, but it's been totally reliable. We did have to ignore a few types of errors from old browsers that don't support wasm properly, but we've never had a bug in current browsers caused by sql.js.
As for apologizing for charging money... you're right there too. A lot of people will complain when someone dares to charge money for learning resources, even those that take multiple full-time staff to maintain. I think that's worn me down a fair bit!
I own https://www.executeprogram.com, which has interactive in-browser courses on various software development technologies. Currently they all cover languages, more or less: TypeScript, SQL, various JS topics, regexes. (Disclosure: it costs money after you finish your 16th lesson.)
Almost all (maybe literally all) of our competitors are amenable to binging. It's true of books, video learning platforms, and most/all other interactive learning platforms.
Execute Program is very intentionally non-bingeable. When you start a course, you get 5 lessons on the first day, then it stops you and tells you to come back tomorrow. On the next day, you get some brief reviews of yesterday's lessons, then a few new lessons, then it stops you again until the next day. That cadence repeats until you finish the course. You can't binge/cram even if you want to.
(A bit more technical detail: it's a spaced repetition system with exponential review intervals, similar to those used for language learning in e.g. WaniKani and Anki. But it also has a lot of fine-grained knowledge of its own course structure, so it can use reviews to intelligently unlock different lessons depending on how the user performed on their reviews.)
Occasionally, we get support email from new users who don't like this. They want to cram a whole course in a day. But cramming is a very time-inefficient way to learn, so this is self-defeating! Since launch, we've had good success adjusting the app's behavior and internal explanations to reduce these complaints.
However, we still get emails from long-term users who appreciate the time limitations. Generally these fall into two categories:
1. Users like that an enforced break before the reviews provides tangible evidence that "yes, I genuinely understood yesterday's lessons". If we allowed cramming, that reassurance wouldn't exist; it's too easy to succeed at a review when you just finished the lesson 30 seconds ago.
2. Users like that the usage limits remove a source of anxiety and worry. You do your reviews and lessons, you finish, and then you wait until tomorrow. There's no temptation to think "I really should've done 10 lessons today instead of 5; I'm so lazy".
It's still possible for a very dedicated user to do all of our courses in parallel within their first monthly billing cycle. (Median course start-to-finish time is 8-18 days depending on the course.) So this scheme doesn't make users pay us more than they would otherwise. And they're spending the same amount of wall-clock time that they'd spend if they crammed all of the lessons in one day. That makes it pure win: they memorize the topics more deeply, they worry less, and they get those benefits for no extra time expenditure. The only exception I can think of would be people who think "I must get exposure to all TypeScript syntax and semantics before tomorrow morning, even if that significantly reduces my ability to remember what I learned."
Obviously I'm very biased here, and the goals that we're optimizing for don't even exist in most other product spaces. But I thought it would be nice to have a counterexample to "engagement at all costs".
We run the entirety of SQLite, compiled to wasm, in the browser, among other things. It depends on the course, but our version checks are site-wide because it's all one app.
We use a single git repo with no npm packages defined, other than the package.json in the root because we have to put dependencies etc. in there. The directory structure for our source code is dead simple:
src/server
src/client
src/common
For example, the API endpoint definitions are common code, so you'll see stuff like this in client code that uses the API: import * as quizApi from "../../common/api/pages/quiz"
The ".."s are annoying, but working around them isn't worth the effort. Even when we heavily reorganize the file organization, it only ends up taking a few minutes to mechanically update these imports with a vim macro, or even with sed if it's a perfectly mechanical change.We run two copies of tsc: one for the client and one for the server, building to build/server and build/client. That results in a weird build directory structure:
src/server/db.js
src/client/app.tsx
src/common/endpoints.ts
build/server/server/db.js
build/server/common/endpoints.ts
build/client/client/app.tsx
build/client/common/endpoints.ts
If you need separate build settings for client and server, this weirdness is going to show up one way or another. However, we only introduced this build separation in the last month or so. For the first 1.5 years of the project, we got away with a single tsc process and tsconfig.json, with no build separation at all between client and server. If anyone who's newer to TS reads this, I'd encourage looking for simple solutions like that; you can get to the weirder stuff down the road if you need it (and you may never need it!)We don't use ts-node for our dev processes because it adds some annoying startup delay. We keep the TS compiler running at all times on our dev machines, and we run our server/tests/etc. from the TS compiler's build output directory. This hasn't been a big pain point for us so I wouldn't worry about it much.
Related, just in case: I definitely wouldn't use ts-node in production. If your deployed code fails to typecheck for some reason, you don't want to learn that by seeing your backend server failing to boot in production. I think you should always compile your app during your deploy process, then have production boot the compiled JS as if TS weren't involved at all.
Every step has had a unique ID since day one. Database migrations would be wildly more complex than our actual solution and involve a huge increase in maintenance and (depending on what you're imagining) increase database server load by multiple orders of magnitude.
Today, we can edit lessons in completely arbitrary ways, including adding and removing steps, and the system adapts dynamically. With your proposed solution, lesson content changes that take seconds today would require writing and running an entire database migration.
Your scheme doesn't address metadata at all, but metadata is one of the biggest complications discussed in the post. If you're imagining a database schema that knows about the structure of the metadata, broken into separate tables, then your scheme would amplify a single write per step (as implemented today) into hundreds of separate writes per step (with your change). Those writes would happen for every step advancement in a lesson, so a user completing a single lesson would result in thousands or possibly tens of thousands of database writes instead of the roughly 25 writes that happen today.
But regardless of whether you're imagining the metadata in an opaque blob or separate tables, it doesn't actually solve the metadata migration problem! Imagine this situation: You have tens of thousands of half-finished lesson sessions sitting in the database. Now you add a new piece of lesson metadata to the system. What do you do with those old sessions when you run your database migration? What if the relevant metadata can't be reconstructed for those old sessions (which will be the normal case)? Do you make up a fake, incorrect value for the metadata? Do you make that metadata field nullable, forever, which over time will result in all metadata being nullable, defeating the type system guarantees? Our scheme solves all of this with a relatively simple code change, and it works automatically in all situations, and it has minimal runtime overhead or database load, and it requires no ongoing maintenance whatsoever.
The metadata is mostly used for analytics. I didn't even get into this in the post, but "throw the incomplete lessons away when the metadata structure changes" is equivalent to saying "don't fabricate analytics data that will generate incorrect analytics results, which will cause us to optimize the business incorrectly."
Edit: All of these edits to your posts after I've already replied are very confusing.