
最近技術圈里有個話題挺有意思一個12歲的小學生給一段代碼做了“重構”還把前后對比曬了出來。很多人看完第一反應是——“這也叫重構不就是改了個變量名加了點注釋嗎”另一撥人卻覺得一個剛開始學編程的孩子能主動去美化代碼、拆分邏輯這個意識本身就比技巧更值得鼓勵。老實說事情的關鍵不在于小學生寫了什么高級代碼而在于“重構”這個詞被反復提起之后很多人對它的理解其實還很模糊。有人把重構等同于格式化有人把重構等同于重寫還有人覺得代碼能運行就行折騰結構純屬多余。本文不打算去評價那個小學生寫得怎么樣而是借這個話題把“代碼重構”拆開講清楚它到底是什么、常見手法有哪些、一次像樣的重構應該怎么做以及新手在重構時最容易踩哪些坑。1. 12歲小學生“重構代碼”為什么能引起討論1.1 兩派觀點是無用功還是好習慣這個話題能火本質上是“業余認知”和“工程認知”的碰撞。第一派人的視角很直接如果我看到一段代碼只是變量名從c改成chinese_score或者把一段循環單獨提出來包成一個函數我不會覺得這是一次重構。因為在成熟的工程團隊里這些屬于最基本的代碼衛生習慣是寫代碼時順手就該做掉的事不值得拿出來稱贊。另一派人的視角更寬容對小學階段的孩子來說信息課作業通常停留在“結果正確”層面老師判分也不會關心你變量名叫什么。這個時候孩子愿意回頭改代碼說明他已經開始意識到代碼不僅僅是寫給機器執行的更是寫給將來的人閱讀的。這種“代碼可讀性”意識的覺醒恰恰是很多科班學生到大三都不一定形成的。這兩種說法都有道理。技術含量和成長意義不是同一件事。與其爭“小學生到底算不算重構”不如冷靜下來把“重構”這個概念本身梳理一遍。1.2 重構是有層次和粒度的如果把重構看成一個從淺到深的光譜大致可以分成三個層次層次目標常見操作第一層代碼整理讓眼前這段代碼更可讀重命名變量、提取函數、補充注釋、統一格式第二層結構改進讓模塊和類邊界更合理拆分大類、消除重復邏輯、優化條件判斷第三層架構演進讓系統整體更易擴展引入設計模式、模塊解耦、重構數據存儲方式對一個初學編程的小學生來說能做到第一層已經很了不起。對工作三五年的工程師來說如果只停留在第一層那確實會被團隊質疑“重構深度不夠”。所以那篇帖子之所以引發爭議很大程度上是因為大家對“重構”的預期標準完全不同。1.3 這件事真正值得關注的地方拋開小學生本身不談“12歲小學生重構代碼”能成為熱詞說明大眾對編程教育的關注度在提高。與此同時這也反映出很多人對代碼質量的忽視是普遍存在的。包括一些已經工作幾年的開發者拿到需求就開寫從來沒有回頭清理過自己代碼里的壞味道。之前有公司分享過“用AI在兩天內重構2萬行Vue項目”的實踐當時也引起過不少討論。這個案例說明一個趨勢重構不是可有可無的“潔癖行為”而是大型項目持續演進過程中繞不開的工程任務。代碼在業務迭代中會不斷增加功能如果每隔一段時間不進行結構性整理代碼復雜度會指數級上升直到下一個接手的人徹底崩潰。所以與其嘲諷小學生“重構了個寂寞”不如把這次熱議當成一次契機我們到底能不能清楚地講明白一次合格的重構應該包含哪些步驟2. 重構的核心概念與判斷標準2.1 重構到底怎么定義提到重構繞不開 Martin Fowler 在《重構改善既有代碼的設計》里給出的定義重構是一種對軟件內部結構的調整目的是在不改變軟件可觀察行為的前提下提高代碼的可理解性降低修改成本。這句話里有幾個關鍵詞值得拆開理解。第一個關鍵詞是“不改變可觀察行為”。意思是重構前后用戶輸入同樣的數據程序輸出結果必須保持一致。不能用“順手優化了一下功能”這種理由把行為變化混進重構里。否則一旦出現問題你很難判斷是重構改壞了還是新需求改壞了。第二個關鍵詞是“內部結構”。重構不是調整界面布局不是改文字也不是換一套色值。它針對的是代碼組織方式變量怎么命名、函數怎么拆分、類與類之間怎么協作、數據怎么流轉。第三個關鍵詞是“可理解性”和“修改成本”。重構不是為了給誰表演而是為了降低未來改動代碼時的認知負擔。一段代碼如果連原作者三個月后都看不懂那它的維護成本就非常高重構價值也最大。2.2 重構與格式化、重寫、性能優化有什么不同這幾個概念經常被混為一談實際差別很大。格式化只是調整空格、換行、縮進讓排版符合規范。它不會改變代碼結構也沒有重新組織邏輯。所以把“代碼不規范”直接說成“需要重構”并不準確。重寫的動作通常比重構大得多。重寫意味著把舊代碼推到一邊用新方案重新實現一遍。重構則是在現有代碼基礎上逐步調整每一步改動范圍都盡量縮小。重寫風險很高容易丟失舊系統里那些隱藏的業務規則重構風險相對可控因為它要求每一步都不能改變外部行為。性能優化是指為了提升運行效率而調整算法或緩存策略。性能優化很可能改變外部行為比如響應時間、內存占用、返回結果順序這些都可能發生變化。所以在很多開發規范里性能優化和重構要分開提交分別測試。舉個例子快速排序代碼里如果只是把變量i改成left把j改成right這是重構的一部分但如果你把遞歸實現改成非遞歸實現并且換了分區策略那就不只是重構還夾雜了性能優化和算法替換需要按新功能對待。2.3 一次重構是否成功的三個標準判斷重構是否成功不需要看“改了多少行”而是看下面三個方面第一行為是否保持不變。最直接的辦法是跑一遍原有測試用例如果可以再做一次手工驗證。行為變化越多重構的把握就越低。第二結構是否明顯改善。所謂改善可以從重復代碼是否減少、函數長度是否更合理、依賴方向是否更清晰、命名是否更能表達意圖等角度判斷。如果你改完之后別人看代碼依然要猜來猜去那就說明重構沒有到位。第三后續維護成本是否下降。這一點需要時間驗證。理想情況是重構之后新功能加進來時需要改動的文件更少、改動的位置更集中、測試補起來更容易。如果只是把代碼從 A 風格改成 B 風格而沒有降低未來的修改成本那這次重構的意義就有限。3. 用一個小項目還原“重構前”的代碼3.1 場景教學版學生成績管理系統為了更貼近“小學生、課堂代碼、重構”這些關鍵詞這里用一個非常常見的教學項目舉例學生成績管理系統。功能很簡單支持錄入學生姓名、語文/數學/英語三科成績然后計算總分、平均分和等級最后打印成績單。這種項目在少兒編程課程里很常見業務規則非常簡單非常適合用來觀察代碼結構問題。下面先用一種“新手常見的寫法”實現這個功能。代碼能跑但結構上存在不少問題。3.2 原始代碼全貌# score_v1.py students [] while True: print(1. 添加學生) print(2. 顯示成績) print(3. 退出) a input(請選擇) if a 1: name input(姓名) c float(input(語文)) m float(input(數學)) e float(input(英語)) zf c m e pj zf / 3 s [name, c, m, e, zf, pj] students.append(s) elif a 2: for x in students: pj x[5] if pj 90: lv A elif pj 80: lv B else: lv C print(x[0], x[4], pj, lv) elif a 3: break else: print(輸入錯誤)如果你已經寫過幾年代碼看到這段代碼大概會有一種“馬上要大改”的沖動。對初學者來說它確實能用但問題非常明顯。3.3 這段代碼的“壞味道”先看變量命名。a代表用戶選項c代表語文成績m代表數學成績e代表英語成績zf代表總分pj代表平均分lv代表等級。這些縮寫對寫代碼的人自己來說當然記得住但放到一個稍微大一點的項目里別人讀起來就非常痛苦。更重要的是三個月后你自己回來讀也要先花時間回憶這幾個字母分別代表什么。再看數據結構。學生信息被存成了一個列表[name, c, m, e, zf, pj]。也就是說訪問姓名用的是x[0]訪問總分用的是x[4]訪問平均分用的是x[5]。這完全是“魔法下標”只要中間插入一個新字段比如性別后面所有下標都要跟著改。一旦漏改程序就會在運行時暴露出各種奇怪問題。再看邏輯邊界。等級判斷這段規則被直接寫在了輸出分支里如果以后要新增一個錄入“優、良、及格、不及格”的等級邏輯就需要復制一份判斷代碼。重復代碼一旦出現就會造成“改了這處忘了那處”的維護問題。最后是健壯性。輸入語文成績時如果直接輸入一個abc程序會拋出ValueError并退出。對用戶而言這個體驗是很糟糕的。4. 把這段代碼逐步重構4.1 第一步讓命名能表達業務含義重構不需要一上來就推翻重寫。最安全的第一步是把那些含義不清的變量名改成“讀起來像人話”的名字。比如a改成choicec改成chinese_scorem改成math_scoree改成english_scorezf改成total_scorepj改成average_scorelv改成level。這一步只改變名字不改變任何邏輯。改完之后代碼會變長一些但可讀性會明顯提高。更關鍵的是這種重命名操作在 IDE 里有專門的重構功能可以直接批量替換不需要一個一個手動改。這一步的意義在于讓代碼讀起來像一段自然語言。比如average_score total_score / 3比pj zf / 3清晰得多。4.2 第二步用數據結構表達真實業務對象接著處理“魔法下標”。學生信息本質上是一個業務對象包含姓名和三科成績。與其塞在一個 list 里靠下標訪問不如用一個數據類來描述它。在 Python 中可以用dataclasses來定義from dataclasses import dataclass dataclass class Student: name: str chinese: float math: float english: float有了這個類之后訪問學生信息就不需要再去記憶“第4個位置是總分”。總分、平均分、等級這些派生數據可以直接定義成只讀屬性或者方法。這個步驟的重點是把現實世界里的對象映射成代碼里的結構。它屬于“用數據結構表達業務”的典型重構手法。很多初學者覺得這多余但等到項目里出現幾十個字段時就會發現列表和字典的脆弱的版本管理方式完全不夠用。4.3 第三步拆分函數讓每個函數只做一件事接下來把整個 while 循環里的邏輯拆成函數。一個常見的拆分思路是read_score讀取單科成績并校驗add_student添加學生show_report打印成績單main控制菜單邏輯拆函數不是為了看上去更“規范”而是讓每一段邏輯可以被單獨測試、單獨修改。比如等級判斷可以抽成 Student 的方法def grade(self) - str: if self.average 90: return A if self.average 80: return B return C以后如果新增一個“平均分大于等于60為及格”的規則只需要改這個方法不影響到其他代碼。4.4 第四步補上輸入校驗和異常處理這一部分雖然不是典型的重構操作但在工程實踐中很重要。因為重構要求“不改變外部行為”而原來的程序在輸入非法字符時會崩潰。如果直接把非法輸入判定的行為修好其實已經算行為變化了。更合理的做法是先把原有行為用測試固定下來再在單獨提交中加入輸入校驗。不過對于教學示例我們可以把健壯性作為重構的一部分來演示只要在文章里說明這和純粹的重構不完全相同即可。加入校驗之后程序不會再因為一個非法輸入就退出def read_score(subject: str) - float: while True: try: value float(input(f請輸入{subject}成績)) if not 0 value 100: print(成績應在 0 到 100 之間請重新輸入。) continue return value except ValueError: print(輸入無效請輸入數字。)這個函數的邏輯是反復讀取直到拿到一個合法成績為止。它同時處理了“不是數字”和“成績超出范圍”兩種情況。4.5 重構后的完整代碼下面給出完整的重構版本。這個版本就是前面幾步的綜合結果# score_v2.py from dataclasses import dataclass dataclass class Student: name: str chinese: float math: float english: float property def total(self) - float: return self.chinese self.math self.english property def average(self) - float: return self.total / 3 def grade(self) - str: if self.average 90: return A if self.average 80: return B return C def read_score(subject: str) - float: while True: try: value float(input(f請輸入{subject}成績)) if not 0 value 100: print(成績應在 0 到 100 之間請重新輸入。) continue return value except ValueError: print(輸入無效請輸入數字。) def add_student(students: list) - None: name input(請輸入學生姓名).strip() if not name: print(姓名不能為空。) return students.append( Student( namename, chineseread_score(語文), mathread_score(數學), englishread_score(英語), ) ) print(f已添加學生{name}) def show_report(students: list) - None: if not students: print(當前沒有學生數據。) return print(\n姓名\t總分\t平均分\t等級) for stu in students: print(f{stu.name}\t{stu.total:.1f}\t{stu.average:.1f}\t{stu.grade()}) def main() - None: students [] actions {1: add_student, 2: show_report} while True: print(\n學生成績管理系統) print(1. 添加學生) print(2. 顯示成績) print(3. 退出) choice input(請選擇操作).strip() if choice 3: break action actions.get(choice) if action: action(students) else: print(無效選擇請輸入 1、2 或 3。) if __name__ __main__: main()這個版本在功能上和原版本基本一致但結構已經完全不同學生信息由Student數據類統一管理總分、平均分、等級邏輯內聚在類內部每個函數職責單一輸入非法數據不會直接崩潰菜單邏輯和業務邏輯分離4.6 運行與驗證python score_v2.py運行后可以簡單驗證一下學生成績管理系統 1. 添加學生 2. 顯示成績 3. 退出 請選擇操作1 請輸入學生姓名小明 請輸入語文成績88 請輸入數學成績95 請輸入英語成績90 已添加學生小明 學生成績管理系統 1. 添加學生 2. 顯示成績 3. 退出 請選擇操作2 姓名 總分 平均分 等級 小明 273.0 91.0 A如果此時輸入abc作為成績程序會提示“輸入無效請輸入數字”而不是拋出異常退出。這正是健壯性提升帶來的直觀效果。5. 哪些“重構”其實不是真正的重構5.1 只改變量名和加注釋前面提到過重命名是重構的一部分但如果一次“重構”只做了重命名和注釋沒有改善代碼結構那它的價值確實有限。比如下面這種操作# 偽重構只把變量名改了結構沒有變 for item in students: avg item[5] level A if avg 90 else B print(item[0], item[4], avg, level)這段代碼看起來比原來清晰了一點但魔法下標依然存在。如果后續在item中插入新字段這里依然會出錯。判斷一次重構是否有效要看“結構風險”有沒有下降而不是看名字是否變長。5.2 大范圍重寫不是重構有些開發者會把“重構”當成“重寫”的擋箭牌。他們把整個模塊推翻換了一套新框架或新數據結構然后對外宣稱“重構了”。重構的特點是小步快走每一步都盡量不改變行為。重寫則是一次性替換風險更高。沒有一個合格的測試保護大范圍重寫很容易把原本穩定運行的功能改壞并且很難定位問題。如果確實需要重寫建議明確寫成“重寫方案”而不是用“重構”來模糊范圍。5.3 為了炫技而過度設計還有一種常見情況是代碼本來很簡單為了體現“架構能力”強行引入設計模式、抽象類、工廠方法、依賴注入結果一個 50 行的腳本變成了 10 個類。這種過度設計是可讀性的敵人。重構的目的是讓修改更簡單不是讓代碼看起來更復雜。比如一個只需要用列表存幾個學生的教學程序完全沒必要拆出AbstractStudentFactory。判斷標準很簡單改一個需求時你愿意改幾處代碼如果反而比以前要改更多文件那這就不叫重構叫過度工程。6. 重構的工程實踐與工具建議6.1 在測試保護下小步重構無論你重構成熟度如何最重要的一條原則是先有測試再動手。重構要求不改變外部行為而唯一可靠驗證“行為是否改變”的方式就是對比運行結果。如果你在重構前沒有任何測試重構后程序突然報錯你很難知道是自己哪一步改錯了還是原來就存在隱患。對 Python 項目來說最簡單的做法是編寫幾個針對核心函數的單元測試。比如在上面的學生成績系統里可以針對average屬性和grade方法寫測試。對小項目來說哪怕沒有自動化測試也應該在重構前手動準備多組輸入用例并把預期輸出記錄下來。重構完再跑一遍確保結果完全一致。6.2 使用版本控制保存每個中間狀態重構不是一次“大爆炸式”的修改。更推薦的方式是把重構拆成多個小步驟每個步驟提交一次。比如先提交“重命名變量”再提交“引入 Student 數據類”再提交“拆分函數”。在本地開發時建議把倉庫初始化好并定期推送到遠程。Gitee、GitHub 這些平臺都適合保存提交點。這樣一旦某一步重構出了問題可以直接git revert回退到上一個安全狀態不需要撤銷整個項目。版本控制還有一個好處代碼評審的時候同事可以按提交記錄查看每次改動的意圖而不是面對一個 1000 行的巨型 diff 無從下手。6.3 借助代碼診斷與靜態掃描工具有些壞味道靠肉眼不一定能發現尤其是重復代碼和復雜度過高的問題。此時可以借助工具。IDE 自帶的代碼診斷插件比如 PyCharm 會直接提示“This inspection may highlight spelling errors in names”ESLint 會提示代碼中未使用的變量和復雜度過高的函數。SonarQube 可以掃描本地代碼輸出代碼重復率、圈復雜度、注釋密度、潛在 bug 等指標。Pylint、Flake8 這類 Python 工具也能快速定位命名不符合規范、函數太復雜、缺少模塊注釋等問題。工具不能代替人的判斷但它們可以充當“第一道防線”幫助你在重構之前迅速鎖定問題密集的區域。6.4 AI 輔助重構正在成為新趨勢近年來大模型輔助重構的案例越來越多。之前有人分享過“用 AI 在兩天內重構 2 萬行 Vue 項目”的實踐具體做法是先給 AI 提供項目結構和示例代碼再讓它對重復組件做抽象、對復雜邏輯做拆分最后由人工逐段驗收。這表明 AI 在處理“機械性重構”方面確實有潛力比如批量重命名、消除重復代碼、統一錯誤處理等。但 AI 重構仍然需要人工把關因為工具無法理解業務語義可能把一個看起來“重復”但語義不同的代碼錯誤合并。對小學生的編程學習來說AI 更像一個輔助工具。更重要的是思考“為什么這樣改”而不是讓 AI 一次性把代碼改好然后復制粘貼交作業。6.5 多讀優秀代碼和經典實現提升重構能力最快的方法之一是閱讀優秀代碼。比如同樣是排序有人能用幾行簡潔代碼實現快速排序但可讀性很差也有人用兩個職責清晰的函數寫好分區和遞歸雖然長一點但“人還能看懂”。學習 JavaScript 防抖和節流時也是同理。市面上有各種版本有的用大量閉包技巧壓縮在一行有的則把timer的維護拆解成清晰的狀態管理。初學者很容易被“一行代碼實現”吸引但真正到團隊協作時可讀性往往比短更重要。讀代碼時可以問自己一個問題如果讓我在三個月后維護這段代碼我需要花多長時間理解它愿意為這個目標去修改結構就是踏上了正確的重構之路。7. 寫在最后回到“12歲小學生重構代碼究竟寫了啥”這個題目。最值得關注的不是那個孩子具體寫了哪幾行而是我們能不能通過這個熱鬧的話題把“重構”這顆種子種進更多人的意識里。如果你也想實際體驗一下重構最簡單的方式是找一個你自己寫過的最早的“能跑但很亂”的小程序按本文第 4 節的順序改一遍。先改命名再封裝數據再拆分函數最后補異常處理。改完之后找一個沒看過原代碼的同學來讀看他能不能很快說出每個函數在做什么。等你跑通這個流程后還可以繼續挑戰把學生名單保存到文件里下次啟動程序時自動加載增加一個“按總分排序”的功能甚至把命令行菜單改成簡單的 Web 頁面。每一次新功能加入都是檢驗上一次重構是否到位的機會。重構不是一個一次性的動作而是一種寫代碼時持續存在的判斷力。真正的收益不在重構發生的那一刻而在下一次需求變動時你發現自己只需要改一處而不是滿文件找線索。