分析一套源代码的代码规范和风格并讨论如何改进优化代码
- 结合工程实践选题相关的一套源代码,根据其编程语言或项目特点,分析其在源代码目录结构、文件名/类名/函数名/变量名等命名、接口定义规范和单元测试组织形式等方面的做法和特点;
我本次的工程实践选题是与动作识别相关的。所以我就先在github上找了一个人体动作识别的一套代码。该项目使用的编程语言是python,使用了CNN。
1、首先看看整个项目的源代码目录结构,如下图1所示:
该项目的目录结构很清晰,主要解释如下:
(1)lisi/
: 存放动作清单
(2)pretrained_model/
: 存放项目的训练模型
(3)test_video/
: 存放项目测试的ven动作视频
(4)venv/:
存放用的一些库文件和
放自定义模块和包;项目所有的源代码
(5)README
: 项目说明文件。本项目的说明文件如图2所示:
README可以给软件的使用带来很大的帮助,包括软件介绍、功能定位、安装启动使用方法、有建议或bug怎么联系作者等,对使用者来说很重要。每一个项目都应该有README说明,好的README应该至少包括以下几方面的内容:
(1)软件的简要介绍、功能定位、适用场景等
(2)软件的安装、环境依赖、启动方法、常见使用命令(使用说明)等
(3)代码的目录结构说明
(4)常见问题说明
(5)遇到建议或bug如何联系作者或项目组
(6)如果再编写的更详细,可以考虑简述软件的基本原理。这方面最好的参考就是开源软件的README,如nginx,redis等。
所以本项目的的README文件缺少了(3)和(4),不够完善。
2、文件名/类名/函数名/变量名等命名
本项目的文件名函数名和变量名命名较为符合命名的规范。如文件名:pretrained_model、test_video、Lib、real_time_input_data.py和c3d_model.py分别代表训练的模型、测试动作视频、输入数据和训练的CNN模型程序。关于函数名,有build_c3d_model()、real_time_recognition(video_path)、clip_images_to_tensor()分别代表构建模型、识别动作和把图像处理成tensor。最后看一下变量的命名如下截取了一部分,可以看出变量命名也有明确的含义,便于记忆和代码的阅读。
3、接口定义规范
接口泛指把自己供给外界调用、使用、访问等的一种抽象。Python中,对象公开方法或者属性的子集,让对象在系统中扮演特定的角色。在Python中“X类对象”与“X接口”指的是同一个东西,X类对象,就是方法和属性抽象的集合,方法的具体实现不在对象里,而在类定义里。协议是接口,但不是正式的,因为协议由文档和约定定义,不能像正式接口那样施加限制。抽象类表示接口 (源自C++之父Stroustrup)。本次项目中未使用到接口,故只介绍了一下python的接口。python也有自己的接口规范,例如Python所有的数据库接口程序都在一定程度上遵守 Python DB-API 规范。DB-API 是一个规范,它定义了一系列必须的对象和数据库存取方式,以便为各种各样的底层数据库系统和多种多样的数据库接口程序提供一致的访问接口。由于DB-API 为不同的数据库提供了一致的访问接口, 在不同的数据库之间移植代码成为一件轻松的事情。
4、单元测试组织形式
单元测试有自上而下、自下而上和分离法组织法,组织法是制定单元测试策略和测试计划的关键因素,选择不适当的方法会对单元测试成本和软件维护开支造成不利影响。本文代代码在测试的时候,是一个.py进行运行看看有没有错误,然后再运行最高层的real_time_c3d.py.
- 列举哪些做法符合代码规范和风格一般要求;
(1)Python语言规范
1. imports 仅仅用做包和模块的导入,包的导入每个尽量独占一行
2. packages 导入模块尽量使用模块的全路径
(2)代码编排
1.缩进:4个空格实现缩进,尽量不使用Tab,禁止混用Tab和空格
2.行:每行最大长度不超过79,换行可以使用反斜杠(\)。最好使用圆括号将换行内容括起来,不建议使用“;”
3.空格:括号内的第一个位置,不要空格。紧靠右括号的位置也不要空格。冒号(:)、逗号(,)、分号(;)之前不要加空格。切片木有参数,不要加空格等
(3)命名规范
命名还有通俗懂,易于代码阅读的
(4)注释规范
1.块注释,在一段代码前增加的注释。在‘#’后加一空格。段落之间以只有‘#’的行间隔。比如:# @File : real_time_c3d.py
2.行注释,在一句代码后加注释。比如:x = x + 1 # Increment x 但是这种方式尽量少使用。
3.避免无谓的注释。
4.顶层函数和类定义间使用两个空行,不同函数组之间使用两个空行隔离,空行的作用就是隔离不同函数类等,使层次分明。
5.文档注释用"""包围,例如:
"""
real time video classification
:param video_path:the origin video_path
:return:
"""
(5)编程
1.使用def来定义函数,而不是将匿名函数赋给某个变量
2.尽量使代码整齐,简洁
- 列举哪些做法有悖于“代码的简洁、清晰、无歧义”的基本原则,及如何进一步优化改进;
(1)有些代码未避免不必要的空格
(2)连续行使用两种方式使封装元素成为一行:括号内垂直隐式连接 & 悬挂式缩进。 使用悬挂式缩进应该注意第一行不应该有参数,连续行要使用进一步的缩进来区分。悬挂式缩进一般四个空格。
(3)一些注释不规范
- 总结同类编程语言或项目在代码规范和风格的一般要求。
(1)项目目录结构:
属于可读性和可维护性的范畴,故项目目录结构也应该遵循规范。假设你的项目名为foo,建议的最方便快捷目录结构为:
bin/
: 存放项目的一些可执行文件,当然你可以起名script/
之类的也行。- conf/:存放项目配置文件
foo/
: 存放项目的所有源代码。(1) 源代码中的所有模块、包都应该放在此目录。不要置于顶层目录。(2) 其子目录tests/
存放单元测试代码; (3) 程序的入口最好命名为main.py
。docs/
: 存放一些文档。setup.py
: 安装、部署、打包的脚本。requirements.txt
: 存放软件依赖的外部Python包列表。README
: 项目说明文件。
(2)文件名函数名变量名的命名规范
1. 驼峰命名法(Camel Case):
a、小驼峰命名法(Lower Camel Case):第一个单词的首字母小写;第二个单词开始每个单词的的首字母大写。例如:firstName、lastName。
b、大驼峰命名法(Upper Camel Case:每一个单词的首字母都大写。例如:FirstName、LastName、CamelCase。也被称为 Pascal 命名法(Pascal Case),源自于 Pascal 语言的命名惯例。
2. Snake Case:这是书写复合词或短语的一种惯例。复合词或短语中的各个单词之间用下划线分隔并且没有空格。复合词中的每一个单词的首字母通常都是小写的,并且复合词的第一个字母既可以是大写的又可以是小写的,例如:“foo_bar”和“Hello_world”。一般认为 Snake Case 的可读性要比 Camel Case 要强。
3. 匈牙利命名法:首次在BCPL语言中被大量使用。由于BCPL只有机器字这一种数据类型,因此匈牙利命名法通过明确每个变量的数据类型来解决这个问题。
a、系统型匈牙利命名法(System Hungarian):前缀代表了变量的实际数据类型。
lAccountNum:变量是一个长整数("l");
arru8NumberList:变量是一个无符号8位整型数组("arru8");
szName:变量是一个零结束字符串("sz"),这是西蒙尼最开始建议的前缀之一。
b、应用型匈牙利命名法(Apps Hungarian):前缀给出了变量目的的提示,或者说它代表了什么。
rwPosition:变量代表一个行("rw")。
usName:变量代表一个非安全字符串("us"),需要在使用前处理。
strName:变量代表一个包含名字的字符串("str")但是没有指明这个字符串是如何实现的。
对于python来说,接口规范的很多优点可以通过适当的组件测试规则来获得。还有一个工具PyChecker,它可以用来查找由于子类的问题。Python 2.6增加了一个abc
模块,可以让你定义抽象基类(ABCs)。然后isinstance()
,您可以使用并issubclass()
检查实例或类是否实现特定的ABC。该 collections
模块定义了一组有用的ABC如 Iterable
,Container
,和 MutableMapping
。
(4)测试组织
单元测试负责对最小的软件设计单元(模块)进行验证,unittest
是Python自带的单元测试框架。
一、原因
1、长期的工作中,发现大多数程序员的代码可读性差
2、不同的程序员之间的协作很重要,代码可读性必须很好
3、版本升级时,要基于源码升级
4、不友好的代码会影响python的执行效率
二、基于的规范
1、PEP 8: Style Guide for Python Code---------Python的编码风格建议
2、Google Python Style Guide--------------Goole Python编码风格建议
三、Python语言规范
1. imports 仅仅用做包和模块的导入,包的导入每个尽量独占一行
2. packages 导入模块尽量使用模块的全路径
3. Exceptions 必须小心使用
4. Global variables 避免使用全局变量
5. Generator
6. lambda 函数仅仅适用于一行代码能实现的简单函数
7. True or False 尽量使用[],'',{},0,None来隐式表示False
四、Python风格规范
1、代码编排
1 缩进:4个空格实现缩进,尽量不使用Tab,禁止混用Tab和空格
2 行:每行最大长度不超过79,换行可以使用反斜杠(\)。最好使用圆括号将换行内容括起来,不建议使用“;”
3 空行:类和top-level函数定义之间空两行;类中的方法定义之间空一行;函数内逻辑无关段落之间空一行;其他地方尽量不要再空行。
4 空格:括号内的第一个位置,不要空格。紧靠右括号的位置也不要空格。冒号(:)、逗号(,)、分号(;)之前不要加空格。在任何地方避免使用尾随空格。
在二元运算符周围不用空格,表示关键字参数或默认参数值时,不要使用空格。
5 括号:对于单元素tuple一定要加,和括号
6.当 if 语句过长时,可选的处理方式
7. 当闭环括号内元素跨行时,可以采用以下方式
8.同行多语句不建议使用
9.模块导入总是位于文件顶部,在模块注释和文档字符串之后,模块全局变量和常量之前。导入应该按照以下顺序分组,不同组间用空行隔离。标准库 imports 、相关第三方 imports、本地特定应用/库 imports
10.模块级别 “dunders”(即具有两个前导和两个后缀下划线的名称),例如 __all__,__author__,__version__ 等应放在模块 docstring 之后,但在任何 import 语句之前,但是除了 __future__ 导入。 Python 强制 future-imports 必须在除了 docstrings 之外的任何其他代码之前出现在模块中。
2、命名规范
module_name
package_name
ClassName
method_name
ExceptionName
function_name
GLOBAL_CONSTANT_NAME
global_var_name
instance_var_name
function_parameter_name
local_var_name
以下命名风格通常区分彼此使用:
b (单个小写字母)
B (单个大写字母)
lowercase(小写)
lower_case_with_underscores(带下划线的小写)
UPPERCASE(大写)
UPPER_CASE_WITH_UNDERSCORES(带下划线的大写)
CapitalizedWords(驼峰式,蒙古包式 whatever.)
Note: 使用驼峰式时,缩写全部大写,例如:HTTPServerError 好于 HttpServerError
mixedCase (乌鬼头)
Capitalized_Words_With_Underscores (丑!不解释!)
_single_leading_underscore : 弱地 "内部使用" 指示器. 例如,from M import * 不会导入下划线开头的对象
single_trailing_underscore_ : 用来避免和 python 关键字冲突,
__double_leading_underscore : 当对类属性命名时,调用名改变 (在 FooBar 类内,__boo 变成了 _FooBar__boo;后面有介绍)
__double_leading_and_trailing_underscore__ : "魔幻的" 对象或属性,只生存于用户控制的命名空间。例如, __init__ ,__import__ 或 __file__ 。千万不要臆造这种命名; only use them as documented.
3、注释规范
1.块注释,在一段代码前增加的注释。在‘#’后加一空格。段落之间以只有‘#’的行间隔。比如:
# Description : Module config. # # Input : None # # Output : None
2. 行注释,在一句代码后加注释。行注释和代码声明间至少间隔两个空格,不要使用无聊的行注释。
3. 避免无谓的注释。
4. 糟糕的注释不如没有注释,最好要用 English 注释!
4、编程建议
1. 字符串拼接,尽量使用join。使用str的方法而不是内置方法。使用startswith或endswith拉检查前缀和后缀
2. 单例对象,尽量使用is 、is not,不要使用==。另外,小心使用 if x 如果你的本意是 if x is not None,如果 x 是个布尔变量值 false,那可就完蛋了。
3. 使用is not而不是not is
4. 使用def来定义函数,而不是将匿名函数赋给某个变量
5. 尽量使代码整齐,简洁
6. 使用isinstance()来判断instance的类型
7.当使用 rich comparisons 实现排序操作时,最好是实现所有六种操作(__eq__,__ne__, __lt__, __le__, __gt__, __ge__)而不要依赖其他的代码去单独实现某一类比较。为了减少劳动,functools.total_ordering() decorator 提供了一个生成缺失比较方法的工具。
8.捕获的异常要说明 "错误出在哪里了 ?" 而不是仅仅说明 "哎呀!出问题了!"。
9.正确使用异常链接。在 Python 3 中,应该使用 "raise X from Y" 来表示显式替换并且不会丢失原始追溯。
10.当捕获异常时,尽可能提及具体的异常而不是使用一个赤裸裸的 except 子句。一个裸露的 except: 子句将捕获 SystemExit 和 KeyboardInterrupt 异常,这样的话就难于使用 control-c 中断程序,并可能掩盖其他问题。如果想要捕获标志程序错误的所有异常的话,用 except Exception:(裸露的 except 子句等同于 except BaseException:)。
11.特定代码块的本地资源使用 with 语句确保使用后立即释放,不能自动释放的使用 try/finally 也可以。
12.除了申请和释放资源,任何时候都应该使用单独的函数和方法调用 Context managers
13.函数返回语句要一致。在一个函数内的所有返回语句要么都返回一个表达式,要么都不返回。如果任何一个返回语句返回了表达式,那么其他任何没有返回值的语句应该明确声明为 return None。在函数结束部分必须出现返回语句。