重構一詞涵蓋了廣泛的動作和定義。盡管該術語本身通常會導致一個共同的目標,即建立一個更干凈,更好的代碼庫,但仍有許多動作可以視為“重構”,即將依賴項升級到較新版本、重命名您的代碼功能,類,模塊等、重新組織代碼庫,將功能從文件移動到另一個文件、改進功能的實現以提高性能、重新格式化代碼以使其符合標準。
重構之前
盡管從一種編程語言到另一種編程語言有所區別,但重構時通常仍要保留一些步驟和原則。
第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的信息,請繼續關注中培偉業。