接手前端新项目?这里有些注意点你可能需要留意一下
前段时间加入公司内一个新开业务线的前端组,由于是新开的业务线,做的也是小程序这一块,所以几乎没有任何历史包袱,组内成员都是项目代码第一手产出者
我加入的时机较晚,没有经历过最开始的初创阶段,不太清楚一开始的状况,不过听说是蛮折磨人的,需要踩坑无数,经常需要加班(虽然互联网行业加班本来就是常态,不过现在熬过初始阶段就好多了),这让我即庆幸又遗憾,庆幸的是我不用加班那么晚了,遗憾的是,没有参与到一条崭新开业务线最开始的建立阶段,不知道以后还有没有这个机会了
不过,我加入的时机也不是太晚,项目依旧存在很多需要填补的地方,经过这段时间对代码的阅读,以及平时在做需求过程中所遇到的问题,从前端方面,暂时总结出了一些关于项目架构或构建可维护代码方面的小小经验。
组件化开发最佳实践
现在前端的组件化开发基本上已经成为主流了,既然已经是组件化开发了,那么自然就要遵守一些组件化开发的最佳实践
每个组件文件代码总行数不要超过 400
行
至于为什么是 400行,这个数字是我当初在某篇技术文章上看到的,至于是哪个我忘记,但是不知为何印象很深刻,一直记着,并且根据我多年具备的经验来看,这个数字还是蛮准确的,一般要么不会超过这个数,或者在这个数字左右徘徊,要么就是比这个数字大很多的
这些都很好理解,既然是组件化开发,那么如果一个组件文件体积太大,存在几十个方法、几十个 data
数据(如果用的是 vue
框架),那就说明这个组件大概率包含的功能点太多,是可以被继续细化出多个单一功能的子组件的
太多的方法与 data
数据,对于开发者来说根本就是一种折磨,别的不说,单是给这些方法起名字以及查找都是一件麻烦事,至于日后的维护,那更是噩梦一般的存在,哪怕是当初亲手写下这些代码的程序猿,过一段时间再让他来维护这些代码,他十有八九都要如履薄冰,尽管确认了好多遍,但还是会担心自己修改某个数据或者某个方法,会不会对以前某个较为隐蔽的逻辑造成什么不可预知的破坏,于是一遍又一遍地在几十个方法与数据间测试、检查
我曾经看到过包含七十多个 data
的组件
这种情况还算是好的,碰到不负责任的程序猿,可能直接就嫌麻烦,随便在一大堆代码中找个地方写下自己的新代码,检查都不检查就立马提测上线了,因为反正以前上了那么多需求,不小心搞乱了其中某个需求的逻辑,谁特么知道?
这还仅仅是往旧代码中加新代码罢了,如果是下线之前过期的活动代码,或者删除无用的逻辑,那就更让人掉头发了,几十个方法、几十个数据缠在一起,谁知道哪些方法与方法、方法与数据、数据与数据间有着怎样的联系?删掉了这段看起来应该是无用的代码,会不会导致其他有用代码出什么故障?算了算了,不删了,反正后端接口已经关掉,这段代码也显示不出来,就暂时放在上面吧。 殊不知,这所谓的暂时,就是海枯石烂的永远
爷爷爷爷,这段代码真的是你写的吗?
于是,页面上无用代码越来越多,旧代码与新代码交相辉映,文件体积迅速增大,随着时间的推移,本来轻装上阵的新鲜代码库,逐渐背上了一个又一个历史包袱,然后被后面接手的人大骂,这写的到底是个什么东西?
世上本没有历史包袱,丢包袱的人多了,也就有了历史包袱。
每个函数不要超过 100行
这是接上面的,想想也能明白,一个组件最好不超过 400行,只是一个方法就超过了 100行,那还怎么写? 当然,这里的数字 100
根据我的经验,也是蛮准确的,超过这个行数,好好想下,这个方法是不是包含了太多逻辑,遵循函数功能单一原则,不要让一个方法函数包含过多的逻辑功能,是用于拉取数据,那就让它只拉取数据,是用来整合字段的那就让它只整合字段,别干其他的事情
多么性感的臀部啊,答应我,别用它来拉shi好吗?
这样一来,方法函数不仅功能明确,维护起来不必畏手畏脚,同时也能够增加方法的复用性,例如,A方法中包含了拉取页面基本数据的功能,后来需求迭代,另外一个后来添加的 B方法也需要拉取页面基础的功能,刚想复用之前 A方法,发现 A方法中除了拉取数据的代码,还有判断是否显示弹窗、修改数据字段、埋点等多个用 if...else...
连起来的代码,于是写 B方法的人为了避免麻烦,或者发现根本无法复用,只好又把拉取数据的功能重写了一遍
当然,这只是一个原则,实际需求中,如果某两个功能逻辑就是紧密联系不可分割的,你也可以写在一个方法中,总之,在参考这个原则的前提下,灵活变通
定义方法与数据要物以类聚
指具备相似能力或者共同作用于某个功能的方法和数据,最好定义在靠近的位置,方便查找,某个功能受到那些方法和数据所控制,某个数据体包含哪些字段全都一目了然,无论是以后要修改还是删除,都能做到胸有成竹,不会遗漏
像我这么 diao的还有 107个
例如,生命周期方法写在一起,并且按照执行的顺序,不与自定义方法混插;自定义方法也要按照各自的功能进行归类书写,例如请求接口的方法写到一起,切换页面弹窗的方法写到一起,页面上与业务无关的工具方法要放到一起,用户信息的数据都定义在靠近的位置,如果你用的是 vue
,那么与模板渲染无关的变量,不要放到 data
中,既提升了渲染性能,也易于区分变量的功能
// 工具方法都放到一起
// 金额 分 转 元
fen2yuan(fenNum) {
return fenNum / 100
}
// 修改链接协议
changeUrl(url) {
return url.replace('http', 'https')
}
// 用户信息的数据都定义在靠近的位置
// 当然,你甚至可以把下面这些数据,都定义在一个大的对象中
let avatar = 'https://avarat.com/a.png'
let userName = '小明'
let age = 19
顺带一提,CSS
的书写最好也按照这种规则,CSS
书写顺序的判断依据是 css
代码影响的方面,例如,影响元素位置的属性 left top
,影响元素长相的 color background
,字体 font-size font-weight
,这些有同类功能的最好都写在一起
另外,我习惯于把能引起页面回流的放在能引起页面的重绘属性的前面,对元素 影响程度 越高的属性越放在最前面,至于什么叫 影响程度 我也说不清楚,下面是我一般写 css
的属性的顺序,大家可以自行领悟一下
<!-- position 放在最前面 -->
position: relative;
<!-- 然后是参照 position 进行定位的属性 -->
top: 100px;
left: 10px;
<!-- 然后是宽高 -->
width: 100px;
height: 100px;
line-height: 20px;
<!-- 然后是 margin padding -->
margin: 10px;
padding: 20px 30px;
<!-- font -->
font-size: 20px;
font-weight: 700;
<!-- border -->
border: 1px solid red;
border-radius: 4px;
<!-- background -->
background-color: pink;
<!-- z-index -->
z-index: 10;
一开始我也不习惯这种略带有束缚的写法,但是时间长了就习惯了,这些属性的书写顺序,完全不用思考就迅速依次写下,甚至有时候看到别人写的乱七八糟不符合自己习惯的写法,还会顺手改一改。
这样做的好处是易于查找和维护,想改 width
,那就肯定是在这个元素 css
属性序列的最前面,想改 background
,那肯定就从后面扫,也避免了在元素属性过多的情况下,可能导致的某个属性出现多次的情况,我曾经不止一次在代码库中看到某个属性,例如 background
写了两次的事情,一个 background
写在属性序列的上半部分,然后可能由于这个元素的属性太多,后来维护的人没有看到那个 backgound
,或者太乱了也不想看,于是就直接在属性序列的最后面又写了一个
抛开上述的好处不说,最起码这种有条理、有顺序的书写次序,对于某些星座的人来说,看着也赏心悦目,无形之中也能让自己与外面那些妖艳贱货区别开来
努力学习却不装逼,那将毫无意义
必要而准确的注释
需要长期维护的项目,必要而准确的注释必不可少,甚至宁滥勿缺
一行代码写下去,或许你三天之内还能知道自己当初为什么这么写,里面的变量代表什么意思,有什么作用,但是一个月后呢,半年后呢,一年后呢?就算你记得,其他人呢?
我不知道这段代码的作用是什么,但是把它删掉程序就不正常了
如果让你维护一段你早就不记得是谁写的,也不知道到底代表什么意思代码,你要做的第一件事肯定就是先看代码,弄明白到底什么意思,然后才能进行后续维护,而对于某些较为复杂的逻辑代码,例如网站首页,里面嵌入了数十个弹窗,这数十个弹窗分别对应数十个功能点,让你修改其中某个 A弹窗的弹出逻辑,并且还要与另外某个 B弹窗进行优先级配合,光是弄明白A、B弹窗的逻辑你都要花费不少时间吧?
而如果在这段代码上面就有一行对这行代码的准确注释,就算你稍后还是需要读一遍代码,也肯定比没有代码时,你理解的更快更准确
一段代码节约一点时间,那么十段呢?一个文件的代码呢?整个代码库的代码量呢?
所以千万不要觉得注释可有可无,还是那句话,宁滥勿缺,宁愿多写点也不要少写点,你写的代码都是给人读的,反正现在基本上都接自动化打包编译工具(例如 webpack),注释这些东西最后在项目发布阶段都会自动删掉,也不会占用代码体积
变量和方法的命名最好有一定的规律
同样是为了更好的阅读体验,也是为了避免过渡注释(宁滥勿缺当然可行,但是少写点无用的注释总不会是坏事),最好的注释就是让代码自己“说出”自己的作用,即命名要有规律性
例如,用于存储数组的变量以 List
作为名字后缀,用于某种信息的对象变量以 Info
作为名字后缀,用于判断某种逻辑的变量以 is
最为前缀,这样一眼看上去就知道变量的大致功能,避免出现让 object
类型的对象调用 map
的蠢事
const namesList = ['xiaoming', 'xiaohong']
const userInfo = {
name: 'John',
age: 20,
gender: 'male'
}
const isEven = 10 % 2 === 0
方法的命名同上,另外,为了更容易体现出方法的功能点,方法的名称最好不要太“宽泛”,例如拉取数据的方法,最好不要命名为类似 getData
这种,因为如果这个组件中还存在其他的拉取数据方法,就容易让人迷惑,不会那么一下子就知道这个 getData
到底是针对哪个数据接口的,应该进一步精确到相应的接口,例如命名为 getUserData
关于如何给变量以及方法命名这件事,存在很多细致的解决方案,能写两篇文章出来,就不一一展开了
通用的功能要封装成组件
通用的组件,最好确定好功能点后,封装成统一的通用组件,通用组件一定要覆盖大部分的通用功能点,否则还不如不要
大一点的,例如弹窗,可能包括标题、关闭按钮、内容、遮罩层这些结构,小一点的,例如页面的遮罩层,主体包括一层遮罩样式,那在封装这个组件的时候,就要把这些结构考虑进去
我曾经看到过某个组件中,包含了多个弹窗,但是没有封装通用的弹窗组件,于是相同的元素以及样式被写了好几遍,印象最深刻的是遮罩层元素也出现了好几遍,每个弹窗都专门写一个遮罩层样式,写过的人都知道,单是这一个遮罩层的样式都有好几行了,何苦来哉
DRY原则
Don't repeat yourself
这一条可以与上条结合使用,无论你是写什么代码,需求是长期还是短期的,作为一个有素质的程序猿,你最好都要遵守这条规则
这里的 DRY
,不仅是指完全一模一样的代码字母,还在于同样的逻辑,这里举个例子,表单验证是很常见的场景,一般的验证方法都是不假思索的数个乃至是数十个 if
语句依次排列,整整齐齐气势惊人,但问题是几个 if
语句连在一起或许不太明显,难以触碰到你的 G点,但是十几个乃至是几十个 if
语句堆在一起,你难道还能不觉得别扭吗?
if (age > 19) {
// ...
}
if (name.indexOf('zhao')) {
// ...
}
if (gender === 1) {
// ...
}
if (weight > 75) {
// ...
}
// 无穷无尽
// ...
只要我复制粘贴得足够快,bug就追不上我
关于表单验证这个东西,有篇文章写得很好,大家可以参考一下
单独的业务代码之间、业务代码与非业务代码之间进行必要的隔离
业务代码,以 if...else
琳琅满目为主要标志之一,多个需求的业务代码混合在一起那就意味着数倍琳琅满目的 if...else
,如果再在这些 if...else
中躲猫猫般穿插进非业务代码,那么恭喜你,如今你得到的这份代码,其实有个流传于江湖已久的响当当名号:意大利面条式代码
隔离单独的业务代码,能够直接减少头发掉落数,能间接降低 bug出现率,这很好理解
一般的项目都是长期迭代而来,一个页面上可能堆积了数十个需求的功能点,每个功能点都对应几大段的方法和数据,并且这些需求还可能从诞生到现在被修改了数次,搞不好有的业务间还存在相互依赖与重叠
如果把这些业务包含的方法和数据全都放在一起,那酸爽……诶,这个方法是属于A需求的吗?如果是那为什么这里面还包含了B需求的数据?怎么这里还调用了C需求的方法?C需求的这个方法修改的这个数据是C需求的吗?看起来不太像啊?算了算了,应该是,先这样写吧,等测试提 bug了再说……
下班晚不是因为你需求多,而是你自己写得代码给你找的事多
与位置无关的元素聚集书写
如果页面上元素少的话,或许没有区别,但是当页面上存在大量元素,例如网站首页、商品详情页这些比较重要的页面,就很容易感觉出来了
像 modal
弹窗、toast
等辅助性元素,页面上可能存在好多个,这种元素一般与位置无关,样式设置的都是 position: asbolute
或者 position: fixed;
,无论放在哪个位置基本上都 ok
,那么建议找好一个固定的位置聚集存放这些元素,例如页面的顶部或者底部,并写好注释,方便寻找与修改,也方便统计,任意穿插在各种 DOM间,维护起来都是一件麻烦事
<!-- 新手好礼的引导弹窗 -->
<ModalA />
<!-- 领奖弹窗 -->
<ModalB />
<!-- 新人提示 -->
<ToastA />
<!-- 资质不够提示 -->
<ToastB />
<!-- 风险用户提示 -->
<ToastC />
删掉不必要的代码
包括无效代码、注释的代码、不必要的调试代码
做过活动页的应该都知道,这种活动运营页寿命一般都很短,但是所要付出的精力却不少,活动下线后代码可能就直接无效了,大部分情况下这些失效的活动代码不会对页面有效逻辑产生什么影响,所以赶着进入下一个需求的程序猿们,本着多一事不如少一事的原则,很可能就任由无效代码一直存在于页面,直到地老天荒,这种事情最起码从我的经历来看,很常见
有的需求在开发阶段频繁变动,辛辛苦苦写的逻辑还没来得及被上传到线上服务器就夭折了,恼羞成怒的程序猿不甘心掉落的头发连一点成果都没有留下,于是机智地按下了 Ctrl + /
快捷键,幻想着这段被封印的代码总有重见天日的那天,殊不知,又是直到地老天荒,这种事情最起码从我的经历来看,很常见
虽然某些自动化打包编译工具支持删除类似于 console.log
、debugger
之类的调试代码,但问题是在 dev
阶段这些代码都是存在的,每个人在每个需求中都加入 5个 console.log
,那么只需要十个人次,控制台上就可以出现 50
行 console.log
,特别是这些 console.log
基本上都是秉持着哪里有位置就写在哪里的原则,就算是想删也需要耗费大量时间,苦逼的程序猿什么都没干,先在控制台看到几十行别人写的 console.log
,然后在一堆 console.log
中找到自己需要的那个,并在需求完成时,顺便又在原先几十个 console.log
的基础上,又贡献了自己的一份力量,这种事情最起码从我的经历来看,很常见
编辑器每多显示一行无效代码、控制台每多输出一行
console.log
,都将耗费一定的电量,全球几千万的程序员,积少成多,足以加重全球温室效应,加速两级冰川融化,可怜的北极熊宝宝和企鹅宝宝找不到爸爸妈妈,人间有真情人间有真爱,请坚定地按下backspace
或delete
吧
良好的沟通,避免无效的产出
永远不要相信 PM说的这个需求肯定不会再变了的话
PM改变需求我们无法劝阻,但是我们可以明确现有的需求,不写无效的代码,从而进一步避免了大段注释,保住了更多的头发,不要等到快上线的时候,才发现自己好像弄错了某段逻辑,或者少写了某段逻辑,最可恨的是白写了某段逻辑
代码容错
这个世界上不存在没有 Bug的代码,只要你写的不是 demo,那么一定要做好代码容错处理,因为你永远不知道接口给你返回的是 |
还是 丨
(亲身经历,此梗参见有哪些让你目瞪口呆的 bug?)
还有一些可能不叫错误,例如页面数据的初始化,在获取到数据之前,页面上可能会渲染出 undefined
这种鬼东西,为了更好的用户体验,最好还是给个体验更好的初始值吧
遵守制定好的规范
多人协作规范很重要,无论是 jshint
还是eslint
大行其道就是明证
,对于一个项目来说,规范可能包括从 es
版本、缩进类型到页面结构、组件拆分等,你既可以直接照抄成熟的规范,也可以自定义规范,但无论是什么样的规范,只要制定下来,那就要遵守,不要因个人原因,与团队貌合神离
有种效应叫破窗效应,这个项目中出现了不一样的风格,后来进入的人第一印象就是规范也不是那么严谨,偶尔打破一下也是可以的,然后又有后来的人看到,第一印象就是……于是不同的风格越积越多,规范也就无从谈起了,从一开始就稳不住,难道还想着半路突然硬一下?
你可以不喜欢项目现在的页面结构划分方式,也可以认为路由的划分方式很SB,但既然你在当初制定这个规范时没有反对没有提出异议(无论是忘记了还是被无视还是没有机会提,总之当初你没反对),那么现在就要遵守,或者你也可以尝试着改变这个规范,但请做好善后事宜,例如确保在新规范制定后,项目中之前存在的老规范要被全部修改过来,坚决保证代码风格统一化,至于是谁来做这种吃力不讨好的事情,你懂得。
自己挑的shi,跪着也要吃完
定期的代码 review
旁观者清,自己一般很难发现自己的错误,否则的话也不会那么容易触犯了,无论是代码的规范还是逻辑的偏差,有些错误如果其他人不及时指出来,我们可能永远无法意识到,定期的代码 review就能够很好地解决这个问题
不过,代码虽好,也没必要 review得太频繁了,不然只会变成一种负担,大家都这么忙,谁没事帮你天天 review代码
必要的踩坑与经验文档
这个 很重要,无论你用的是什么框架,写的是小程序还是 webview
,肯定都存在着各种各样的坑,如果老旧代码有历史包袱不想搞的话,那么新启项目就要好好对待了,文档改写的就要写,不要怕麻烦,不过等到新人加入,或者干脆是移交他人的时候,你就知道什么叫 敲码一时爽,交接火葬场了
小结
曾经看到过某个帖子,大意是 题主认为大部分前端做的事情,其实无论是五年经验还是外包或者实习生都能做,凭什么五年经验人拿得工资就高?
我当时也是闲得蛋疼,回答:这个问题不仅仅是在前端,后端、客户端等各种领域都会给你这种错觉,我以前也是这么认为的,后来某天当我看到一个相同的 bug,组长看了一眼就准确定位并顺手解决,而实习生抓耳挠腮了大半天,不仅没能明白到底是怎么回事,甚至还多引入了几个 bug后,我就知道人家那么多钱不是白拿的
当然,大部分工作经验高拿钱多的人并不是仅仅是靠改 bug快这一个原因,另外也不排除有些人本来就天赋异禀,只工作了一年但能力抵得上别人工作三年的,而有些人工作三年只有一年的能力