ylib代码库指引(1225更新 )

背景

程序设计好之后,写的具体代码核心要满足可读性.方便未来的debug与修改功能.本规范试图把程序设计领域的经验智慧与团队的具体实际相结合.

for-if-else不能超过三层

Flat is better than nested. --The Zen of Python

  • 其中第三层的if与else分支不允许超过2个。
    可以通过增加elif,或者抽取出函数,或者分拆成多步来达到这个目标.
    例如: 写一个很复杂的循环求td_head_list, col_name_list两个值, 可以变为写第一个循环求col_name_list, 第二个循环再求td_head_list
  • 不允许逻辑判断过于复杂(超过30个字符或含有超过两个逻辑运算符)
    可以包装在函数里,利用is_somecase()这样的函数来说明含义.
def get_page_amount(self) {
  if (is_dead()): 
      return self.dead_amount();
  if (is_separated()): 
      return self.separated_amount();
  return self.normal_pay_amount();
}; 
  • if与else后面不允许紧跟着判断语句
if is_comment():
     do_something()
else:     # 改为 elif: is_code(): do_something2(); else: do_something3()
     if is_code():
          do_something2()
     else
          do_something3()
}; 
  • 利用DataFrame这样的数据结构减少循环与判断,可以参考一下两段代码.
even_list = []
for i in int_list:
    if i is None:
         continue
    elif i % 2 == 0:
        even_list.append(i)
int_frm = pd.DataFrame(columns=['int'], data=int_list)
even_frm = int_frm[int_frm['int'] % 2 == 0]
  • 对浮点数,不允许使用等于或者大于等于来判断大小.可以转化为整数之后再判断.
注释
  • 必须要注释的地方:类声明,超过3行的方法。位置如下:
class SmartBoy()
"""初始化需要iq变量"""
    def find_girl(self, place):
    """place需要时一个地址"""
  • 项目中的注释行数大于等于代码行的50%。
  • 注释推荐使用中文.内部的一些专有名词使用英文.例如line_list 的成员是.py文件一行
  • 代码修改之后要修改相应的注释.
  • 一般避免在函数体类进行注释, 可以考虑采用变量命名方法让程序自解释.参考如下的重构方式
if a>b and c<2: #代表居中的情况
    do_something()
if is_center(): 
    do_something()

注释的写法可以参考pandas
以及:https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.htm

try-except中的try语句不超过两行
  • try:包裹的语句出现问题难以调试,因为可能掩盖了其中的bug。
  • except 语句需要捕捉到的错误类型要注明。
print 规范

print必须包含name, 这样知道print是从哪里来的.

print('{}: start!'.format(__name__))
默认参数不能是可变对象

任何函数的默认参数都不可以是可变对象,以下默认参数的写法是错的.这样会导致python程序重复创建等问题.

def __init__(self, io_behavior=IoBehavior(),a_dict = dict()):

可以写为:

def __init__(self, io_bhv=None):
    if io_bhv is None:
        self.io_bhv= IoBhv()

或使用ensure_class 函数简化代码.

from helper import ensure_class # 从ylib.helper导入
# 如果a_var为None的话,会返回IoBhv(), 不为None的时候会检查io_bhv是否属于IoBhv类型.
self.frm = ensure_class(io_bhv, IoBhv)
判断语句
  • 与None判断的时候需要用is, 要注意pandas元素中None与python中自带的None是有区别的, 使用is None会返回False
  • is是判断内存中指向同一个值,有时候会发生莫名其妙的错误.建议全部改用==

frm中的None

如果frm中某一列是正整数,需要使用yfrm.INT_NONE来表示None的含义.如果直接使用python自带的none会把整个一列都变成为浮点类型.
如果frm中某一列的有效值允许为负整数,要么确保所有的数都大于-999,要么直接转化为浮点数来表示.

for循环中,不允许改动迭代对象
for i in list1:
    list1.pop() # 改动了list1,不允许.
如果本.py文件中存在中文字符,需要声明编码

#coding=utf-8

不允许出现重复3遍的代码组团

一旦发现这个确实是同一个逻辑重复了3遍.应该重构为一个方法或函数. 平常可以积累自己常用的重构方法.参考下面的重构方法:

py_codeline_dict[IMPORT_NUM] = len(import_lines)
py_codeline_dict[CLASS_NUM:class_lines] = len(class_lines)
# 后面还有好几行
cal_dict = {IMPORT_NUM: import_lines, CLASS_NUM:class_lines}
py_codeline_dict = {}
for key_i in cal_dict:
    py_codeline_dict[key_i] = len(cal_dict[key_i])

所有的requests都要设置time_out参数.

避免程序卡死,也方便在超时的时候发现问题所在.一般为5秒.

使用的所有常量都要集中管理

  • 不允许在代码中出现没有预先定义的常量
  • 如果是这个类共享的常量:放在类名下面定
  • 如果本文件夹中的类都要共享的常量:单独建立一个config.py文件,把常数放进去.
  • 对于字符串的命名,一般为 YES ='yes' 这样的形式.不允许 YES ='y'.

一行代码不要过长

不要超过pycharm给的推荐限制. 此时需要分行。

每个方法的代码不允许超过15行

不包括assert以及raise语句。可以通过提取结构, 优化语句等方式实现。

命名规范

[数据命名]

  • dict的元素含有list或dict的时候都使用_json来命名.
  • list的元素含有dict: _dict_list,

[pickle文件后缀名]

  • 父类一般使用Base来开头
  • 数据类型与后缀的关系:
    pandas.Series -> .srs
    pandas.DataFrame -> .frm
    dict -> .dict
    list - > .list
    string -> .str
    set -> .set
    orderedict -> .odict
    defualtdict -> .ddict
    tuple -> .tuple

[文件名,变量名与类名<16个字符]

  • 超过之后可读性就会很差.可以考虑: 1. 运用英文缩写(把元音字母去掉), 例如 behavior -> bhvr, control -> ctrl, coodinator-> cdntr
  • 把长的英文单词用的短的英文单词代替. 例如: get_page_source -> get_page_url, download_pdf -> fetch_pdf, frm_coordinator->frm_crdnt

[不允许出现高度相似的名称]
典型如只有一个字符的区别: divc_tag之于 divct_tag, sse_worker 之于 sze_worker
实在无法避免可以把字符重复两遍,例如div_tt_frmdiv_cc_frm

[定义好概念]

  • 写项目之前,需要定义概念,然后遵循这些概念来定义变量名.
    例如: 定义 file_line 为.py文件的每一行, code_line代表.py文件中的每一行代码.

基本类型的临时变量的值通常不允许变更

  • 临时变量指的是只作用在本方法或函数中的变量。只有在for循环等有必要的地方才允许变更.
a = 89
b = a +3
a = 12 # 不想直接变更值,可以改为c = 12
  • 数字,字符串,字符等都是基本类型。

warnings

避免代码在执行中因为使用了外部库不建议的用法而出现warnings

设计规范

[状态码]

  • 所有的状态使用100~999的三位整数来表示
  • 通用的规则(借鉴了HTTP状态码):
    100:新建
    200:成功
    204:无需处理
    400:出错。例如发现文件是坏的,要求重新下载。 4XX可以用来表示具体的错误原因。
    404:文件未找到。例如发现给的file_path是没有找到文件)

类名规范

  • 类名不建议使用Get开头.如果表示一个步骤使用Behv后缀.
  • 利用设计模式的时候,建议使用behv的后缀来代表行为类. 利用cdnt后缀来表示中介者与外观模式中的中介者与外观, 相应的wrkr后缀表示模式中的细分逻辑类.
  • 利用观察者模式的时候, 被观察者建议使用sgnl后缀表示, 观察者使用obs后缀表示.

类方法规则

  • __init__>原子方法>分子方法 原子方法代表不依赖与本类任何的方法.分子方法代表会依赖于本类的属性或方法的方法.
  • 不允许在子类里面出现init函数
  • 不允许在init以外的地方定义新的实例属性, 参考下面的重构方法.
class Duck:
def __init__(self, name):
    self.name = name
def fly(self, wings):
    self.wings = wings
class Duck:
def __init__(self, name, wings):
    self.name = name
    self.wings = wings
def fly(self):
    print(self.wings)
  • 子类重写父类的方法时,要保持相同的参数列表。
    示例:下面代码中的RedDuck的fly方法与父类中的参数不一致。不允许。
class Duck:
def __init__(self, name):
    self.name = name
def fly(self):
    print(self.name)
​
class RedDuck(Duck):
def fly(self, height):
    print(height)
  • 父类中需要子类重写的方法必须有NotImplementError提示。
class Duck:
    def fly(self):
       raise NotImplementedError
class RedDuck:
    def fly(self):
       print("[RED_DUCK]I can fly!")
  • 凡是会被其他类作为参数的类都必须有父类.
  • 不希望外界调用的方法注明秘方. 除了本module以外的代码不允许调用秘方. 通常一个方法要么属于必须重写的方法(父类中是 NotImplementError),要么是秘方。
def get_apple(self):
"""[秘方] 获取苹果"""
  • 类的__init__方法的参数不允许超过5个. 参考一下重构:
class Duck:
    def __init__(self, where, time, height, type, weight, color):
       # N行赋值语句
    def fly(self):
        # 利用实例属性完成飞行任务
class Duck:
    def __init__(self, type, weight, color): #与这个类本性有关的参数放在这里,仅仅与单次行为有关的放在方法的参数里.
       # N行赋值语句
    def fly(self,where, time, height):
        # 利用实例属性以及本次方法的参数完成飞行任务
  • 另外的方案是实现一个工厂类,这个工厂类有一个create_instance方式,可以根据参数来创建不同的类实例.

日期参数

  • 所有的日期参数使用180502这样的整形传递. 可以命名为date_int. 这样能够减少误用.

--------------------------------------2018-08-10 新增--------------------------------------

is_方法.

函数或方法如果返回的值为Ture or False, 那么以is作为前缀

do_方法

执行类的多步操作的方法要以do为前缀.

class Worker():
    def action_1(self):
        # some code
    def action_2(self):
        # some code
    def do_job(self):
        self.action_1()
        self.action_2()

Bhv,Stp后缀,

行为类有Bhv后缀, 步骤类有Stp后缀.

Base前缀

  • 父类一般有Base前缀,除非有更好的名字.
  • 父类至少两个方法.可以把子类一些通用的代码放在父类里面.

常数定义

常数通常放在类定义下方定义,多个类要共享的常数放在config.py里面.
--------------------------------------2018-08-11 新增--------------------------------------

.py文件分隔

-原则上一个步骤类来与它依赖的行为类放在一个.py文件里面. 如果有多个步骤类有共同依赖的行为类,那么这个行为类可以单独拆分出来放在bhv.py文件里.

  • 原则上一个步骤类对应tests文件夹中的一个test文件.

类的参数不可以为文件路径

  • 除非类名中标注了IO(专门负责文件读写), 否则不允许使用文件路径作为参数.这样会引入了iostate这样的不必要的依赖了. iostate的依赖可以在测试用例中引入.参考下面的重构.
class md2line():
def get_code_lines.do(the_path)
      res = io_state.read(the_path)# 引入了不必要的依赖,而且调用者无法直接传递res参数
def get_code_lines.do(the_frm)# 规范, 参数从路径变为数据

不允许在类里面新建其他类实例

  • 除非是Fct(工厂类)后缀的类, 不允许在类的方法里面新建另外的类实例,只能作为参数传进来.参考下面的重构案例
class RedDuck:
    def __init__(self):
       self.fly_bhv = FlyBehavior()
    def fly(self):
        self.fly_bhv.fly()
class RedDuck:
    def __init__(self, fly_behavior):
       self.fly_behavior = fly_behavior
    def fly(self):
        self.fly_behavior.fly()
类型断言

Errors should never pass silently. -- The Zen of Python

  • 函数的每个参数需要做类型断言.经常会需要使用如下代码:
assert isinstance(a_var, pd.DataFrame)
  • 要求每个函数的返回值也要写assert来判断类型是否正确.如果有多个可能性的话,需要在各个分支上面分别检查类型.例如:
if deal_frm():
    assert isinastance(res, pd.DataFrame)
    return res
elif deal_list():
    assert isinastance(res, list)
    return res
else:
    return 0 # 直接return一个常数的话不需要保证类型.
  • 方法不允许既可能返回None,又可以返回其他类型的值.参考下面的重构案例:
def read(file_path):
     if os.path.exists(file_path): # 不满足的话会返回None
        return self.read_pickle(file_path)
def read(file_path):
     if os.path.exists(file_path): # 不满足的话会直接报错
        return self.read_pickle(file_path)
      raise ValueError

__init__方法自定义实例变量不应该超过2个.

如果发现自己代码中init方法中的实例变量超过两个,那么可以采用某个中间类来封装这些类变量.

def __init__(HtmlIo, IoBhv, FtpBhv) # 需要重构,可以使用状态模式封装,或者用工厂模式封装类的创建.

2018-08-13新增

引入第三方依赖

所有的第三方依赖库都要从yfack中引入(除了pandas).确保大家的版本都能够统一. 也能够节省大家写import的时间.范例:

from ypack import np, es, requests

注释里面要写数据结构的样式

[类别标志]

  • 开发中经常需要用到分类。如果分类超过了3个,分类需要用类别码(整数)来标志。例如:
BLOCK_CMNT = 20
INLINE_CMNT = 21
CODE_LINE = 30
  • 杜绝直接使用字符串或整数,需要定义成常量集中管理, 可以参考如下的代码重构
if is_block_comment():
    the_type =  'block_comment'  #这些常数散落在程序各处,未来很难以管理
elif is_inline_comment():
    the_type = 'inline_comment'
INLINE_COMMNET = 'inline_comment' #这些常数通常集中在一起
BLOCK_COMMNET = 'block_comment'
# other codes...
if is_block_comment():
    the_type = BLOCK_COMMNET 
elif is_inline_comment():
    the_type = INLINE_COMMNET 

2018-08-17新增

  1. 每个类至少有2个或以上的方法(继承的方法不算,含有__init__方法).
    如果类过小,可以考虑与其他的类合并或者把父类某些参数做成可配置的.参考下面的重构:
class BaseSpider():
HEADERS = "abc"
// other codes

class SHSpider():
HEADERS = "bcd"
class BaseSpider():
HEADERS = "abc"
def change_paras(headers):
    self.HEADERS = headers #这样就不需要SHSpider类了.

2018-12-25新增
【datadesign规范】
1.每次项目之前都要设计数据结构。需要有一个data_design.py文件放上所有input与output数据的schema。
2.参考ylib\helper\tests\test_yassert.py中的方式判定数据结构。dataframe的数据格式判定可以通过把它转化为dict之后再判定(或者直接自己写helper函数)
2.data_design.py有任何变动,需要在早会与下午会中讨论。

你可能感兴趣的:(ylib代码库指引(1225更新 ))