返回介绍

建议6:编写函数的4个原则

发布于 2024-01-30 22:19:09 字数 3897 浏览 0 评论 0 收藏 0

函数能够带来最大化的代码重用和最小化的代码冗余。精心设计的函数不仅可以提高程序的健壮性,还可以增强可读性、减少维护成本。先来看以下示例代码:

def SendContent(ServerAdr,PagePath,StartLine,EndLine,sender,
        receiver,smtpserver,username,password):
  http = httplib.HTTP(ServerAdr)
  http.putrequest('GET', PagePath)
  http.putheader('Accept', 'text/html')  
  http.putheader('Accept', 'text/plain')  
  http.endheaders()
  httpcode, httpmsg, headers = http.getreply() 
  if httpcode != 200:raise "Could not get document: Check URL and Path."   
  doc = http.getfile()
  data = doc.read()
  doc.close()
  lstr=data.splitlines()
  j=0
  for i in lstr:
    j=j+1
    if i.strip() == StartLine: slice_start=j #find slice start
    elif i.strip() == EndLine: slice_end=j #find slice end
  subject = "Contented get from the web"
  msg = MIMEText(string.join(lstr[slice_start:slice_end]),'plain','utf-8')
  msg['Subject'] = Header(subject, 'utf-8')
  smtp = smtplib.SMTP()
  smtp.connect(smtpserver)
  smtp.login(username, password)
  smtp.sendmail(sender, receiver, msg.as_string())
  smtp.quit()

函数SendContent主要的作用是抓取网页中固定的内容,然后将其发送给用户。代码本身并不复杂,但足以说明一些问题。读者可以先思考一下:到底有什么不是之处?能否进一步改进?怎样才能做到一目了然呢?一般来说函数设计有以下基本原则可以参考:

原则1 函数设计要尽量短小,嵌套层次不宜过深。所谓短小,就是跟前面所提到的一样尽量避免过长函数,因为这样不需要上下拉动滚动条就能获得整体感观,而不是来回翻动屏幕去寻找某个变量或者某条逻辑判断等。函数中需要用到if、elif、while、for等循环语句的地方,尽量不要嵌套过深,最好能控制在3层以内。相信很多人有过这样的经历:为了弄清楚哪段代码属于内部嵌套,哪段属于中间层次的嵌套,哪段属于更外一层的嵌套所花费的时间比读代码细节所用时间更多。

原则2 函数申明应该做到合理、简单、易于使用。除了函数名能够正确反映其大体功能外,参数的设计也应该简洁明了,参数个数不宜太多。参数太多带来的弊端是:调用者需要花费更多的时间去理解每个参数的意思,测试人员需要花费更多的精力来设计测试用例,以确保参数的组合能够有合理的输出,这使覆盖测试的难度大大增加。因此函数参数设计最好经过深思熟虑。

原则3 函数参数设计应该考虑向下兼容。实际工作中我们可能面临这样的情况:随着需求的变更和版本的升级,在前一个版本中设计的函数可能需要进行一定的修改才能满足这个版本的要求。因此在设计过程中除了着眼当前的需求还得考虑向下兼容。如以下示例:

>>> def readfile(filename):             
①函数实现的第一版本
...   print "file read completed"
...
>>> readfile("test.txt")
file read completed
>>>
>>> import logging
>>>
>>> def readfile(filename,logger):       
②函数实现的第二版本
...   print "file read completed"
...
>>> readfile("test.txt")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readfile() takes exactly 2 arguments (1 given)

上面的代码是相同功能的函数不同版本的实现,唯一不同的是在更高级的版本中为了便于内部维护加入了日志处理,但这样的变动却导致程序中函数调用的接口发生了改变。这并不是最佳设计,更好的方法是通过加入默认参数来避免这种退化,做到向下兼容。上例可以将第一行代码修改为:

def readfile(filename,logger=logger.info):

原则4 一个函数只做一件事,尽量保证函数语句粒度的一致性。如本节开头所示代码中就有3个不同的任务:获取网页内容、查找指定网页内容、发送邮件。要保证一个函数只做一件事,就要尽量保证抽象层级的一致性,所有的语句尽量在一个粒度上。如上例既有http.getfile()这样较高层级抽象的语句,也有细粒度的字符处理语句。同时在一个函数中处理多件事情也不利于代码的重用,在上例中,如果程序中还有类似发送邮件的需求,必然造成代码的冗余。

最后,根据以上几点原则,上面对本节最开始处的代码进行修改,来看看修改后的代码是不是可读性要好一些。

def GetContent(ServerAdr,PagePath):
  http = httplib.HTTP(ServerAdr)
  http.putrequest('GET', PagePath)
  http.putheader('Accept', 'text/html')  
  http.putheader('Accept', 'text/plain')  
  http.endheaders()
  httpcode, httpmsg, headers = http.getreply()  
  if httpcode != 200:
    raise "Could not get document: Check URL and Path."   
  doc = http.getfile()
  data = doc.read()                   # read file
  doc.close()
  return data
def ExtractData(inputstring, start_line, end_line): 
  lstr=inputstring.splitlines()             #split
  j=0                             #set counter to zero
  for i in lstr:
    j=j+1
    if i.strip() == start_line: slice_start=j     #find slice start
    elif i.strip() == end_line: slice_end=j     #find slice end
  return lstr[slice_start:slice_end]          #return slice
def SendEmail(sender,receiver,smtpserver,username,password,content):
  subject = "Contented get from the web"
  msg = MIMEText(content,'plain','utf-8')
  msg['Subject'] = Header(subject, 'utf-8')
  smtp = smtplib.SMTP()
  smtp.connect(smtpserver)
  smtp.login(username, password)
  smtp.sendmail(sender, receiver, msg.as_string())
  smtp.quit()

Python中函数设计的好习惯还包括:不要在函数中定义可变对象作为默认值,使用异常替换返回错误,保证通过单元测试等。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文