HNHacker News
TopNewBestAskShowJobs

gary_bernhardt

2,312 karma · joined August 30, 2013

submissionscomments
gary_bernhardt··on Show HN: Beta test Execute Program's interactive "Python for Programmers" course
Yes, currently there's no way to jump to the middle of a course. That lets us keep the lessons shorter, without needing to recap earlier topics. We're planning to add a way to skip, but that's not in yet unfortunately.

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.

gary_bernhardt··on It's OK if your code is just good enough
This is a great excuse to repeat one of my favorite quips, from the late Jim Weirich (from memory, but this is at least very close): "half-assed is OK as long as it's the right half of the ass."
gary_bernhardt··on Network protocols for anyone who knows a programming language (2017)
That sentence's grammar is intentional. (I wrote the sentence.)

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.

gary_bernhardt··on Tremor – The React library to build dashboards fast
Almost none of the e2e tests for https://www.executeprogram.com meet this description. They're all tests like:

  - 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.)

gary_bernhardt··on Switching from QWERTY to Colemak and Back
I switched off of Dvorak because of vim, and because keyboard shortcuts in general were so awkward. Shortcuts are better in Norman, but vim is still awkward.
gary_bernhardt··on Switching from QWERTY to Colemak and Back
I second this.

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!)

gary_bernhardt··on Effective Spaced Repetition
I'm the founder of Execute Program (https://www.executeprogram.com), where we've done a similar thing (knowledge graph + SRS) for programming languages/tools since 2019. Interesting to see that you have a graphviz render of a subgraph right on the landing page! We've toyed with the idea of exposing the graph visually, but haven't done it yet.
gary_bernhardt··on Ask HN: Those who have taken extended time off, any advice?
Yeah, you can get lucky. But if you actually get denied entry and put on the next flight back, you'll wish you'd printed that flight receipt. All it takes is one officer having a bad day. (Or one who doesn't like [insert part of your identity here].)
gary_bernhardt··on Ask HN: Those who have taken extended time off, any advice?
I second this advice, having done it. I flew one-way from Seattle to Dublin in 2010 with no plan beyond the first week. 25ish cities and 4.5 months later, I flew back. You'll figure it out as you go.

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.

gary_bernhardt··on A Way to Manage Dotfiles
I use `git clean` maybe once per year on average, so very low risk for me. And if that ever happens... that's why I have hourly backup snapshots! If I didn't trust my backups, I'd definitely address that before worrying about dotfile management. If you don't have a good safety net then you end up reinventing the net 100 times in other ways and everything gets more complicated.
gary_bernhardt··on A Way to Manage Dotfiles
I've had my dotfiles under version control for over a decade (hg for a couple years, then git). I've never used or felt the need for any special tools, and I don't use any extra symlinking.

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.

gary_bernhardt··on SQLite is not a toy database
Seconded; it's a fantastic tool and ecosystem. We use sql.js in production (https://www.executeprogram.com). The SQL course's live code examples all run directly in the browser via sql.js.

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.

gary_bernhardt··on “User engagement” is code for “addiction”
All good feedback, thanks! An upcoming landing rework will get the first two, and I added "separate sign-up list for new courses" to my queue.
gary_bernhardt··on “User engagement” is code for “addiction”
I appreciate it. We've struggled with communicating the non-binge aspect, and usually approach it from the other side: by talking about reviews, spaced repetition, remembering, etc. But I think you're right that approaching it from the "non-binge" side is a good idea. Coincidentally, we're about to do a major landing page revision, so the timing is right to give that a try!

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!

gary_bernhardt··on “User engagement” is code for “addiction”
I think your argument here could be made about pretty much any activity that humans do repeatedly. The critical difference is that both WaniKani and Execute Program quickly tell you "go home, you're done", so it's difficult to dump hours of low-quality time into them. Whereas advertisement-driven platforms are incentivized to retain your eyeballs for as long as possible, as you pointed out.
gary_bernhardt··on “User engagement” is code for “addiction”
Here's an anecdote about what happens when you design a product in the opposite way.

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".

gary_bernhardt··on Teaching the Unfortunate Parts
Thanks, I updated the post. I've been using JS since the 90s so I'm not sure how I managed to forget that!
gary_bernhardt··on Teaching the Unfortunate Parts
Only people using Safari < 13 will see that message. 13 is 1.5 years old, so yours must be older. We saw tons of wasm failures on Safari < 13, to the point that it was better to stop people before they got deep into something and hit a failure.

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.

gary_bernhardt··on Teaching the Unfortunate Parts
Yep. Originally I had a SQL example in there too, but it took a lot more setup to explain and would limit the post's audience.
gary_bernhardt··on Teaching the Unfortunate Parts
They're the same Gary (I wrote this article).
gary_bernhardt··on SQL.js: SQLite Compiled to JavaScript
We use SQL.js to power the SQL course at https://www.executeprogram.com. We've encountered a couple of relatively minor hiccups, but overall it's been great. We've had thousands of users go through that course with no reported problems that are attributable to SQL.js.
gary_bernhardt··on Porting a React Front End to TypeScript
Ahh, if you're taking the microservice path then you're going to have to fight all of that complexity. So far we've stuck to the constraints that I set at the beginning of the project: vanilla Heroku web dynos; exactly one Heroku worker dyno (never 0 or 2); and Postgres is the only data store (no queues etc.)
gary_bernhardt··on Porting a React Front End to TypeScript
(I wrote the article.)

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!)
gary_bernhardt··on Porting a React Front End to TypeScript
(I wrote the article.)

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.

gary_bernhardt··on Restoring React reducer state across browser sessions
We only use it for resuming partially-finished lessons, so we're using it in basically the way that you describe. Overall course progress is tracked in a totally different way.
gary_bernhardt··on Restoring React reducer state across browser sessions
This is right. You'd have to read a ton of our source code to see this first hand, but the reducer state has nothing about React in it. It's a lot of stuff like "what answers did the user give?"; "was the answer correct or not?"; "what were the start and end timestamps for each attempt?"; etc. If we maintained multiple clients, we could reuse that structure because it's all about the application, not about React.
gary_bernhardt··on Restoring React reducer state across browser sessions
I wrote the post.

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."

gary_bernhardt··on How porting to TypeScript solved our API woes
You may enjoy this talk: https://www.destroyallsoftware.com/talks/ideology
gary_bernhardt··on How porting to TypeScript solved our API woes
I rename the "email" key in our register endpoint to "emailAddress" and all code that touches that key turns red less than 1s later, whether it's in the client or server.

Edit: All of these edits to your posts after I've already replied are very confusing.

gary_bernhardt··on How porting to TypeScript solved our API woes
The post isn't about dynamic validation; it's about static type checking of APIs, which is impossible in JavaScript.
Page 1 of 4Next →