软件研发

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

2020-10-19 16:52:15 | 来源:中培企业IT培训网

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

  重构之前

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

  第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的信息,请继续关注中培伟业。

标签: Python 软件研发