It is a well known fact that compile-time errors during development are easier and cheaper to catch than runtime ones in production. Unfortunately people keep forgetting that simple fact. Let me tell you a little story about AWS Lambda hiccup, and how it could easily be avoided.
A developer wrote a simple Lambda function in TypeScript that would receive a parameter of the following type:
interface Notification { slack?: { notify: boolean }; github?: { notify: boolean }; }
The logic is simple – if slack.notify
is true
– send slack notification. If github.notify
is true
– send github commit status notification. The function deployed without issues, and it does send slack notifications. There is one problem: often it would send the same slack notification 3 times in a row.
What could be the reason? Let’s take a look at actual function. It goes something like this:
function notify(notification: any) { if (notification.slack.notify) { // ... } if (notification.github.notify) { // ... } }
Notice anything wrong with it? Yes, that’s right – the type of the parameter is defined as any
, which means no type checks is being done by TypeScript. Now, as you notice both slack
and github
properties are optional. What will happen if the function receives value like
{ slack: { notify: true } }
The first if
condition is satisfied, and the function sends slack notification. But the second one will throw an exception
Cannot read property "notify" of undefined
because github
property is not present in this value. Function throws the exception, and Lambda invocation fails. By default a failed Lambda re-tries invocation 2 times. So the slack notification will be send 2 more times. Mystery solved.
How could this be avoided? By defining function parameter as strongly typed. If you do so – TypeScript won’t let you compile the code as is. Moreover in IDEs like VS Code you will get visual representation of the error:
The only way TypeScript will let this thru if you specify properties as optional
function notify(notification: Notification) { if (notification.slack?.notify) { // ... } if (notification.github?.notify) { // ... } }
With this version, in the above scenario if github
property isn’t there – it won’t even bother to check for .notify
value, the second if condition will fail before that.
Moral of this story: Let TypeScript do its thing, the types are there for a reason, and often the reason is to prevent you from shooting yourself in the foot.