您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
代码质量与技术债系列分享之一 - 如何做好 Code Review
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
代码质量与技术债系列分享之一 - 如何做好 Code Review
434536128_m
2024-03-27
IP归属:北京
100浏览
<h2 data-nodeid="1">TL;DR</h2><p data-nodeid="2"><a data-nodeid="403" href="https://joyspace.jd.com/pages/7Qn6xzEHlxoee2lSkKaN">Code Review 速查手册</a></p><h2 data-nodeid="3">参考资料</h2><p data-nodeid="4"><a data-nodeid="407" href="https://composity.com/post/too-busy-to-improve">https://composity.com/post/too-busy-to-improve</a><br/><a data-nodeid="411" href="https://composity.com/post/too-busy-to-improve">https://commadot.com/wtf-per-minute/</a><br/><a data-nodeid="415" href="https://dl.acm.org/doi/10.1145/3585004#d1e372">https://dl.acm.org/doi/10.1145/3585004#d1e372</a><br/><a data-nodeid="419" href="https://google.github.io/eng-practices/review/reviewer/standard.html">https://google.github.io/eng-practices/review/reviewer/standard.html</a><br/><a data-nodeid="423" href="https://book.douban.com/subject/35513153/">https://book.douban.com/subject/35513153/</a><br/><a data-nodeid="427" href="https://zhuanlan.zhihu.com/p/549019453">https://zhuanlan.zhihu.com/p/549019453</a></p><h2 data-nodeid="5">名词解释</h2><blockquote data-nodeid="6"><p data-nodeid="7">CR: Code Review<br/>CR:代码审查<br/>CL: Stands for "changelist", which means one self-contained change that has been submitted to version control or which is undergoing code review. Other organizations often call this a "change", "patch", or "pull-request".<br/>CL:代表“变更列表”,表示已提交到版本控制或正在进行代码审查的独立更改。其他组织通常将其称为“更改”、“补丁”或“拉取请求”。<br/>LGTM: Means "Looks Good to Me". It is what a code reviewer says when approving a CL.<br/>LGTM:意思是“对我来说看起来不错”。这是代码审查员在批准 CL 时所说的。</p></blockquote><h1 data-nodeid="8">CR意义</h1><h2 data-nodeid="9">灵魂拷问:为什么我们接手的每个代码库都如此难以维护?</h2><p data-nodeid="10"><img data-nodeid="464" alt="image.png" src="https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-03-24-18-32rFuoBUCFuaAVgd9.png"/><br/>重要原因之一:Code Review 执行不彻底<br/>万能借口:太忙!</p><ul data-nodeid="11" class=" list-paddingleft-2"><li><p data-nodeid="13">疲于应付眼前</p></li><li><p data-nodeid="15">不可见,意识不到</p></li><li><p data-nodeid="17">CR 非功能性开发</p></li><li><p data-nodeid="19">CR 不是当务之急,没有眼前收益</p></li><li><p data-nodeid="21">危害被低估,忽视“复利”的威力(非线性)</p></li></ul><h2 data-nodeid="22">意义</h2><p data-nodeid="23"><strong data-nodeid="486">现代代码评审【modern Code Review】,业内认为有效的、基于最佳实践的质量保证工作流,可通过人工审代码<span style="color: #f10909">降低风险、增强可维护性和提升研发效率</span>,同时可以有效<span style="color: #f10909">提升个人和团队技术能力</span>。更是一种对代码精益求精、追求极致的态度、是“工匠精神”的一种体现。</strong></p><h1 data-nodeid="24">CR原则</h1><ul data-nodeid="25" class=" list-paddingleft-2"><li><p data-nodeid="27"><strong data-nodeid="491">只要CL可以提高整体代码健康状态,就应该倾向于批准合入,即使CL并不完美</strong></p></li><li><p data-nodeid="29"><strong data-nodeid="495">基于技术事实和数据的沟通</strong></p></li><ul data-nodeid="30" style="list-style-type: square;" class=" list-paddingleft-2"><li><p data-nodeid="32">基于技术事实和数据否决个人偏好和意见</p></li><li><p data-nodeid="34">软件设计问题不能简单归结为个人偏好</p></li></ul><li><p data-nodeid="36"><strong data-nodeid="501">解决冲突:不要因为无法达成一致而卡壳</strong></p></li><li><p data-nodeid="38"><strong data-nodeid="505">善用工具</strong></p></li><ul data-nodeid="39" style="list-style-type: square;" class=" list-paddingleft-2"><li><p data-nodeid="41">基于Lint、公司代码规范等工具</p></li><li><p data-nodeid="43">大模型辅助</p></li></ul></ul><h1 data-nodeid="44">发起CR</h1><h2 data-nodeid="45">发起前的准备</h2><ul data-nodeid="46" class=" list-paddingleft-2"><li><p data-nodeid="48">推荐自己做一个 checklist</p></li><li><p data-nodeid="50">把自己当作 Reviewer 来对自己的代码进行 CR</p></li><li><p data-nodeid="52">预估代码可能出问题的地方</p></li><li><p data-nodeid="54">进行充分自测,保证代码可运行</p></li><li><p data-nodeid="56">不要指望别人帮你找出问题</p></li></ul><p data-nodeid="57">checklist 可参考<a data-nodeid="518" href="https://joyspace.jd.com/pages/7Qn6xzEHlxoee2lSkKaN">Code Review 速查手册</a></p><h2 data-nodeid="58">利用自动化工具进行前置检查</h2><ul data-nodeid="59" class=" list-paddingleft-2"><li><p data-nodeid="61">单元测试检查</p></li><li><p data-nodeid="63">新增单元测试检查</p></li><li><p data-nodeid="65">方法行数过多</p></li><li><p data-nodeid="67">圈复杂度过高</p></li><li><p data-nodeid="69">代码规范检查</p></li><li><p data-nodeid="71">lint 检查</p></li><li><p data-nodeid="73">体积监控</p></li></ul><p data-nodeid="74"><code data-nodeid="527" data-backticks="1">建议平均时长不要超过10分钟, 所以 e2e,性能检查等建议不阻塞发起MR流程</code></p><h2 data-nodeid="75">合理的规模</h2><p data-nodeid="76"><img data-nodeid="531" alt="image.png" src="https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-03-19-16-56ymg56pBioMjwNQK8.png"/><br/><img data-nodeid="535" alt="image.png" src="https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-03-19-16-56JRrzA8PdavPtWob.png"/><br/><a data-nodeid="539" href="https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/">https://smartbear.com/learn/code-review/best-practices-for-peer-code-review/</a></p><ul data-nodeid="77" class=" list-paddingleft-2"><li><p data-nodeid="79">一次评审200LOC为佳,最多400LOC</p></li><li><p data-nodeid="81">评审量应低于500LOC/小时</p></li><li><p data-nodeid="83">一次评审不要超过60分钟</p></li><li><p data-nodeid="85">采用轻量级评审方式</p></li><li><p data-nodeid="87">全员参与代码评审</p></li><li><p data-nodeid="89">每周花费0.5~1天开展CR</p></li><li><p data-nodeid="91">严格且彻底的评审</p></li></ul><h3 data-nodeid="92">如何拆分 CL</h3><p data-nodeid="93"><a data-nodeid="552" href="https://google.github.io/eng-practices/review/developer/small-cls.html">https://google.github.io/eng-practices/review/developer/small-cls.html</a></p><h2 data-nodeid="94">Commit 描述</h2><p data-nodeid="95">Bad Case:<br/><img data-nodeid="558" alt="image.png" src="https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-03-19-16-57F3PuJQHjV3hUW7W.png"/><br/>“修复错误”是不充分的 CL 描述。什么 bug?你做了什么来修复它?</p><p data-nodeid="96">其他类似的不良描述包括:</p><ul data-nodeid="97" class=" list-paddingleft-2"><li><p data-nodeid="99">逻辑修复</p></li><li><p data-nodeid="101">添加补丁</p></li><li><p data-nodeid="103">增加XX规则</p></li><li><p data-nodeid="105">删除XX逻辑</p></li></ul><p data-nodeid="106">Good Case:<br/>◆ 摘要:【xx模块】新增xx功能<br/>◆ 背景: 新功能需求,要求xxx, 详见[卡片链接]<br/>◆ 说明: 由于xx,新增xx处理模块…</p><ul data-nodeid="107" class=" list-paddingleft-2"><li><p data-nodeid="109">摘要:删除 RPC 服务器消息自由列表的大小限制</p></li><li><p data-nodeid="111">说明:像 FizzBuzz 这样的服务器有非常大的消息,可以从重用中受益。使自由列表更大,并添加一个 goroutine 随着时间的推移慢慢释放自由列表条目,以便空闲服务器最终释放所有自由列表条目。</p></li></ul><p data-nodeid="112">必要时,应使用 cz 等工具进行规范。</p><h2 data-nodeid="113">心态</h2><ul data-nodeid="114" class=" list-paddingleft-2"><li><p data-nodeid="116">一次 CR 其实是开启的一次“对话”</p></li><li><p data-nodeid="118">应该期待评论和反馈,并及时进行回复</p></li><li><p data-nodeid="120">做好心理准备自己的代码可能会有缺陷</p></li><li><p data-nodeid="122">CR 的目的之一就是发现问题, 所以不要有抵触</p></li></ul><h1 data-nodeid="123">CR内容</h1><blockquote data-nodeid="124"><p data-nodeid="125">代码是写给人看的, 不是写给机器看的,只是顺便计算机可以执行而已。------《计算机程序的构造和解释》</p></blockquote><h2 data-nodeid="126">应该被 CR 的内容:</h2><p data-nodeid="127">自上而下,优先级从高到低:<br/><img style="max-width: 600px !important" src="https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-03-24-19-063CwOcz6UkF7hjzD.png"/></p><table data-nodeid="129"><thead data-nodeid="130"><tr data-nodeid="131" class="firstRow"><th data-nodeid="133">模块</th><th data-nodeid="134">简介</th></tr></thead><tbody data-nodeid="137"><tr data-nodeid="138"><td data-nodeid="139">设计</td><td data-nodeid="140">代码是否设计良好并适合您的系统?</td></tr><tr data-nodeid="141"><td data-nodeid="142">功能</td><td data-nodeid="143">代码的行为是否符合作者的预期?代码的行为方式对其用户有好处?</td></tr><tr data-nodeid="144"><td data-nodeid="145">安全性</td><td data-nodeid="146">代码是否安全合规?</td></tr><tr data-nodeid="147"><td data-nodeid="148">复杂性</td><td data-nodeid="149">代码可以更简单吗?当其他开发人员将来遇到此代码时,他们是否能够轻松理解和使用此代码?</td></tr><tr data-nodeid="150"><td data-nodeid="151">测试</td><td data-nodeid="152">代码是否具有正确且设计良好的自动化测试?</td></tr><tr data-nodeid="153"><td data-nodeid="154">命名</td><td data-nodeid="155">开发人员是否为变量、类、方法等选择了明确的名称?</td></tr><tr data-nodeid="156"><td data-nodeid="157">注释</td><td data-nodeid="158">注释是否清晰有用?</td></tr><tr data-nodeid="159"><td data-nodeid="160">风格</td><td data-nodeid="161">代码是否遵循京东代码风格规范?</td></tr><tr data-nodeid="162"><td data-nodeid="163">文档</td><td data-nodeid="164">开发人员是否更新了相关文档?</td></tr></tbody></table><p data-nodeid="165"><a data-nodeid="612" href="https://google.github.io/eng-practices/review/reviewer/looking-for.html">https://google.github.io/eng-practices/review/reviewer/looking-for.html</a></p><h2 data-nodeid="166">CR流程顺序</h2><p data-nodeid="167"><img data-nodeid="616" alt="image.png" src="https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-03-24-19-26B08hydglswOUneO.png"/><br/><a data-nodeid="620" href="https://google.github.io/eng-practices/review/reviewer/navigate">https://google.github.io/eng-practices/review/reviewer/navigate</a></p><h2 data-nodeid="168">京东实际代码片段评审讲解</h2><h3 data-nodeid="169">设计</h3><h4 data-nodeid="170">应该有合理的职责划分,合理的封装</h4><p data-nodeid="171">good case</p><pre data-nodeid="172" class="lang-js">componentDidMount() { this.fetchUserInfo(); this.fetchCommonInfo(); this.fetchBankDesc();}</pre><p data-nodeid="173">bad case</p><pre data-nodeid="174" class="lang-js">componentDidMount() { const { location, dispatch, accountInfoList } = this.props; const { token, af } = getLocationParams(location) || {}; dispatch({ type: 'zpmUserCenter/fetchUserInfo', payload: { token, }, }).catch(e => { const zpmOpenAuthUrlLogin = decodeURIComponent(getCookie('zpmOpenAuthUrlLogin')); // 如果token过期则跳转回第三方平台 if ([TOKEN_EXPIRED_CODE, '300000'].includes(e.errorCode) && (isSaveUrl(af) || isSaveUrl(zpmOpenAuthUrlLogin))) { setTimeout(() => { window.location.href = isSaveUrl(af) ? af : zpmOpenAuthUrlLogin; }, 2000); } }); if (!this.showWhichHeader() && !this.showGatewayHeader()) { dispatch({ type: 'zpmUserCenter/fetchAccountInfo', payload: { token, }, }); } this.getBlackList()}</pre><p data-nodeid="175">问题1,fetchUserInfo 未进行封装<br/>问题2,af 命名过于随意<br/>问题3,‘300000’ 魔法字符串<br/>问题4,选择使用 af 或 zpm 这两个URL的逻辑建议封装,避免多次调用 isSaveUrl</p><h4 data-nodeid="176">优秀代码设计的特质 CLEAN</h4><p data-nodeid="177">• Cohesive:内聚的代码更容易理解和查找bug<br/>• Loosely Coupled:松耦合的代码让实体之间的副作用更少,更容易测试、复用、扩展<br/>• Encapsulated:封装良好的代码有助于管理复杂度,也更容易修改<br/>• Assertive:自主的代码其行为和其所依赖的数据放在一起,不与其它代码互相干预(Tell but not Ask)<br/>• Nonredundant:无冗余的代码意味着可以只在一个地方修复bug和进行更改</p><h4 data-nodeid="178">应避免过度设计</h4><p data-nodeid="179">别人在阅读代码时,能清晰辨别我在代码中的设计模式,并且能够随着这个模式继续维护</p><h3 data-nodeid="180">功能</h3><h4 data-nodeid="181">逻辑正确,逻辑合理,避免晦涩难懂的逻辑</h4><p data-nodeid="182">bad case:一段表单代码(原代码过长,仅贴出其中典型的一段)</p><pre data-nodeid="183" class="lang-jsx">{ hasQuota ? ( ['11', '12'].indexOf(invoiceType) === -1 ? ( <div className="m-b-4 row"> <div className="col-11"> <FormItem label="基础核验" > {basicVerifyStatus ? '已通过' : <div> 未通过 <Tooltip title={basicVerifyMsg} placement="bottom"> <span className='iconfont icon-tishi theme-primary m-l-1 font-14' /> </Tooltip> </div>} </FormItem> </div> <div className="col-11"> <FormItem label="剩余额度" > {formatAmount(availableLimit)} </FormItem> </div> </div>) : <div className="m-b-4 row"> <div className="col-11"> <FormItem label="基础核验" > {basicVerifyStatus ? '已通过' : <div> 未通过 <Tooltip title={basicVerifyMsg} placement="bottom"> <span className='iconfont icon-tishi theme-primary m-l-1 font-14' /> </Tooltip> </div>} </FormItem> </div> </div>) : ['11', '12'].indexOf(invoiceType) === -1 ? <div className="m-b-4 row"> <div className="col-11"> <FormItem label="基础核验" > {basicVerifyStatus ? '已通过' : <div> 未通过 <Tooltip title={basicVerifyMsg} placement="bottom"> <span className='iconfont icon-tishi theme-primary m-l-1 font-14' /> </Tooltip> </div>} </FormItem> </div> </div> : null}</pre><p data-nodeid="184">关键问题:连续三元判断 + 嵌套三元判断<br/>其他问题:</p><ul data-nodeid="185" class=" list-paddingleft-2"><li><p data-nodeid="187">魔法字符串, 且重复出现</p></li></ul><pre data-nodeid="188" class="lang-js">['11', '12'].indexOf(invoiceType) === -1</pre><ul data-nodeid="189" class=" list-paddingleft-2"><li><p data-nodeid="191">无意义的空行,严重影响代码阅读</p></li><li><p data-nodeid="193">FormItem 重复过多</p></li></ul><p data-nodeid="194">Reviewer 建议:</p><ol data-nodeid="195" class=" list-paddingleft-2"><li><p data-nodeid="197">对重复代码,梳理内容,进行合理命名</p></li></ol><pre data-nodeid="198" class="lang-js">const isNotOnlineInvoice = ['11', '12'].indexOf(invoiceType) === -1;</pre><ol data-nodeid="199" start="2" class=" list-paddingleft-2"><li><p data-nodeid="201">每个 FormItem 也进行命名,三元逻辑梳理,重构</p></li></ol><table data-nodeid="203"><thead data-nodeid="204"><tr data-nodeid="205" class="firstRow"><th data-nodeid="207"><br/></th><th data-nodeid="208">isNotOnlineInvoice true</th><th data-nodeid="209">isNotOnlineInvoice false</th></tr></thead><tbody data-nodeid="213"><tr data-nodeid="214"><td data-nodeid="215">hasQuota true</td><td data-nodeid="216">verifyCodeFormItem<br/>invoiceCodeFormItem<br/>goodNameFormItem<br/>modifiedDateFormItem<br/>basicVerifyFormItem<br/>availableLimitFormItem</td><td data-nodeid="217">availableLimitFormItem<br/>modifiedDateFormItem<br/>basicVerifyFormItem</td></tr><tr data-nodeid="218"><td data-nodeid="219">hasQuota false</td><td data-nodeid="220">verifyCodeFormItem<br/>invoiceCodeFormItem<br/>goodNameFormItem<br/>modifiedDateFormItem<br/>basicVerifyFormItem</td><td data-nodeid="221">basicVerifyFormItem<br/>modifiedDateFormItem</td></tr></tbody></table><h3 data-nodeid="222">安全性</h3><p data-nodeid="223">代码中应注意,不要存储敏感内容</p><pre data-nodeid="224">// 微信服务号 生产配置中复写 const WX_APP_ID = 'xxxxxxxxxx'; const WX_APP_SECRET = 'xxxxxxxxxxxxxxxxxxxxx'; // 票将军网站应用配置 (测试环境) const PJJ_APP_ID = 'xxxxxxxxxx'; const PJJ_APP_SECRET = 'xxxxxxxxxxxxxxxxxxxxx';</pre><h3 data-nodeid="225">一致性</h3><ul data-nodeid="226" class=" list-paddingleft-2"><li><p data-nodeid="228">代码一致性:</p></li><ul data-nodeid="229" style="list-style-type: square;" class=" list-paddingleft-2"><li><p data-nodeid="231">函数名和实现一致</p></li><li><p data-nodeid="233">注释和代码一致</p></li></ul><li><p data-nodeid="235">命名方式一致</p></li><li><p data-nodeid="237">异步写法一致(promise, async await,callback混用)</p></li><li><p data-nodeid="239">抽象层级一致</p></li><li><p data-nodeid="241">不建议混用 import 和 require</p></li></ul><h4 data-nodeid="242">注释与代码不一致</h4><pre data-nodeid="243" class="lang-js">getCheckboxProps: record => ({ disabled: !record.basicVerifyStatus || (hasQuota && record.availableLimit <= 0), // 状态为验证成功的时候才可选择使用})</pre><h4 data-nodeid="244">命名不一致</h4><pre data-nodeid="245" class="lang-js">this.state = { isOffline: false, shouldShowFollowLink: false, shouldShowToast: false, ifReceiveNotify: false, bShowAllDocsRedPoint: false, isNewPushNotify: false,}</pre><h4 data-nodeid="246">没事别写注释</h4><p data-nodeid="247">good case:<br/>为什么接下来的代码要这么做<br/>bad case:<br/>接下来的代码做了什么</p><h3 data-nodeid="248">复杂度</h3><ul data-nodeid="249" class=" list-paddingleft-2"><li><p data-nodeid="251">优先使用标准库中的能力</p></li><li><p data-nodeid="253">封装细节</p></li><li><p data-nodeid="255">写的代码越简单, bug越少</p></li><li><p data-nodeid="257">尽量遵守单一职责原则</p></li><li><p data-nodeid="259">DRY——Don’t Repeat Yourself</p></li></ul><h4 data-nodeid="260">无意义的函数封装</h4><pre data-nodeid="261" class="lang-js">// 根据admitStatus判断按钮试算前置灰状态export const isDisabledByAdmitStatus = discountListItem => { if (!discountListItem?.bankInfo?.admitStatus) { return true } else { return false }}</pre><h4 data-nodeid="262">建议使用moment、dayjs等标准时间库处理时间:</h4><pre data-nodeid="263" class="lang-js">// 本季度开始时间、结束时间,返回毫秒值export const getQuarterStartAndEndTime = ({ time = null, isTimestamp = true, split = '/', startDateTime = ' 00:00:00', endDateTime = ' 23:59:59',} = {}) => { let date = checkDate(time) ? new Date(time) : new Date() let year = date.getFullYear() let month = date.getMonth() + 1 let startTime = null let endTime = null if (month <= 3) { startTime = year + split + '01' + split + '01' + startDateTime endTime = year + split + '03' + split + '31' + endDateTime } else if (month > 3 && month <= 6) { startTime = year + split + '04' + split + '01' + startDateTime endTime = year + split + '06' + split + '30' + endDateTime } else if (month > 6 && month <= 9) { startTime = year + split + '07' + split + '01' + startDateTime endTime = year + split + '09' + split + '30' + endDateTime } else { startTime = year + split + '10' + split + '01' + startDateTime endTime = year + split + '12' + split + '31' + endDateTime } // 本季度开始时间 startTime = isTimestamp ? getTimestamp(startTime) : startTime // 本季度结束时间 endTime = isTimestamp ? getTimestamp(endTime) : endTime return { startTime, endTime, }}</pre><h4 data-nodeid="264">DRY——Don’t Repeat Yourself</h4><p data-nodeid="265">下面三个方法中重复逻辑非常多,应该进行合理的封装,降低复杂性。另一个比较常见的问题,console.log 这种调试代码不应该被合入主干。</p><pre data-nodeid="266" class="lang-js">handleMergedInvoice = selectedRows => { const { invoiceList } = this.state; const { limitNum } = this.props; const newInvoiceList = []; for (const item of selectedRows) { if (newInvoiceList.length && newInvoiceList.length >= limitNum) { Message.error(`上传失败,发票数量不可超过${limitNum}张`); return; } item.invoiceUrl = item.invoiceUrl || item.dataUrl || ""; delete item.dataUrl; item.invoiceDate = item.invoiceDate ? moment(item.invoiceDate).format("YYYY-MM-DD") : ""; newInvoiceList.push({ ...item, id: getIncrementCid() }); this.setState({ invoiceList: newInvoiceList }, () => { const { invoiceList: list, errIndexList } = this.state; if (list.length) { this.verifyInvoiceList(list); } this.invoiceListReduce(); }); }};</pre><pre data-nodeid="267" class="lang-js">updateInvoice = ({ ...data }, i) => { const { invoiceList } = this.state; const oldInvoice = invoiceList[i]; data.invoiceUrl = data.invoiceUrl || data.dataUrl || ""; delete data.dataUrl; data.invoiceDate = data.invoiceDate ? moment(data.invoiceDate).format("YYYY-MM-DD") : ""; this.setState( { invoiceList: [ ...invoiceList.slice(0, i), { ...oldInvoice, ...data }, ...invoiceList.slice(i + 1) ] }, () => { this.invoiceListReduce(); } );};</pre><pre data-nodeid="268" class="lang-js">addInvoice = ({ ...data }) => { const { invoiceList } = this.state; const { limitNum } = this.props; if (invoiceList.length && invoiceList.length >= limitNum) { Message.error("上传失败,发票数量不可超过500张"); return; } data.invoiceUrl = data.invoiceUrl || data.dataUrl || ""; delete data.dataUrl; data.invoiceDate = data.invoiceDate ? moment(data.invoiceDate).format("YYYY-MM-DD") : ""; this.setState( { invoiceList: [...invoiceList, { ...data, id: getIncrementCid() }] }, () => { const { invoiceList: list } = this.state; if (list.length) { this.verifyInvoiceList(list); } this.invoiceListReduce(); } );};</pre><h4 data-nodeid="269">封装细节</h4><p data-nodeid="270">看到下面这段代码,大概能够想象 newValidate 出现的原因,为了文章阅读体验, 删除部分代码<br/>这个验证函数,严重违反了单一职责,首先包含了多种校验逻辑,还承担了 submit 数据预处理、submit、error处理;不仅如此,还和视图层耦合,包括了回到顶部、定位到错误位置、错误DOM样式调整的逻辑。<br/>当然了,看到newValidate代码行数,也没有好到哪去。<br/>此处200多行代码就成了这个工程的毒瘤。</p><p data-nodeid="271"><img data-nodeid="729" alt="image.png" src="https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2024-03-19-17-54hTiMzLZ19OypZfDQ.png"/></p><pre data-nodeid="272" class="lang-js">validate = () => { const { form, totalDraftAmount, output, outputBasename } = this.props; const { validateFields } = form; const { financeChannel, orderNo: oldOrderNo, checked } = this.state; const ocrDomList = document.querySelectorAll('.ocrform'); const checkboxDomList = document.querySelectorAll('.ant-checkbox'); // 每次 validate 前先去除上次打的标, Object.values(ocrDomList)为了兼容 ie Object.values(ocrDomList).forEach(item => { item.classList.remove('field', 'error'); }); validateFields(async (err, data) => { if (err) { Message.warn('请核对填写的内容'); if (output) { // 回到顶部 scrollToTop(); return; } // 定位到第一个错误 setTimeout(this.goToError(document.querySelector('.field.error'))); return; } // 验证发票 const { contractAmt, draftInfos = {}, invoiceInfos } = data; /* 此处省略20行 */ if (totalDraftAmount > contractAmt) { /* 此处省略7行 */ } // 访问子组件中的勾选状态 如没勾选,则校验不通过 const { checkedA, checkedB } = this.myRef.current.state; // 只要有一个没勾选就进来 if (!checked || !checkedA || !checkedB) { // 如果是 确认背书未勾选则 提示相应文案 Message.warn('请阅读并同意相关服务协议'); if (output) { // 回到顶部 scrollToTop(); return; } // 先打标记,再定位错误 checkboxDomList[0].classList.add('error'); // 定位到第一个错误 setTimeout( this.goToError(document.querySelector('.ant-checkbox.error')) ); return; } let selectedDraftInfo = []; /* 此处省略 14 行 selectedDraftInfo 数据组装*/ const sendData = { ...data, draftInfos: selectedDraftInfo }; const { couponReleaseNo } = getLocationParams(location) || {}; if (couponReleaseNo) sendData.couponReleaseNo = couponReleaseNo; if (oldOrderNo) sendData.orgOrderNo = oldOrderNo; // 用户提交时选择为无重复背书, 删除已上传重复背书文件 const { billReusedStatus } = this.endorseBillRef.current.state; if (billReusedStatus === billReusedStatusEnum.noReuse) { delete sendData.repeatedEndorseFileUrl; } try { /* 此处省略 19 行 发起接口调用, 成功 + 失败逻辑处理*/ } catch (error) { this.setState({ loading: false }); let errMessage; // 通过 error.message 字符串中 拿到错误模块对应的 invoiceNo errMessage = error.message.split('[')[1]; if (!errMessage) return; errMessage = errMessage.split(']')[0]; // 通过报错信息定位到错误模块索引 const errorInvoiceIndex = invoiceInfos.findIndex( item => item.invoiceNo === errMessage ); // 添加标红样式 ocrDomList[errorInvoiceIndex].classList.add('field', 'error'); if (output) { // 回到顶部 scrollToTop(); return; } // 定位到发票错误位置 setTimeout(this.goToError(ocrDomList[errorInvoiceIndex])); } });};</pre><h4 data-nodeid="273">认知复杂度与圈复杂度</h4><p data-nodeid="274">整体来说正相关, 也有例外:</p><pre data-nodeid="275" class="lang-js">function getWords(number) { // +1 switch (number) { case 1: // +1 return "one"; case 2: // +1 return "a couple"; case 3: // +1 return "a few"; default: return "lots"; }} // 圈复杂度:4function getWords(number) { switch (number) { // +1 case 1: return "one"; case 2: return "a couple"; case 3: return "a few"; default: return "lots"; }} // 认知复杂度:1</pre><pre data-nodeid="276" class="lang-js">function sumOfPrimes(max) { // +1 let total = 0; for (let i = 1; i <= max; i++) { // +1 for (let j = 2; j < i; j++) { // +1 if (i % j === 0) { // +1 continue; } } total += i; } return total;} // 圈复杂度:4function sumOfPrimes(max) { let total = 0; for (let i = 1; i <= max; i++) { // +1 for (let j = 2; j < i; j++) { // +2 if (i % j === 0) { // +3 continue; // +1 } } total += i; } return total;} // 认知复杂度:7</pre><h4 data-nodeid="277">复杂度评判标准</h4><ol data-nodeid="278" class=" list-paddingleft-2"><li><p data-nodeid="280">需要添加“黑客代码(hack)”来保证功能的正常运行。</p></li><li><p data-nodeid="282">总是有其他开发者询问代码的某部分是如何工作的。</p></li><li><p data-nodeid="284">总是有其他开发者因为误用了你的代码而导致出现bug。</p></li><li><p data-nodeid="286">即使是有经验的开发者也无法立即读懂某行代码。</p></li><li><p data-nodeid="288">你害怕修改这一部分代码。</p></li><li><p data-nodeid="290">管理层认真考虑雇用一个以上的开发人员来处理一个类或文件。</p></li><li><p data-nodeid="292">很难搞清楚应该如何增加新功能。</p></li><li><p data-nodeid="294">如何在这部分代码中实现某些东西常常会引起开发者之间的争论。</p></li><li><p data-nodeid="296">人们常常对这部分代码做完全没有必要的修改,这通常在代码评审时,或者在变更被合并进入主干分支后才被发现。<br/>--- 《编程原则》</p></li></ol><h3 data-nodeid="297">规范性</h3><p data-nodeid="298">这部分内容比较多,更多内容见 Code Review 手册</p><h4 data-nodeid="299">import 排序的例子</h4><p data-nodeid="300">可以看到第一段代码,没有规律,阅读成本高,第1行, 第5行出现了重复引用。<br/>reviewer建议:<br/>使用工具进行格式化,提高可读性<br/>https://github.com/lydell/eslint-plugin-simple-import-sort<br/>https://github.com/import-js/eslint-plugin-import/</p><pre data-nodeid="301" class="lang-js">import { ref } from 'vue'import Taro from '@tarojs/taro'import gwApi from '@/api/index-gateway-js'import api from '@/api/index-js'import { onMounted, reactive, watch } from 'vue'import InputRight from '../components/InputRight.vue'import { isweapp, getParams } from '@/utils'import { FACTORY_ADDRESS_ORIGIN } from '@/utils/const.js'import { sgmReportCustom } from '@/utils/log'import { genAddressStr } from '@/utils/address'</pre><pre data-nodeid="302" class="lang-js">import Taro from '@tarojs/taro'import { onMounted, reactive, ref, watch } from 'vue'import api from '@/api/index.js' import gwApi from '@/api/index-gateway-js' import { getParams, isWeapp } from '@/utils' import { genAddressStr } from '@/utils/address' import { FACTORY_ADDRESS_ORIGIN } from '@/utils/const.js' import { sgmReportCustom } from '@/utils/log'import InputRight from '…./components/InputRight.vue'</pre><h4 data-nodeid="303">命名(世上问题千千万,问题命名占一半)</h4><ul data-nodeid="304" class=" list-paddingleft-2"><li><p data-nodeid="306">不用宽泛的模块或文件名</p></li><li><p data-nodeid="308">没有拼写错误,单复数也应该正确</p></li><li><p data-nodeid="310">符合规范:</p></li><ul data-nodeid="311" style="list-style-type: square;" class=" list-paddingleft-2"><li><p data-nodeid="313">文件名kebab-case</p></li><li><p data-nodeid="315">类名PascalCase</p></li><li><p data-nodeid="317">文件作用域内 常量、变量、函数 camelCase</p></li><li><p data-nodeid="319">private 是否采用下划线,应保持一致</p></li></ul></ul><p data-nodeid="320">bad case:</p><pre data-nodeid="321" class="lang-js">// 无意义命名let array = [1, 2, 3, 4, 5]let temp = falseimport Part1 from './Part1';import Part2 from './Part2';import Part3 from './Part3';import Part4 from './Part4';// magic numberlet point = CGPoint(x: 123, у: 456)// 硬编码reportEvent("ImageClickEventId")</pre><h3 data-nodeid="322">其他</h3><h4 data-nodeid="323">连等</h4><pre data-nodeid="324" class="lang-js">// 连等elm.onload = elm.onreadystatechange = resolveFn</pre><h4 data-nodeid="325">一段重试逻辑</h4><p data-nodeid="326">虽然 if 嵌套不多, 但是让人心智负担很重, 无法快速看出 count 值是多少会 false, 代码写的像面试题</p><pre data-nodeid="327" class="lang-js">if ((data && data.eid) || count++ > 20) { if (!data.eid) { webLog.custom({ type: 1, code: 'getEidInfo-empty', msg: data, }) } clearInterval(timer) resolve({ data })}</pre><p data-nodeid="328">reviewer 建议:<br/>使用卫语句提前剔除负向逻辑后, 虽然代码更长, 但是更好理解了。</p><pre data-nodeid="329" class="lang-js">if (!data.eid && count <= 20) { count++ return}if (!data.eid) { webLog.custom({ type: 1, code: 'getEidInfo-empty', msg: data, })}clearInterval(timer)resolve({ data })</pre><h1 data-nodeid="330">CR落地-常见挑战</h1><h2 data-nodeid="331">Code Review时看不出问题</h2><p data-nodeid="332">参考解法:<br/>组织集体审查讨论,提升大家审查能力,在代码质量上达成共识</p><h3 data-nodeid="333">代码审查方式对比</h3><table class="tg"><thead><tr class="firstRow"><th class="tg-lboi">分类方式</th><th class="tg-lboi">审查方法</th><th class="tg-lboi">优点</th><th class="tg-cly1">缺点</th></tr></thead><tbody><tr><td rowspan="4" class="tg-lboi">审查方式</td><td rowspan="3" class="tg-ntxs">线下异步审查</td><td class="tg-ntxs">时间灵活</td><td rowspan="3" class="tg-9j11">实时性差</td></tr><tr><td class="tg-ntxs">干扰小</td></tr><tr><td class="tg-9j11">易于存档</td></tr><tr><td class="tg-cly1">面对面审查</td><td class="tg-cly1">实时性好</td><td class="tg-cly1">对审查者干扰大</td></tr><tr><td rowspan="3" class="tg-cly1">审查人数</td><td rowspan="2" class="tg-9j11">一对一审查</td><td class="tg-9j11">安排容易</td><td rowspan="2" class="tg-9j11">多人同时线下审查容易出现重复工作</td></tr><tr><td class="tg-9j11">干扰小</td></tr><tr><td class="tg-cly1">团队集体审查</td><td class="tg-cly1">讨论深入,审查细致</td><td class="tg-cly1">人数多时,容易效率低下</td></tr><tr><td rowspan="3" class="tg-cly1">审查范围</td><td class="tg-9j11">增量审查</td><td class="tg-9j11">聚焦重点效率高</td><td class="tg-9j11"><br/></td></tr><tr><td rowspan="2" class="tg-cly1">全量审查</td><td class="tg-cly1">系统性</td><td class="tg-cly1">工作量大,不能常常进行</td></tr><tr><td class="tg-cly1">专项集中检查</td><td class="tg-cly1"><br/></td></tr><tr><td rowspan="5" class="tg-cly1">审查时机</td><td class="tg-9j11">代码入库前门禁检查</td><td class="tg-9j11">对于把关入库代码的质量,效果很好</td><td class="tg-9j11">太过死板的话,会降低代码入库效率</td></tr><tr><td rowspan="3" class="tg-cly1">代码入库前的设计时检查</td><td class="tg-cly1">最早发现问题,从而大大降低问题修复成本;</td><td class="tg-cly1"><br/></td></tr><tr><td class="tg-cly1">代码作者抵触情绪小;</td><td class="tg-cly1"><br/></td></tr><tr><td class="tg-cly1">有效的架构讨论工具;</td><td class="tg-cly1"><br/></td></tr><tr><td class="tg-9j11">代码入库后检查</td><td class="tg-9j11">既不阻塞代码入库,又可以对提交的代码进行审查</td><td class="tg-9j11">有问题代码错过检查而上线的风险</td></tr></tbody></table><h2 data-nodeid="335">担心冲突、害怕出错</h2><p data-nodeid="336">比如 A 看出了不少问题,但是发现代码作者非常不耐烦,导致 A 不敢把看到的所有问题都提出来。<br/>参考解法:</p><h4 data-nodeid="337">冲突发生</h4><ul data-nodeid="338" class=" list-paddingleft-2"><li><p data-nodeid="340"><strong data-nodeid="798">解决冲突</strong><br/>✅ Leader协助沟通及仲裁<br/>✅ 协商达成共识<br/>✅ 寻求第三人评估<br/>✅ 组内讨论<br/>❌置若罔闻<br/>❌放任自由</p></li></ul><h4 data-nodeid="341">预防冲突</h4><ul data-nodeid="342" class=" list-paddingleft-2"><li><p data-nodeid="344"><strong data-nodeid="817">沟通技巧</strong><br/>尽量疑向、不要太肯定<br/>✅如果采用......是否会更合适?<br/>❌这里应该......<br/>✅是否考虑过......这样的方案?<br/>❌......方案肯定更好<br/>✅这个地方似乎会影响滚动性能?<br/>❌这样写肯定会影响滚动性能</p></li><li><p data-nodeid="346"><strong data-nodeid="829">发现问题,尽量提供建议</strong><br/>✅......这样会更简洁<br/>❌你这代码复杂度太高了<br/>✅根据......项目规范,这里应该这样...<br/>❌你这代码不符合项目规范</p></li></ul><h4 data-nodeid="347">特别注意</h4><p data-nodeid="348">不要吝啬称赞👍👍👍<br/>没必要力求完美👍👍👍</p><h2 data-nodeid="349">没有时间 CR</h2><blockquote data-nodeid="350"><p data-nodeid="351">浇花很有意义, 但是先把火灭了</p></blockquote><p data-nodeid="352">首先区分紧急与真正紧急:</p><table data-nodeid="354"><thead data-nodeid="355"><tr data-nodeid="356" class="firstRow"><th data-nodeid="358">不是真正紧急情况</th><th data-nodeid="359">真正的紧急情况</th></tr></thead><tbody data-nodeid="362"><tr data-nodeid="363"><td data-nodeid="364">内部计划Deadline</td><td data-nodeid="365">显著影响用户生产环境的Bugfix</td></tr><tr data-nodeid="366"><td data-nodeid="367">长时间开发后需要进行的必要合入日常Bugfix</td><td data-nodeid="368">导致整个团队工作暂停</td></tr><tr data-nodeid="369"><td data-nodeid="370">回滚导致的测试失败或构建破坏</td><td data-nodeid="371">处理紧迫的法律问题</td></tr><tr data-nodeid="372"><td data-nodeid="373"><br/></td><td data-nodeid="374">不跟版本会导致重大损失的Deadline</td></tr><tr data-nodeid="375"><td data-nodeid="376"><br/></td><td data-nodeid="377">安全漏洞等</td></tr></tbody></table><h3 data-nodeid="378">CR 时间不够</h3><h4 data-nodeid="379">工作量评估要包含 CR 时间</h4><p data-nodeid="380">推荐预留 20% 的 CR 时间</p><h4 data-nodeid="381">权衡:</h4><p data-nodeid="382">关注设计大于具体实现;<br/>保证不出线上问题为底线;</p><h4 data-nodeid="383">管理好交付里程碑</h4><p data-nodeid="384">越大的里程碑越容易产生大型CL,会拖慢CR速度<br/>建议数据:400行/小时(样式、dom行可适当剔除)<br/>建议里程碑交付周期:1周,最长2周</p><h3 data-nodeid="385">真正紧急情况</h3><h4 data-nodeid="386">同步CR</h4><p data-nodeid="387">写完代码当面或电话同步Review</p><h4 data-nodeid="388">并行CR</h4><p data-nodeid="389">结对编程</p><h4 data-nodeid="390">紧急情况后门</h4><p data-nodeid="391">google 的做法:自己是Owner,写“To be reviewed”可绕过审查</p><h2 data-nodeid="392">历史包袱过重</h2><p data-nodeid="393"><strong data-nodeid="873">通解: 卡住增量,治理存量</strong><br/>CR的目的是让每一次合并都在改善代码仓库的水平</p><h1 data-nodeid="394">其他-提升团队工程素养</h1><p data-nodeid="395">✓ 设计模式: 掌握24种设计模式<br/>✓ 设计原则: 掌握SOLID原则(单一职责、开闭原则、里氏替换、接口隔离、依赖反转)<br/>✓ 方法学: 了解Devops,极限编程,Scrum,精益,看板,瀑布,结构化分析,结构化设计<br/>✓ 实践: 实践测试驱动开发,面向对象设计,结构化编程,函数式编程,持续集成,结对编程</p><h2 data-nodeid="396">书籍推荐</h2><p data-nodeid="397">《编程原则( understanding software)》<br/>《重构:改善既有代码的设计》<br/>《编写可读代码的艺术》<br/>《代码大全》<br/>《敏捷软件开发》<br/>《架构整洁之道》<br/>《代码整洁之道》</p><h1 data-nodeid="398">相关资源</h1><p data-nodeid="399"><a data-nodeid="899" href="https://joyspace.jd.com/pages/7Qn6xzEHlxoee2lSkKaN">Code Review 速查手册</a></p><p><br/></p>
上一篇:SQL事前巡检插件
下一篇:计算机网络协议介绍
434536128_m
文章数
1
阅读量
0
作者其他文章
01
代码质量与技术债系列分享之一 - 如何做好 Code Review
TL;DRCode Review 速查手册参考资料https://composity.com/post/too-busy-to-improvehttps://commadot.com/wtf-per-minute/https://dl.acm.org/doi/10.1145⁄3585004#d1e372https://google.github.io/eng-practices/review/re
434536128_m
文章数
1
阅读量
0
作者其他文章
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号