我是从C/C++时代走过来的开发者,对静态语言的那些设计上的设定,非常熟悉。然而,我却发现不少只研究JavaScript的程序员,对程序语言的“数据类型”问题,有着迷之自信。
典型表现就是:他们把TypeScript吹捧的过度了,似乎系统开发没使用TypeScript就是不靠谱的系统;开发者没有使用TypeScript就是不合格的。这个结论是荒谬的。
略微推测下,有几种可能原因:
布道者的吹捧。这些鼓吹有些是自发的,有些则是拿了推广费用的。大公司为了推广某些技术,雇佣、赞助布道者写文章、答疑推广,是很常见的事情。谷歌、微软没少干这个。但是这没什么,技术竞争而已。关键是自己要会分析总结,不要听到就认为正确。
JavaScript的缺陷。它确实是有很多问题的编程语言。虽然此语言演化速度非常的快,可是为了向后兼容性,一些根本性的缺陷是很难修复的。
TypeScript的出现解决了不少JavaScript缺乏类型系统导致的天然不足。不过请注意,以传统的C/C++等编程语言为代表的“静态语言”,和以Python、JavaScript等编程语言为代表的“动态语言”,从设计之初,对类型的考虑就是不同的。
换句话说:动态语言缺乏“类型系统”,是设计而非缺陷!它是故意这样做的。为什么?因为动态语言的开发效率很高。
C/C++等静态语言开发是很麻烦的,语言上的限制是直接原因。代码从头到尾考虑类型问题是很大的负担,另外一个就是手工内存管理的负担。静态语言以这些为代价,换取了程序的高性能运行。Python为了严谨,故意要求显式的数据转换,但是仍然比静态语言容易的多了。
静态语言的类型在变量声明这边标记,编译时候确定并检查,而实际上动态语言也不是没有类型,它是存储在了变量内容这边。因为动态语言变量指向的数据,可以随时切换,所以才显得“缺乏类型”。这里先不说类型强弱的话题。
看图,一图抵千言。
上图来自:https://prepinsta.com/python/dynamic-typing-vs-static-typing-in-python/
如果你认为,使用静态语言编程序引发的bug较少,这个其实是不符合统计数据的。统计数据表明,代码的缺陷数量,跟语言是否静态关系不大,跟代码量关系比较大。或者说,只要有足够多的代码行,功能复杂,无论用什么语言写,都会出现软件缺陷。使用静态语言编程序,有类型系统支持,确实可以在编译阶段消除一些问题。但是C/C++写的程序Bug就少么?没有的事情,软件缺陷并不会因为强制性的类型约束完全解决。反而,因为动态语言书写方便,开发效率高,各种库众多,模块化程度更高,代码写的比较少了。总体的缺陷实际是可以更少的。
动态语言有个天然的不足,变量类型在运行中,变来变去,导致很难静态推断程序的执行过程,数据流的加工和变化。这也导致,它开发出的系统,性能较差,很多问题在运行阶段才能发现。
TypeScript引入类型系统,为JavaScript增加了“可选”的类型系统,注意:它是可选的。这个类型系统有不少好处,比如:
- 1、IDE智能提示、智能检验调用关系的参数、结果匹配。如果你使用WebStrom,就会发现它已经这么做了,写错的不匹配的类型,自动标记提示,非常的有用。
2、更严谨的接口设计、数据结果设计。接口定义一下,如果有修改造成不匹配,会在编译阶段就立刻知道,这样就及时消灭了潜在的Bug。
除此以外,选择TypeScript,可以用更新式的、更方便的语法书写程序,可以编译成向后兼容的代码。舒缓运行环境对js版本支持不同引发的问题。
然而,它并不是“银弹”。如果是的话,何必做成“可选”的类型,直接默认强制开“strict – 严格”模式就好了。因为强制的类型会带来开发的负担和兼容性问题,而这点正是需要平衡考虑的事情。如果你认为这个模块可靠性非常重要,那就开严格类型,强制要求。不重要,完全能控制得住,那就直接跳过吧,把调用接口、运行结果控制住,就已经成功了一大半。
开发类型支持是需要大量脑细胞的,不信的话看看:
type Exclude<T, U> = T extends U ? never : T;
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
const InjectColor =
<Props extends { color: string }>(Component: React.ComponentType<Props>): React.ComponentClass<Omit<Props, “color”>> =>
class extends React.Component<Omit<Props, “color”>, any> {
public render() {
return <GetColor>{color => <Component {…this.props} color={color} />}</GetColor>;
}
};
这一坨一坨的泛型,比臭名昭著的C++模板还复杂,你确定这就是使用TypeScript的最大优点么?即便我能理解、有效的使用,团队中的其它人呢?对于软件工程来说,这个风险和成本并不是不用考虑的。这种“类型体操”,多半是危险行为,开发维护成本都很高,并不值得推崇。然而一些TypeScript的铁粉,坚持认为这就是最佳实践,必须把每一处类型都要定义清楚,这才叫“赏心悦目”。迷之自信。连最新版的C++都推荐使用“auto”了,你们怎么还倒退回去了。过犹不及。
对于系统的可靠性,真正重要的是充分的测试、测试、测试。重复三遍。
无论是什么语言开发的系统,只要有了充分的测试,特别是自动化的回归测试,有各种数据支持的测试案例支撑,可靠性、稳定性都不会太差。现代应用程序,因为模块化程度比较高,网络操作深度绑定,调用链条又长,所以很容易在模块边界上出现各种问题,而这都需要用有效的测试来保证,并不是语言本身可以解决的。那,测试岂不成了“银弹”?当然不是,测试解决不了系统设计、开发上的缺陷。它只是尽可能的保证,软件的运行结果符合预期。
结论:没有银弹,类型系统不是,TypeScript也不是。认真细致的设计、遵守规范的开发,严密到位的测试,这才是软件质量的重要保证。