Don’t do ‘this’ — Part Two

The trouble with Typescript

Background

I argued in an earlier article, ‘Shit Javascript Coders Say’, that no-one should use ever Typescript, and I thought it best if I expanded on why I believe that is.

This is part two of a two part article. Jump to the previous part in which I take a massive dump on Object Oriented software, especially as it relates to Javascript.

So let’s talk about Typescript

Typescript is a strongly typed (hence the name) superset of Javascript. It’s a great way for developers coming from strongly-typed OO languages like Java or C# to develop Javascript applications with the security blanket of strong-typing and IDE code completion. It’s also a really great way for inexperienced or incompetent developers to create a truly spectacular mountain of technical debt.

Typescript encourages developers to think they are working in a type-safe and object-oriented language. But the reality is that Typescript compiles down to Javascript, and allows you to intersperse your oh-so-familiar type-safe OO code with raw, unadulterated Javascript as well. And therein lies the rub.

Javascript is not, at its heart, an OO language. Sure it pretends to be one, and, if you are careful, you can write OO style Javascript, but if you slip up, and you will slip up, then you are in for a lot of really hard to debug little fnords.

Typescript is a crutch

But it’s a loose, wobbly crutch.

Typescript encourages the use of OO. In part one I discussed OO, and in particular made the point that Javascript’s OO, is something to be avoided except in the rare cases where you really do want reusable objects.

Typescript is verbose and ugly

Here’s an example. Object decomposition is a vital tool in any Javascript developer’s toolkit. But Typescript can really mess that up for you.

It’s a very common task to map property names from one object into another, say because you’ve pulled some JSON from an API and you need the property names to be different. So you might write an mapping function like:

const mapUser = ({

user_name: username,

first_name: firstName,

last_name: lastName

}) => ({

username,

firstName,

lastName

})

That’s perfectly valid, and very suscinct Javascript, but it’s sure going to confuse the hell out of Typescript. To write that in Typescript you need to do something like this:

const mapUser = ({

user_name: username,

first_name: firstName,

last_name: lastName

} : {

username: string,

firstName: string,

lastname: string

}) => ({

username,

firstName,

lastName

} : any);

A beautifully concise mapping function is now a hideous mess of repetition, all to make sure your inputs are strings. And that’s without the toxic hellstew that is Generics. Throw some angle brackets in there and shed a tear for all the beauty that’s been lost.

If you are using that function to map fields from a JSON file, your fancy type-safe checking is going right out the window anyway.

const getUsers = async url => {

const { json } = await fetch(url)

const data = await json()

return data.map(mapUser)

}

How does Typescript know what’s in data ? It doesn’t. And Typescript’s supposed type-safety vanishes as soon as it’s compiled into Javascript.

Typescript encourages IDE dependency

Maybe it’s just me but I simply can’t abide most IDEs. Things people seem to love about them, like autocompletion, drive me nuts. Software development is not improved by improving your typing speed, but by improving your reasoning. So long as you have decent syntax colouring, an integrated file browser, and good search-and-replace, that’s really all any decent Javascript developer needs. I use Atom with a very minimal set of plugins. I also use TextMate on occasion because for some unfathomable reason Atom doesn’t let you drag to copy files from one project to another.

But to use Typescript properly you really need to buy into the whole Microsoft VS Code thing, or muddle about in WebStorm or worse. People coming from Java or C# might pine for Eclipse or NetBeans, but no-one else would.

Typescript introduces its own weird errors

In his blog post ‘The Good, The Bad, and The Ugly of TypeScript’, Leonardo Freitas explains:

If you are a functional programming fella like me, chaining curried methods with promises can be problematic sometimes. That happens because some frameworks make use of native ES Promises, while others use third-party promise libraries such as Bluebird. Even though you would have no problem chaining them in JS, TypeScript sees them as different types and will refuse to even transpile, therefore throwing a TypeError .

Typescript means you need a pre-processor

Front-end developers are well used to using pre-processors like Babel and build tools like Webpack to turn their super-modern Javascript into something that browsers can understand. But for back-end developers using NodeJS, there’s no need for build tools, and pre-processors just add an annoying layer of complexity that offers no value. Deploying a server that needs to be built before it can be used is tiresome.

Mocking in unit-tests is harder with Typescript

Front end developers get to use test frameworks like Jest which include truly amazing mocking abilities. But these get messy when you add Typescript into the mix. And for back-end developers using Node, where the mocha test framework is more commonly used, along with mocking utilities like proxyquire , interposing any kind of pre-processing build step makes your test setup harder.

Unit-testing is important. I’ve read all kinds of articles that claim you don’t need vigorous unit-testing when you use Typescript because of its supposed compile-time type-safety, but that’s just nonsense. I’ve also read opinions that if you are mocking you are doing testing wrong; that’s also nonsense. The very opposite is true.

Digression: In defence of mocking

True unit tests only test the specific units of code in question. If your code is like:

const fetch = require('node-fetch)

const mapUser = require('src/utils/mapUser') const getUsers = async url => {

const { json } = await fetch(url)

const data = await json()

return data.map(mapUser)

}

You could unit test it like:

const { expect } = require('chai')

const { stub } = require('sinon')

const proxyquire = require('proxyquire') describe('getUsers', () => {

const fetch = stub()

const json = stub()

const data = ['some data']

const mapUser = stub() const response = { json }

const url = 'some-url' const getUsers = proxyquire('src/getUsers', {

'node-fetch': fetch,

'src/utils/mapUser': mapUser

})

const expected = ['some result'] let result before(async () => {

fetch.resolves(response)

json.resolves(data)

mapUser.returns(expected[0])

result = await getUsers(url)

})



it('called fetch with the supplied url', () => {

expect(fetch).to.have.been.calledWith(url)

})



it('called json', () => {

expect(json).to.have.been.calledOnce

})



it('called mapUser once with the right data', () => {

expect(mapUser).to.have.been.calledOnceWith(data[0])

})



it('returned the expected result', () => {

expect(result).to.deep.equal(expected)

})

})

A test like this is comprehensive and fast. Anyone writing unit tests and not ruthlessly mocking is simply not writing efficient unit tests.

Unit-tests are of course no substitute for actual integration tests but that’s a whole other story.

Typescript is the Volvo of programming languages

You know how Volvo drivers think they are the safest drivers in the world because the car they drive has, allegedly, more safety features. But this false sense of security can actually make them dangerously unsafe drivers.