代码质量

代码质量是编程所固有的主题。 ISO 9000用于评估和控制企业管理的质量,产品使用GOST和相同的ISO,但是没有用于质量评估的GOST代码。 对于代码质量也没有确切的定义和标准。



每个开发人员都根据经验以自己的方式理解质量。 傻瓜和领导的观点是不同的,这导致了分歧。 每个项目的团队都以自己的方式评估代码。 团队正在更新,开发人员正在离开,团队领导者正在改变-质量的定义正在改变。 来自Tinkoff.ru的Ivan BotanovStressoID ),Frontend-developer,Angular的在线讲师,会议和会议的发言人,YouTube的讲师以及公司的团队教练有时会尝试帮助解决此问题。

在解读Ivan关于Frontend Conf的报告时,我们将讨论可读性,命名,声明性,代码风格,并间接涉及傻瓜和领导者的关系:错误,耙子和“燃烧”幼仔。

免责声明:请做好心理准备,文本中将会有很多错误的代码 ,这些代码来自“特殊”站点


一点历史


我们都以不同的方式写作。 当您更改工作地点,项目或团队时,您可能已经注意到了这一点-不寻常的事情立即显而易见。 这件事在我身上也发生过很多次,这就是为什么这份报告诞生的原因。 我专注于新手开发人员,但是本文对那些已经写而不是编写或自定义开发流程的人来说将是有用的。 我们要谈论什么:

  • 关于不可读代码的问题。
  • 让我们讨论命名。
  • 让我们看看声明式和命令式之间的区别是什么,以及有哪些问题。
  • 关于代码模块化和类型化。
  • 关于代码样式和技术债务,关于提交和git flow。
  • 关于您可以使用的工具,并修复产品中的错误。

在开始之前,我会问一个问题: “为了使项目停止开发,总线必须起飞多少个程序员?” 。 正确答案:大家。

什么是公交车系数?


有条件地,Petya或Vasya在一个团队中工作,他们了解该项目的一切:他们去找他,问这个和那个,它在这里如何工作,以及它在那里如何工作。 每个人都依赖Petya,该项目的公交车号是1。 数字越小,开发该项目就越困难,因为每个人都会分散Petya的注意力,而且他很酷,必须执行任务,而不回答问题。

您会说成为Pete有多酷! 每个人都爱他,欣赏他,需要他。

事实并非如此。 通常,Petya是团队负责人,他还必须处理其他任务:讨论项目的开发,构建体系结构,管理团队,但不要去沙丘并解释为什么将其写在这里,否则就不要写。

如果代码干净而且不错,那么阅读它会很好,而且琼斯语中的问题也更少了。 干净的代码可以提高项目的可行性,并降低准入门槛 。 当新人出现在团队中时,他们会问的问题会更少。 在这样的项目中,由于入门门槛低, 因此更容易吸引开发人员

质量代码提高了公交车数量。

易读性


缩进,弯曲的命名和强大的嵌套会影响可读性-许多项目都因此而受苦。 除了缩进之外,多行三元运算符,缺少单一的Code样式,开发方法的组合以及对变量的明确定义也降低了可读性。 所有这些都是导致代码可读性差的最常见原因。

对于我自己,我已经确定了术语线性代码 -这是可以像书一样阅读的代码。

从左到右,从上到下读取线性代码,而不必返回到先前编写的代码。

此类代码的示例:

list.forEach((element1) => { if (element1.parent_id == null) { output.push(element1); list.forEach((element2) => { if (element2.parent_id == element1.id) { output.push(element2); list.forEach((element3) => { if (element3.parent_id == element2.id) { output.push(element3); list.forEach((element4) => { if (element4.parent_id == element3.id) { output.push(element4); } }) } }) } }) } }) 

这段代码是线性的,但是还有另一个问题-它是大量嵌套的。 因此,我们还必须监视嵌套。

非线性代码示例:

 if(!brk && html.childNodes[0].value && html.childNodes[0].max) { if(clear) html.childNodes[0].value = 1; else if(html.childNodes[0].value <= html.childNodes[0].max) { ++ html.childNodes[0].value; if(brk) { for(id = 1; id < html.childNodes.length; ++ id) findActive(html.childNodes[id], true); html.parentNode.className = ""; } return null; } else { html.parentNode.className = "Ready"; html.className = ""; return html; } } 

如果我们丢弃所有多余的东西并找出破坏线性的东西,那么我们将看到类似以下内容:

 if (condition) { return; } else { return; } 

如果还有其他地方,首先您必须查看一个地方写的内容,然后再看另一个地方。 如果它是一个很大的嵌套或很大的嵌套,则注意力分散了,并且代码很难阅读。

如何减少嵌套并实现线性代码?


结合条件 。 这是我们可以做的最简单的事情-如果可以合并条件并稍微减少嵌套,则可以嵌套。

那是:

 if (isUser()) { if (isAdmin()) { console.log('admin'); } } 

它变成了:

 if(isUser() && isAdmin()) { console.log('admin'); } 

应用早期返回模式 。 它使您可以完全摆脱其他问题。 我可以用早期返回的if-else替换该方法或一段代码,然后执行一个代码块或另一个代码块。 这非常方便-您不必滚动并返回到代码的某些部分。

那是:

 if (isAdmin()) { return admin; } else { return user; } 

它变成了:

 if (isAdmin()) { return admin; } return user; 


应用承诺链 。 这是典型的量角器代码。 不久之前,我写了E2E测试,这样的代码伤了我的眼睛:

 ptor.findElement(protractor.By.id('q01_D')).click().then(() => { ptor.findElement(protractor.By.id('q02_C')).click().then(() => { ptor.findElement(protractor.By.id('q03_D')).click().then(() => { console.log('done'); }); }); }) 

使用promise链,代码将变为:

 ptor.findElement(protractor.By.id('q01_D')).click() .then(() => { return ptor.findElement(protractor.By.id('q02_C')).click(); }) .then(() => { return ptor.findElement(protractor.By.id('q03_D')).click(); }) .then(() => { console.log('done'); }); 


如果我们使用箭头函数的知识,则可以执行以下操作:

 ptor.findElement(protractor.By.id('q01_D')).click() .then(() => ptor.findElement(protractor.By.id('q02_C')).click()) .then(() => ptor.findElement(protractor.By.id('q03_D')).click()) .then(() => console.log('done')); 

它可读,声明,精美-一切都清晰可见。

高阶可观察。 由于我是有角度的并且使用RxJS,因此我面临着意大利面条代码强烈嵌套的问题-嵌套的Observable (即嵌套订阅)。 有一个流,在流内部您需要获取值,然后与另一个流相关。 一些这样写:

 Observable.of(1,2,3) .subscribe(item => { item += 2; Observable.of(item) .subscribe(element => { element += 1; }) }) 

这确实会影响成人项目。 您可以这样做:

 Observable.of(1,2,3) .mergeMap(item => Observable.of(item + 2)) .mergeMap(element => Observable.of(element + 1)) .subscribe() 

利用RxJS API的知识,由于可以观察更高的顺序 ,因此我们不再使用强嵌套,而是使用了声明式 。 这件事,将内部流的价值抛向外部,就全部了。 但是它干净,线性,美观且没有花钱。

嵌套三元运算符


我认为,代码中最糟糕的是嵌套的三元运算符 。 用块条件语句重写它们,尽量不要使用它们。 我们根本不会谈论隐式条件-这是一个失败。

嵌套三元运算符和隐式条件的示例:

 arr.length > 0 ? arr[1] == 1 ? arr[1] = 2 : arr[1] = 1 : console.log('empty arr'); !a && b && func() 

我在5分钟内编写了这个简单的三元组。 它具有数组的长度和一些操作,但是很难读取,因为某个地方有一个问题,其他地方-一切都不清楚。 可以使用多行嵌套三元运算符来重写此代码:

 arr.length > 0 ? arr[1] === 1 ? arr[1] = 2 : arr[1] = 1 : console.log('empty arr'); 

您会说:

- 好吧!
- 看到了吗?
- 可见!

您对此说什么:

 return query instanceof RegExp ? (function () { fn.each(function (id) { if (id.match(query)) { seatSet.push(id, this); } }); return seatSet; })() : (query.length == 1 ? (function (character) { //user searches just for a particual character fn.each(function () { if (this.char() == character) { seatSet.push(this.settings.id, this); } }); return seatSet; })(query) : (function () { //user runs a more sophisticated query, so let's see if there's a dot return query.indexOf('.') > -1 ? (function () { //there's a dot which separates character and the status var parts = query.split('.'); fn.each(function (seatId) { if (this.char() == parts[0] && this.status() == parts[1]) { seatSet.push(this.settings.id, this); } }); return seatSet; })() : (function () { fn.each(function () { if (this.status() == query) { seatSet.push(this.settings.id, this); } }); return seatSet; })(); })() ); 

有人撰写并支持了这个ternarnik。 通过访问这段代码,将很难弄清楚问号在哪里开始和在哪里结束。 如果您使用三元,请不要互相投资-这很不好。

命名


另一个错误的代码:

 var _0x30119c = function() { var _0x3af68e = { 'data': { 'key': 'cookie', 'value': 'timeout' }, 'setCookie': function(_0x3543f3, _0x13e5c1, _0x586dac, _0x1c9d63) { _0x1c9d63 = _0x1c9d63 || {}; var _0x47b83f = _0x13e5c1 + '=' + _0x586dac; var _0xae3be = 0x0; for (var _0xae3be = 0x0, _0x5d2845 = _0x3543f3['length']; _0xae3be < _0x5d2845; _0xae3be++) { var _0x440369 = _0x3543f3[_0xae3be]; _0x47b83f += ';\x20' + _0x440369; var _0x411875 = _0x3543f3[_0x440369]; _0x3543f3['push'](_0x411875); _0x5d2845 = _0x3543f3['length']; if (_0x411875 !== !![]) { _0x47b83f += '=' + _0x411875; } } _0x1c9d63['cookie'] = _0x47b83f; } }; 


我们可以说这段代码是模糊的 ,但即使如此:我们看到有一个易于理解的功能,一个清晰的“数据”,setCookie做一些事情,然后它只是一揽子,没有什么清楚的- 串联在一起的东西,在某个地方。 一切都非常糟糕。

命名时需要考虑的事项


使用CamelCase表示法camelCaseNotation没有音译,所有方法的名称都只有英文ssylka, vikup, tovar, yslygacheckTovaraNaNalichieTseni失败。 顺便说一下,后者是我刚开始编程时写的。

没有item,data,el,html,arr ,尤其是在遍历数组时。 例如,对于一系列产品或报价,请选择友好名称: product, offer, etc 产品和产品之间的差异不是很大,但是可读性更高。 即使您具有单行功能加上一些东西,一个商业友好的名称也会提高可读性。

private_property 表示私有财产private_property 。 我添加此规则是因为我已经第二年编写TypeScript了,但是JS中没有访问修饰符,并且在命名约定中我们同意下划线为其他开发人员定义私有属性。

大写字母 const BLOCK_WIDTH = 300;const BLOCK_WIDTH = 300; ,以及大写形式的类名: class SomeClass 。 我用TypeScript编写,在那里一切都清晰可见,在ES6中一切都清晰可见,但是也有一些旧项目,其中所有带有new运算符的函数类都以大写形式编写。

没有一个字母变量u = user 。 这是对i的引用-不。 写清楚,即从功能上讲。 无需制作Check方法,该方法可以检查某些内容,但尚不清楚。 写下方法的addProductToCard(); sendFeedback() 名称addProductToCard(); sendFeedback() addProductToCard(); sendFeedback()

必要性


一个小题外话。 命令性与编程同时出现。 那时,他们用汇编语言进行编码,并命令式地编写:详细描述了每个命令,每个步骤,并为该值分配了一个存储单元。 我们生活在2019年,因此不再写JS。



这是简单的命令式代码,具有for循环,变量。 目前尚不清楚为什么在这里添加它们。

 for (let i = 0; i >= 10; i++) { const someItem = conferences[i]; const prefixString = 'Hello '; if (someItem === 'Frontend Conf') { console.log(prefixString + someItem); } } 

强制性代码问题很多变量,这些变量的很多维护构造以及很多注释,因为需要以某种方式描述这些变量-您无法创建变量而忘记它。 所有这些都会影响代码的可读性。

声明性


声明式样式已替换。 我们使用JavaScript编写并且可以使用。 声明式样式如下所示:

 conferences .filter(someItem => someItem === 'Frontend Conf') .forEach(someItem => console.log('Hello' + someItem)); 

这与命令中的命令相同,但更加简单和易于理解。

声明性代码的好处


这样的代码更易于阅读,维护和测试,复杂的代码构造可以隐藏在方法和抽象的后面。 您可以通过煎鸡蛋的示例来了解命令式和声明式之间的区别。 要以命令式的方式煎炸鸡蛋,我们将一个煎锅放在火上,倒入油,然后将鸡蛋打碎,倒出。 我们以声明式的方式说:“煎蛋”,该过程将隐藏在抽象的后面。 我们要煎炒鸡蛋,而不是想知道它是如何工作的。

当不是非常有经验的开发人员来自他们研究Pascal的大学时,问题就开始了,并且这样写:

 const prefix = 'Hello '; conferences .forEach(someItem => { if (someItem === 'Frontend Conf') { const result = prefix + someItem; console.log(result) } }); 


这是声明式和命令式样式的组合 。 没有可读性,没有完整的命令性,有些变量以及ifif有人添加,则是因为他根本不了解过滤器。 如果您是潜在客户并且看到这样的代码,请上前用棍子戳一下链接然后将代码带到声明性代码中。

创建变量


不要为了变量而创建变量-这是一个坏主意。 当我从开发人员那里找出为什么要这样做时,我听到:

-好吧,它增加了可读性!

什么增加了这里的可读性const username = user.name ? 如果要创建变量,请给名称赋予含义。 例如,我们有一个正则表达式:

 const somePattern = /[0-9]+/; str.split(somePattern); const someResult = a + b - c; 

在这里,我将创建一个变量,这样一个人就不会浪费时间在诉讼程序上,而是要阅读该常规文件以检查电话,然后走得更远。 如果您有数学运算,也要写一个变量,因为可以肯定的是,数学运算有一个业务实体,例如某个业务界标,可以计算篮子或打折。 在这种情况下,您可以创建一个变量。

创建变量来创建变量是不值得的。

重新定义变量


假设我们创建了一个element变量,但其名称尚不清楚。 我们写了一个DOM element ,出于某种原因在数组中写了它的覆盖,然后离开了:

 let element = document.getElementById('someId'); arr.forEach(item => { // ... //      // ... element = document.getElementById('someItem') if (typeof item === 'string') { let element = document.getElementById('some' + item); element.appendChild(); } }); 

一切都好,消失了,被遗忘了。 在我们团队工作的Petya之后,加入了if块。 怎么了 再次重新定义变量,然后离开。 而且范围已经不同。 当下一个开发人员尝试理解此代码时,尤其是如果方法很大时,它将等待someIdsomeItem ,而根本没有。 在这里,您可能会浪费大量时间来查找问题所在。 我们将编写一个debugger ,放一个brake point ,看看那里有什么-通常,不要那样写。

分成方法


我们简要地考虑了方法划分,并顺利地进行了抽象。

方法应该具有原子功能一种方法一行动 。 如果您有单行动作,则不要混用,只是因为方法太小。 该方法不应超过10行。 该声明激起了一个高潮,现在也“射击”,因此写信给我或在评论中,我将解释为什么我编写此规则。

代码模块化


模块化通过拆分成多个抽象 提高代码的可读性有助于“隐藏”难以阅读的代码, 易于测试,并且易于修复错误 。 我将更详细地解释。

隐藏在抽象背后


例如,有一些代码创建一个按钮,为它分配一个ID,一个类并单击它-一切都很简单。

 const element = document.createElement('button'); element.id = 'id_button'; element.classList = 'red'; document.body.appendChild(element); element.click(); 

您可以在按钮代码中添加一个函数,包装它,并在创建按钮时使用createButton函数:

 const.button = createButton('id_button'); button.click(); function createButton((id') { element = document.createElement('button'); element.id = id; element.classList = 'red'; document.body.appendChild(element); return element; } 

通过“交谈”名称,可以清楚地知道函数的作用以及传递的ID。 如果我们想创建一个按钮而不了解它的创建方式和原因,则可以使用此函数编写代码。

 button.component.js let button = createButton('id_button'); button.click(); 

接下来,我们编写helper ,稍后其他开发人员将使用它。 如果他们想了解炒鸡蛋的油炸方式或想改变食谱-添加或除去盐,它们将流连忘返。

 button.helpers.js function createButton(id) { let button = document.createElement('button'); button.id = id; button.classList = 'red'; document.body.appendChild(element); return button; } 

打字


我不会谈论打字很长时间了-有很多报告。 我喜欢用TypeScript编写,但是仍然有流程和其他工具。 如果您没有在项目中键入内容,那么是时候实施它了。 这有助于调试许多错误。

代码气味


代码的气味与我的主题非常相关,因为编写质量低下的代码会产生这种气味。 看看Alexei Okhrimenko的精彩报告 ,他详细谈到了这个话题。

代码风格


这是开发人员遵循的一组团队,项目或公司的规则。 好的代码样式包含好的和坏的代码示例。 可以在任何方便的工具和地方编写它。 我们有这个Wiki,对于一家小型公司而言,Word中的文件就足够了。 您还可以采用现成的代码样式,该样式已被其他公司使用: JQueryGoogleAirbnb-最受欢迎的代码样式。

如果使用特定的技术或框架,它们通常也具有自己的代码样式,这值得一看。 例如,在Angular中,这是Angular样式指南或Airbnb的React / JSX样式指南

这是我们代码风格的一个例子。



这是用于创建变量的部分,并描述了如何不执行以及如何执行。

技术债务


这是对我们曾经割过的地方的一种报偿。 当我们没有时间完成一项任务并写一个提醒以后再返回时,通常会产生技术债务。 在与业务功能无关的情况下,例如,这是更新框架。

技术债务催生了拐杖和低质量的代码。

由于技术欠债,我编写了错误的代码和拐杖。 下一位开发人员将一目了然,看到门框并添加另一个拐杖:“如果仍然有支持,那就没错。” 科技债务催生拐杖,质量下降,再次催生拐杖,它们使科技债务增加的更多。

有一种“破窗”理论 。 如果破碎的窗户出现在建筑物中并且未更改,则过一会儿将出现第二个破碎的窗户,第三个是涂鸦。 人们看到没有人跟随建筑物,因此不应对破窗进行惩罚。 代码也是如此。 在旧项目中,代码经常被拐杖包围,因为有条件的Petit和Vasya看到拐杖并思考:“没关系,我将成为第一个。” 因此,在普通公司中,技术债务有足够的时间-他们可以通过配额或技术冲刺来解决问题。 如果您是领导者,或者以某种方式影响冲刺的过程以及要执行的任务列表,请注意技术债务-这很重要。

资料库


让我们讨论提交消息。 该图显示了我在不同项目中看到的真实消息的示例。 您认为其中哪几个能提供参考?



正确答案
信息性消息呈块状显示,但“添加了功能”,“修复了错误”-没有提供信息。

提交讯息


我用WebStorm编写并喜欢它。 在其中,您可以配置任务编号的突出显示,单击“任务跟踪器”时的过渡很酷。 如果某人不使用WebStorm,那么该是时候了,因为与他一起,可以获得高质量的提交消息。 什么是质量提交消息? 这是一个提交,其中包含任务编号和简短但简洁的更改本质说明 “制作了一个新模块”,“添加了一个按钮”,“添加了用于创建组件的功能”,而不是一个不露面的“添加的功能”。 查看提交时,将很清楚在何处添加了组件以及在何处修复了错误。 即使在提交消息中,也必须指出更改类型 功能,错误修正,以便清楚地知道更改发生的位置。

Gitflow


我将简要介绍Gitflow。 文森特德森文章翻译的详细说明。 Gitflow是最流行和非常成功的存储库管理模型之一,它具有主要分支 -开发,主,预生产,生产和临时分支 :功能,错误,发行版。 当我们开始任务时,我们将特征分支从开发分支中转移出来。 在功能分支上通过代码审查后,我们将其重新投入开发。 最后,我们收集来自development的发行版和master发行版。



工具


首先是Commitizen 。 这是我不久前了解的软件实用程序-我看上去,感觉到,喜欢它。 它允许您标准化消息提交,具有一个不错的控制台界面,您可以在其中选择功能。 如果您本着“更正功能”或“修复错误”的精神进行工作,那么现在是时候向大家展示Commitizen,以便他们至少可以从头开始使用它,然后您可以从头开始编写它。

Linters是每个项目中的必备工具。 linter中有许多现成的配置,但是您可以编写自己的规则 。 如果您有自己的规则,则短绒棉绒应该将这些规则放短-您将需要为您的代码样式编写规则。
关于linter的有用链接:


一个单独的段落分配了sonarJS 这是一个允许您将代码验证集成到CI中的工具。 例如,我们发出请求,然后sonarJS编写请求以询问关于我们学校的请求,无论是否感到沮丧。 这很酷-我喜欢它。 即使有条件的Vasya认为没有人会注意到他的伪善-他也会注意到sonarJS。

该工具可以轻松集成到Jenkins中。 我们的家伙内置速度足够快。 它很可能集成到其他系统中,但我们尚未尝试过。 SonarJS仍在检查代码中的代码味道。 老实说,我不知道普通棉短绒是否会这样做。

格式化程序或样式是根据配置(例如Prettier)格式化代码的工具。 您可以将其配置为pre-push hook ,并在存储库中获得统一的代码样式。 我们团队的Petya可以放置500个空间,或者根本不写分号-储存库中的所有内容都会干净整洁。

格式化程序使您可以将代码保持单一样式。

我想讲一个发生在我们身上的故事。 我们在编写了很多任务的项目中实现了Prettier,因此决定不通过它来运行整个项目,而只运行具有功能的代码段。 在我们看来,逐渐地,我们会出来并且不会破坏提交的历史:在注释中,我们将看到谁最后统治。 那是一个错误的决定。 当我们完成任务和请求请求并更改了几行后,Prettier格式化了整个文件,而当我们观看请求请求时,只有两行! 这花费了大量的代码审查时间。 因此, 如果要实施Prettier,请运行整个项目

还有另一个工具-运行时错误跟踪 。 , , . - — . Error tracking DOM-, , . Sentry , TrackJS , .

Sentry, . 怎么了 , Sentry. .


.


, , : «, iOS, , , Android — , iOS».

Sentry StackTrace — , .


Sentry. , — : . , , : « , ?». — . , . , — «» .

-


- , .

  • .
  • — .
  • .
  • . , frontend-developer Tinkoff.ru.
  • Code style, . — .
  • — . , , .
  • Git Flow — , . — .
  • — , . , .

. « » , — , . . , , , . , , .

: Twitter Facebook .

Frontend Conf . ? Frontend Conf ++ : , , .

— FrontendConf ++. — , . , 27

Source: https://habr.com/ru/post/zh-CN433326/


All Articles