”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 将 JavaScript 转换为 TypeScript 的多年经验:我的关怀意见

将 JavaScript 转换为 TypeScript 的多年经验:我的关怀意见

发布于2024-11-08
浏览:829

Years of Experience Converting JavaScript to TypeScript: My Caring Opinions

I started my JS career in 2015, spent a year working exclusively with it, and then transitioned to TypeScript. I’d love to say 'And never looked back again!', but I can’t; since 2016 there’s a chore I have to do basically at every company I worked for: converting existing codebases from Javascript to TypeScript. These are going to be my subjective, sometimes scolding opinions.

In a hurry? Here’s the tl;dr:

  • The False Sense of Velocity: skipping to deal with data drifting and edge cases is surely faster than dealing with them, but the price will be paid nonetheless.
  • Data Drifting: When data schemas evolve without a type system following them, it’s not just migration to TypeScript that gets hard, you will be plagued by support issues. Best way to deal with it is to standardize first your data collections and have strict typing afterwards.
  • Poisonous Optionals: When your data schema lacks uniformity, you would have to deal with handling of undefined properties every time you deal with these. TS is just a reminder, not the cause for it.
  • any Means Trouble: Using any hides potential issues and makes it harder to track your actual state of TS migration; define first your API types, and type inference will lay out a migration “plan”.
  • Forgetting about Promises: Forgetting to react to promises either with await or with .then/.catch happens more often than we think. Failing to do so might cause you long hours of investigating weird bugs like an entry created for a missing photo.
  • Undefined Variables: A classic refactoring mistake in JavaScript when you rename/move out things from scope, but not all references to them get updated; TypeScript helps catch these before runtime.
  • Unsafe Array Operations: It’s quite easy to forget about edge cases of arrays; yet another area where TypeScript helps prevent this.
  • The Saga of Magic Code: Complex JavaScript patterns can be very hard to type; but do you actually need them, are they good, recognizable patterns?

The False Sense of Velocity

Every now and then I encounter a JS purist, who sneers at us with their harsh opinion: they simply work way too fast to get slowed down by the extra steps required for TypeScript. Sounds badass, but this confidence usually falls apart when I change the extension from .js to .ts, because I get blasted by the following typical errors.

Data drifting

I wish to start with this, as I rarely read about it in blogs, but it is, in my opinion, one of the hardest parts of TS conversion: the authors start with data having ShapeA a couple of years before the conversion. Then ShapeA changes to ShapeB, ShapeC, then there are competing versions for ShapeD1 and ShapeD2. In the end you will have all shapes of the data in your (probably NoSQL) database accumulated during many years: [ShapeA, ShapeD1, ShapeD1, ShapeB, ShapeD2, ShapeB, …].

An example: recently I had sampled from a Firestore collection items of users. The birthday property had the following types:

type User = {
  // …
  birthday?: string | Date | { time: string } | null;
}

Of course if I use this type that accurately describes what is in the database the entire codebase goes up in flames: hundreds of TS compilation errors show up, while the morale goes down. Usually it’s warning me about potentially undefined values, or missing conversions (e.g. a string is not a Date).

Not dealing with this toxic variability, a developer might feel very productive, but these are going to cause a lot of trouble to customers, customer support and data analysts (maybe we should have an annual “Hug and comfort a data analyst, who puts up with your data leniency” day so we don't forget about these folks?). We waste their time, but us? We’re fast as lightning without the confines of strict types.

After trying to solve it in through million different ways, I reckon the only way of dealing with this is to...

  • first, define the ideal type you want to work with
  • and second: standardize the data you have accordingly (which means backing up and sweaty scripting of long running tasks)

Poisonous optionals

If you have paid attention to how the example type was defined you would see how this a problem directly related to data drifting:

There can be a million different ways an expected property to be not in an object.

In a JS codebase (especially if you choose a simple text editor over an IDE) you are not going to get warned about these, since really, there is now way for your IDE to tell if an object passed in as a parameter has or hasn’t the expected property. However TS will warn you about these missing optionals (i.e. not just you might not have birthday.__time__ you might not even have a birthday property) and then you would be forced to cover all these cases (including null as opposed to undefined - take a second look at the type above ?). This is usually means to do the repetitive task of writing birthday?.time ? birthDay.time : typeof birthDay === ‘string’ ? new Date(birthday) : birthDay, etc. (of course not in this brainf*** format).

This often leads to a lot of frustration, and not just to devs new to TS. It does feel like having types slow us down.

But in reality this is the consequence of data drifting: whose fault is that you cannot be ever sure whether an object has a birthday property?

How any gets in the way of TS migration

I have a clear preference for where to start in a project to do the TypeScript migration and it is where external data enters; either through fetch or through a form.

Let's say that for a given piece of data I have 3 relevant files:

  • fetch-data.js where the data gets queried from an endpoint
  • ParentComponent.js where data is partially transformed and passed as a prop to the next component
  • ChildComponent.js where the passed data from ParentComponent.js is used

Now if during migration I choose to convert to ParentComponent.js and as I don't know what exact data is fetched in fetch-data.js I use any temporarily to silence the annoying compiler, I might set myself up for failure.

Of course, if we had these 3 files clearly laid out to us we would not start with ParentComponent.js, but in real-world we would have hundreds of JS files and when a bug occurs in ParentComponent.js we feel tempted to convert it to TypeScript.

In this case the following happens:

  • exact shapes of data in fetch-data.js stays unknown
  • ParentComponent.tsx uses it with any
  • ChildComponent.js receives it and it's going to be an unknown as well

Now a bit later someone else adds the types to both fetch-data.js and ChildComponent.js. Looking at the extension it seems ParentComponent.tsx got migrated and trusted, but in reality the relevant data for ChildComponent.tsx would be still unknown but in this case we would not even know about it. The type inference would break in ParentComponent.tsx and we are still poking in the darkness in ChildComponent.tsx.

In my opinion this case is worse than having the component completely untyped as I would know to tread more carefully.

To deal with this, I suggest starting from converting the files where data is originating from, which is going to be where the data is fetched or where it is produced through a form.

TypeScript is great at inferring these schemas (unless you do the magic code; see below), so when you incrementally change your codebase you can then rely on these sources being correct.

Order grows out from the seeds of well-defined data sowed at the entry of your data flow.

A real world example is when I finally knew what a validationError object looked like, I could correct a bug of a label for a validation error never showing up, e.g. the component code expected a singular validationErrors.article property, but the actual property was validationErros.articles. This is a super easy thing to miss.

Note, that it was for a reason I have mentioned data drifting and data standardization: if you do these 2 steps correctly, now you are going to deal with robust types with very little ambiguity.

Forgetting about Promises

Let’s say your block of code might receive an image, convert it to another image format, save it to storage and create an entry for it. However, saving the image never gets awaited, and the code always goes to the next step, which is saving the data that might point to a non-existent storage location. Local testing does not find it, only some users complain that sometimes their uploaded photos disappear.

While I haven’t encountered a developer who is fine with this, with JS codebases it happens way more often than people think.

Undefined variables, unsafe array or object operations, optional everything

Recently I have found an ironic bug: an error never got reported as the error message used a variable that was defined in a different scope. No errors, no problem!

As I said, it wastes so much time, because the error might get to you way later when you accidentally stumble upon it in the error logs of your container. Or it’s customers who report it.

This is, just like “forgetting about promises” is a very frequent issue of JS codebases. It’s not a “developer issue”. It usually happens under certain circumstances, for example during a mentally taxing refactoring step: you move code around a lot, and then a variable like this slips through. (Obviously a good test coverage can spot these; that’s for another time.)

Even if you work with very disciplined developers, some errors are just very hard to spot by looking at them, therefore people frequently forget about them. For example there are unsafe array operations: for example you want to grab the title of the first item in an array (articles[0].title) there can be cases when the query returns 0 items, therefore articles[0] will be undefined.

Again, this is an everyday, average slip, it’s not a “character issue” of an (otherwise great) engineer. But we have a tool to warn us, and that’s TypeScript.

The Saga of Magic Code That Could Not Have Been Typed

Finally there is a situation where the “inconvenience of TypeScript” can actually help you write simpler code through making convoluted code painful to write; I will explain what I mean by this later.

The flexibility of JavaScript allows transformations (e.g. add properties to a function) that are extremely complicated to accurately type. I remember a magic function transformer that took a function, stapled variables from a redux store onto it binding the parameters and redux actions and the function together.

The problem was that this custom piece of code was very hard to understand and took a lot of time to for every team member to get it. Contrary to this, working along coding standards saves a lot of time for engineers of an organization: any member from any team is now going to be able to deal with the code without needing to spend time and familiarize themselves with the inner workings of a contraption.

I want to say redux-saga is a similar case because of the usage of generator functions: there is an error by design there, I must mention. I have found typing these generator functions to be extremely hard. Try typing a saga (and using it in another TypeScript file) that for every yields might return a different value type. First an undefined value (as in no explicit returned value), then a Promise and finally an explicit value (say the third yield returns a sanitized user object).

One can argue that typing this and dealing with its ambiguity is just too much effort, therefore TypeScript puts shackles on the brilliant mind of a competent Javascript programmer, but my counter-argument is why do we need this flexibility?

I mentioned “inconvenience” at the start of this paragraph: since most devs who work with TS are just not that level to be able to type these contraptions, they are not going to write these magic functions; which in my opinion is a very good thing.

Writing an extremely complicated function should be only a last resort anyways, after exhausting all simpler options. If TypeScript makes it harder, that’s even better.

Conclusion

In my professional experience, fuzzy, heterogenous sources of data and broken chains of data flow cause the most issues in a TypeScript conversion, however I argue that it is not TS being at fault here; it is the toxic flexibility of JavaScript that delays dealing with the consequences of coding mistakes to runtime.

I sometimes call it “HDD”: hope driven development.

You hope that the data would look like how you’d expect it to look like. You hope that the unit tests are describing the data accurately and you won’t have false positives. You hope it will blow up before it blows up at the user.

While it is exhausting to deal with these errors, it is way cheaper to address these during compile time than waiting until accidents happen to the customers. And for the entire company!

With a solid TS codebase you will unlock your true velocity because then you can rely on your data and the tooling around it.

版本声明 本文转载于:https://dev.to/latobibor/7-years-of-experience-converting-javascript-to-typescript-my-caring-opinions-36ci?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • Java 的可选类型如何简化“Get”调用链中空值的处理?
    Java 的可选类型如何简化“Get”调用链中空值的处理?
    使用可选的“Get”调用链安全导航在 Java 编程中,经常会遇到“get”调用链,如下所示:house.getFloor(0).getWall(WEST).getDoor().getDoorknob();为了避免潜在的 NullPointerExceptions,开发人员通常采用详细的 null ...
    编程 发布于2024-11-08
  • 大泥球:理解反模式以及如何避免它
    大泥球:理解反模式以及如何避免它
    前端开发中最臭名昭著的架构反模式可能是大泥球。术语“大泥球”适用于没有明显结构或模块化组织的系统。代码库有机且混乱地增长,成为维护的噩梦。这是许多开发人员发现自己所处的情况,特别是当他们面临着按时完成任务并开发大量功能的压力时。 这就是当前文章的内容:大泥球反模式以及前端开发中的示例,为什么它如此常...
    编程 发布于2024-11-08
  • 如何正确使用带 Map 参数的“reflect.Call”函数?
    如何正确使用带 Map 参数的“reflect.Call”函数?
    解决reflect包中的.Call使用问题在reflect包中使用.Call函数时,遵守所需的参数格式至关重要。本文将指导您完成正确使用 .Call 函数并操作 in 变量以满足目标方法的过程。提供的示例代码中:params := "some map[string][]string&quo...
    编程 发布于2024-11-08
  • 如何使用 HTML 和 CSS 创建翻页卡动画
    如何使用 HTML 和 CSS 创建翻页卡动画
    在这篇文章中,我们将了解如何使用 HTML 和 CSS 以及渐变背景创建时尚的 3D 翻转卡片动画。 访问我的网站 了解结构 我们将使用卡片的两侧(正面和背面)来创建翻转效果。此效果将在悬停时使用 CSS 过渡激活。 <div class="card"> <...
    编程 发布于2024-11-08
  • Python 中的 len() 函数有多高效?
    Python 中的 len() 函数有多高效?
    Python 中 len() 函数的成本影响len() 函数是 Python 内置功能的组成部分,提供有关各种数据结构的长度的信息。具体来说,它通常与列表、元组、字符串和字典一起使用,以确定它们所包含的元素或字符的数量。与直观的感知相反,len() 函数的计算成本保持不变跨越所有上述数据类型。这意味...
    编程 发布于2024-11-08
  • 如何在 Java 中将 Long 值转换为字节数组并返回?
    如何在 Java 中将 Long 值转换为字节数组并返回?
    在 Java 中将 Long 转换为字节数组并返回在 Java 中,将 long 基本数据类型转换为字节数组 (byte[] ),反之亦然是各种操作的常见任务,例如通过 TCP 连接发送数据。下面是实现此转换的全面解决方案:Long 到 Byte Arraypublic byte[] longToB...
    编程 发布于2024-11-08
  • 如何使用 Selenium 在 Google Chrome 中模拟 Microsoft Edge Mobile?
    如何使用 Selenium 在 Google Chrome 中模拟 Microsoft Edge Mobile?
    使用 Selenium 更改 Google Chrome 中的用户代理在 Selenium 自动化脚本中,为浏览器窗口设置特定的用户代理对于模拟设备行为和确保网站渲染至关重要正如预期的那样。在这种情况下,我们的目标是将 Google Chrome 中的用户代理修改为 Microsoft Edge M...
    编程 发布于2024-11-08
  • 哪种 MySQL 获取函数适合您的 PHP 应用程序:`mysql_fetch_array`、`mysql_fetch_assoc` 和 `mysql_fetch_object` 的比较
    哪种 MySQL 获取函数适合您的 PHP 应用程序:`mysql_fetch_array`、`mysql_fetch_assoc` 和 `mysql_fetch_object` 的比较
    比较 mysql_fetch_array、mysql_fetch_assoc 和 mysql_fetch_object:综合分析mysql 函数系列在从 MySQL 查询中检索结果中起着至关重要的作用在 PHP 中。在这些函数中,mysql_fetch_array、mysql_fetch_assoc...
    编程 发布于2024-11-08
  • Lerna – Monorepo 管理的关键
    Lerna – Monorepo 管理的关键
    欢迎回到莫诺雷波城堡! 现在城堡已经建成,每个房间(项目)都已就位。但如果没有正确的管理,事情可能会变得混乱。谁来帮助城堡顺利运转?这时勒纳登场了——一位强大的巫师,拥有神奇的命令,可以让一切保持秩序。 Lerna 是您在 monorepo 土地上的向导,确保所有房间(项目)同步,所有包都链接,并且...
    编程 发布于2024-11-08
  • 如何在 PHP 中循环嵌套数组并显示特定值?
    如何在 PHP 中循环嵌套数组并显示特定值?
    PHP foreach 与嵌套数组:综合指南在 PHP 中,浏览嵌套数组可能是一个常见的挑战。本讨论重点讨论特定场景,您的目标是显示嵌套数组的子集,特别是第二个嵌套数组中的值。将 foreach 与嵌套数组结合使用要使用 foreach 处理嵌套数组,可以采用以下方法:示例:$tmpArray = ...
    编程 发布于2024-11-08
  • 提升 Web 性能:前端开发人员指南
    提升 Web 性能:前端开发人员指南
    大家好!自从我上次写博客以来已经有一段时间了,我承认,这让我有点难过。现实是,有太多东西需要学习,有时感觉永远没有足够的时间来深入了解所有内容。我在跟谁开玩笑呢?事实是,我最近拖延得很厉害。 但最近,我一直在探索网络性能——这对于任何前端开发人员来说都是一个至关重要的话题——我很高兴分享我所学到的东...
    编程 发布于2024-11-08
  • 如何利用先进的加密技术增强数据保护?
    如何利用先进的加密技术增强数据保护?
    对称密钥加密:FernetPython 拥有强大的加密库,提供 Fernet,这是一种安全、最佳实践的加密方案。 Fernet 采用 AES CBC 加密、HMAC 签名以及版本和时间戳信息来保护数据。建议使用 Fernet.generate_key() 生成密钥。from cryptography...
    编程 发布于2024-11-08
  • 什么是本地主机?本地主机作为开发人员的用途
    什么是本地主机?本地主机作为开发人员的用途
    您有没有想过当开发人员在将网站上线之前测试网站时会发生什么?或者网络管理员如何检查他们的系统是否正常工作?答案在于一个强大但经常被误解的概念,称为 localhost。让我们深入了解 localhost 是什么、它为何重要以及它如何变得非常有用。 什么是本地主机? 用最简单的术语来说...
    编程 发布于2024-11-08
  • 为什么 Debian 和 Ubuntu Docker 容器之间的标准输出缓冲不同?
    为什么 Debian 和 Ubuntu Docker 容器之间的标准输出缓冲不同?
    Docker 容器中的标准输出缓冲:Debian 与 Ubuntu 的案例在 Docker 容器中执行代码时,标准输出缓冲可能会发生在在某些情况下,但在其他情况下则不然。在使用 io.MultiWriter 将 stdout 定向到控制台和日志文件的情况下会出现此问题。根本原因:平台差异根本原因造成...
    编程 发布于2024-11-08
  • 语义HTML
    语义HTML
    语义 HTML 是 HTML 的一部分,可帮助您以维护和 SEO 友好的方式组织您的网站。 SEO 代表:搜索引擎优化。 当您在构建网站时遵循 HTML 语义时,该网站往往会更容易被搜索引擎排名更高,当然也能让屏幕阅读器更轻松地浏览您的网站。 以下是一些语义 HTML 标签: 1- “标题”标...
    编程 发布于2024-11-08

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3