-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
代码质量建设 #59
Comments
代码规范建设ESLint 与 Prettier 结合最佳实践 |
在过去,与大多数工程师一样,我认为前端代码的设计水平大多与工程师的能力有直接关系。但随着接手了几个多人协作的大型前端项目,我开始意识到,这种认知对短生命周期的小型项目可能适用,但对真正的大型项目,仅靠提升工程师质量有时并不能直接提升代码的质量。 本文将结合自己的一些实际经验,来阐述自己的一个观点:构建大型高质量前端工程,合理的代码约束与正确的团队运转机制可能更为重要。 什么是高质量的工程代码?**高质量的工程代码,**并不等价于性能最优,技术最新,复用性最强的技术选型。回顾几年前的前端领域:JQuery 时代,虽然要手动操作 DOM,但其实在那时, Google Closure 和 Ext.js 团队就已经提供了完整的组件化概念,甚至 Ext.js 还提供了组件冒泡这样的创新事件机制。那时用 Zepto 维护的代码,编码速度甚至比现在写一些 React 项目还要快。不同的技术只是工具,怎么用工具,能把工具用到什么程度,最终取决于开发者自身,所以高质量的工程代码,更多应该从业务和工程的角度考虑问题,而非技术选型。 举个例子,当整个公司都在使用 React 开发时,虽然我们知道 Vue 使用可能会更简单便捷,但我们一定不会去用,因为这个时候,虽然看起来写代码更简单了,但其他人在 React 方向沉淀的经验,你无法复用,额外还需要整个团队去学习一套全新的技术,这样的工程设计,在这个背景下,显然是不合理的。 Thenewstack 做过 2019 年的开发者数据统计,开发者 32% 的时间用于业务开发,19% 的时间在维护代码,也就是工程师真正能投入到研发中的时间也只有工作时间的一半。对于开发者来说,这个时候通过合理的代码设计,提升代码的**可扩展性,可维护性,**降低开发和维护代码的时间,才是最强的诉求。 所以,高质量的工程代码应该是结合业务与团队情况,真正能够提升研发效率,降低项目维护成本的代码。 谁决定了工程代码的质量?这里可以用木桶理论来类比:木桶中的水位,不取决于最高的木板,而取决于最低的木板。同理,前端工程的质量,不取决于团队的平均能力,而取决于团队经验较少的技术同学的能力。在工作压力比较大的情况下,这些同学由于经验不足,短期又要完成需求,所以很多时候,并没有考虑过工程上的问题,而是直接面向实现功能编程,基本上我们现在面对的难以维护的代码,都是在这种条件下产生的。 我们当然可以寄希望于经验较少的同学通过不断的成长来提升项目的工程质量,但实践下来,这并不可行。原因在于,工程能力的积累需要大量的编码经验,缺少实践经验的问题并不是短期就能够迅速解决的,任何好的工程师都是在不断犯错学习的过程中成长起来的。同时,工程开发过程中很可能会遭遇人员变动,一个团队的成员不肯能永远全部都是能力很强的。 那么我们就需要换一个策略来保障我们的代码质量,我们可以换个角度思考:是否可以通过一些规则,流程,编码上的约束,让编码能力不同的工程师,尽量写出质量相对较高的一致性代码。 通过约束提升工程质量约束让事情变得更简单工作没有约束,工作中我们就难以形成共识,也无法判断工作做的好与坏。写代码也是一样的,没有约束,那么我们也无法判断代码是否合理。在流行的库和框架中,其实到处都是约束的影子,这里拿 Redux 和 React 的设计来举例: Redux 给出了单一数据源,State 只读,使用纯函数来执行修改这三个基本原则,同时要求通过 Action 的方式触发Reducer 的执行,这是 Redux 的约束;React 也给出了单向数据流这样的约束概念。 框架之所以是能够复用,能够得到推广,就是因为它们进行了封装,仅仅提供有限的约束能力供大家使用,这样大家才能形成一致的理念,编写互相能够读得懂的代码。理解了这一点,我们再来看业务工程的代码,实际上要提高开发效率和扩展性,无非也是要提供合理的约束。 工程代码的约束,更多带有一定的工程属性,如:
这些业务的约束,并不等同于 Eslint,不同的业务对代码的要求有可能千差万别,所以业务上的约束,需要研发人员充分的沟通交流,碰撞探讨,以及坚决执行。不同团队的同学,可能讨论出的结果完全不同,但约束的结论是什么本身不重要,重要的是形成一致的开发共识。 通过机制实现约束的落地约束本身并不难制定,对于工程侧的设计,工程师通过讨论比较容易形成博奕后的结论。但机制的落地是相对困难的一环。这里分享几个可执行的保障机制:
通过这样的一些机制,保障约束有效的落地,那么我们就可以抹平团队成员技术能力的差异,形成一致性的编码风格。虽然这种约束下的代码并不一定是最优雅的代码,但至少工程质量不会差。所以这里我认为,约束实际上帮助我们保障的是工程质量的下限,那么接着我们来谈如何通过技术创新,提升工程质量的上限。 在约束之上寻求创新大家可能会有这样的问题:“项目的约束,会不会限制技术的创新”。针对短生命周期的小型项目,这可能是对的,这种项目,使用更多的新技术进行探索突破可能会带来更多的团队技术储备;但对于大型项目来说,我们每天所做的代码设计决策,都可能会影响到明天业务系统的发展进程,任何技术升级都一定要慎重,这时候,我们不应该把约束当作创新的阻碍,而应该把约束当作创新的练兵场。 如果你在大型项目中,想突破约束,使用新技术,进行技术革新,那么一定意味着你要做到以下几件事情:
很多时候,我们做的技术创新,其实只是技术栈的更新,并没有为团队和业务侧带来任何的价值,但当我们想清楚这些问题,能够有信服力的证明新技术或者创新点是有价值的时候,关于系统的升级可能才是真正有价值的。 在约束上的创新,可以让工程师结合业务有更多的思考,产出真正有价值的创新。而**这些有质量的思考和创新,决定了工程质量的上限,**同时也会培养出更多优秀的工程师。 如何提升已有工程质量?对于一个全新的大型项目,我们可以通过上述的方式,分阶段进行架构设计和优化。但是,大多数情况下,我们接手的项目,可能在接手时就会发现其工程质量较低,那么我们应该如何对已有代码进行改良呢? 判断你的系统是否需要改良一个系统的生命周期,可以总结为三个阶段:
对于发展期的系统和稳定期的系统来说,合理的工程设计未来能带来的性能,稳定性等方面的收益十分明显,这个时候,我们可以考虑对系统进行技术升级。而对于衰退期的系统,虽然短期开发维护效率不高,但无法看到未来系统的发展潜力,这时候,继续维护老系统可能是一个更好的选择。并不是每一个系统都必须要改良,精益求精固然好,但是否要做还是要回归到对业务价值的判断上。 如何进行工程改进大型项目的工程改良,可以分为两种方式,自上而下,和自下而上。对于大型项目来说,自上而下的全部重构,成本很大,除非你对系统特别了解,否则并不推荐采用这种方法。相反,目前的主流框架,React, Vue 都是可以对局部 DOM 进行托管的,所以自下而上的逐步升级可能是更好的策略,这种方法有两个优势:成本低,风险小。举个自己工程中的例子,我们需要把 JQuery 升级至 React,采用了这种方式,逐层向上的对 JQuery 中的 Backbone 代码进行替换: export default View.extend({
componentName: 'AuctionDetailContainer',
initialize(options) {
const { dataSchemaV1, pageSchema } = options;
this.ref = React.createRef();
this.dataSchemaV1 = dataSchemaV1;
this.children = pageSchema.getChildren()[0];
this.attributes = pageSchema.getAttributes() || {};
},
render() {
ReactDOM.render((
<AuctionDetailContainerWithRef
ref={this.ref}
taskFields={this.dataSchemaV1}
attributes={this.attributes}
crossTableData={this.children}
/>
), this.$el[0]);
return this;
},
}); 每一次替换,我们只要测试替换部分的逻辑即可,不会影响外部的其他逻辑,这样逐层替换,在保障稳定性和系统升级的双向要求下,做到了很好的平衡。同时,在接手新项目的时候,这种升级的方法还可以逐步帮你梳理清楚业务的逻辑,了解业务。 在这样的逐步替换过程中,结合之前说到的编码约束,我们就可以将系统的代码质量逐步完成提升。而之后,则可以通过创新的方式,进一步对项目优化完善,从而完成整个重构过程。 在这个过程中,有一些工具也可以帮助到我们,举几个例子:
结语本文涉及到的具体编码内容不多,希望从另一个方面能够给你带来一些工程测的启发和思考,一些观点冲突也欢迎大家沟通讨论。
|
防御性编程
是防御式设计的一种具体体现,它是为了保证,对程序的不可预见的使用,不会造成程序功能上的损坏。它可以被看作是为了减少或消除墨菲定律效力的想法
UI 的防御性
防白屏 -- 白屏时间小于1秒(小于200ms最佳)
防布局错乱 -- 布局被动态内容撑垮
防极端内容 -- 缺失 / 超长 / 连续字符 / 未转义
防慢 -- 网络慢 / 响应慢 / 渲染慢 / 执行慢
防卡 -- 卡顿 / 假死
防一致性问题 -- 不一致的交互方式、图标、 标准组件等
防UI状态不全 -- 五层UI状态栈(加载状态/空状态/部分缺失状态/出错状态/理想状态)
防样式污染 -- 样式冲突,局部模块的样式影响全局
防Chartjunk -- 可读性差的图表用法
防误操作 / 危险操作 -- 对不可逆的操作二次确认+强提示
代码的防御性
防报错 -- 语法错误 / 逻辑错误
防兼容性问题
防安全性问题
防意外输入和交互
防数据 -- 防极端数据 / 无效数据 / 接囗变更
防代码坏味道 / 防工程腐化 -- 代码复杂度 / 重复率 / 反模式 / 死代码等
防语法风格不一致
防代码冲突
防代码冗余
参考
防御性编程
The text was updated successfully, but these errors were encountered: