2020 Google开发者大会重磅开幕 了解详情

JavaScript工具怎么就这么烂

2019 年 9 月 25 日

JavaScript工具怎么就这么烂

JavaScript 工具确实超级难用,但这并不是大家的错。


如果大家点进来看了,就证明各位肯定对此抱有同感。哈哈,我也这么觉得。我的日常工作就是跟 JavaScript 工具打交道,而我的观点是,这些工具至少可以做得更好。毕竟“无视问题,就是作恶”。


但不少朋友可能也并不认同我的观点,别担心,其实我自己也并不完全认同文章的标题。但是,各位或多或少都在生活当中遇到过 JavaScript 工具很不好用的情况。作为 JavaScript 开发者,我们花了很多时间来学习如何修复并掩盖自己使用的工具,但却很少问问自己这一切到底是为什么。(当然,这也非常正常,我会在后文中展开来讨论。)


最后,如果您点开这篇文章只是为了拓展一下思路……呃,真的有这么客观冷静的读者朋友吗?无论如何,同样欢迎您的光临。


当然,以上都是假设,而不是客观事实。我想做的只是解释和分析,而非粗暴判断。我的所有博文都出于自己当下的观点,我的观点经常会变。而且我也要强调一点:本文只谈缺点,暂时不涉及 JS 工具那些重要的优势。


JavaScript 在最初设计上与当前使用场景不符


JavaScript 工具之所以这么烂,是因为 JavaScript 在最初设计上就没有考虑到关键业务工具与应用程序的需求。


JS 的开发周期是出了名的快——整个过程不到 10 天,而且当时没人想得到软件会吞噬整个世界、浏览器软件会吞噬操作系统软件,而 JavaScript 又能跳过浏览器在操作系统中占据一席之地。


JS 的文化无疑继承自其最初设计与决策。凭借着易于启动与运行的特性,JS 为众多程序员打开了开发的大门;但一系列缺陷也由此而来,包括未定义属性访问不会导致运行时错误,乃至著名的“undefined 并不是函数”错误等等。我之所以将 JS 目前的很多问题归结为一种“原罪”,是因为 JS 的本性确实非常独特,也使得很多开发者始终拒绝在 JS 当中添加编译时间检查机制。更有甚者,不少“极端分子”甚至拒绝使用 Prettier。


Web Assembly 可能终有一天会解释那些不想使用 JS、但却没有更好选择的开发人员,但在此之前,他们仍然得继续忍耐下去。


这就意味着……


庞大的社区


JavaScript 工具之所以这么烂,是因为它不是单纯为某一类用户打造的。


作为地球最庞大的非电子表格编程语言,无论是代码风格、软件交付还是测试方法,开发人员们都能为此爆发出激烈的争夺——甚至上升到宗教战争级别的口诛笔伐。其中最具争议的工具当数 Babel!


虽然庞大的技术社区通常是项目具有活力的表现,但同时也会带来种种人们意识不到的问题。正所谓林子大了,什么鸟都有;每个人其实都有着非常具体,甚至是相互矛盾的诉求。因此,我们越来越难以“做好一件事”来让每个人都开心。


让人们开心的最简单方法,当然是在配置之后再另配置,这就是我们的主要工作内容。然而,这种方式既会带来地狱般的复杂配置体系,也将严重影响工具之间的互操作性。我个人非常赞成 Jared Palmer 的观点,即应当只关注主干结构以及 80%的用例。另外,我也建议很多用户尝试使用 patch-package 等工具维护自己的低成本分叉。


大社区好是好,但会导致……


不过变化的目标,不过变化的边界


JavaScript 工具之所以这么烂,是因为它在设计之初并未考虑到单一目标运行时。


很多人提出,JS 库是否应当能够同时在浏览器与 Node 当中运行?有人不太赞成。


此外,人们也不赞成 JS 工具应当转换自身依赖关系的观点。


人们也不希望处理遗留浏览器的问题。随着时间的推移,这方面问题会越来越小,但得承认问题仍然存在。Create-React App 已经就此进行了长达两年的讨论。


人们也不愿对工具进行重复测试。Kent C Dodds 在 JS 测试方面做了大量工作,但时至今日仍有不少工具还完全没有完成自己的审查——这一点在 Windows 等不同环境中表现得尤为突出。甚至有意见指出,没有工具可能都要好于使用一大堆未经测试的工具。


最后,人们也无法在工具需要解决的问题方面达成一致。举个极端的例子,这就像是初学者询问 A 和 B 之间有何差异(因为二者经常相互替代),而维护者回答称 A 与 B 属于“正交关系”。但事实上,A 的专家(只关注 A)对 B 的专家(只关注 B)一直非常不满。


更要命的是,有时候我们似乎距离达成一致就只差一步,但这时候边界线变了——带来了新的争议与难题。在这种情况下,人们宁愿在彼此之间的争议身上投入精力和时间,而不愿支持并提醒对方还有更重要的大方向需要把控。这必然会让参与者们最终都筋疲力尽。


这又会导致……


无人负责


JavaScript 工具之所以这么烂,是因为它是自下而上创造出来的——而非自上而下。


JavaScrip 不只是一种语言,也不存在“JavaScript SDK”或者“标准库”。


TC39 负责语言管理工作(我记不清出处了,但确实有意见认为不该着眼于单一功能对 JS 进行年度设计规划),但工具方面却没人控制。这里面 npm 倒是发挥着一定程度的作用,不过大家似乎不愿意让他们管理除此之外的其他事务。Node 非常成功,但同样身陷原罪的泥潭之内——其创造者也开始努力寻找替代方案。在集权与分权的对抗中,JavaScript 将分权推向了极致(无独有偶,Python 也有类似的情况,好在其仍然保有比较明确的集中话语权)。


没错,我们每隔一段时间就会迎来一套新的前端框架(但绝不像很多人开玩笑说得那样每个月甚至每天都有新框架出来,在这方面我建议大家保持严谨的态度。不了解情况就随意发表意见,实际上就是对实际工作的人们的诋毁)。没错,我们也有 3 到 4 套不同的捆绑包,以及不少根本没人用的文件格式。另外,每个月的更新包下载量高达 9300 万次。


这是种极大的重复与浪费,带来了不安全感与疲惫情绪,同时也提高了学习的难度。


我们都知道。但这是我们在确保交付给每位用户之前,对成果进行充分体验与测试的最佳方法。如果某些工具不好用,我们至少还有其它指望。而如果替代方案确实更好,那么相信原有工具也会自我改善并慢慢赶上……或者彻底消失在人们的视野中。


当然,我不是说 JavaScript 在传递与发布信息方面做得完美无缺。虽然理论上“使用正确的工具来完成正确的工作”是种无比正确的选择,但绝大多数人其实并没有时间或者意愿去尝试其他人从未用过的工具。


这实际上意味着,我们自己在拒绝检查自己使用的工具是否正确。


我们无法真正拥有自己使用的工具


JavaScript 工具之所以这么烂,是因为它在本质上只是一堆脆弱的抽象堆栈。


说到这里,我打算聊聊最近发生的一系列现实事件。我一般不怎么谈时事,但为了充分讨论这个问题,我只能破一次例。


在默认情况下,所有 npm install 都会利用插入符(^)进行工具包分配,该插入符与工具包的次要及主要版本相匹配,且假定该工具包遵循 semver。然而,semver 只是一种无责任约定。如果有用户的正常运行建立在存在 bug 的基础上,那么修复 bug 就会发生破坏性的影响。因此,semver 采用的是一种非常技术化的 bug 定义方式,即“软件未能像我们预期的那样工作。”更重要的是,semver 在发布时不会以任何具有实际意义的方式强制执行,因此它无法干涉我们的实际下载过程。但是,我们之所以默认使用插入符,需要的正是这种干涉。


当 event-stream 发生时,即使已经存在于注册表内,包也有会被删除,而所有基于该包的 JS 工具及应用程序也会发生故障。对包管理机制的安全保障无比重要,但最终决定一切的居然只是一个小小的字符:^。


那一次,我持续部署的所有 JavaScript build 都受到了影响;这是因为 flatmap-stream 依赖于 event-stream,后者处于 fs-events 之内,fs-events 又是 Vue、React、Angular 以及几乎每一种其他工具的依赖对象。像 JavaScript 这样一个号称竞争性、大规模、多样化的生态系统,怎么会存在单一故障点?我真的很慌,但好在知道该如何解决。我猜,那天很多初学者因此放弃了 JavaScript。可能很多用户像我一样,当时正在向人们展示自己的软件,并第一次遇到这样的情况。但观众们不会管问题来自哪里——谁在做展示,就是谁的问题。


问题大概就是这样,但也不止于此。npm 可以通过默认安装固定版本来修复我的场景(显然 Go 生态系统就是这么做的),但却不能从根本上解决“我不知道自己在使用什么”的问题。事实上,那天我是第一次听说 flatmap-stream。


我承认这是我的错,而同时也是每一位使用者的错。正如这一节的小标题所言,“我们无法真正拥有自己使用的工具”。


因此,我改变了主意。


很多著名的开发人员都会说,大家应该像处理自己的代码那样处理 node_modules。但这根本就不现实,只会让我们在出了问题之后怪罪自己。事实上,我们几乎不可能完全掌控整个依赖链。


Thomas Young 是最后一位无所不知的大能,但他在 1829 年就去世了。


Rich Harris 也曾经指出这种小模块方法的缺陷。具体来讲,他更重视软件的可发现性与完整性:“每个人都有自己对于大海捞针问题的解决方法,但没人会考虑海里的水滴在干什么。”他说得很实在,但这么简单的道理在庞大的社区当中却很难得到贯彻。


即使大家身为模型开发者,也不可能真的与社区隔绝。您总得跟那些非模型开发者们协作,而他们仍然掌握不了自己使用的工具。因此,我们将不得不使用自己不熟悉的工具,这确实是一种客观存在的统计学概率。


基本上,如果我们整个工具链/生产力策略的可靠性取决于每一项依赖,那么我们必须想办法施以变革,否则问题将永远得不到解决。(这里要强调一点,我并没有办法,目前只能向大家传达出问题所在。)


我们把工具开发者视为工具人


JavaScript 工具之所以这么烂,是因为我们自己不开发工具,而且不把开发这些工具的人当人看。


我们不需要了解一切,但这并不是说我们就不用在乎那些辛苦开发工具的工作者。这是个真正的问题,而且正在毒害整个社区。


可能大家对此没有深刻的体会,但各位可以想想,自己身边有没有人会投入大量时间来开发 JavaScript 工具——即使有,他们也是绝对的少数。下载数字不会说谎。TypeScript 的贡献者有 412 名,Webpack 有 547 名,Babel 有 757 名,React 有 1331 名。所有这些工具的用户,至少要比贡献者多 3 个数量级。另外,甚至还存在着这样一种无法否认的现实:大多数贡献者只负责修正拼写错误以及编写说明文档(虽然也很重要,但大家懂我的意思)!


如果大家只需要最常规的 JavaScript 或者 TypeScript 功能,那可能压根不需要这些工具,所以不认识也不关心这些工具到底是由谁在维护。您可能在一家财富五百强企业工作,也可能效力于一家苦苦挣扎的初创企业;您可能身在一家光鲜的数字代理商,或者只打算在工作之余给自谋求一条跳槽的后路。无论如何,您想要的跟无数其他用户一样:把自己想要的东西显示在屏幕上!


这种不对称性,意味着当工具用户与工具开发者进行交互时,必然存在巨大的差异性。由于这种相对知识差异,以及支持者习惯保持的善意心态,工具开发者们往往被迫妥协,帮助用户诊断问题甚至填写最基础的 bug 报告。


接下来要说的问题,比 bug 报告要严重得多。无数讲师靠着传授 JavaScript 开发知识赚得盆满钵满,而开具开发者却只能在 Patreon 和 GitHub 上靠打赏生活。工程经理与 CTO 们拿着数百万美元的年薪,并依靠着漂亮的图表保持自己“俯视众生”的优越感。如果您也曾像他们一样,在面对工具中的问题时总是保持“俯视”的态度,而从来没有参与其中进行检查或者提供助力,那么您就和我自己一样成了问题的一部分。工具,特别是那些年轻的工具,不可能自行完善。只有你我的参与,才有可能让它变得越来越好。


更糟糕的是,所有这一切都有着看似合理的理由。把一大堆自己不熟悉的东西整理起来,共同打包成光鲜亮丽的产品进行出售,才是绝大多数人真正想做的事情。虽然我也鼓励大家在进行技术选择时抱持谨慎态度,但出于良知,我们真的应该更积极地做出自己的贡献。另外,至少不要把工具开发者当成活该付出的工具人。


目前的整体趋势是,初级工程师们整天摆弄工具,而高级工程师则扔下了实际工作开始探讨更重大的目标。我个人,真的希望老手们能多帮帮那些新人。“我年轻的时候就知道怎么用软件了,现在这帮孩子居然连编程都不会,简直太可笑了”这种屁话真的只会令人生厌,但却不断出现在训练营当以及应届毕业生耳边。没错,高级人员需要考虑的不只是工具,他们还要操心更多关于软件生产与管理的工作。但是,我希望能有更好的途径传递以及传播他们的经验教训,而不只是在社交媒体上对初学者们冷嘲热讽。


当然,我不指望有人会看了这篇文章就改变自己的态度乃至行为。我也不大相信工具开发者们会在我的鼓舞之下,怒吼“赶紧给老子送钱来!”


资金


JavaScript 工具之所以这么烂,是因为我们还没弄明白该如何在填饱肚子的同时实现免费开源。


我把最重要的问题留在了最后。JavaScript 有 97%的组成部分属于开源成果,因此开源的问题自然也就成了 JavaScript 的问题。


开源维护的问题相信已经不需要细聊,获取开源资金的尝试多种多样——但其中很多办法明显只是失败的馊主意。经过最近的一系列尝试,Feross 得出结论,“开源维护者,就像是一位拿不到任何回报的初创企业创始人。”这话说得没错,虽然开源维护者偶尔也能够凭借自己的工作成果建立初创公司,但这种机率实在是太低太低了。


我不喜欢的解决方案:


  • 广告业务模式(在文档与控制台当中发布广告)

  • 在Patreon上求打赏

  • 在GitHub上求打赏

  • 任何由开发人员自己——而非企业雇主——出钱的财务资助

  • Gitcoin等赏金机制


我喜欢的解决方案:


  • 培训(即React Training)

  • 管理服务(Laravel Forge、Meteor Galaxy等)

  • 我强烈推荐Joseph Jacks提出的“Open Core”以及“OSS商业模式”思考。但需要强调的是,这些方法都是在牺牲开放合作空间以换取更高的软件成果回报。



除了对开源商业模式的经济学思考之外,我们也不太可能指望外部驱动力解决问题。但遗憾的是,我们天然喜爱外部驱动力,或者说喜欢由他人帮忙解决问题。Dan Pink 对这个问题总结得非常到位:



“在孩提时代,我们主要受到内驱力的影响,我们自发学习并主动帮助他人。但随着年龄的增长,我们被社会所影响并越来越依赖于外驱力:只要我们能够克服问题、努力学习、勤奋工作,我们就能得到他人的善意、更高的职级以及丰厚的薪水。慢慢的,我们丧失了内驱力。长大的过程,就是天性被消磨殆尽的过程。”


考虑到对自身基本需求的满足,我非常赞同这种内驱力才是实现开源项目可持续发展的最佳方式。虽然大家选择的 OSS 商业模式可能在财务上取得成功,但如果一直受到外部激励的驱动,那么总有一天我们会因这种外来因素做出与解决核心问题无关的选择乃至妥协。


也有不少出色的工具方案


JavaScript 工具并不全是这么烂,至少要比以前那些工具好得多!


最后,我想聊点积极的势头,毕竟好的趋势值得肯定,我也不是那种不分青红皂白乱黑一通的家伙。我们今天的工具可以说是广受支持,Jest、Express、GraphQL 在 2018 年可谓如日中天。除此之外,Yarn 以及 Prettier 也同样得到了用户们的广泛好评。


在最新的 JavaScript 现状调查当中,受访者表现出的态度也相当积极:



结合前文中我提出的问题,下面来看相关改善趋势:


  • 设计: JavaScript语言正变得愈发严肃,能够与ES6配合使用,因此其实际是在使用的过程中不断设计完成的。TypeScript也迎来了很好的接受度,占npm用户中的63%(虽然还无法解决所有问题,但至少比以往的方案好了很多)。

  • 社区: 我们在制定行为准则方面做得越来越好。GitHub提供问题模板与反馈机制,以促使人们更好地开展合作。在这些模式提供的高压力测试当中,我们正不断总结出更好的结构与抽象模式。

  • 目标变化: 众多常青树级浏览器的存在,有助于提供更加稳定的目标。虽然跨浏览器的不一致问题仍然需要抽象层帮助解决,但不同浏览器间的区别已经快速缩小,这一点在工具选择以及相关影响方面体现得尤为明显。

  • 无人负责: 这是一种特性,而非错误。

  • 所有权: 我暂时还没什么办法。

  • 工具开发者: 我们正在传授知识。Ander Helsberg拥有35年的语言与工具开发经验,其目前正在研究TypeScript并培训新一代工程师。我们也积极通过各类开发者大会传授知识。

  • 资金: 虽然我已经解释了自己倾向于保守的观点,但必须承认仍有不少举措有助于解决开源项目的资金问题,而且这些举措非常重要。


原文链接:


Why JavaScript Tooling Sucks


2019 年 9 月 25 日 19:47 3713

评论

发布
暂无评论
发现更多内容

ZooKeeper,到底如何选主?

奈学教育

产品周刊 | 第 15 期(20200517)

Herbert

产品 设计 产品经理 产品设计

换脸新潮流:BIGO风靡全球的人脸风格迁移技术

DT极客

MySQL事务解析

一个有志气的DB

MySQL 事务隔离级别 mysql事务

程序员的晚餐 | 5 月 18 日 瓠子,年少时的味道

清远

美食

Web3极客日报 #128

谢锐 | Frozen

区块链 开源 技术社区 Rebase Web3 Daily

Web3极客日报#127

谢锐 | Frozen

区块链 开源 技术社区 Rebase Web3 Daily

给苹果提醒APP配个助手

BabyKing

提醒助手 TODO 奇妙清单 Reminders Helper

Spring Security 中的授权操作原来这么简单

江南一点雨

Java spring Spring Boot spring security

识别代码中的坏味道(三)

Page

敏捷开发 面向对象 重构 代码质量 代码坏味道

中小企业如何做运维自动化?

Spug运维

运维 spug 运维自动化 jenkins ansible

从零开始制作一台计算机-概述

小兵

计算机基础

谈谈控制感(7):底线思维与控制感

史方远

职场 心理 成长

DDD 实践手册(番外篇: 事件风暴-概念)

Joshua

领域驱动设计 DDD 事件风暴 事件驱动 Event Storming

重新强调完成的定义

Bob Jiang

Scrum 完成的定义 DoD definition of done

Redis缓存三大问题

Bruce Duan

redis 缓存穿透 缓存击穿 缓存雪崩

游戏夜读 | Two Sum问题的八个解

game1night

JAVA主流锁

颇风

多线程 Java、

设计模式前传——为什么要学设计模式

海星

Java 面试 设计模式

Live2D for Unity入门篇 4.x

波波

编程 游戏开发 Live2D Unity

Kotlin 协程实践(2)之 异步和Callback地狱

陈吉米

Java kotlin 协程

Vue+SpringBoot+SpreadJS 实现的在线文档

Geek_Willie

Spring Boot Vue SpreadJS

NIO看破也说破(四)—— Java的NIO

小眼睛聊技术

Java 开源 学习方法 架构 后端

回“疫”录(20):世界从来不会欺负听话的人

小天同学

疫情 回忆录 现实纪录 纪实

半小时手工解决的活,让我意外学会了 python 的 pdfkit 库

Sicolas Flamel

Python python教程

Kafka系列第7篇:你必须要知道集群内部工作原理的一些事!

z小赵

大数据 kafka 实时计算

npm下载electron缓慢的问题

玏佾

npm Electron

如何更自信的写作

七镜花园-董一凡

写作

Deno 入门手册:附大量 TypeScript 代码实例

寇云

node.js typescript

东哥和刘亦菲的故事

张利东

R

单核小鸡上的Minikube实践(一)

摩登土狗

Docker Linux DevOps k8s minikube

2020中国技术力量年度榜单盛典

2020中国技术力量年度榜单盛典

JavaScript工具怎么就这么烂-InfoQ