LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

WEB开发如何编写高质量的前端代码

admin
2024年11月21日 16:39 本文热度 87

1.引言

1.1 为什么要写高质量的代码

在业务开发中,我们经常出现的一种情形,在项目初期高效地实现业务需求,但随着时间推移,添加新功能的速度逐渐减慢。我们需要花费更多的事件去思考如何将新功能塞进现有的代码库,不断蹦出来的bug修复起来也变得越来越难。代码库看起来就像在补丁上打补丁,最终需要进行繁琐的考古工作才能理解系统的运行方式。

高质量的代码通常更易于理解和修改,这可以减少在维护和更新代码时所需的时间和精力。高质量的代码通常更易于重用和扩展,这可以帮助开发者更快地开发新的功能和应用。高质量的前端代码通常意味着更少的错误,更快的加载速度,更好的响应性,这些都可以直接影响用户的体验。

好文章的标准有三条:思想、逻辑和修辞。思想是根本,是内容。逻辑与修辞则是形式,是工具。逻辑是理性的形式与工具,修辞是感性的形式与工具。写代码和写文章一样,首先保证的根本是功能可用,准确表达出业务含义;其次也要追求形式上的优雅,编码规范就是对代码的组织形式与风格的一种约束。好的代码不仅逻辑清晰,风格一致,而且无论多少人参与,都如同是一个人在编码。

1.2 什么是高质量的代码

  1. 有良好的可读性,代码应易于理解。如使用清晰的命名、保持函数和方法简短、使用注释来解释复杂的代码段等。应该尽可能地简洁,避免不必要的复杂性和冗余。在一个项目中,代码应该有一致的风格和模式。这使得代码更易于阅读和理解。

  2. 可为维护性高,应该易于修改和扩展。这通常意味着代码应该遵循某种设计模式,避免过度复杂的依赖关系,并且有良好的模块化。好的代码应该是直观的,当有人需要进行修改时,他们应能迅速找到需要修改的部分,能够快速进行更改,而且不容易引入新的错误。

  3. 健壮性有考量,对于边界场景有覆盖和处理。应该能够处理各种预期和未预期的输入情况,并在出现问题时优雅地失败。遵循最佳的安全实践,避免可能的安全漏洞。

  4. 高效运行,提供优异的用户体验。较少的资源文件数量和大小;地理位置更近的CDN资源请求,资源懒加载;合理的数据结构和算法,减少CPU时间、内存使用和磁盘I/O。

1.3 如何编写高质量的代码

  1. 工程师的编码素养

编程不仅仅是关于如何编写出能够运行的代码,它更是关于如何编写出优雅高效、易于维护的代码。这种艺术涉及到的不仅仅是技术层面的问题,更是关于思维方式、解决问题的策略、以及对于质量和细节的追求。优秀的编程艺术,就像一座建筑的设计,既要考虑到功能和效率,也要考虑到美感和人性化。

前端开发涉及到许多不同的技术和工具,如果开发者对这些技术和工具的理解不够深入,或者缺乏必要的编程技能,可能会难以编写高质量的代码。在工作协同上,我们也常常需要在项目里程碑节点和有限的资源中寻找平衡。因此,是对我们软性素质和专业技能的双重考验。

  1. 电商前端研发规范

前端开发已经成为了软件工程的重要组成部分。它是用户与应用程序之间的桥梁,直接影响着用户体验和满意度。然而,前端开发的复杂性和挑战性也在不断增加,涉及到多种技术、工具、框架,以及不断变化的业务需求和用户期望。因此,建立一套有效的前端团队研发规范,对于提高开发效率、保证代码质量、促进团队协作,以及满足业务和用户需求,具有至关重要的意义。

前端团队研发规范不仅包括编码规范,也包括研发流程规范、代码审查规范、安全与性能规范等等。这些规范应该反映出对质量、效率、协作和持续改进的追求。大家应对规范活学活用,使我们能够更好地应对挑战,提供优秀的产品和服务,以满足业务和用户的期望。

2.工程师的编码素养

先从形成良好的编码习惯开始,注重编程的基本素养和要求。先写出可读性、可维护性高的代码。再逐步提升专业技能,写出健壮、高效、交互优异的代码,对业务工程的全声明周期进行把控,负责其功能迭代、架构设计、甚至项目重构。

2.1 有意义的命名

命名是开发过程中至关重要的技能,有一个易于理解的名字可以承载很信息,某种程度上是一种更好的注释,一个糟糕的命名,可能会引起别人的误解,对开发效率和项目质量影响很大;相反,遵循一套严格的命名规范,无论是对自己还是接手项目的人,都会大大降低代码的维护成本。命名规范涵盖的面比较广,一般包括变量或常量名、函数或类名、文件或工程目录名、工程名以及空间名等。

把信息装到名字里,从字面含义可以关联其代码中的用途。名字应该尽量精确、专业、不要有多余。不会误解的名字,阅读你代码的人应该理解你的本意,并且不会有其他的理解。

2.1.1 基本要求

  1. 选择专业的单词。比如分发事件时 send 可以用 dispatch 替代。

  2. 避免空泛的名字。如在定义变量时使用temp、arr、obj。

  3. 用具体的名字代替抽象的名字。比如我们定义一个订单状态,应使用orderState,而不是写成thisState。

  4. 使用前缀或后缀来给名字附带更多信息。比如用setPageSize来描述设置列表的分页条目数。

  5. 合理的名字长度。为作用域大的名字采用更长的名字。

  6. 利用名字的格式来表达含义。有目的的使用命名方式、大小写、下划线等。比如用全大写下划线命(MY_CONSTANT_NAME)方式表达常亮。

  7. 使用行业/团队范式命名。加上像is、has、can 或should这样的词,让布尔值变得更明确。类或者构造函数首字母大写。

以下列举的不规范的命名方式,在任何情况下,你都不应该考虑使用它们:

  1. 单词拼写错误

提交表单中,把 Form 写成了 From,如submitFrom

  1. 中英文混用

let chanpinList;这个变量名混用中英文,很不容易理解。除非是一些被创造出来但已经被广泛接受了的名词,如淘宝-taobao,微博-weibo,其他的情况都建议用英文;

  1. 中文词汇缩写

    1. 比如表达服务市场时,直接使用fwsc,对于第一次接触的人完全不理解含义

  2. 以1-9或a-z命名

比如页面上有几个按钮,直接命名成 btn1,btn2,btn3,...或者 btnA,btnB,btnC,...,这样看似简单,实际上从这些命名里面读取不到任何信息,时间久了就加无法与业务对应

  1. 混用命名格式

比如表示评论列表,有地方叫 comments,另一个地方叫 comment-list,还有的地方叫 commentList,几种规范混在一起,就感觉很不规范

  1. 单复数不分

比如有两个操作,一个是下载全部订单数据,一个是下载当前订单数据,结果分别命名为 downloadOrderData 与 downloadOrder,如果没有单复数,是不能很好地表达出业务含义的

  1. 正反义词错用

比如有两个操作,一个是显示弹窗,一个是关闭弹窗,结果分别命名为 showEditDialog 与 closeEditDialog。show 和 close ,一个是显示,一个是关闭,显然不是一组正反义词

  1. 容易被过滤的单词

ad、banner、gg、guanggao 等有机会和广告挂勾的字眠不建议直接用来做ClassName,因为有些浏览器插件(Chrome的广告拦截插件等)会直接过滤这些类名

2.1.3 团队规范

  1. 变量命名规范

变量名【应该】使用小驼峰式命名法,且前缀应当是名词,尽量在名字中体现类型,如 length、count 表示数字,而name、title表示字符串;

  1. // 好的变量命名方式
  2. var maxCount = 10;
  3. var tableTitle = 'LoginTable';
  4. // 不好的变量命名方式
  5. var setCount = 10;
  6. var getTitle = 'LoginTable';
  1. 函数命名规范

函数名【应该】使用小驼峰式命名法,且前缀应当是动词,常用的动词前缀如下表所示;

动词  

含义

返回值

can

判断是否可执行某个动作

函数返回一个布尔值。true:可执行;false:不可执行

has

判断是否含有某个值

函数返回一个布尔值。true:含有此值;false:不含有此值

get

获取某个值

函数返回一个非布尔值

set

设置某个值

无返回值、返回是否设置成功或者返回链式对象

load/query

加载某些数据

无返回值或者返回是否加载完成的结果

save/update

保存或修改某些数据

无返回值或者返回是否操作成功的结果

  1. // 好的函数命名方式
  2. function queryProductList() {
  3. // ...
  4. }
  5. // 不好的函数命名方式
  6. function productList() {
  7. // ...
  8. }
  1. 常量命名规范

常量名【应该】使用全部使用大写字母和下划线来组合来命名,下划线用以分割单词;

  1. // 好的常量命名方式
  2. const MAX_IMAGE_SIZE = 10 * 1024 * 1024;
  3. // 不好的常量命名方式
  4. const MaxImageSize = 10 * 1024 * 1024;
  5. const maximagesize = 10 * 1024 * 1024;
  6. const maxImageSize = 10 * 1024 * 1024;
  1. 类或构造函数命名规范

类名或构造函数【应该】使用大驼峰式命名法,即首字母大写。类的成员属性和方法的命名跟变量和函数保持一致,只是私有属性和方法名应该以下划线开头;

  1. // 构造函数名
  2. function Student(name) {
  3. var _name = name; // 私有成员
  4. // 公共方法
  5. this.getName = function () {
  6. return _name;
  7. }
  8. // 公共方法
  9. this.setName = function (value) {
  10. _name = value;
  11. }
  12. }

2.2 恰如其分的注释

注释是对于代码中巧妙的、 晦涩的或重要的地方加以解释。

2.2.1 基本要求

  1. 优先考虑命名而不是注释。注释固然很重要,但最好的文档其实就是代码本身。优先考虑使用有意义的类型名和变量名,不要为了注释而注释,某种程度上,因为需要注释常常因为它不是很好读,这个时候应该先考虑你的函数名和变量名是不是应该改改。

不要给不好的名字加注释

  1. // 不好的
  2. // 删除表格中指定id的订单数据
  3. delete(id)

应该把名字改好

  1. // 好的
  2. deleteOrderItemById(id)
  1. 声明高层次的意图而非细节。不要描述显而易见的现象,永远不要用自然语言翻译代码,而应当解释代码为什么要这么做,或者是为了让代码文档化。比如 为接口提供功能说明,为复杂的实现提供逻辑说明,以阐述为什么是这样而不是那样,标注代码中的缺陷,解释读者意料之外的行为等。

对代码的翻译,是没有价值的注释

  1. // 不好的
  2. // 这是 Account类 的定义
  3. class Account {
  4. // 给 profile 赋予新的值
  5. setProfile(profile);
  6. }

说明背后为什么是它,而不是其他写法

  1. // 好的
  2. // 权衡图片大小/质量,图片质量设置的最佳值为0.72
  3. image_quality =0.72;
  1. 公布可能得陷阱,提供总结性注释。难免在实现中引入hack代码或考虑但未处理的边界场景,此时应为后来者显示标注,以便后续回溯和修复。在大块长函数前,总结其用途和用法。

2.2.2 团队规范

JS支持两种不同类型的注释:单行注释和多行注释。

  1. 使用 // 作为单行注释,【应该】在注释前插入一个空行且使 // 与注释文字之间保留一个空格。

  1. // 不推荐
  2. var active = true;  // is current tab
  3. // 推荐
  4. // is current tab
  5. var active = true;
  6. // 不推荐
  7. function getType() {
  8. console.log('fetching type...');
  9. // set the default type to 'no type'
  10. var type = this.type || 'no type';
  11. return type;
  12. }
  13. // 推荐
  14. function getType() {
  15. console.log('fetching type...');
  16. // set the default type to 'no type'
  17. var type = this.type || 'no type';
  18. return type;
  19. }
  1. 使用 /** ... */ 作为多行注释,包含描述、指定所有参数和返回值的类型和值。若开始 /*  和结束 */ 都在一行,【应该】采用单行注释。若至少三行注释时,【应该】第一行为 /*,最后行为 */,其他行以 * 开始,并且注释文字与 * 保留一个空格。

  2. 函数(方法)注释也是多行注释的一种,但是包含了特殊的注释要求,常见的注释关键字有@param、@return、@author、@version、@example,更多用法参照JSDoc

  1. // 不推荐
  2. // make() returns a new element
  3. // based on the passed in tag name
  4. //
  5. // @param {String} tag
  6. // @return {Element} element
  7. function make(tag) {
  8. // ...stuff...
  9. return element;
  10. }
  11. // 推荐
  12. /**
  13. * make() returns a new element
  14. * based on the passed in tag name
  15. *
  16. * @param {String} tag
  17. * @return {Element} element
  18. */
  19. function make(tag) {
  20. // ...stuff...
  21. return element;
  22. }
  23. // 推荐
  24. /**
  25.  * merge cells
  26.  * @param grid {Ext.Grid.Panel} Grid that needs to be merged
  27.  * @param cols {Array} Index array that need to be merged; counting from 0.
  28.  * @return void
  29.  * @author ben 2021/11/11
  30.  * @example
  31.  * _________________                             _________________
  32.  * |  age |  name |                             |  age |  name |
  33.  * -----------------      mergeCells(grid,[0])   -----------------
  34.  * |  18   |  jack |              =>             |       |  jack |
  35.  * -----------------                             -  18   ---------
  36.  * |  18   |  tony |                             |       |  tony |
  37.  * -----------------                             -----------------
  38.  */
  39. function mergeCells(grid: Ext.Grid.Panel, cols: Number[]) {
  40. // Do Something
  41. }
  1. 使用 // @TODO  标注问题及问题的解决方式;

  1. function Calculator() {
  2. // @TODO: total should be configurable by an options param
  3. this.total = 0;
  4. return this;
  5. }

2.3 合理地组织代码

  1. 把流程控制变得易读

条件语句中变化的值放左边,稳定的值放右边

  1. // 不好的
  2. if (10 <= length)
  3. // 好的
  4. if (length >= 10)

优先处理条件为true的逻辑、简单的情况、有趣和可疑的情况

  1. fs.readFile('/file-does-not-exist', (err, data) => {
  2. if (err) {
  3. // 优先处理error
  4. } else {
  5. // 其次处理data
  6. }
  7. })

通过提早返回来减少嵌套

  1. if (user_result == 'SUCCESS') {
  2. if (permission_result != 'SUCCESS') {
  3. // 成功且有权限时,逻辑处理
  4. return
  5. }
  6. // 成功且无权限时,逻辑处理
  7. }else{
  8. // 用户操作失败时,逻辑处理
  9. }
  10. if (user_result != 'SUCCESS') {
  11. // 用户操作失败时,逻辑处理
  12. return
  13. }
  14. if (permission_result != 'SUCCESS') {
  15. // 成功且无权限时,逻辑处理
  16. return
  17. }
  18. // 成功且有权限时,逻辑处理
  1. 拆分过长的表达式

三目运算符只在最简单的情况下使用,优先用if/else;不要滥用短路逻辑,部分判断逻辑可以交由后端处理。

  1. // 嵌套过深的运算符
  2. mode === 'multi' ? hasSelectedAll ? '已选中所有项' : '未选中所有项' : mode === 'single' ? '仅可单选' : null
  3. if (
  4. state === 'INIT' && sign_state === ''
  5. || state === 'CHECKING' && sign_state === 'NOT_SIGNABLE'
  6. || state === 'AUDITING' && sign_state === 'SIGNED'
  7. ) {
  8. return '此状态下返回的文案'
  9. }

使用易懂的临时变量,或封装成函数

  1. if (request.user.id == document.owner_id){
  2. // 用户有编辑权限时,逻辑处理
  3. }
  4. const user_owns_document = (request.user.id = document.owner_id)
  1. 别引入无谓的变量,减小变量的作用域

变量当然是越少越好,太多则难以跟踪它们的动向,要去掉那些临时变量、中间结果、控制流变量。

  1. const now = Data.now()
  2. const isCurrent = timestamp === now

缩小变量的作用域,让你的变量对尽量少的代码可见,防止命名空间污染。

  1. const name = 'foo'
  2. function getUserName () {
  3. const name = 'bar'
  4. return name
  5. }

只写一次的变量更好,不断变化的值让人难以理解,跟踪这种变量的值很有难度,善用typescript与const。

  1. 通用逻辑提取与封装

对于多次重复使用的值,可以提取为定义为变量/常量。

  1. if (response.data.user.status === 1) {
  2. // 逻辑处理
  3. } else if (response.data.user.status === 2) {
  4. // 逻辑处理
  5. } else if (response.data.user.status === 3) {
  6. // 逻辑处理
  7. } else {
  8. // 逻辑处理
  9. }
  10. const status = response.data.user.status;
  11. enum STATUS_MAP {
  12. PROCESSING = 1
  13. ACTICATED = 2,
  14. DISABLED = 3
  15. }
  16. switch (status) {
  17. case STATUS_MAP.PROCESSING:
  18. // 逻辑处理
  19. break;
  20. case STATUS_MAP.ACTICATED:
  21. // 逻辑处理
  22. break;
  23. case STATUS_MAP.DISABLED:
  24. // 逻辑处理
  25. break;
  26. default:
  27. // 逻辑处理
  28. break;
  29. }

提取重复且通用的函数,以提供更好的可读性、可维护性、及复用的可能。一段代码一次只做一件事,可以通过拆分为段落/函数/类,让其更清晰。

  1. function errorHandler(error) {
  2. alert('服务器繁忙,请稍后再试')
  3. log('axios response err', error)
  4. }
  5. async function getUser() {
  6. try {
  7. const response = await axios.get('/user?ID=12345')
  8. console.log(response)
  9. } catch (error) {
  10. errorHandler(error)
  11. }
  12. }

组件的封装,遵循物料规范封装。

2.4 前端技能提升

  1. 对于前端基础与框架,经常通读其整个API,主动了解其原理,保持对他们的熟悉程度。熟知其能力边界,并在编码过程中加以应用和实践。

  2. 善用已有的类库/物料库,比如用浮点数运算的decimal.js、时间和日期计算的day.js,以及团队的工程模板、函数库、物料库。

    一方面已封装的类库有较完备的建设,如使用文档、测试用例、符合团队规范等。另一方面,也通过广泛应用完成踩坑,有更好的稳定性。一个好的类库,也是我们学习的对象,可以多问几个为什么,它为什么产生?它能做什么?它的代码组织形式有什么优点?它存在什么潜在风险?等等。

2.5 前端安全与用户体验

  1. 安全无小事,前端是Web安全的第一道屏障。

用户输入验证和清理:需要验证和清理用户输入的数据,以防止SQL注入和其他形式的攻击。不要信任用户输入的任何数据。服务端返回的HTML不要直接渲染在页面中。

跨站脚本攻击(XSS):这是最常见的前端安全问题之一。攻击者通过在网站上注入恶意脚本,当其他用户访问该网站时,这些脚本会在他们的浏览器上运行。为了防止XSS攻击,需要确保你的应用不会接受或执行用户提供的未经验证和清理的HTML代码。

跨站请求伪造(CSRF):在这种攻击中,攻击者会诱导用户去点击一个链接或者加载一个页面,这个链接或页面会包含一个请求,这个请求会在用户的浏览器中向另一个网站发送。为了防止CSRF攻击,你可以使用一些防护措施,例如使用同步的防伪令牌(anti-forgery token)。

HTTPS和HTTP严格传输安全(HSTS):HTTPS通过加密你的网站的流量来提供安全,而HSTS则确保浏览器只通过HTTPS与你的网站通信,即使用户尝试通过HTTP访问。

内容安全策略(CSP):CSP是一个额外的安全层,它帮助检测和缓解某些类型的攻击,包括XSS和数据注入攻击。CSP允许网页开发者声明页面的内容来源,浏览器只会执行或渲染从这些来源加载的代码。

  1. 前端用户体验是指用户在使用网站或应用时的感受。包括了网站的性能、页面布局、页面交互、错误处理等多个方面。

    在处理需求时,需要多站在用户角度思考,针对不同手机(小屏幕/折叠屏)是怎样的体验;对于按钮点击/数据请求时,会不会产生页面无响应、无UI反馈、多次请求等异常CASE;操作流畅性、学习和认知成本会不会过高等等。

3.电商前端研发规范

3.1 语言框架与物料

基于团队共识,约定Web开发中心所有增量工程均采用 React 技术栈

PC端中后台项目基础组件库默认为 MUI

移动端增量页面首选一码多投解决方案 :RTX文档,组件库:KproM、Kid-ui-plus

注:MUI、RTX、KproM、Kid-ui-plus等为快手自研技术产品,后续会一一为大家介绍。

3.2 工程编码规范

代码风格上的一致性,借助于Lint工具和类型检查器,既约束了代码风格,有规避了语法错误。

  1. 编码风格

ESLint 负责JavaScript 的语法检测

StyleLint 负责 CSS 的语法检查及排版优化

Prettier 负责所有文件的格式化

在团队的工程体系下,可以使用 Jia 命令来触发代码进行检查和自动修复。其背后使用code-spec-unid,他将ESLint、Stylelint、Prettier 三者结合使用,Prettier 的配置为基础,去覆盖 ESLint 与 Stylelint 的配置中关于代码格式化的部分,让 ESLint 与 Stylelint 专注于做 JS 与 CSS的语法检查, 而所有的代码格式化工作交由 Prettier 完成。

jia check

注:jia命令为快手电商前端自研开发工具,后续会为大家介绍。

  1. 提交规范

Husky 结合 Lint-staged 与 CommitLint 规范代码提交

提交消息时须遵循约定格式 <type>: <subject>,type 是 提交的类别,subject 是对提交的简短描述。注意冒号后有空格,比如:git commit -m 'feat: 增加国际化功能'。以下是 type 的枚举:

  1. 1. upd:更新某功能(不是 feat, 不是 fix)
  2. 2. feat:新功能(feature)
  3. 3. fix:修补bug
  4. 4. docs:文档(documentation)
  5. 5. style:格式(不影响代码运行的变动)
  6. 6. refactor:重构(即不是新增功能,也不是修改bug的代码变动)
  7. 7. chore:构建过程或辅助工具的变动
  8. 8. revert:回滚某个更早之前的提交
  1. 兼容性处理

Browserslist 提供浏览器兼容性个性化配置

Babel 转译高版本 JavaScript 以向后兼容

Postcss 结合其插件  Autoprefixer 为 CSS进行预处理以向后兼容

在实际开发过程中,绝大部分情况无需感知其配置,通过 Jia 命令生成的项目已实现相关配置。

3.4 代码静态检查

1. kdev 代码扫描(代码分)


  1. 天穹

源码检查:对仓库源码进行敏感词、代码规范、内外部域名、License等检查,打造符合规范的仓库源码

产物检查:检查构建产物类型、大小,是否含有sourcemap文件等, 保障线上产物安全稳定

动态检查:检查页面代码覆盖率、LCP、FMP等指标,提供性能优化建议,带您全面感知页面性能

注:kdev、天穹为快手自研技术产品,后续为大家介绍详细功能。


该文章在 2024/11/21 17:28:28 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved