Type Checking in JavaScript
techblog.badoo.com
techblog.badoo.com
var mostRecent = '';
if (type(c.messages) === 'array' ||
type(c.messages[0]) === 'object' ||
type(c.messages[0].text) === 'string') {
mostRecent = c.messages[0].text.substring(0, 30);
}
return name + ': ' + mostRecent;
.. apart from the fact that he probably meant && instead of || in that condition, we've also now got a system that silently swallows errors. Bugs can lurk in it without any more symptoms than producing empty strings where more interesting strings were expected.I much prefer my systems to crash when a programmer made an error, and do so noisily and detectably, so that the problem can be fixed. I also prefer the actual logic of my code to make up more than 20% of the lines. If you're going to add boilerplate type checks all over, the signal-to-noise ratio gets really bad.
So don't do this. Use Typescript if you want to check types.
One could use this system for asserts though:
assert(type.isObject({})));
assert(type.isNumber(NaN)); // fails
assert(type.isElement(document.createElement('div')));
assert(type.isRegExp(/abc/);
This could increase readability (because it documents the data type expectations) and would also break on some errors as soon as possible.Secondly I completely agree about wanting a program to crash noisily and detectably in development, but in production I would prefer that the error is handled and the user can continue to use the app.
Finally I also agree that if type checking is really important to you, a compile to JS language like TypeScript or Dart are the best options. The techniques / approach I am discussing in this article are really for people who already have a large vanilla JS code base or want to learn a bit more about types in JavaScript.
Maybe that's ok if it's a game or something, but this is a dangerous strategy. How do you know it's ok to use the app if there's an error? You have to very carefully isolate and control where your errors can be, and I'm not sure this approach allows you to do that.
interface IA { }
interface IB { foo(): void; }
function fn(passedFn: (a: IA) => void) {
passedFn({});
}
// compiles but throws TypeError at runtime!
fn((b: IB) => b.foo()); ### Numbers
kind(37) === 'Number'
kind(3.14) === 'Number'
kind(Math.LN2) === 'Number'
kind(Infinity) === 'Number'
kind(Number(1)) === 'Number'
kind(new Number(1)) === 'Number'
### NaN
kind(NaN) === 'NaN'
### Strings
kind('') === 'String'
kind('bla') === 'String'
kind(String("abc")) === 'String'
kind(new String("abc")) === 'String'
### Booleans
kind(true) === 'Boolean'
kind(false) === 'Boolean'
kind(new Boolean(true)) === 'Boolean'
### Arrays
kind([1, 2, 4]) === 'Array'
kind(new Array(1, 2, 3)) === 'Array'
### Objects
kind({a:1}) === 'Object'
kind(new Object()) === 'Object'
### Dates
kind(new Date()) === 'Date'
### Functions
kind(function(){}) === 'Function'
kind(new Function("console.log(arguments)")) === 'Function'
kind(Math.sin) === 'Function'
### undefined
kind(undefined) === 'undefined'
### null
kind(null) === 'null' function doStuffWithAVarietyOfParameters(s, a, b, i, ni, p)
{
var f = 'contractCategoriesAndAddExpandButtons()';
UTILS.checkArgs(f, arguments, ['string', 'array', 'boolean', 'int', 'negativeInt', 'Particle']);
// Do stuff.
}
For functions with many parameters, I like to pass the parameters inside an object and give the keys descriptive names. function doStuffWithAVarietyOfParameters(o)
{
var f = 'contractCategoriesAndAddExpandButtons()';
UTILS.validator.checkObject
(
o,
{
arrayParam : 'array' ,
boolParam : 'boolean' ,
nullOrFloatParam: 'nullOrFloat',
particleParam : 'Particle' ,
positiveIntParam: 'positiveInt',
stringParam : 'string'
}
);
// Do stuff.
}
Both functions will throw an exception if the arguments list is not as expected. The code can be found via the links below.https://github.com/tomcdonnell/lib_tom/blob/master/js/utils/...
https://github.com/tomcdonnell/lib_tom/blob/master/js/utils/...
I do type checking similarly in PHP.
http://tomcdonnell-tech.blogspot.com.au/2011/09/favourite-ph...
Secondly: it's ridiculous that their way of handling type errors is by just silently hiding them. They could be making a lot of errors that will become really hard to find this way.
Thirdly: performing type checking on runtime in JavaScript is just stupid, things like this could be performed at "compile time" with TypeScript or the Google Closure Compiler.
I really just can't stand all the type checking all over the place, it makes the code feel like all your reading is type checking and not the real logic of what is going on in this function.
Additionally, returning an empty string when i mess up a data structure is horrible for debugging. I would have to actually go into the code now to figure out why an empty string came back instead of processing as I thought it should.
if your really worried about exposing errors to the end user. wrap that whole thing up in a try catch block instead, log an error and don't display anything to the end user.
He's doing this because he wants type safety in his functions. If you have that, you eliminate one source of bugs.
>I really just can't stand all the type checking all over the place, it makes the code feel like all your reading is type checking and not the real logic of what is going on in this function.
That's a big problem with his approach. It's not scalable, and you can't rely on it (inevitably, developers will take shortcuts and you're going to have a hodgepodge of type checks littering your code). He might as well use TypeScript or Dart or Google Closure Compiler.
>I would have to actually go into the code now to figure out why an empty string came back instead of processing as I thought it should.
Problem number two with his approach. He doesn't actually have compile-time checks which is where you want your type-checks to be. Instead it's all run-time (and he ignores the errors), which raises the question, what the heck is he actually gaining from all this?
Again, he should just use TypeScript or Dart or Google Closure Compiler.
e.g.:
var check = require('check-types');
check.unemptyString(''); // returns false
check.maybe.unemptyString(); // returns true
check.verify.unemptyString(''); // throws Error
check.verify.maybe.unemptyString(); // doesn't throw
[1] https://github.com/philbooth/check-types.jsWe have worked for years on the Opa technology, cf. http://opalang.org. Opa is a typechecked language with a JS syntax that compiles to JavaScript. Unlike JS though, the semantics of the language borrows to functional programming languages, to ensure the type system is sound and type checking feasible with almost full type inference.
Opa does more than type-checking to JS, but even if you just want to typecheck JS, basically you should look to design and implement a new programming language, with a syntax as close as possible to JS but with proper semantics and then that compiles to JS after type-checking. You could add a set of tools to help port actual JS code to this new language.
T([Number, Number])(function(x) { return x; })
For this method, a function type is defined and then serves as a constructor that accepts a function as argument. function type(a) {
var t = a.constructor.name.toLowerCase();
return t.match(/^html/i) ? 'element' : t
}
...handles the rest. Still, I liked this post quite a bit; it shows pretty clearly how odd type checking can be in a loosely typed language. type.isNumber(4); // true
And it'd be easy to add validation versions, too, which might help clarify the types as one call per parameter; type.ensureIsNumber(myParameter); // throws exception if not a number
which might help your code fail faster.I have written code in untyped languages all my life and it has never been a problem for me.
The only problem I see with bigger JavaScript apps is the callback-hell.
Funny thing is, most to-JavaScript languages focus on the type aspect.
If I could write linear asynchronous code everything would be much more readable after coming back to some code a few months later.
The big one for me is inheriting other people's code bases, in dynamic languages they're a complete and utter nightmare, especially when written by incompetent programmers, which seems to make up a worryingly large number of programmers. Or someone just learning and not aware of conventions.
You just can't assume a method or function actually does what it says it does. Or returns what it says it does. And you can't even see what it's expecting or what things are being passed to it. You have to fine-toothcomb the method, it slows you down massively.
I have worked on a fair few bad code bases now and luckily for me the majority of that's been in static languages. It makes it so much easier.
Some of the other advantages of static typing include:
1. having incredibly good auto code completion
2. never having to remember or look up a method signature
3. to refactor massive chunks of code almost completely fearlessly
4. to fix crap code without any fear of breaking anything else
5. as long as you name your methods well, never having to annotate anything mundane (depending on your audience ofc)
There are also other things like that tend to go hand-in-hand because they're usually explicit, like not worrying about implicit conversion (apart from VB with option explicit off).
I've used both quite a lot over the last 10 odd years professionally programming and I've recently been thinking that I would actually prefer a mix, static typing on boundaries like method entry and new object declartion and dynamic everywhere else. I know little about compiler or language design, so I'm not sure it's even possible. C# kinda gets close with the var keyword, but the various disadvantages of static typing still rear their head quite a lot.
As a slight aside, People on HN always seem to talk as if they only write new programs, not maintain old ones. Am I the weird one for frequently having to pick up code that was written by someone else? Who often had coded themselves into a corner and then made up some excuse as to why they couldn't keep the client anymore?
When using libraries, their functions always wanted their arguments to be some strange classes or implement some of their interfaces, which always seemed like big overhead to me.
Stuff like Smalltalk, PHP and JavaScript felt much faster in development speed.
IMO, this is one of the major issue of javascript.
The specified classes for arguments is a good thing since you dont have to dig through piles of documentation or code review every method you use to understand their behavior.
What do you expect the arguments of the following function to be: 1. A file descriptor, or 2. A path to a file that will be opened by the function?
def print_log(file): ...
One could argue it's bad naming convention but i see shit like this way too often to think it can be solved by conventions. Documenting doesn't work either for the same reason, documentation becomes more boilerplate than specifying types in a type safe language btw.
Contrast this with the C# equivalent
void PrintLog(File file) { ... }
Bang!, You instantly know that the file parameter is a File-object and you can focus your mind on more important things.
IMO, real advantages of dynamic typing vs static typing are
* Simplicity. You don't need to learn a complex type system to start coding.
* Flexibility. Dynamic type systems don't stop you from doing useful things ike polymorphism and generics. In a static type system those need to be build in from day 1.
And having to build more advanced type system features on top of a simpler type system is hardly a good thing either - type theory is not trivial and chances that you will get it wrong are large. Even experts in the field make wrong decisions, for example array covariance in Java an C#. And even if you get it right you will usually not enjoy (all) the benefits of compile time type checking and fail only at runtime.
It felt like even the implementers of those type-systems didn't get it right.
I've never used Smalltalk, but the other two you mention suffer from massive problems when you get to a certain size codebase.
I'm extremely wary of saying this, but I generally think that uni professors/tutors can't program. Basically unless they've written a large open source program or actually worked in the industry for a lot of years, they're probably going to be useless programmers. And even then, there's a good chance they're useless programmers.
That may be a slightly old fashioned view, but most lectures I've seen from unis online tend to completely miss the point of basic concepts (OO being the most common one). And it's rare to meet graduate CS who can actually program anything on their own from scratch. Google's fine, but cut & pasting code you don't understand and changing random stuff as it sounds kinda right, that's not programming, that's a bloody rain dance.
Programming is a skill that needs years of work before you're even vaguely good. And the worst part is you're almost constantly suffering from the Dunning-Kruger effect, thinking you're much, much better than you actually are. I probably still am.
It's one of the reasons we see security holes time and time again, novices don't even realise the problem exists!
For example, I have no idea what to do with crash dump in windbg. I wouldn't even know where to start. Is that a massive knowledge gap? But give me a stack trace of a problem & the source in almost any web language and I'd probably be able to fix it, even if I've never used the programming language before.
But I have to say, my programming prof. was rather fond of Java. Biggest part of the lecture on OOP was about type systems (and I hated it lol).
Most of the stuff I learned about programming, I didn't learn in university, but at work.
But since my experience with Java at university was so bad, I searched for jobs in different languages, like JavaScript.
A few years ago I tried to learn Scala, because it seemed like a better Java to me. But somehow I never got along with it. The type-system seemed logical, but it had so much other stuff around that made starting rather difficult, so I went back to JavaScript.
In basic ruby ,unless you mess with classes, [1,2]+1 WILL fail , though ruby is not staticaly typed. Ruby is STRONGLY typed, and that's a great thing.
Javascript will do everything it can not to fail:
String([1,2]) => "1,2"
+ 1 => "1,21"
IMHOThat's madness,because Javascript is basically making a decision for you.That was acceptable in 1995, that is not in 2013.
And that's its biggest flaw.Writing javascript means working against the language.
But there is a reason for that. Javascript is not good at throwing errors or handling them, at all.
So it's not about static vs dynamic typing.
There is no good solution for that problem unless TC39 decides to fix it. Enforcing dependency injection concepts and duck typing might work, but not always. For instance, you could check if a string is a string with
function(x){
x=x.substr(); // will throw a TypeError if I pass an Array.
}
Before doing anything with x, it might help reduce some mistakes. and you might use it on return types. But it's verbose and doesnt work all the time.Good static typing saves you about half the units tests. And you don't even have to worry too much about that `half' being correct, because the type machinery in your compiler has been checked by thousands of people before. Whereas your tests are normally written from scratch.
Types also make your testing life much easier. QuickCheck is about the gold standard in rules based testing, and QuickCheck in a type-inferred language is just so much less verbose than when you have to either manually add all type annotiations or have to go without types. (That's mostly because Haskell allows overloading by return type.)
So you can do stuff like this:
callToRemoteApi().then(processData)
.then(makeAnotherCallToAnotherApi)
.then(processAllTheData)Although with generators you "can use normal control flow constructs". Except in most cases you probably use map or forEach instead of loops anyway. And try-catch is really broken.