在业务开发中,我们经常出现的一种情形,在项目初期高效地实现业务需求,但随着时间推移,添加新功能的速度逐渐减慢。我们需要花费更多的事件去思考如何将新功能塞进现有的代码库,不断蹦出来的bug修复起来也变得越来越难。代码库看起来就像在补丁上打补丁,最终需要进行繁琐的考古工作才能理解系统的运行方式。
高质量的代码通常更易于理解和修改,这可以减少在维护和更新代码时所需的时间和精力。高质量的代码通常更易于重用和扩展,这可以帮助开发者更快地开发新的功能和应用。高质量的前端代码通常意味着更少的错误,更快的加载速度,更好的响应性,这些都可以直接影响用户的体验。
好文章的标准有三条:思想、逻辑和修辞。思想是根本,是内容。逻辑与修辞则是形式,是工具。逻辑是理性的形式与工具,修辞是感性的形式与工具。写代码和写文章一样,首先保证的根本是功能可用,准确表达出业务含义;其次也要追求形式上的优雅,编码规范就是对代码的组织形式与风格的一种约束。好的代码不仅逻辑清晰,风格一致,而且无论多少人参与,都如同是一个人在编码。
1. 工程师的编码素养
编程不仅仅是关于如何编写出能够运行的代码,它更是关于如何编写出优雅高效、易于维护的代码。这种艺术涉及到的不仅仅是技术层面的问题,更是关于思维方式、解决问题的策略、以及对于质量和细节的追求。优秀的编程艺术,就像一座建筑的设计,既要考虑到功能和效率,也要考虑到美感和人性化。
前端开发涉及到许多不同的技术和工具,如果开发者对这些技术和工具的理解不够深入,或者缺乏必要的编程技能,可能会难以编写高质量的代码。在工作协同上,我们也常常需要在项目里程碑节点和有限的资源中寻找平衡。因此,是对我们软性素质和专业技能的双重考验。
2. 电商前端研发规范
前端开发已经成为了软件工程的重要组成部分。它是用户与应用程序之间的桥梁,直接影响着用户体验和满意度。然而,前端开发的复杂性和挑战性也在不断增加,涉及到多种技术、工具、框架,以及不断变化的业务需求和用户期望。因此,建立一套有效的前端团队研发规范,对于提高开发效率、保证代码质量、促进团队协作,以及满足业务和用户需求,具有至关重要的意义。
前端团队研发规范不仅包括编码规范,也包括研发流程规范、代码审查规范、安全与性能规范等等。这些规范应该反映出对质量、效率、协作和持续改进的追求。大家应对规范活学活用,使我们能够更好地应对挑战,提供优秀的产品和服务,以满足业务和用户的期望。
先从形成良好的编码习惯开始,注重编程的基本素养和要求。先写出可读性、可维护性高的代码。再逐步提升专业技能,写出健壮、高效、交互优异的代码,对业务工程的全声明周期进行把控,负责其功能迭代、架构设计、甚至项目重构。
命名是开发过程中至关重要的技能,有一个易于理解的名字可以承载很信息,某种程度上是一种更好的注释,一个糟糕的命名,可能会引起别人的误解,对开发效率和项目质量影响很大;相反,遵循一套严格的命名规范,无论是对自己还是接手项目的人,都会大大降低代码的维护成本。命名规范涵盖的面比较广,一般包括变量或常量名、函数或类名、文件或工程目录名、工程名以及空间名等。
把信息装到名字里,从字面含义可以关联其代码中的用途。名字应该尽量精确、专业、不要有多余。不会误解的名字,阅读你代码的人应该理解你的本意,并且不会有其他的理解。
以下列举的不规范的命名方式,在任何情况下,你都不应该考虑使用它们:
// 好的变量命名方式
var maxCount = 10;
var tableTitle = 'LoginTable';
// 不好的变量命名方式
var setCount = 10;
var getTitle = 'LoginTable';
函数名【应该】使用小驼峰式命名法,且前缀应当是动词,常用的动词前缀如下表所示
动词 | 含义 | 返回值 |
---|---|---|
can | 判断是否可执行某个动作 | 函数返回一个布尔值。true:可执行;false:不可执行 |
has | 判断是否含有某个值 | 函数返回一个布尔值。true:含有此值;false:不含有此值 |
get | 获取某个值 | 函数返回一个非布尔值 |
set | 无返回值、返回是否设置成功或者返回链式对象 | 无返回值、返回是否设置成功或者返回链式对象 |
load/query | 无返回值或者返回是否加载完成的结果 | 无返回值或者返回是否加载完成的结果 |
save/update | 无返回值或者返回是否操作成功的结果 | 无返回值或者返回是否操作成功的结果 |
// 好的函数命名方式
function queryProductList() {
// ...
}
// 不好的函数命名方式
function productList() {
// ...
}
常量名【应该】使用全部使用大写字母和下划线来组合来命名,下划线用以分割单词;
// 好的常量命名方式
const MAX_IMAGE_SIZE = 10 * 1024 * 1024;
// 不好的常量命名方式
const MaxImageSize = 10 * 1024 * 1024;
const maximagesize = 10 * 1024 * 1024;
const maxImageSize = 10 * 1024 * 1024;
类名或构造函数【应该】使用大驼峰式命名法,即首字母大写。类的成员属性和方法的命名跟变量和函数保持一致,只是私有属性和方法名应该以下划线开头;
// 构造函数名
function Student(name) {
var _name = name; // 私有成员
// 公共方法
this.getName = function () {
return _name;
}
// 公共方法
this.setName = function (value) {
_name = value;
}
}
注释是对于代码中巧妙的、 晦涩的或重要的地方加以解释。
// 不好的
// 删除表格中指定id的订单数据
delete(id) 应该把名字改好
// 好的
deleteOrderItemById(id)
对代码的翻译,是没有价值的注释
// 不好的
// 这是 Account类 的定义
class Account {
// 给 profile 赋予新的值
setProfile(profile);
}
说明背后为什么是它,而不是其他写法
// 好的
// 权衡图片大小/质量,图片质量设置的最佳值为0.72
image_quality =0.72;
JS支持两种不同类型的注释:单行注释和多行注释。
// 不推荐
var active = true; // is current tab
// 推荐
// is current tab
var active = true;
// 不推荐
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
var type = this.type || 'no type';
return type;
}
// 推荐
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
var type = this.type || 'no type';
return type;
}
使用 /** … / 作为多行注释,包含描述、指定所有参数和返回值的类型和值。若开始 / 和结束 /
都在一行,【应该】采用单行注释。若至少三行注释时,【应该】第一行为 /,最后行为 */,其他行以 * 开始,并且注释文字与 *
保留一个空格。
函数(方法)注释也是多行注释的一种,但是包含了特殊的注释要求,常见的注释关键字有@param、@return、@author、@version、
@example,更多用法参照JSDoc
// 不推荐
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
// ...stuff...
return element;
}
// 推荐
/**
* make() returns a new element
* based on the passed in tag name
*
* @param {String} tag
* @return {Element} element
*/
function make(tag) {
// ...stuff...
return element;
}
// 推荐
/**
* merge cells
* @param grid {Ext.Grid.Panel} Grid that needs to be merged
* @param cols {Array} Index array that need to be merged; counting from 0.
* @return void
* @author ben 2021/11/11
* @example
* _________________ _________________
* | age | name | | age | name |
* ----------------- mergeCells(grid,[0]) -----------------
* | 18 | jack | => | | jack |
* ----------------- - 18 ---------
* | 18 | tony | | | tony |
* ----------------- -----------------
*/
function mergeCells(grid: Ext.Grid.Panel, cols: Number[]) {
// Do Something
}
function Calculator() {
// @TODO: total should be configurable by an options param
this.total = 0;
return this;
}`
条件语句中变化的值放左边,稳定的值放右边
// 不好的
if (10 <= length)
// 好的
if (length >= 10)
优先处理条件为true的逻辑、简单的情况、有趣和可疑的情况
fs.readFile('/file-does-not-exist', (err, data) => {
if (err) {
// 优先处理error
} else {
// 其次处理data
}
})
if (user_result == 'SUCCESS') {
if (permission_result != 'SUCCESS') {
// 成功且有权限时,逻辑处理
return
}
// 成功且无权限时,逻辑处理
}else{
// 用户操作失败时,逻辑处理
}
if (user_result != 'SUCCESS') {
// 用户操作失败时,逻辑处理
return
}
if (permission_result != 'SUCCESS') {
// 成功且无权限时,逻辑处理
return
}
// 成功且有权限时,逻辑处理
三目运算符只在最简单的情况下使用,优先用if/else;不要滥用短路逻辑,部分判断逻辑可以交由后端处理。
// 嵌套过深的运算符
mode === 'multi' ? hasSelectedAll ? '已选中所有项' : '未选中所有项' : mode === 'single' ? '仅可单选' : null
if (
state === 'INIT' && sign_state === ''
|| state === 'CHECKING' && sign_state === 'NOT_SIGNABLE'
|| state === 'AUDITING' && sign_state === 'SIGNED'
) {
return '此状态下返回的文案'
}
使用易懂的临时变量,或封装成函数
if (request.user.id == document.owner_id){
// 用户有编辑权限时,逻辑处理
}
const user_owns_document = (request.user.id = document.owner_id)
变量当然是越少越好,太多则难以跟踪它们的动向,要去掉那些临时变量、中间结果、控制流变量。
const now = Data.now()
const isCurrent = timestamp === now
缩小变量的作用域,让你的变量对尽量少的代码可见,防止命名空间污染。
const name = 'foo'
function getUserName () {
const name = 'bar'
return name
}
只写一次的变量更好,不断变化的值让人难以理解,跟踪这种变量的值很有难度,善用typescript与const。
对于多次重复使用的值,可以提取为定义为变量/常量。
if (response.data.user.status === 1) {
// 逻辑处理
} else if (response.data.user.status === 2) {
// 逻辑处理
} else if (response.data.user.status === 3) {
// 逻辑处理
} else {
// 逻辑处理
}
const status = response.data.user.status;
enum STATUS_MAP {
PROCESSING = 1,
ACTICATED = 2,
DISABLED = 3
}
switch (status) {
case STATUS_MAP.PROCESSING:
// 逻辑处理
break;
case STATUS_MAP.ACTICATED:
// 逻辑处理
break;
case STATUS_MAP.DISABLED:
// 逻辑处理
break;
default:
// 逻辑处理
break;
}
提取重复且通用的函数,以提供更好的可读性、可维护性、及复用的可能。一段代码一次只做一件事,可以通过拆分为段落/函数/类,让其更清晰。
function errorHandler(error) {
alert('服务器繁忙,请稍后再试')
log('axios response err', error)
}
async function getUser() {
try {
const response = await axios.get('/user?ID=12345')
console.log(response)
} catch (error) {
errorHandler(error)
}
}
组件的封装,遵循物料规范封装。
一方面已封装的类库有较完备的建设,如使用文档、测试用例、符合团队规范等。另一方面,也通过广泛应用完成踩坑,有更好的稳定性。一个好的类库,也是我们学习的对象,可以多问几个为什么,它为什么产生?它能做什么?它的代码组织形式有什么优点?它存在什么潜在风险?等等。
用户输入验证和清理:需要验证和清理用户输入的数据,以防止SQL注入和其他形式的攻击。不要信任用户输入的任何数据。服务端返回的HTML不要直接渲染在页面中。
跨站脚本攻击(XSS):这是最常见的前端安全问题之一。攻击者通过在网站上注入恶意脚本,当其他用户访问该网站时,这些脚本会在他们的浏览器上运行。为了防止XSS攻击,需要确保你的应用不会接受或执行用户提供的未经验证和清理的HTML代码。
跨站请求伪造(CSRF):在这种攻击中,攻击者会诱导用户去点击一个链接或者加载一个页面,这个链接或页面会包含一个请求,这个请求会在用户的浏览器中向另一个网站发送。为了防止CSRF攻击,你可以使用一些防护措施,例如使用同步的防伪令牌(anti-forgery token)。
HTTPS和HTTP严格传输安全(HSTS):HTTPS通过加密你的网站的流量来提供安全,而HSTS则确保浏览器只通过HTTPS与你的网站通信,即使用户尝试通过HTTP访问。
内容安全策略(CSP):CSP是一个额外的安全层,它帮助检测和缓解某些类型的攻击,包括XSS和数据注入攻击。CSP允许网页开发者声明页面的内容来源,浏览器只会执行或渲染从这些来源加载的代码。
前端用户体验是指用户在使用网站或应用时的感受。包括了网站的性能、页面布局、页面交互、错误处理等多个方面。
在处理需求时,需要多站在用户角度思考,针对不同手机(小屏幕/折叠屏)是怎样的体验;对于按钮点击/数据请求时,会不会产生页面无响应、无UI反馈、多次请求等异常CASE;操作流畅性、学习和认知成本会不会过高等等。