2

前端代码质量的思考与实践

编写可读的代码,对于以代码谋生的程序员而言,是一件极为重要的事。从某种角度来说,代码最重要的功能是能够被阅读,其次才是能够被正确执行。一段无法正确执行的代码,也许会使项目延期几天,但它造成的危害只是暂时和轻微的,毕竟这种代码无法通过测试并影响最终的产品;但是,一段能够正确执行,但缺乏条理、难以阅读的代码,它造成的危害却是深远和广泛的:这种代码会提高产品后续迭代和维护的成本,影响产品的稳定,破坏团队的团结,除非我们花费数倍于编写这段代码的时间和精力,来消除它对项目造成的负面影响。

JavaScript 是动态和弱类型的语言,使用起来比较轻松随意,在IE6那个刀耕火种的时代,轻松随意的习惯确实不是什么大问题,反而能节省时间,提高出活儿的速度。但是,随着当下前端工程化技术的快速发展,前端项目规模的不断膨胀,以往那种轻松随意的编码习惯,已经成为项目推进的一大阻力。

分享与共勉

  1. Any fool can write code that a computer can understand. Good programmers write code that humans can understand. —— Martin Fowler
  2. Good code is its own best documentation. As you’re about to add a comment, ask yourself, “How can I improve the code so that this comment isn’t needed?” ——Steve McConnell
  3. Programs must be written for people to read, and only incidentally for machines to execute. —— Harold Abelson

HTML中的实践

  • 使用语义化标签
  • 清晰、简洁的层级嵌套结构
  • 尽可能少地使用无意义的标签,如div和span
  • 语义不明显,可以用p也可以用div,优先用p标签
  • 在无法用标签表明语义的场景下增加适当的注释

CSS中的实践

  • 引入sass或其他css预处理器
  • 命名可以参考借鉴BEM命名法和中划线命名法
  • 根节点用id选择器,其它用类选择器,子代替代后代
  • css属性书写采用约定的顺序
  • 类命名中划线分隔不超过4级,超过了从0开始
  • 尽量避免使用float,position,采用flex布局和grid布局
  • 如果只能直接编写css,也可以参考上述体现的思路
  • 如果无法直接引入sass,可用IDE或终端进行手动转译

javascript和jQuery的实践

变量

  变量命名是编写可读代码的基础。只有变量被赋予了一个合适的名字,才能表达出它在环境中的意义。

  命名必须传递足够的信息,形如 getData 这样的函数命名就没能提供足够的信息,读者也完全无法猜测这个函数会做出些什么事情。而 fetchUserInfoAsync 也许就好很多,读者至少会猜测出,这个函数大约会远程地获取用户信息;而且因为它有一个 Async 后缀,读者甚至能猜出这个函数会返回一个 Promise 对象。

  • 命名的基础。用名词命名对象,用动词命名函数,用复数表示集合,也可以加上List或Map后缀来显示地表示出来,使代码接近于自然语言。变量命名建议用小驼峰,类建议用大驼峰。
  • 命名规范。时刻按照某种规则来命名变量和函数,不用担心变量污染和能够见名知意了。如:fetch或async代表异步,get代表获取,set代表设置,is、has、can代表一个布尔值,handle代表普通函数等。.
  • 命名的上下文。变量都是处在上下文(作用域)之内,变量的命名应与上下文相契合,同一个变量,在不同的上下文中,命名可以不同。
  • 匈牙利命名法。基本原则:变量名=属性+类型+描述。其中每一个对象的名称都要求有明确含义,可以取对象全称或简写。例如:sUserName,代表用户姓名的字符串。拥有一定的学习成本,自行取舍。.

常量

  在ECMAScript 6之前,JavaScript中并没有真正的常量的概念。然而,这并不能组织开发者将变量用作常量。为了区分普通的变量(变量的值是可变的)和常量(常量的值初始化之后就不能变了),一种通用的命名约定应运而生。这个约定源自于C语言,它使用大写字母和下划线来命名,下划线用以分隔单词。

  • 约定命名。变量名全部用大写字母,多个单词用下划线分隔
  • 魔术常量。同一个常量(值)在不同的上下文中可能代表着不同的含义。引申到js中,就是要使用有意义的变量名代替魔术常量,以提高代码的维护性和可读性。
  • 避免硬编码。对于代码中重复使用次数较多、未来可能会变动的数据,最好抽离成变量或配置项,实现代码逻辑和业务松耦合,增强维护性。
  • 常量的声明。按关键字优先使用顺序:const => let => var。能用const和let的尽量不要用var,var声明有副作用。

注释

  何时添加注释是具有争议的一个话提。代码注释不是越多越好。(注:语义化的命名可以减少很多不必要的注释,最好的代码是自解释的,不要过分地追求注释,影响代码的阅读。)

  • 难于理解的代码。逻辑比较复杂或特殊的业务逻辑时都应当加注释。关键是让其他人更容易读懂这段代码。
  • 可能被错误理解的代码。在团队开发中,当自己写的代码可能会被其他同事认为有错误时,需要添加注释。
  • 浏览器特性hack。兼容性代码会让人看不明白,此时应当添加适当的注释解释其用途。
  • 文档注释。对于工具类等公共方法,添加文档注释,解释其用途、参数和返回值,让人看注释就能明白使用方法,而不用关心方法内部的实现。

函数

  JS是一种多范式编程语言。函数式编程是一种编程范式,一种构建计算机程序结构和元素的方式,将计算视为数学函数的评估并避免改变状态和可变数据。它使你能够构建无副作用的功能,而函数式编程的一些优点,也使得系统变得更加容易维护。

  • 纯函数。给出相同的参数,它返回相同的结果(确定性);不会引起任何可观察到的副作用;容易测试。
  • 闭包。通过闭包可以在函数外部能访问到函数内部的变量,过度地使用闭包会造成内存泄露。
  • 单一原则。如果一个方法承担了过多的职责,在需求发生变化的过程中,需要改写这个方法的可能性就越大。
  • 面向切面编程。非侵入性的改造函数。保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用现有功能模块。
  • 健壮性。web开发安全守则:永远不要相信用户的输入。对数据最好做异常处理,增强其健壮性。例如XSS。
  • 参数。形参越少越好,不超过3个,超过3的用对象代替,这样可以极大地减少该方法的UT自测场景。
  • 注:尾调用和尾递归

前端异常的处理

  前端异常的处理也属于代码健壮性的一部分。JavaScript不像Java一样,有专门的语法去定义和实现异常处理,前端代码是离用户最近的代码,异常的处理更多的是从用户体验和代码健壮性出发的,后端代码异常的处理更多的是从业务角度和数据安全性出发的。所以JavaScript的异常处理方法主要是对页面内容(用户输入和接口输入)的校验上,在异常的时候能给出用户能看懂的提示,另一方面,从代码角度来上来说,研发也能够从页面的提示上快速地定位问题、修复问题,提升解决问题的效率。

使用全等===替代相等==(副作用)

  javascript具有强制类型转换机制,判断相等的操作是很微妙的。对于某些运算来说,为了得到成功的结果,强制类型转换会驱使某种类型的变量自动转换成其他不同类型,这种情形往往会造成意想不到的结果。

  发生强制类型转换最常见的场景就是,使用了判断相等运算符==和!=的时候。当要比较的两个值的类型不同时,这两个运算符都会有强制类型转换。但在很多实际情况中,代码并不按照我所期望的方式运行。Crockford的编程规范、jQuery核心风格指南、SproutCore编程风格指南推荐使用===和!==。

jQuery效率提升建议

  • 正确使用选择器。Id > tag > class > 属性和伪类
  • 层级选择器尽量使用find方法,效率较高
  • 缓存 jQuery 对象
  • 链式调用
  • 事件委托
  • 少改动DOM

设计模式之单例模式

  设计模式是在软件设计过程中针对特定问题的简洁而优雅的解决方案。《parctical common lisp》的作者曾说,如果你需要一种模式,那一定是哪里出了问题。他所说的问题是指因为语言的天生缺陷,不得不去寻求和总结一种通用的解决方案。不管是弱类型或强类型,静态或动态语言,命令式或说明式语言、每种语言都有天生的优缺点。

  单例模式比较好理解,它是一个对象,我们创建这个对象的目地是为了系统管理代码中的一些公用的变量,对象,函数,避免出现变量污染,一般我们声明一些变量和函数的时候,我们是在封闭的函数中,一般不会造成变量污染情况,但是以防万一,原因是在js中,全局变量和局部变量的关系比较复杂(有时候还夹杂着一部分变量提升问题),不少coder经常会因为此问题出现bug老半天找不到原因,创建好这个对象以后我们只是对外暴露一个对象入口,使用里面的变量时我们可以以object.变量名的形式来调用变量,其实也是为了实现js代码块的划分命名空间来设计的。

  我之前写了几年的php和jQuery,经过了项目中不断地摸索与总结,得出了一个结论:在jquery技术栈的实际项目开发中,近乎95%的需求都可以通过单例模式来满足实现。

设计模式之适配器模式

  适配器模式是将一个类(对象)的接口(方法或属性)转化成客户希望的另外一个接口(方法或属性),适配器模式使得原本由于接口不兼容而不能一起工作的那些类(对象)可以一些工作。

  适用场景:

  • 使用一个已经存在的对象,但其方法或属性接口不符合你的要求
  • 你想创建一个可复用的对象,该对象可以与其它不相关的对象或不可见对象(即接口方法或属性不兼容的对象)协同工作
  • 想使用已经存在的对象,但是不能对每一个都进行原型继承以匹配它的接口。对象适配器可以适配它的父对象接口方法或属性

推荐ES6,其次ES5,最后ES3

  • 使用const,let,不使用var
  • 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号
  • 使用数组成员对变量赋值时,优先使用解构赋值
  • 立即执行函数可以写成箭头函数的形式。
  • 对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。
  • 使用扩展运算符(...)拷贝数组。

VUE中的实践

大道至简,通俗易懂

  • 语义化的标签,可以配合类名实现
  • 层次结构清晰、简洁,用最少的嵌套实现最复杂的结构
  • 充分利用sass的作用域,样式私有化,把css样式影响范围最小化
  • 使用大众化的写法,减少学习的成本
  • 尽量多使用类选择器,少用标签选择器,实现复用
  • CSS样式属性的顺序可以参考前面章节《CSS中的实践》
  • 把视图展示的部分放在template中,视图层主要负责展示
  • 把数据(entity)放在data和computed中,数据层主要负责声明(interface, abstract)
  • 把逻辑放在methods和其他几个api里,控制层主要负责实现
  • 单向数据流的思想。数据改变可记录、可跟踪,源头易追溯;数据只有唯一入口和出口,更直观更容易理解,提高可维护性。
  • 注:95%的业务场景最多只需要使用到3-4个生命周期,如果超过了,检查一下自己的代码
  • 代码风格可参考:https://cn.vuejs.org/v2/style...

前端发展的各个阶段

前端框架的对比

常用设计模式与代码的结合

JavaScript语言的特点

  • 声明提升。代码在执行的时候,js解析器会先把funtion声明和var声明放在前面,然后顺序执行对应的语句。推荐用es6的const,其次let,不推荐用var。
  • 私有变量。可以通过闭包来实现私有变量的声明,但闭包会让私有变量常驻内存,滥用闭包会造成内存泄露,所以要谨慎使用闭包。
  • for-in。对象可以采用传统for-in循环遍历,但该方式会遍历出原型链上的属性,产生意想不到的结果。推荐使用ES6的Object.keys和Object.values结合数组的api。
  • 多态。JS没有多态,某种意义上说多态与函数的单一原则是对立的,所以建议多类型参数执行的是同一类逻辑的时候用多态。参考:jquery源码的Sizzle引擎。
  • 单线程。通过异步解决单线程的弊端。但传统异步解决方案通过回调实现,大量嵌套的回调会造成回调地狱的现象,代码可读性很差,不利于维护。推荐使用ES6的Promise和async/await。
  • 副作用。全面拥抱函数式编程,避免使用有副作用的代码,如ES3中var的声明提升,for-in的副作用,==的隐式转换, BOM中alert的阻塞效果等。

书籍以及社区

  • 《JavaScript权威指南》:犀牛书可以先大致通读几遍,也可以把其当作工具书,时时翻阅。
  • 《JavaScript高级程序设计》:红宝书虽然号称高级,但其实是帮助入门的。小红书配合犀牛书,相互印证。
  • 《你不知道的JavaScript 上中下》:这本绝对是神书,让你了解JavaScript不为人知的另一面,把闭包、异步这些讲得很通透。
  • 《ES6 标准入门(第3版)》阮老师的书,ES6入门书籍。
  • 《锋利的jQuery》循序渐进地对jQuery的各种API进行了介绍
  • 《 JavaScript模式》陈新 译,系统地讲解了编写高质量代码的技巧和开发过程中最常用的几种设计模式。
  • 《重构-改善既有代码的设计》一些写代码的思想层次的东西。
  • 《编写可维护的JavaScript 》包括具体风格和原则的介绍,也包括示例和技巧说明
  • 思否社区:中国版stackoverflow

心得与体会

  软件bug的修改是需要成本的,并且这项成本总是在不断地增加,特别是对于已经广泛发布的产品代码而言,更是如此。最好的情况是当我们一发现bug,立刻就可以修改它,这种情况只发生在刚写完这些代码后不久。否则转移到新的任务上,忘记了这部分代码,就需要重新阅读这些代码。

  对于大型项目而言,还存在着另一个问题,就是最终修改代码的人,往往并不是当初写代码的人,也不是发现bug的人。因此,减少理解自己以前写的代码的时间,或者减少理解团队中他人写的代码的时间,就变得非常关键。

  另外一个事实在于,软件开发人员通常读代码比写代码更耗时间。通常的情形是,当我们专注于某个问题时,会坐下来花一下午的时间编写出大量的代码。这些代码可能当天就可运行,但要想成为一项成熟的应用,需要我们对代码进行重新检查、重新校正、重新调整。

  编写高质量的代码是十分重要的,不仅对于成功地完成项目十分有利,而且有利于开发者与开发团队的其他人员进行交流。提高代码的质量,可能最初只是几小时工时写出来的代码,最终需要花费几周的工时来阅读。这就是为什么编写易维护的高质量的代码对项目具有举足轻重的作用。

  代码的质量从某种意义上来说就是一个系统的生命线,任何上线的系统都是通过了测试的,但代码的质量决定了这个系统能走多远!我在新闻上听说过有人删库跑路的,也见过有人接手老项目后马上离职的,其中有一些就和老代码的质量问题有着必然的联系。

  代码质量和效率之间有着天然的矛盾,怎么去平衡值得我们去思考,下面是我的一点心得与体会:1、代码风格;2、code review;3、抽离、封装、复用;4、持续学习和持续实践;5、开发周期与代码质量的平衡。


科瑞兹曼
72 声望7 粉丝