使用Python3實(shí)現(xiàn)判斷函數(shù)的圈復(fù)雜度
你有沒(méi)有見(jiàn)過(guò)那種長(zhǎng)達(dá)幾百行、邏輯錯(cuò)綜復(fù)雜的“巨無(wú)霸”函數(shù)?那樣的函數(shù)不光難讀,改起來(lái)同樣困難重重,人人唯恐避之不及。
編寫(xiě)函數(shù)最重要的原則就是:別寫(xiě)太復(fù)雜的函數(shù)。那什么樣的函數(shù)才能算是過(guò)于復(fù)雜?一般會(huì)通過(guò)兩個(gè)標(biāo)準(zhǔn)來(lái)判斷,長(zhǎng)度和圈復(fù)雜度。
長(zhǎng)度
長(zhǎng)度也就是函數(shù)有多少行代碼。不過(guò)不能武斷地說(shuō),長(zhǎng)函數(shù)就一定比短函數(shù)復(fù)雜。因?yàn)樵诓煌木幊田L(fēng)格下,相同行數(shù)的代碼所實(shí)現(xiàn)的功能可以有巨大差別,有人甚至能把一個(gè)完整的俄羅斯方塊游戲塞進(jìn)一行代碼內(nèi)。
但即便如此,長(zhǎng)度對(duì)于判斷函數(shù)復(fù)雜度來(lái)說(shuō)仍然有巨大價(jià)值。在著作《代碼大全(第 2 版)》中,Steve McConnell 提到函數(shù)的理想長(zhǎng)度范圍是 65 到 200 行,一旦超過(guò) 200 行,代碼出現(xiàn) bug 的概率就會(huì)顯著增加。
對(duì)于 Python 這種強(qiáng)表現(xiàn)力的語(yǔ)言來(lái)說(shuō),65 行已經(jīng)非常值得警惕了。假如你的函數(shù)超過(guò) 65 行,很大概率代表函數(shù)已經(jīng)過(guò)于復(fù)雜,承擔(dān)了太多職責(zé),請(qǐng)考慮將它拆分為多個(gè)小而簡(jiǎn)單的子函數(shù)(類)吧。
圈復(fù)雜度
“圈復(fù)雜度”是由 Thomas J. McCabe 在 1976 年提出的用于評(píng)估函數(shù)復(fù)雜度的指標(biāo)。它的值是一個(gè)正整數(shù),代表程序內(nèi)線性獨(dú)立路徑的數(shù)量。圈復(fù)雜度的值越大,表示程序可能的執(zhí)行路徑就越多,邏輯就越復(fù)雜。
如果某個(gè)函數(shù)的圈復(fù)雜度超過(guò)10,就代表它已經(jīng)太復(fù)雜了,代碼編寫(xiě)者應(yīng)該想辦法簡(jiǎn)化。優(yōu)化寫(xiě)法或者拆分成子函數(shù)都是不錯(cuò)的選擇。接下來(lái),我們通過(guò)實(shí)際代碼來(lái)體驗(yàn)一下圈復(fù)雜度的計(jì)算過(guò)程。
在Python中,可以通過(guò)radon工具計(jì)算函數(shù)的圈復(fù)雜度。安裝命令:
pip3 install radon
假設(shè)我們有段代碼示例如下,實(shí)現(xiàn)的功能是猜數(shù)字游戲,里面有1個(gè)whilie和2個(gè)if-else分支判斷邏輯,文件名:complex_func.py。
import random def guess_number(): # 生成一個(gè)隨機(jī)數(shù)作為答案 answer = random.randint(1, 100) # 初始化猜測(cè)次數(shù) guesses = 0 print("歡迎來(lái)到猜數(shù)字游戲!我已經(jīng)想好了一個(gè)1到100之間的數(shù)字,你需要猜出這個(gè)數(shù)字是多少。") # 開(kāi)始循環(huán),直到玩家猜中數(shù)字為止 while True: # 獲取玩家的猜測(cè) guess = int(input("請(qǐng)輸入你猜測(cè)的數(shù)字:")) # 增加猜測(cè)次數(shù) guesses += 1 # 檢查玩家猜測(cè)的數(shù)字與答案的關(guān)系 if guess < answer: print("你猜的數(shù)字太小了,請(qǐng)繼續(xù)努力!") elif guess > answer: print("你猜的數(shù)字太大了,請(qǐng)?jiān)僭囈淮危?) else: print(f"恭喜你,你猜對(duì)了!答案是 {answer}。你一共猜了 {guesses} 次。") break # 結(jié)束循環(huán) # 調(diào)用函數(shù)開(kāi)始游戲 guess_number()
接下來(lái)我們使用radon來(lái)計(jì)算這個(gè)文件對(duì)應(yīng)函數(shù)的圈復(fù)雜度,文件名:calculate_cyclomatic_complexity.py
from radon.complexity import cc_visit # 定義一個(gè)Python文件路徑 file_path = 'complex_func.py' # 使用cc_visit函數(shù)計(jì)算代碼的圈復(fù)雜度 with open(file_path, 'r') as file: code = file.read() results = cc_visit(code) print(results) # 打印結(jié)果 for result in results: print(result)
執(zhí)行結(jié)果:可以看到函數(shù)圈復(fù)雜度為 4。
$ python3 calculate_cyclomatic_complexity.py
[Function(name='guess_number', lineno=3, col_offset=0, endline=27, is_method=False, classname=None, closures=[], complexity=4)]
F 3:0->27 guess_number - 4
我們接下來(lái)看另外一個(gè)完整的代碼示例,其中被計(jì)算的函數(shù)為rank(),功能是按照電影分?jǐn)?shù)計(jì)算評(píng)級(jí),最后輸出了圈復(fù)雜度和對(duì)應(yīng)的評(píng)分等級(jí),文件名:
get_film_score.py
import radon from radon.complexity import cc_rank, cc_visit def calculate_complexity(source_code): """ Calculate the cyclomatic complexity of the given source code. Parameters: source_code (str): The source code to analyze. Returns: int: The cyclomatic complexity. str: The complexity rating. """ try: # Visit the AST and calculate the complexity results = cc_visit(source_code) complexity = results[0].complexity # Get the complexity rating rating = cc_rank(complexity) return complexity, rating except Exception as e: print("Error:", e) return None, None # Example usage: if __name__ == "__main__": code = """ def rank(self): rating_num = float(self.rating) if rating_num >= 8.5: return 'S' elif rating_num >= 8: return 'A' elif rating_num >= 7: return 'B' elif rating_num >= 6: return 'C' else: return 'D' """ complexity, rating = calculate_complexity(code) if complexity is not None and rating is not None: print("Cyclomatic Complexity:", complexity) print("Complexity Rating:", rating)
運(yùn)行結(jié)果:可以看到函數(shù)圈復(fù)雜度為 5,評(píng)級(jí)為 A。
雖然這個(gè)值沒(méi)有達(dá)到危險(xiǎn)線 10,但考慮到函數(shù)只有短短 10 行,5 已經(jīng)足夠引起重視了。
$ python3 get_film_score.py
Cyclomatic Complexity: 5
Complexity Rating: A
作為對(duì)比,我們?cè)儆?jì)算一下案例中使用bisect模塊重構(gòu)后的 rank() 函數(shù):
def rank(self): breakpoints = (6, 7, 8, 8.5) grades = ('D', 'C', 'B', 'A', 'S') index = bisect.bisect(breakpoints, float(self.rating)) return grades[index]
運(yùn)行結(jié)果:可以看到函數(shù)圈復(fù)雜度為 1,評(píng)級(jí)為 A。
$ python3 get_film_score.py
Cyclomatic Complexity: 1
Complexity Rating: A
可以看到,新函數(shù)的圈復(fù)雜度從 5 降至 1。1 是一個(gè)非常理想化的值,如果一個(gè)函數(shù)的圈復(fù)雜度為 1,就代表這個(gè)函數(shù)只有一條主路徑,沒(méi)有任何其他執(zhí)行路徑,這樣的函數(shù)通常來(lái)說(shuō)都十分簡(jiǎn)單、容易維護(hù)。
當(dāng)然,在正常的項(xiàng)目開(kāi)發(fā)流程中,我們一般不會(huì)在每次寫(xiě)完代碼后,都手動(dòng)執(zhí)行一次 radon 命令檢查函數(shù)圈復(fù)雜度是否符合標(biāo)準(zhǔn),而會(huì)將這種檢查配置到開(kāi)發(fā)或部署流程中自動(dòng)執(zhí)行。
到此這篇關(guān)于使用Python3實(shí)現(xiàn)判斷函數(shù)的圈復(fù)雜度的文章就介紹到這了,更多相關(guān)Python3函數(shù)圈復(fù)雜度內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python實(shí)現(xiàn)提取百度搜索結(jié)果的方法
這篇文章主要介紹了python實(shí)現(xiàn)提取百度搜索結(jié)果的方法,涉及Python網(wǎng)頁(yè)及字符串操作的相關(guān)技巧,需要的朋友可以參考下2015-05-05Python中線程的MQ消息隊(duì)列實(shí)現(xiàn)以及消息隊(duì)列的優(yōu)點(diǎn)解析
消息隊(duì)列(MQ,Message Queue)在消息數(shù)據(jù)傳輸中的保存作用為數(shù)據(jù)通信提供了保障和實(shí)時(shí)處理上的便利,這里我們就來(lái)看一下Python中線程的MQ消息隊(duì)列實(shí)現(xiàn)以及消息隊(duì)列的優(yōu)點(diǎn)解析2016-06-06Python從list類型、range()序列簡(jiǎn)單認(rèn)識(shí)類(class)【可迭代】
這篇文章主要介紹了Python從list類型、range()序列簡(jiǎn)單認(rèn)識(shí)類(class),結(jié)合實(shí)例形式分析了list、range及自定義類等可迭代數(shù)據(jù)類型相關(guān)使用技巧,需要的朋友可以參考下2019-05-05使用Python操作FTP實(shí)現(xiàn)上傳和下載的方法
今天小編就為大家分享一篇關(guān)于使用Python操作FTP實(shí)現(xiàn)上傳和下載的方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-04-04ubuntu 18.04搭建python環(huán)境(pycharm+anaconda)
這篇文章主要為大家詳細(xì)介紹了ubuntu 18.04搭建python環(huán)境,包括Anaconda安裝、Pycharm安裝及初始配置,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06