Schwartz.World!

Welcome Back

Part of why I haven’t posted here in forever is because I gave my wife the computer that I built this site on, and I didn’t want to deal with setting up the app on a new machine. A bigger part of it was that I changed jobs during the pandemic. I also broke my foot quite badly in July of last year. I’d like to revisit some opinions I’ve loudly delcared here on the blog, and I’m probably going to write a rather long post about my foot injury. Maybe several long posts. Everybody I know is probably sick of hearing me talk about it, but the one year anniversary of my injury is coming up in a couple of months, and I need a place to dump those emotions, so brace yaselves.

Changing jobs was a really great choice. My new position has much more autonomy, and the codebase is structured in a way that the work of one team doesn’t have as much impact on the others as it did at my previous job.

I don’t feel the need to, like, name names, but that job was an interesting exercise in how HR practices affect engineering and code quality. That probably deserves its own post too.

Regarding my earlier post on sleep apnea, I’m doing great. I am used to sleeping with the cpap on and it definitely works. How I got there? Another post to come.

Today I want to talk about TypeScript. I have completely reversed my position on TypeScript.

I’ve moved this site off of Netlify and am now hosting it myself on a VPS using nginx. I’m trying to learn more about the server admin/devops side of things. I’m also building a shared online tildeverse type of space, although currently I’m the only user.

I was wrong

At my last job, the codebase was large and very tightly coupled. BTW, the opinions I’m about to express aren’t a condemnation of the company, and I know other devs there who had different interpretations of life there, but it does directly relate to my feeling about the tooling being used.

Lots of people came and went from the project. Many, many hands supported it. There is a natural progression that follows when you onboard to any new project, especially one where you are unfamiliar with the tools being used. You pick up tasks, see how they’ve been tackled elsewhere in the codebase and replicate them. It can take a while of using these tools before you fully understand what you are doing.

Now imagine a project with so many hands touching it was built with tooling that almost none of the developers involved had ever worked with. This was probably done in an effort to use popular technologies and attract new talent, and it absolutely worked.

However, lots of code was reused poorly, and I spent most of my time fighting with the tooling, particularly GraphQL and TypeScript. I still don’t really think there’s a use-case for GraphQL in the majority of projects, although I’m sure there are exceptions. Maybe it’s great and we were just using it poorly, but REST would have been 10,000% percent simpler for all involved.

And TypeScript… Man, I just wanted to type any. Damn, can’t I just any this? I didn’t, because I knew you shouldn’t, but oof, you take 1 day to get the code working and 2 days to fight with TypeScript afterward. The compiler error messages were huge and almost inscrutable, and because of how props are passed in React, the type errors would propagate and flood the terminal.

TypeScript does a pretty amazing thing, in that it can type all existing JavaScript code. You can write the shittiest, JavaScriptiest code you want, and TS can handle it. Doing this is pointless though. It doesn’t give you anything to document things.

Embracing types

I write a lot of types now. Let’s imagine a common front-end developer task. We’re going to fetch some data from and API and represent it as a table. No problem. The first set of types we have to write is the shape of the data returning from the API. This usually is a large nested object with lots of string and number values. If you can automatically generate this type definition, you should.

The backend rarely communicates exactly what we need on the frontend. APIs are generally built around efficiently communicating information, not making things easy for UI devs. So I usually transform the backend JSON into something… smarter. Like, for example, a backend might communicate a timestamp as a string or integer. const foo = { expires: '2022-05-03T01:51:17.535Z' } Then your UI gets peppered with little code snippets to work with that raw string Your foo will go bar at {new Date(foo.expires).toLocaleTimeString()}. Instead, I want the thing I pass around to have a Date as part of its internal state and a limited set of methods for querying that internal state so I can write it more like Your foo will go bar at {foo.getBarDate()}. In service of this I often reach for the class keyword, which plays so nice with TypeScript. The best part of Object Oriented Programming is the encapsulation of logic, and that works really well when writing functional(ish) code for React. Deciding on the interface for the data and THEN implementing it has a name, as it turns out. It’s called type-driven development, and damnit, I like it.

Lots of types

I found that the more explicit I was with typing, the easier the error messages became to parse. Instead of finding out your data is in the wrong shape when you try to pass it to some function, if you add return types to your methods you get that error message inside the body of the function. This is sort of hard to explain verbally, but quite easy to demonstrate with screenshots of my IDE:

Here’s some TypeScript code. Everything compiles and works great.

Code with no errors

But what happens if we change line 2 and introduce a bug. Forgive the silliness of the example. You’ll notice that the type error we get is on line 14 when we pass the value of getFoo to the next function.

Code with an unhelpful error

The situation improves somewhat by adding a type to the variable f.

Type variable

But the real magic is when you add a return type to your function, because you get the feedback in the place where it’s most useful.

Code with return type

Most of the people I’ve worked with’s first exposure to TS is via React, and you don’t really need to declare return types on React components, so maybe that’s why I don’t see return types used more.

Return types also have another upside, which is that it’s annoying to write and read complex return types, and I think that it guides you away from writing functions that return lots of different types of things. Just because TypeScript can type all JS code doesn’t mean you should. If you are afraid of writing complex types, write code that lets you avoid doing so. Your code will only benefit.

Generics

Lots of times when I struggled with types was because generics were underused. This is a product of not having experienced TS developers on the project. Until you grasp them, you are doomed to struggle. Almost any time I wanted to reach for any, it probably should have been T. Type-driven development relying heavily on generics allows you to really write reusable code in a clean way.

TDD or TDD?

I always thought test-driven development was an unattainable ideal, especially with something like UI code. I now see the similarities between test and type-driven development. Both require you to decide on the API of a function before you actually write the code. Type-driven code is easy to test, and testing is important. Certain classes of test can be omitted if you are strict about your TypeScript, and the tests you do write become much more straightforward. I’ll expand more on this in a coming post about writing MVC-inspired patterns in React.