百度高效研发实战训练营-Step2

百度高效研发实战训练营Step2


2.1 代码的艺术

2.1.1《代码的艺术》目的解读

这门课程的目的主要有以下四点:
(1) 了解公司与学校写代码的不同
(2) 消除对于程序员这个职业的误解
(3) 建立对软件编程的正确认识
(4) 明确作为软件工程师的修炼方向

1、了解公司与学校写代码的不同

在公司写程序和在学校写程序有很大的不同,在学校写程序时,对代码的质量要求比较低,当进入公司之后,做的是工业级的产品,服务用户量可能会达到亿万级,所以与代码的质量要求比较高,一些伟大产品中的代码,甚至可能被称为是艺术品。

2、消除对于程序员这个职业的误解

很多人都对程序员这个职业有误解,认为程序员就是码农,认为程序员35岁之后会面临失业问题,还有人认为程序员未来的唯一出路是做管理,希望通过接下来的学习,可以对于程序员有一个新的认识,从而消除误解。

3、建立对软件编程的正确认识

在做一件事物时,我们常说要“知”“行”合一,即我们需要对这件事物有一个正确的认识,才会有正确的行动。同理,写出好代码的前提,是对软件编程有正确的认识。

4、明确作为软件工程师的修炼方向

明确作为软件工程师的修炼方向,艺术品是由艺术家创造的,艺术家的修炼是有方式方法的,同样软件工程师的修炼也是有方法的,希望通过接下里的内容,能够对软件工程师这个职业有一个全新的认识。

2.1.2 代码与艺术之间的关系

1、代码是可以被称为艺术的

我们知道,艺术是多种多样的、丰富多彩的。同时,艺术也是有多个层次的。
想象一下我们编写的代码,我们的脑海中也会有类似的感觉,艺术就是人类通过借助特殊的物质材料与工具,运用一定的审美能力和技巧,在精神与物质材料,心灵与审美对象的相互作用下进行的,充满激情与活力的创造性劳动,可以说它是一种精神文化的创造行为,是人的意识形态和生产形态的有机结合体。

同样,写代码也要经历这样的一个过程,在编写代码的过程中,我们借助的物质是计算机系统,借助的工具是设计、编写、编译、调试、测试等,同样编写代码需要激情,而且编写代码是一件非常具有创造性的工作,总之,代码是人类智慧的结晶,代码反映了一个团队或一个人的精神。

代码是可以被称为艺术的!

2、艺术可以从不同的角度进行解读、研究与创造

达芬奇有多幅著名的画作,那著名的《蒙娜丽莎》这幅画来举例,站在观众的角度,可能只是在欣赏画中的人物微笑,但是对于画家来说,可能就会考虑画画的手法、构图、光线明暗、色彩对比等等方面。

同样,在《达芬奇自画像》这幅画作中,光有这个人物还不够,我们还要通过这个人物的个性与背景等等因素来揣摩这幅画作。

综上所述,在艺术方面,我们可以站在很多不同的角度进行解读,但是如果要成为一名创作者,我们需要的不仅仅是欣赏的能力,更重要的是从多角度进行解读、研究与创造的能力。

3、写代码如同艺术创作

写代码就如同艺术创作一般,并不是一件容易的事,写代码的内涵是:

  • ①写代码这个过程,是一个从无序到有序的过程。
  • ②写代码需要把现实问题转化为数学问题。在写代码的过程中,我们需要有很好的模型能力。
  • ③写代码实际上是一个认识的过程。很多时候,编码的过程也是我们认识未知问题的过程。
  • ④在写代码的过程中,我们需要综合的全方位的能力。包括把握问题的能力、建立模型的能力、沟通协助的能力、编码执行的能力等等。
  • ⑤在写好代码之前,首先需要建立编码品味。品味是指我们首先要知道什么是好的代码,什么是不好的代码。这样我们才能去不断地调整自己的行为,然后去学习,去提高我们的编码能力,写出具有艺术感的代码。

2.1.3 软件工程师不等于码农

软件工程师不能只会写代码,更需要具有综合的素质。这个综合的素质包括:
(1)技术
技术能力是基础。包括但不限于编码能力、数据结构和算法能力、系统结构知识、操作系统知识、计算机网络知识、分布式系统知识等等。
(2)产品
要对产品业务有深刻的理解,需要了解产品交互设计、产品数据统计、产品业务运营等。
(3)其他
要了解一些管理知识,需要知识项目是怎么管理的,如何去协调多个人一起去完成一个项目,如何去协调多个人一起去完成一个项目。有一些项目需要具有很强的研究和创新方面的能力。

以上这些能力素质,是一个软件工程师需要具有的综合素质,要成为一个全部掌握这些素质的系统工程师,至少需要8~10年的时间,所以软件工程师绝对不是一个只会简单编写代码就可以的职业。

软件工程师不等于码农。

2.1.4 正确认识代码实践方面的问题

代码实践方面的问题,重点包括以下三个方面:

1、什么是好的代码,好的代码有哪些标准

好代码的标准是:

  • 高效(Fast)
  • 鲁棒(Solid and Robust)
  • 简洁(Maintainable and Simple)
  • 简短(Small)
  • 可共享(Re-usable)
  • 可测试(Testable)
  • 可移植(Portable)
  • 可监控(Monitorable)
  • 可运维(Operational)
  • 可扩展(Scalable & Extensible)

将以上十条标准进行总结精简,可以归纳为

  • 1.代码的正确和性能
  • 2.代码的可读和可维护性
  • 3.代码的可运维和可运营
  • 4.代码的可共享和可重用

了解完好代码的标准,接下来看一下不好的代码主要表现在哪些方面:

  • 不好的函数名
    比如在函数名中,加上my等单词,这属于很不专业的用法。

  • 不好的变量名
    比如看不出任何含义的a b c j k temp等变量名

  • 没有注释或注释不清晰
    没有注释的代码是非常难读懂的,注释不倾斜往往是因为文字功底或者描述能力欠缺,从而导致无法通过注释把代码的执行原理讲解清楚。

  • 一个函数执行多个功能
    比如LoadFfomFileAndCalculate()函数,它既执行了文件中去加载数据,还执行了计算功能。一般像这样的函数,我们建议把它切分成两个单独的函数。

  • 不好的代码样式排版
    代码样式排版在某种程度上体现了代码的一种逻辑,好的代码排版能增强代码的可读性和逻辑性,我们在写代码时,要规避不好的代码样式排版。

  • 难以测试的代码
    代码没法测试,就难以编写测试用例。

以上这些都是一些不好的表现。

2、好的代码从哪里来

代码不只是“写”出来的,实际上,在整个项目中,真正的编码时间约占项目整体时间的10%,好的代码是多个环节工作共同作用的结果。

这些环节包括:

  • 1.在编码前,要进行需求分析和系统设计。
  • 2.在编码过程中,要注意单元测试。
  • 3.在编码后,要做集成测试,要上线,要持续运营/迭代改进。

一个好的系统/产品是以上过程持续循环的结果。

下面着重介绍一下重点环节:

  • 1.认识需求分析和系统设计的重要性
    需求分析和系统设计在软件开发中经常被忽略或轻视,但是这两点都是非常重要的环节,人们的直觉就是拿到一个项目就想尽快把它写出来并运行,感觉这样的路径是最快的,但是实际上在前期需求分析和系统设计投入更多的成本,会在后期节省更多的消耗。

    也即前期更多的投入,收益往往最大。
    原因是,如果我们开始的设计做错的话,那么后期开发、测试、上线、调试这些成本都会被浪费掉。

  • 2.清楚需求分析和系统设计的差别

需求分析和系统设计是有泾渭分明的区别的,为了避免这两者相互混杂,我们需要清楚需求分析和系统设计各自的内涵。

需求分析,主要是定义系统或软件的黑盒行为,即外部行为,比如系统从外部来看能够执行什么功能。
系统设计,主要是设计系统或软件的白盒机制,即内部行为,比如系统从内部来看是怎么做出来的,为什么这么做?

  • 3.需求分析的注意要点

需求分析,主要有两个要点:

  • 要点一:清楚怎么用寥寥数语勾勒出一个系统的功能。
    每个系统都有自己的定位,我们可以从简洁的总体描述,展开到具体的需求描述。需求描述的内容,基本包括系统类型描述、系统规模描述、系统定位和系统差异描述、系统对外接口功能描述。

  • 要点二:需求分析需要用精确的数字来描述,需求分析中会涉及到大量的数据分析,这些分析都需要精确的数字来进行支撑。

  • 4.系统设计的注意要点

  • 要点1:清楚什么是系统架构
    系统架构(System Architectrue),在wiki上有一个英文定义,阐述了系统架构是一个概念的模型,它定义了系统的结构、行为、更多的视图,进一步解读系统架构。

它的几个要素是,系统要完成哪些功能,系统如何组成,在这些组成部分之间如何划分。

  • 要点2:注意系统设计的约束

    注意系统设计的约束,重点是资源的限制。比如计算的资源限制、存储的资源限制、IO/网络的资源限制等。

  • 要点3:清楚需求是系统设计决策的来源

    精确定义需求中的各个细节,以及量的定义,对系统设计的决策起着重要的作用。

  • 要点4:系统设计的风格与哲学
    在同样的需求下,可能会出现不同的设计方式,其目的相同,设计不同。
    比如复杂指令集和精简指令集的设计差异。
    一个好的系统,是在合适假设下的精确平衡;一个通用的系统,在某些方面,不如专业系统的。

  • 每个组件(子系统或模块)的功能都应该足够的专一和单一。(每个组件,是指子系统或模块等。)

  • 功能的单一,是指复用和扩展的基础。
    倘若不单一,未来就有很可能很难进行复用和扩展。

  • 子系统或模块之间的关系,应该是简单而清晰的。

  • 软件中最复杂的是耦合。
    如果各系统之间的接口定义非常复杂,那么未来便很难控制系统的健康发展.
    值得注意的是,使用全局变量就是在增加系统的耦合,从而增加系统的复杂性。所以在系统中,需要减少使用全局变量。

  • 要点5:清楚接口的重要性

接口,英文名Interface,系统对外的接口比系统实现本身还要更加重要。
接口的设计开发不容忽视,接口主要包括:
1.模块对外的函数接口

2.平台对外的API
这些API很多是基于RPC或者是基于Web API来实现的

3.系统间通信的协议

4.系统间存在依赖的数据
比如:给另一个系统提供的词表

以上,这些都是接口,都需要去重视和很好的定义。

接口重要的原因在于:

1 接口定义了功能
如果定义的功能不正确,那么系统的可用性与价值便会大打折扣。

2 接口决定了系统与系统外部之间的关系
相对于内部而言,外部关系确定后非常难以修改。因此接口的修改需要非常慎重且要考虑周全。

后期接口修改时,主要注意以下两点:

1 合理好用
新改的接口,应该是非常合理好用的,不能使调度方感觉我们做的接口非常难以使用。

2 修改时需要向前兼容
新改的接口,应该尽量实现前项的兼容,不能出现当新接口上线时,其他程序无法使用的情况。

3、如何写好代码

关于具体怎么写代码,下面主要从两个维度来进行介绍:

(1)代码也是一种表达方式

在一个项目中,软件的维护成本远远高于开发成本,而且超过50%的项目时间都是用于沟通。常规意义的沟通方式,主要有面对面交流、Email邮件、文档或网络电话会议等。

但其实代码也是一种沟通方式,在计算机早期,我们使用机器语言或汇编语言,更多考虑代码如何更高效率地执行,然而,随着技术的进步,代码编译器逐渐完善,我们写代码时,更多的是要考虑如何让其他人看得懂、看得清楚。
于是编程规范就应运而生了,编程规范主要包含:

  • 如何规范的表达代码
  • 语言使用的相关注意事项

基于编程规范,看代码的理想场景是:

  • 看别人的代码,感觉和看自己的代码一样。
  • 看代码时能够专注逻辑,而不是格式方面。
  • 看代码时不用想太多

(2)代码书写过程中的细节问题
关于代码数学过程中的细节问题,我们将从模块、类和函数、面向对象、模块内部组成、函数、代码注释、代码块、命名、系统的运营等。

(1)关于模块

模块,是程序的基本组成单位。
在一个模块内,会涉及到它的数据,函数或类。
对于Python、Go、C语言这样的程序来说,一个后缀名为.py、.c、.go的文件就是一个模块。每一个模块需要有明确的功能,需要符合紧内聚、松耦合。

关于模块切分的是否合理,对于软件架构的稳定起着至关重要的作用。

切分模块的方法,先区分数据类的模块和过程类的模块。
数据类的模块,主要是要完成对数据的封装,封装往往是通过模块内部变量,或类的内部变量来实现的。
过程类的模块,本身不含数据,可以从文件中去读取一个数据,或执行一些相关的操作。过程类模块,可以调用其他数据类模块或过程类模块。

编写程序时,我们需要注意减少模块间的耦合,从而有利于降低软件复杂性,明确接口关系。

(2)关于类和函数

类和函数,是两种不同的类型,有它们各自适用的范围。
另外,遇见和类的成员变量无关的函数时,可以将该函数抽出来,作为一个独立的函数使用,这样便于未来的复用。

(3)关于面向对象

面向对象是一个优秀的编程方法和范式,但是真正理解的人并不多,面向对象的本质是数据封装,这就要求我们,在写程序的过程中应该从数据的角度开始想问题,而不是从执行过程的角度开始想问题。

同时,我们需要注意一个普遍的错误认知,即C语言是面向过程的,C++是面向对象的,实际上,C语言也是基于对象的,C和C++的区别,主要是没有多态和继承。

C++是一个经常被滥用的语言,因为C++有太强的功能,作为软件工程师,我们最重要的任务是去实现我们所需要的功能,语言只是一种解决问题的工具。

我们应该谨慎地使用多态和继承,如果一个系统中,类的继承超过三层,那么这个系统的复杂度便很难把握。

有这样一个悖论,很好的继承模型是基于对需求的准确把握,而我们的初始设计阶段往往对需求理解的透彻。

系统在初始阶段,可能只是一个很简单的原型,然后通过不断地迭代完善,才逐步发展起来变好的。

(4)关于模块内部的组成

一个模块,比如.py、.c、.go这样的模块,其内部组成主要是:

在文件头中,需要对模块的功能进行简要说明,需要把文件的修改历史写清楚,包括修改时间、修改人和修改内容。
在模块内,内容的顺序尽量保持一致,以方便未来对内容的搜索查询,

(5)关于函数

函数的切分同样十分重要。
对于一个函数来说,要有明确的单一功能。
函数描述三要素,包括功能、传入参数和返回值

功能描述,是指描述这个函数是做什么的,实现了哪些功能。
传入参数描述,是指描述这个函数中,传入参数的含义和限制条件。
返回值描述,是指描述这个函数中,返回值都有哪些可能性。

函数的规模要足够的短小,这是写好程序的秘诀之一。
bug往往出现在那些非常长的函数里。

在函数头中,需要对函数的语义做出清晰和准确的说明。
我们需要注意函数的返回值,在写函数时,要判断函数的语义,确定返回值的类型。
基于函数的语义,函数的返回值有三种类型:

  • 1.“逻辑判断型”函数
    “逻辑判断型”函数,返回布尔类型的值,True或False,表示“真”或“假”。
  • 2.“操作型”函数
    “操作型”函数,作为一个动作,返回成功或者失败的结果——SUCCESS或ERROR。
  • 3.“获取数据型”函数
    在“获取数据型”函数中,返回一个“数据”或者返回“无数据/获取数据失败”。

在这里,推荐一种函数的书写方式:

以“单入口、单出口”的方式书写,这种方式能够比较清晰地反映出函数的逻辑。
尤其是在实现多线程的数据表中,推荐使用一个内部函数来实现,“单入口 单出口”的方式。

(6)关于代码注释

要重视注释。
书写注释,要做到清晰明确,在编写程序的过程中,先写注释后写代码。

(7)关于代码块

代码块的讨论范围,是在一个函数类的代码实现。
书写代码块内的思路,是先把代码中的段落分清楚,文章有段落,代码同样有段落,
代码的段落背后表达的是我们对于代码的逻辑理解,包括代码的层次、段落划分、逻辑等。

代码中的空行或空格,是帮助我们表达代码逻辑的符号,并非可有可无。
好的代码,可以使人在观看时,一眼明了。

(8)关于命名

命名,包括系统命名、子系统命名、模块命名、函数命名、变量命名、常量命名等。

我们要清楚命名的重要性,命名重要的主要原因为:
1.“望文生义”是人的自然反应。不准确的命名会使人产生误导,
2.概念是建立模型的出发点。(概念、逻辑推理=>模型体系)

好的命名是系统设计的基础。
命名中普遍存在的问题有:
1.名字中不携带任何信息
2.名字中携带的信息是错误的

命名不是一件容易的事情,关系着代码的可读性,需要仔细思考。
命名的基本要求是:准确、易懂。
提高代码命名可读性的方式之一:在名字的格式中加入下划线、驼峰等。

(9)关于系统的运营

在互联网时代,系统非常依赖运营,并不是我们把代码写完调试通了就可以,在系统运营过程中,代码的可监测性非常重要,很多程序都是通过线上的不断运行、不断监测、不断优化而迭代完善的。

所以,我们在编写代码的过程中,要注意尽可能多地暴露出可监控接口。

对于一个系统来说,数据和功能同等重要。
数据收集很重要,数据量够大才能知道这个项目或这个系统的具体收益。

关于系统的运营,我们在设计和编码阶段就需要考虑系统的运营,也就要求:

  • 提供足够的状态记录
  • 提供方便的对外接口

2.1.5 怎样修炼成为优秀的软件工程师

通常,人们在判断一名软件工程师的水平时,都会用工作时间、代码量、学历、曾就职的公司等等,这类外部因素作为评判标准。

但事实上,修炼成为优秀的软件工程师,重要的因素有三点:

(1)学习-思考-实践

1.多学习

  • 软件编写的历史已经超过半个世纪,有太多的经验可以借鉴。
  • 技术更新进步迅速,要不断的学习进步。

2.多思考

  • 学而不思则罔,思而不学则殆。
  • 对于做过的项目要去深入思考,复盘写心得。

3.多实践

  • 知行合一谓之善,要做到知行合一,我们大部分的心得和成长,其实是来自于实践中的经历。
  • 在学习和思考的基础之上,要多做项目,把学到的理论运用到真正的工作中去。

(2)知识-方法-精神

互联网的发展日新月异,对于软件开发来说,知识永远在增加。
所以在变化快速的知识世界中,最好的方式是找到方法。

方法就是用来分析问题和解决问题的,虽然说起来简单,但是适合每个人的方法都需要自己去寻找和总结。

在大多数人的成长过程中,并不单单只是鲜花和掌声,更多的是和困难荆棘做斗争,而真正能做出成就的人,都有着远大理想和宏伟志向。

所以光有知识和方法往往是不够的,还需要有精神作为支撑。

下面列出几个精神理念:
1 自由精神、独立思想
人一定要有自己的思考,不要人云亦云,不要随波逐流。

2 对完美的不懈追求
不要做到一定程度就满意了,而是要去不断的追求一个更好的结果。

(3)基础知识是根本

唐朝著名宰相魏征曾经对唐太宗说过,“求木之长者,必固其根;欲流之远者,必浚其泉源”,充分表达了基础乃治学之根本。

对于一个软件工程师来说,需要掌握的基础知识是非常繁杂的,包括以下种种:

  • 数据结构、算法、操作系统、系统结构、计算机网络
  • 软件工程、编程思想
  • 逻辑思维能力、归纳总结能力、表达能力
  • 研究能力、分析问题、解决问题的能力等

这些基础的建立,至少也要5~8年的时间。

欲速则不达,希望可以沉下心来,尤其是在自己职业生涯开始的几年,将基础打好。这样,才能在未来有更长远的发展。

2.1.6《代码的艺术》课程总结

通过这部分的学习,我们了解了公司与学校写代码有很大的不同,消除了对程序员这个职业的误解,建立了对软件编程的正确认识,明确了作为软件工程师的修炼方向。

在最后,还需要强调一下几个要点:

  • 软件工程师不等于码农,软件工程师是一个很有深度的职业。

  • 代码,我们可以把它携程艺术品。最终成品完全在于我们自身的修养。

  • 不要忘记我们为什么出发。我们的目的是改变世界,而不是学习编程或炫耀技术。

  • 好代码的来源不是写好代码,好代码是系列工作的结果。

  • 代码是写给别人看的,别人看不懂的代码就是失败的。

  • 写好代码是有方法的。系统工程师至少需要学习8~10年的积累。希望能够沉下心来,把基础打好,提升能力。


2.2 Mini-spider实践

本部分将从两个方面来讲解Mini-Spider框架在实际场景下的使用和注意事项等问题:

2.2.1 多线程编程

2.2.1.1 数据互斥访问

多线程中数据互斥访问的情况非常常见,在真实的生产环境中,经常会有开发人员会将对一张表的“添加”和“判断是否存在”分为两个接口,这是一种非常常见的错误。

以下图中的代码为例,左边的代码是正确的写法,将添加和判断写进一个函数中,右边的代码是典型的错误代码,编写了两个函数,分别是添加和判断函数。事实上,这种将添加和判断写进一个函数,并且运行的实现机制,是同8086的底层指令集支持密不可分的。

2.2.1.2 临界区的注意事项

在代码中,用锁来保护的区域被称为临界区。
以下图代码为例,临界区为self.lock.acquireself.lock.release两句话之间的区域。在使用临界区的时候要注意,不要把耗费时间的操作放在临界区内执行。

很多开发人员在编写多线程的时候,会将耗费时间很多的逻辑放入临界区内,这样会导致无法发挥多线程对于硬件资源最大化利用的优势。

2.2.1.3 I/O操作的处理

在多线程编程中,还要注意对于IO操作的处理。
首先,在编写代码的时候,要注意不能出现无捕捉的exception,
以下图最左边的代码为例,如果不对异常进行捕捉,那么一旦出现问题,就不会执行self.lock.release()语句,进而导致死锁的发生。

其次,因为异常处理是非常消耗资源的,所以我们也不能像图中中间的代码一样,将异常放在临界区内,要像最右边的代码一样处理。

2.2.2 具体细节处理

下面来讲解一下Mini-Spider过程中的细节处理问题,

2.2.2.1 种子信息的读取

很多开发人员会将种子信息读取的逻辑,和其他逻辑耦合在一起,这样是错误的。

以图中代码为例,虽然通过_get_seeds函数直接读取文件中的信息,并灭有书写错误,但是如果后续的开发中文件的格式发生了变化,那就需要重新回来修改这部分的代码。

通过上述代码可以发现,模块划分和逻辑的复杂程度是没有关系的。
即使是逻辑简单的代码,如果没有做好模块划分,也会变得难以维护。

2.2.2.2 程序优雅退出

在真实的应用中,很多开发人员在实现程序退出功能的时候,使用了非常复杂的机制,导致后期维护难度较高。
在实际应用中,可以使用Python系统库中,关于队列的task_done()join()的机制,以下图中的代码为例,左边的代码就是使用了task_done(),中间的代码是主程序中的一种常规逻辑使用,右边的是对中间主程序的一种优化,增加了spider.wait(),让整个逻辑可读性更强,更容易被理解。

2.2.2.3 爬虫的主逻辑编码

下面来讲一下爬虫主逻辑代码,编写需要注意的地方:
很多开发人员编写的主逻辑,非常的复杂且难懂,事实上图中的代码就是一个爬虫的主逻辑的所有代码,可以看到,里面包含了六个步骤:
第一步:从队列中拿到任务
第二步:读取内容,如果读取内容失败,则重新读取。如果读取成功,则执行第三步。
第三步:存储数据,
第四步:检查数据深度
第五步:如果数据深度不足,就进一步解析,并且放到队列中
第六步:结束任务

通过对六个步骤的编写和分析,可以看到好的代码是可以很快理解的,可读性是非常好的,这是开发人员在编写代码中需要注意的一个点。


2.3 代码检查规则背景及总体介绍

2.3.1 代码检测的意义

1 提高代码可读性

  • 规范统一
  • 方便他人维护
  • 长远来看,符合公司内部开源战略

2 发现代码缺陷

  • 帮助发现代码缺陷,弥补人工代码评审的疏漏
  • 节省代码评审的时间与成本

3 提前发现问题

  • 有助于提前发现问题,节约时间成本
  • 降低缺陷修复成本

4 规范制定与准入检查

  • 促进公司编码规范的落地
  • 在规范制定后,借助工具进行准入检查

5 提升编码规范可运营

  • 提升编码规范的可运营性
  • 针对反馈较多的不合理规范进行调整更新

2.3.2 代码检查场景及工具

1 代码检查场景

首先是本地研发环节,借助客户端工具,在push发起评审之前进行检查。
若本地代码扫描没有出现问题,就进入第二个环节,代码准入环节,即为Code Review,这一环节进行增量检查,属于公司强制要求。第三个环节,持续集成环节Agile,当代码合入到代码库之后进行全量检查,业务线根据自身需求来配置。

2 代码检查工具与服务

代码检查的产品、客户端、SCM(icode)、CI(Agile)之间具有交互性,共同构成整个代码检查环节。

3 代码检查覆盖范围

在覆盖语言方面,代码检查目前已经覆盖了包括C++、Java/android、PHP、Python、Go、JS、OC、Kotlin等在内的几乎所有主流语言。

在检查维度方面,代码检查包括编码规范、代码缺陷、开源框架引用安全检查、可维护性(MI)。

4 代码检查速度

编码规范
只扫描变更文件,检查代码变更行是否复合规范,速度较快。

缺陷检查
需考虑文件依赖、函数调用关系、代码上下文等环节,因此相对耗时。

针对bugbye侧耗时的策略,默认超时时间10分钟,达到超时时间后,为了不影响代码合入,可选择“重新触发”或“跳过检查”。

2.3.3 代码检查规则分级

1 规则等级梳理

Code Review阶段,所有维度扫描出的问题,可以分为以下三个等级:
(1)Error

Error,属于需要强制解决的类型,影响代码的合入。
应视具体情况不同,采取修复、临时豁免、标记误报等措施及时处理。

(2)Warning

Warning,属于非强制性解决类型,不影响代码的合入。
但很可能存在风险,应尽量修复。

(3)Advice

非强制解决类型,级别相对较低,不影响代码合入。
可以选择性修复。

2 机检任务统一

在旧的模式下,旧的机器检查任务分类过多,缺乏重点,不契合用户需求,针对这一问题进行了调整,新模式下机器检查任务合并,评审页行间标注问题并分类显示。

3 评审页间行提示

评审业行间提示,指出具体的编码规范和缺陷检查。

4 针对豁免、误报、咨询的说明

豁免分为临时豁免与永久豁免。
临时豁免,本次检查临时豁免,下次遇到相同问题会再次报错,每人每月限额豁免10次,特殊需求可申请增加5次。

永久豁免,针对老代码迁移,机器生成文件等不适合按代码规范检查的情况,可申请永久豁免,由CMC审批。

标记误报
行间点击误报并填写理由,也可直接反馈服务号。

问题咨询
对扫出的bug有疑问,或者检查失败等,可通过服务号咨询。


2.4 代码检测规则:Python语言案例详解

这部分的主要内容分为两个部分,Python的代码检查规则与Python的编码惯例,内涵涵盖了共计34条代码检查规则与4个Python编码惯例。这些规则与惯例都将在未来的编码规则中被反复应用。

2.4.2 Python的代码检查规则

一套良好的代码检查规则,可以带来代码质量提高,降低缺陷修复和后期维护成本等一系列收益,这里为大家提供了一套完备的代码检查体系。

规定的Python代码检查规则中,主要分为四个大类,分别是:
1.代码风格规范
2.引用规范
3.定义规范
4.异常处理规范

(1)代码风格规范

这一大类的规范规则较为繁琐,具体有可细分为以下七个小类(共计17条规则):

1.程序规范规模
(1)每行≤120个字符
每行不得超过120个字符
(2))函数长度≤120行
定义的函数长度不得超过120行,这意味着,在编写代码时,要时刻注意自己编写的程序规模,要避免冗余,确保简洁而高效。

2.语句规范
因为Python语言与其他语言不同,可以不需要明确的标识符标示语句的结尾,所以有以下规定:
(3)禁止以分号结束语句
(4)在任何情况下,一行只能写一条语句

3.括号使用规范
(5)除非用于明确算术表达式优先级、元组或者隐式行连接,否则尽量避免冗余的括号

4.缩进规范
(6)禁止使用Tab进行缩进,而统一使用4个空格进行缩进
在需要将单行内容拆成多行写时,我们规定
(7)与首行保持对齐,或者首行留空,从第二行起统一缩进四个空格

5.空行规范
(8)文件级定义(类或全局函数)之间,相隔两个空行;
类方法之间,相隔一个空行

6.空格规范
(9)括号之间均不添加空格
(10)参数列表、索引或切片的左括号前不应加空格
(11)逗号、分号、冒号前均不添加空格,而是在它们之后添加一个空格
(12)所有二元运算符前后各加一个空格
(13)关键字参数或参数默认值的等号前后不加空格

7.注释规范
(14)每个文件都必须有文件声明
声明必须包括以下三个方面的信息:

  • (a) 版权声明
  • (b) 功能和用途介绍
  • (c) 修改人及联系方式

用文档字符串(docstring)进行注释时,规定
(15)使用docstring描述模块、函数、类和类方法接口时,docstring必须用三个双引号括起来。
(16)对外接口部分,必须使用docstring描述,内部接口视情况自行决定是否写docstring
(17)接口的docstring描述内容,至少包括以下三个方面的信息:

  • 功能简介
  • 参数
  • 返回值

如果可能抛出异常,则特别注明会发生。

(2)引用规范

一个良好的代码风格,是程序员写出优美代码的基础,而当我们想要写出更为间接的代码,我们很难在不导入各种的模块、库与包的情况下完成它。

(1)禁止使用from ...import...句式直接导入类或函数
而应自导入库后再行调用,
(2)每行只导入一个库
(3)按标准库、第三方库、应用程序自有库的的顺序排列import
三个部分之间分别留一个空行。

(3)定义规范

下面我们将分别介绍各种定义的规范,具体可分为三个小类:
(1) 变量定义规范
(2) 函数定义规范
(3) 类定义规范

1.变量定义规范

在变量定义方面,我们有强制的规范规定:
①局部变量使用全小写字母,单词间使用下划线分割;
②定义的全局变量必须写在文件头部
③常量使用全大写字母,单词间使用下划线分隔

2.函数的定义规范
函数的定义规范,主要体现在函数的返回值,以及默认参数的定义上,为提高代码可读性,对于函数的返回值,规范要求为:
①函数返回值必须小于或者等于三个,若返回值大于三个,则必须通过各种具名的形式进行包装。

为了保障函数运行效率,以及降低后期维护和纠错成本,对于函数默认参数的定义有如下要求:
②仅可使用以下基本类型的常量或字面常量作为默认参数(整数、浮点数、bool、浮点数、字符串、None)

3.类定义的规范
类定义的规范,包括了四个方面的内容:
①类的命名,使用首字母大写的驼峰式命名法
②对于类定义的成员,protected成员使用单下划线前缀,private成员使用双下划线前缀。
③如果一个类没有基类,必须继承自ovject类。
④类构造函数应尽量简单,不能包含可能失败或过于复杂的操作

(4)异常处理规范

在代码编写中应该尽量避免代码异常状态的出现,然而错误有时也在所难免,对于这些异常状态的处理,有着明确的规范要求。
①禁止使用双参数形式或字符串形式的语法抛出异常
②如需自定义异常,应在模块内定义名为Error的异常基类,并且该基类必须继承自Exception,其他异常均由该基类派生而来。
③除非重新抛出异常,禁止使用except语句捕获所有异常,一般情况下,应使用except...语句捕获具体的异常
④捕捉异常时,应当使用as语法,禁止使用逗号语法。

2.4.3 Python的编码惯例

下面介绍几个常见的Python编码惯例,便于大家理解日后所见的代码:

它们分别是:

1.让模块即可被导入又可被执行

2.in运算符的使用

3.不适用临时变量交换两个值

4.用序列构建字符串

(1)让模块即可被导入又可被执行

Python不同于编译型语言,而属于脚本语言,是动态的逐行解释运行,没有统一的程序入口。

所以,为了方便模块之间的相互导入,我们通过自定义一个main函数,并使用一个if语句,if内置变量__name__=='__main__',在这个if条件下,再去执行main函数,这样我们就能够实现,让模块即可被导入又可被执行。

(2) in运算符的使用

in是一种简洁而高效的运算符,很多时候,合理的使用in运算符,可以替代大量的重复判断过程,降低时间复杂度,提高代码的运行效率。

(3) 不适用临时变量交换两个值

Python有简洁而高效的语句,可以实现交换两个值的操作,并无必要引入临时变量来交换两个值。

(4) 用序列构建字符串

对于一个字符串列表、元组等,可以用序列来构建字符串,利用一个空字符串和join函数,可以避免重复,从而高效完成字符串的构建。

posted @ 2022-06-06 22:01  Xu_Lin  阅读(189)  评论(0编辑  收藏  举报