最新新闻,最快送达

为什么要重构?如何重组Python包?

  • 发布时间:2020-10-19
  • 来源:中培教育网

  重构一词涵盖了广泛的动作和定义。尽管该术语本身通常会导致一个共同的目标,即建立一个更干净,更好的代码库,但仍有许多动作可以视为“重构”,即将依赖项升级到较新版本、重命名您的代码功能,类,模块等、重新组织代码库,将功能从文件移动到另一个文件、改进功能的实现以提高性能、重新格式化代码以使其符合标准。

  重构之前

  尽管从一种编程语言到另一种编程语言有所区别,但重构时通常仍要保留一些步骤和原则。

  第1步:了解代码质量。

  运行静态分析工具。静态分析工具为您提供了带有定量统计信息的摘要报告,可以在不时重构时进行比较。

  在Python中,我个人使用Prospector,一个静态分析套件包含其他工具,例如pep8,Pylint等。我对Prospector的最喜欢的东西是它能够检测并适应我使用的框架。

  请注意,并非所有找到的消息都是有效的,因此有可能出现误报。

  步骤2:准备和验证测试用例

  未经测试的代码在设计上是不好的代码。您有测试用例吗?如果是这样,它们的完整性如何?这些测试用例是否最新?

  为什么在重构之前需要测试用例?您可以辩称,即使进行了简单的更改,也不需要任何东西。好吧,相信我,您会被自己修饰的杰作所回应的意外行为所吸引。

  我不会说服您一般而言具有测试代码的重要性。重构之前测试代码是为了确保重构后系统的行为保持一致。

  即使有测试用例,这为您提供了开绿灯,您仍然应该验证测试代码。让我来告诉你为什么。

  想象一下,在尝试进行重构之前,您将运行可用的任何测试,并且得到以下结果。

  但是,当您进一步研究时,您发现了这个测试用例。

  “”“

  软件包:utils.tests

  错误的测试用例

  ”“”

  导入单元测试

  UtilsTestCase(unittest.TestCase)类:

  def setUp(self):

  通过

  def test_is_empty(self):

  从utils.common.helpers导入is_empty

  #问题:没有断言

  is_empty('')

  is_empty(None)

  is_empty('',object_type ='json')

  is_empty('{}',object_type ='json')

  def test_is_ok(self):

  #问题:这是空的。

  通过

  def test_is_number(self):

  #问题:这将通过。

  如果不是is_empty(''):

  print('Fail')

  那么,您看到验证的重点了吗?

  此外,测试用例通常是软件的最佳文档。在代码内导航时,它们也是您最好的GPS导航器。

  开始重构—重组/重组

  在本节中,我将config.py通过重组结构,合并重复的方法,分解和编写测试代码以确保向后兼容性,向您展示一个示例。

  config.py 看起来像这样:

  “”“

  软件包:

  重组

  ”“” 之前的utils.config

  CONFIG_NAME = {

  “ ENABLE_LOGGING”:“ enable_logging”,

  “ LOGGING_LEVEL”:“ logging_level”,

  }

  def get_logging_level():

  通过

  ConfigHelper类:

  def get(self,config_name,default = None):

  通过

  def set(self,config_name,value):

  通过

  def _get_settings_helper(self):

  通过

  def get_logging_level():

  通过

  def is_logging_enabled():

  通过

  类LOGGING_LEVEL:

  VERBOSE =“详细”

  STANDARD =“标准”

  步骤1:编写向后兼容代码

  此步骤至关重要。在重构我们的代码之前,必须有测试用例。在这种情况下,我们编写向后兼容的代码,以确保对类/函数/常量的所有引用仍然有效。

  在中__init__.py,我们将重新定义类/方法签名:

  “”“

  在__init__.py

  这是向后兼容的代码的生命。

  这是为了确保重构的包支持

  进口的旧路。

  这是不完整的,我们将在后面再讲__init__.py

  ‘’”

  CONFIG_NAME = {}

  def get_logging_level(* args,** kwargs):

  通过

  ConfigHelper类:

  def get(self,* args,** kwargs):

  pass

  def set(自我,* args,** kwargs):

  通过

  def _get_settings_helper(self):

  通过

  def get_logging_level(self):

  通过

  def is_logging_enabled(self):

  通过

  LOGGING_LEVEL类:

  通过

  目前__init__.py尚不完整。稍后我们将重新访问该文件。

  接下来,我们编写一个测试用例,以确保我们仍然可以像导入旧包一样导入该包。

  tests.py中的“””

  简单的向后兼容性测试用例

  “””

  类ConfigHelperCompatibilityTestCase(unittest.TestCase):

  def test_backward_compatibility(self):

  试试:

  从.config导入CONFIG_NAME,

  从.config导入LOGGING_LEVEL

  从.config导入get_logging_level 从.config导入ConfigHelper,

  但ImportError除外,例如e:

  self.fail(e.message)

  这是一个简单的测试用例,您可能会注意到在该测试用例中未捕获到某些向后兼容性问题。

  步骤2:重组套件结构

  本节为您提供有关如何重新组织Python软件包的想法。让我们回顾一下config.py我们拥有的:

  “”“

  软件包:

  重组

  ”“” 之前的utils.config

  CONFIG_NAME = {

  “ ENABLE_LOGGING”:“ enable_logging”,

  “ LOGGING_LEVEL”:“ logging_level”,

  }

  def get_logging_level():

  通过

  ConfigHelper类:

  def get(self,config_name,default = None):

  通过

  def set(self,config_name,value):

  通过

  def _get_settings_helper(self):

  通过

  def get_logging_level():

  通过

  def is_logging_enabled():

  通过

  类LOGGING_LEVEL:

  VERBOSE =“详细”

  STANDARD =“标准”

  你能发现这里有什么问题吗?这很杂乱,在一个文件中有常量,助手,重复的代码。当代码config.py变大时,将难以导航。通过这种凌乱的结构,您可以为循环依赖,隐藏的耦合和优化最美味的意大利面条代码的配方提供一个地方。

  您如何重组config.py?对我而言,关注的分离是我的脑海。以下结构通常被认为是构建Python包的一种好习惯(在Django中也使用了该结构)。

  config/

  ├── abstracts.py # All the abstract classes should live here

  ├── constants.py # All the constants should live here

  ├── exceptions.py # All custom exceptions should live here

  ├── helpers.py # All helpers should live here

  ├── __init__.py # All backward compatible code in here

  ├── mixins.py # All mixins goes to here

  ├── serializers.py # All common serializers goes to here

  └── tests.py # All `config` related tests should live here

  让我们config.py在重构之前重新访问一下,并确定各个代码段应位于何处。

  “”“

  软件包:

  重组

  ”“” 之前的utils.config

  #这看起来像属于utils.config.constants

  CONFIG_NAME = {

  “” ENABLE_LOGGING“:” enable_logging“,

  ” LOGGING_LEVEL“:” logging_level“,

  }

  #看起来像一个辅助函数,转到utils.config.helpers

  def get_logging_level():

  #看起来像是重复的方法

  传递

  #这看起来像一个帮助程序类,转到utils.config.helpers

  类ConfigHelper:

  def get((自我,config_name,默认=无):

  通过

  def set(self,config_name,value):

  通过

  def _get_settings_helper(self):

  通过

  def get_logging_level():

  #这看起来像是重复的方法

  传递

  def is_logging_enabled():

  通过

  #这看起来像另一个常量,转到utils.config.constants

  类LOGGING_LEVEL:

  VERBOSE =“详细”

  STANDARD =“标准”

  在重构之后,config.py应该成为一个Python包config用__init__.py它。

  实用程序/

  ├──config.py # To be removed

  └──config/

  ├── constants.py

  ├── helpers.py

  ├── __init__.py

  └── tests.py

  在utils.config.constants :

  “”“

  软件包:

  重组

  ”“” 之后的utils.config.constants

  #不一致的编程构造

  CONFIG_NAME = {

  “” ENABLE_LOGGING“:” enable_logging“,

  ” LOGGING_LEVEL“:” logging_level“,

  }

  #不一致的编程构造

  类LOGGING_LEVEL:

  VERBOSE =“详细”

  STANDARD =“标准”

  在utils.config.helpers :

  “”“

  软件包:

  重组

  ”“” 之后的utils.config.constants

  def get_logging_level():

  #这是重复的,删除了此

  通行证

  ConfigHelper类:

  def get(self,config_name,default = None):

  通过

  def set(self,config_name,value):

  通过

  def _get_settings_helper(self):

  通过

  def get_logging_level():

  通过

  def is_logging_enabled():

  通过

  步骤3:消除和合并重复项

  在中utils.config.helpers ,有2个相似的方法/功能get_logging_level()和ConfigHelper()._get_logging_level() 。假设两个实现都相同,则意味着我们必须找到一个最佳的位置来托管该功能。

  在这种情况下,我将删除独立服务器get_logging_level()并将其保留在中ConfigHelper。

  “”“

  软件包:

  删除重复项后的utils.config.constants

  ”“”

  ConfigHelper类:

  def get(self,config_name,default = None):

  通过

  def set(self,config_name,value):

  通过

  def _get_settings_helper(self):

  通过

  def get_logging_level():

  通过

  def is_logging_enabled():

  通过

  步骤4:分解

  我个人是分解爱好者。除了拥有一个类之外ConfigHelper,我们还可以进一步分解ConfigHelper为类和mixin的层次结构。

  我们托管AbstractBaseConfigHelper在abstracts.py:

  “”“

  在abstracts.py

  ‘’”

  从ABC进口ABCMeta

  class AbstractBaseConfigHelper:

  __metaclass__ = ABCMeta

  def get(self,config_name):

  通过

  def set(self,config_name,value):

  通过

  def _get_settings_helper(self):

  通过

  在mixins.py :

  “”“

  在mixins.py

  ‘’”

  类LoggingConfigMixin:

  def is_logging_enabled():

  通过

  def get_logging_level():

  通过

  在helpers.py :

  “”

  在helpers.py中的

  “””

  类ConfigHelper(

  AbstractBaseConfigHelper,

  LoggingConfigMixin

  ):

  通过

  ConfigHelper 现在分解为多个类和混合。

  步骤5:填写我们的向后兼容代码

  在步骤1中,我们在中添加了一些代码。__init__.py. 但是,它基本上是不完整的。让我们重新访问该文件:

  “”“

  在__init__.py

  这是向后兼容的代码的生命。

  这是为了确保重构的包支持

  进口的旧路。

  这是不完整的,我们将在后面再讲__init__.py

  ‘’”

  CONFIG_NAME = {}

  def get_logging_level(* args,** kwargs):

  通过

  ConfigHelper类:

  def get(self,* args,** kwargs):

  pass

  def set(自我,* args,** kwargs):

  通过

  def _get_settings_helper(self):

  通过

  def get_logging_level(self):

  通过

  def is_logging_enabled(self):

  通过

  LOGGING_LEVEL类:

  通过

  请注意,上面的代码与我们新组织的config软件包之间的桥梁仍然缺失。要建立桥梁,我们将其编辑__init__.py为:

  __init__.py中的““”

  这是向后兼容代码所在的地方。

  这是为了确保重构的程序包支持

  旧的导入方式。

  “ 。”

  从.constants导入CONFIG_NAME,

  从.helpers导入LOGGING_LEVEL 从ConfigHelper导入为_ConfigHelper

  def get_logging_level(* args,** kwargs):

  返回_ConfigHelper()。get_logging_level()

  类ConfigHelper(_ConfigHelper):

  通过

  步骤6:通知开发人员

  直到第5步,我们config的重构都正确了。但是,我们需要及时通知开发人员有关更改的信息。有什么简单的方法吗?是。每当开发人员尝试导入过时的函数/类/方法时,我们都可以发出警告消息。例如,我们用装饰器注释旧的函数/类/方法:

  “”“

  decorators.py

  ”“”

  def refactored_class(消息):

  def cls_wrapper(cls):

  类包装(cls,对象):

  def __init __(self,* args,** kwargs):

  warnings.warn(message,FutureWarning)

  super(Wrapped,self).__ init __( * args,** kwargs)

  return包装的

  return cls_wrapper

  def重构(消息):

  def装饰器(func):

  def glow_warning(* args,** kwargs):

  warnings.warn(消息,FutureWarning)

  返回func(* args,** kwargs)

  返回return_warning

  返回装饰器

  在我们的中__init__.py ,我们添加装饰器,如下所示:

  __init__.py中的““”

  这是向后兼容代码所在的地方。

  这是为了确保重构的程序包支持

  旧的导入方式。

  “ 。”

  从.constants导入CONFIG_NAME,

  从.helpers导入LOGGING_LEVEL 从ConfigHelper导入为_ConfigHelper

  @refactored('get_logging_level()被重构和弃用。')

  def get_logging_level(* args,** kwargs):

  返回_ConfigHelper()。get_logging_level()

  @refactored_class( 'config.ConfigHelper被重构和弃用请使用config.helpers.ConfigHelper。')

  类ConfigHelper(_ConfigHelper):

  通过

  重组后

  重组我们的Python包之后,我们运行测试用例并确保已通过所有测试。

  结论

  到现在为止,您应该能够了解代码库的质量,了解重构的概念,确定重构的需求,并了解如何重构/重组Python包。想了解更多关于Python的信息,请继续关注中培教育。

近期开班

phone
全国报名服务热线: 400-626-7377
合作伙伴
  • 国际APMG组织
  • 思科(Cisco)
  • 国际OPEN GROUP组织
  • 国家外专局培训中心
  • 上海银行
  • 中国石油管道局工程有限公司
  • 朗新科技
  • 中国国家博物馆
金牌客户
  • 中国石油天然气集团公司
  • 中国农业银行
  • 中国建设银行
  • 波司登集团
  • 浙江移动
合作机构
  • 美国项目管理协会
  • 人力资源和社会保障部
  • 中华人民共和国工业和信息化部
  • 中国计算机技术职业资格网
  • CITIF中国电子信息行业联合会
  • PEOPLECERT
微信公众号
公众号

微信号:zpitedu

  • 中培教育 Copyright © 2006-2020 北京中培伟业管理咨询有限公司 .All Rights Reserved
  • 京ICP备13024721号  gonganimg  京公网安备11010602007294号  增值电信业务经营许可证:京B2-20201348  全国统一报名专线:400-626-7377