文字搜尋與程式碼理解

為什麼文字搜尋與程式碼理解不同

內部網路 2026 年 5 月 18 日 , , ,

一位開發人員首次開啟一個大型遺留程式碼庫。他需要了解帳戶關閉時客戶記錄會發生什麼變化:哪些程式會更新記錄,哪些批次作業會讀取記錄,哪些欄位會被修改,以及是否有任何下游系統依賴最終狀態。自然而然的第一步就是搜尋。他使用 grep 命令搜尋字段名,瀏覽搜尋結果,打開幾個文件,開始閱讀。不到一個小時,他就在十二個程式、三個 SQL 腳本和一個 JCL 作業流程中找到了相關參考。他還在十七個註釋區塊、四個日誌格式字串、兩個測試案例以及一個完全不相關的子系統中恰好同名的變數中找到了相同的字段名。僅憑搜尋結果,他無法判斷哪些是實際的資料讀取,哪些是寫入,哪些是資料轉換,哪些只是巧合的名稱衝突。他知道欄位的名稱,但還不了解程式碼是如何使用它的。

程式碼理解從這裡開始

SMART TS XL 建構整個程式碼庫的結構模型,映射每種語言和平台之間的依賴關係。

請點擊這里

找出字串和理解程式碼之間的這種鴻溝,並非透過改進搜尋就能彌合的。它源自於兩種截然不同的探究方式:一種是“這段文本出現在哪裡?”,另一種是“這段程式碼的作用是什麼?”。文字搜尋對於第一個問題來說是絕佳的答案,但它根本無法回答第二個問題。將二者混淆是軟體開發中造成資源浪費、遺漏依賴關係和錯誤影響評估最常見的原因之一。這種區別在大型異質企業系統中比在小型現代程式碼庫中更為重要,因為這些系統包含數十年累積的結構、跨語言依賴關係以及僅存在於程式碼行為中的隱式關係,而這些關係並不存在於其原始檔案中的任何字串中。正如分析中所探討的… 程式碼品質指標及其影響程式碼庫的複雜性會顯著影響可維護性,而且僅從文字模式得出的任何指標都無法捕捉到控製程式碼實際行為的結構關係。

文字搜尋究竟能做什麼

文字搜尋是一種子字串匹配操作,應用於被視為原始字元序列的檔案。查詢語句是一個字串或模式。結果是一個包含該模式的所有位置清單。該工具不了解文件所使用的語言,不理解賦予文本結構的語法,也不了解文本所代表的程式碼元素之間的關係。對一百萬行 COBOL 原始程式碼進行 grep 操作與對一百萬行 HTML 程式碼進行 grep 操作遵循相同的模型:按檔案路徑分組的檔案中的字元序列,並在字元序列匹配時傳回結果。

這對於特定類型的任務非常有用:尋找已知字串出現的位置、確認特定術語是否被使用、快速檢查命名規範是否合理、定位包含特定錯誤訊息的文件。對於這些任務,文字搜尋是合適的工具,因為這些任務本質上就是尋找字串。 grep 及其同類工具的速度、便攜性和零配置特性,完美契合「此字串是否存在於這些檔案中,如果存在,則位於何處?」這類問題。

當使用文字搜尋來解答並非字串相關的問題時,問題就出現了。 「什麼呼叫了這個函數?」並非詢問函數名稱出現的位置,而是詢問呼叫圖。呼叫圖是程式碼的結構屬性,需要透過解析和語義分析來建構。 「這個欄位寫在哪裡?」並非詢問欄位名稱出現的位置,而是詢問資料流,需要理解特定語言中的賦值語意才能回答。 「如果我更改這個接口,會發生什麼?」並非詢問接口名出現的位置,而是詢問依賴關係,需要解析導入、繼承和跨模組耦合才能正確回答。

這些問題都以名稱作為切入點,這很容易讓人將其視為搜尋任務。但名稱僅僅是入口點。答案存在於程式碼的結構模型中,而非原始檔的文字中。

噪音問題:太多毫無意義的結果

文字搜尋應用於程式碼理解任務的第一個失敗模式是過度生成:傳回的結果遠遠多於相關的結果,並且沒有機制來識別哪些結果具有結構意義,哪些結果只是巧合。

類似這樣的簡短標識符 status, id, type, 或者 date 在大型程式碼庫中,識別碼可能會出現數千次。更長的標識符甚至會在不同語言和命名空間之間發生衝突: calculate_tax 無論是 Python 模組中的函數名稱、COBOL 段落名稱、資料庫預存程序、JavaScript 輔助函數,或是日誌配置中的字串,都會產生相符的文字搜尋結果。收到這些結果的開發人員必須手動篩選,運用自身對程式碼的理解來決定哪些匹配項是相關的。這種手動篩選本身就是一項程式碼理解任務,這意味著開發人員在工具無法提供任何幫助的情況下,完成了原本應該由工具完成的工作。

在實踐中,開發人員通常憑藉直覺和經驗進行篩選。他們意識到測試文件中的結果很可能不是生產環境中的呼叫者。他們也意識到註解區塊中的結果只是文件說明,而非實際的程式碼呼叫。他們會忽略那些他們認為無關緊要的文件中的結果。然而,這些篩選方法並非萬無一失,也無法驗證。自信地進行篩選的開發人員可能會犯錯。謹慎篩選的開發人員可能要花費數小時。無論哪種情況,最終得到的都只是一組反映開發人員主觀判斷的發現,而非經過驗證的程式碼結構分析。

舉個具體的例子。 COBOL 開發人員在刪除段落之前會先搜尋段落名稱:

科博爾

SEARCH-RESULTS FOR "CALC-INTEREST":

1. CALC-INTEREST.PGM        line   5  : IDENTIFICATION DIVISION.
2. CALC-INTEREST.PGM        line  42  : CALC-INTEREST.
3. FINPROCESS.CBL            line 178  : PERFORM CALC-INTEREST
4. RPTMONTH.CBL              line  91  : * Old routine: CALC-INTEREST replaced by CALC-INT-V2
5. CUSTBATCH.CBL             line 234  : PERFORM CALC-INTEREST THRU CALC-INTEREST-EXIT
6. DATADICT.txt              line  12  : CALC-INTEREST - computes monthly interest for savings accts
7. TESTHARNESS.CBL           line  67  : PERFORM CALC-INTEREST
8. ARCHIVEJOB.CBL            line 156  : * PERFORM CALC-INTEREST (disabled 2019-03-14)

在這八個結果中,恰好有兩個是活躍的呼叫點,如果刪除該段落,程式就會崩潰:分別是第 3 行和第 5 行。第 2 行是定義。第 4 行和第 8 行是註解。第 6 行是資料字典條目。第 7 行是測試框架。要確定這八個結果中的哪兩個代表活躍的調用點,需要結合上下文閱讀每個文件,理解 COBOL 語法,並判斷第 8 行註釋中的“disabled”對執行的實際意義。文字搜尋提供了原始素材,而程式碼理解則提供了答案。

沉默問題:從未回傳的相關結果

第二種失敗模式是產量不足:由於結果沒有以文字搜尋可以匹配的形式表達,因此缺少具有結構性意義的結果。

間接呼叫是導致搜尋結果缺失最常見的原因。例如,當函數 A 呼叫函數 B,而函數 B 又呼叫已棄用的函數 C 時,對函數 C 的名稱進行文字搜索,會找到函數 B 作為直接呼叫者,但找不到函數 A 作為間接呼叫者。 A 是否是相關結果取決於搜尋的目的:如果目標是了解所有觸發函數 C 的因素,那麼 A 至關重要;如果目標只是尋找直接呼叫者,那麼 A 則無關緊要。文字搜尋無法區分這兩種情況,因為它沒有呼叫圖的概念。它傳回所有符合的文本,而不知道這些符合的文本屬於哪個呼叫圖。

跨語言引用是一個系統性缺失的類別。例如,一個 Java 服務透過中間件層按名稱呼叫 COBOL 程序,該 Java 服務會將 COBOL 程式名稱作為字串字面量包含在內,文字搜尋可以找到它。但是,同一個 Java 服務如果動態建構程式名稱、從設定檔讀取程式名稱或透過抽象層進行分發,則完全不包含程式名稱。無論文字搜尋多麼徹底,都無法找到這些呼叫者。正如在以下上下文中分析的那樣: 對混淆和動態產生的程式碼進行靜態分析當執行路徑透過配置、模板或運行時調度機制間接表達時,它們所代表的結構關係無法僅從來源文件的文字中恢復。

欄位別名和轉換會造成另一類隱性錯誤。一個名為 COBOL 的字段 WS-ACCT-BAL 寫入到名為 ACCT_BALANCE隨後由 Java 服務讀取為 accountBalance並最終序列化為 account_balance 在 JSON 回應中,同一個資料元素可能對應四個不同的文字字串。搜尋其中任何一個字串都會遺漏其他三個。要理解這四個字串都指向同一個底層業務概念,需要理解轉換鏈,而不是僅僅查找某個名稱的所有出現位置。

真正理解程式碼需要什麼

程式碼理解作為一種技術能力,是指能夠透過分析程式碼的結構和語義,而非僅僅關注其表面文本,來回答有關程式碼的問題。它需要建立並查詢一個程式碼模型,該模型能夠展現程式碼的真正意義,而不僅僅是它所表達的內容。

要勝任大型企業系統開發任務所需的程式碼理解水平,其最低技術要求非常高。每一項技術要求都代表著文字搜尋所不具備的能力,而且任何文字搜尋與人工操作相結合的方式都無法大規模可靠地複製這些能力。

解析:從文字到結構

文字搜尋之後的第一步是解析:根據原始程式碼語言的語法讀取​​原始程式碼,並產生結構化的表示形式,通常是抽象語法樹,該表示形式編碼了程式碼元素之間的語法關係。解析後的表示形式 PERFORM CALC-INTEREST THRU CALC-INTEREST-EXIT 它不是一個字串;它是一個結構化對象,表示這是一個帶有範圍目標的 PERFORM 語句,其中兩個端點都是當前程式中的段落名稱,可以根據程式的 PROCEDURE DIVISION 結構進行解析。

解析是特定於語言的。 COBOL 解析器理解 COBOL 語法。 Java 解析器理解 Java 語法。 JCL 解析器理解 JCL 語法。在多語言企業系統中,程式碼理解需要為環境中存在的每種語言都配備一個解析器,以產生可以跨語言進行一致推理的結構化表示。正如在詳細分析中所討論的… 企業級 TypeScript 靜態分析結構和語義分析,即理解程式碼的組織方式、模組的互動方式以及控制流程和資料流在應用程式中的方式,是超越語法檢查,實現真正程式碼智慧的基礎。

符號解析:從名稱到實體

解析之後,原始碼中的名稱必須解析為它們所指涉的實體。識別符 CALC-INTEREST PERFORM 語句中的方法名稱必須解析為特定程式或副本中的特定段落定義。 calculateLegacyFee 在 Java 呼叫中,必須解析為特定類別中的特定方法定義,並考慮繼承和重載。列名 ACCT_BALANCE SQL 查詢必須解析為資料庫模式中特定表中的特定欄位。

符號解析是將字串中的名稱轉換為對特定、可識別的程式碼實體的引用,該實體具有位置、類型以及與其他實體的一組關係。如果沒有符號解析,所有程式碼查詢都只是文字查詢。有了符號解析,查詢「此函數的所有呼叫者」就變成了針對已解析的呼叫關係圖的結構化查詢,它只會傳回實際呼叫特定函數的結果,而不是函數名稱恰好出現的所有檔案。

在多語言環境中,符號解析變得異常複雜,因為同一個概念在不同語言中可能有不同的名稱。跨語言的領域等價解析,如同在更廣泛的背景下所檢視的… 透過跨語言索引縮短平均恢復時間是任何旨在追蹤跨語言邊界資料或控制流的結構分析的先決條件。缺少它,分析就會在邊界終止,其提供的理解也是不完整的。

控制流程分析:理解執行路徑

控制流程分析描繪了程式可能的執行路徑:在什麼條件下執行哪些分支,哪些語句可達,哪些程式碼路徑無效,以及語句之間的執行順序。這些資訊以控制流程圖的形式呈現,其中節點代表基本的順序代碼區塊,邊代表有條件或無條件的控制轉移。

控制流程分析使得回答諸如「這段程式碼路徑在什麼條件下執行?」和「這段程式碼是否可以從任何入口點到達?」之類的問題成為可能。文字搜尋無法回答這些問題,因為它們關乎執行路徑,而非字串出現的位置。出現在原始碼中的語句是否執行,取決於其所在分支的執行條件。模組中定義的函數是否被調用,取決於是否存在任何執行路徑到達調用點。只有控制流分析才能決定這些屬性。正如在對…的分析中所探討的那樣 在現代化過程中優先處理靜態程式碼問題了解哪些程式碼路徑實際執行、運行頻率以及在什麼條件下激活,才能將可操作的分析與看似重要但並不反映實際運行情況的發現區分開來。

資料流分析:追蹤程式碼中的值

資料流分析追蹤數值在程式中的流動方式:變數在哪裡被賦值,在哪裡被讀取值,賦值和使用之間進行了哪些轉換,以及一個變數的值是否依賴另一個變數的值。這些資訊可以回答諸如「這個欄位的值來自哪裡?」和「如果這個欄位的值發生變化,哪些程式碼會受到影響?」之類的問題。

資料流分析是值層級欄位追蹤、污點分析和依賴關係追蹤的技術基礎。它作用於程式的控制流程圖,沿著執行路徑傳播值訊息,並記錄數值的來源、流向和使用位置。最終產生的資料流程圖將定義與使用關聯起來,覆蓋程式的整個執行空間,而不僅限於原始檔案的順序文字。

在企業系統中,資料流分析必須跨越語言邊界才能發揮作用。例如,一個源自 COBOL 程式的值,經過資料庫寫入,最終被 Java 服務讀取,其資料流跨越了兩種語言邊界。追蹤這種資料流需要一種能夠理解 COBOL 賦值語意、SQL 資料移動和 Java 變數賦值的資料流分析方法,並將這三者整合到同一個統一的分析框架中,而不是將其視為三個獨立的分析,然後手動連接結果。正如分析中詳細闡述的… 從 COBOL 專家到現代開發團隊的知識轉移要讓複雜的 COBOL 系統能夠被現代開發人員理解,而無需他們掌握該語言,就需要進行結構分析,以超越原始程式碼的形式表示系統的行為。

那些差異最為關鍵的任務

文字搜尋和程式碼理解之間的差異並非紙上談兵。在一些高風險的開發任務中,這種差異尤其突出:使用錯誤的工具可能會產生看似完整但實際上並不完整的結果,而基於不完整結果採取行動則會帶來可衡量的後果。

變更前的影響分析

在修改函數簽章、重新命名欄位或變更共用工具的行為之前,開發人員需要了解哪些部分會受到影響。這就是影響分析:枚舉所有依賴被更改元素的元件,以便安全地進行更改並更新所有受影響的元件。影響分析是一項程式碼理解任務。它需要解析元件之間的依賴關係,從被改變的元素向外遍歷這些關係,並傳回依賴樹中任何層級上所有會受到影響的元件。

文字搜尋透過尋找被更改元素名稱出現的位置來近似進行影響分析。但它無法區分依賴項和註解、直接依賴項和傳遞依賴項,也無法區分活躍依賴項和死程式碼中的參考。如果開發人員在進行重大變更之前依賴文字搜尋進行影響分析,那麼他們實際上是在基於近似值做出關乎安全的關鍵決策。在小型單語言程式碼庫中,這種近似值可能足夠接近實際情況。但在具有跨語言依賴關係、多個服務使用的共享庫以及數十年累積的呼叫關係的企業系統中,文字搜尋返回的結果與更改的實際影響之間可能存在顯著差距。

考慮一下這兩種方法在對常用資料庫列進行模式變更時所傳回的結果差異:

開發者需要了解什麼文字搜尋結果程式碼理解結果
讀取本專欄的程序所有包含列名的文件,包括註釋只有包含引用此列的 SQL SELECT 語句的程式才會執行此操作
編寫此專欄的程序相同的未過濾列表只有使用 SQL INSERT 或 UPDATE 語句寫入此列的程式才會出現此問題。
本欄所依賴的服務沒有跨語言可見性用於將欄位對應到物件欄位的 Java、Python 和 .NET 服務
死代碼引用結果已包含,但未標記。單獨排除或標記
及物依存關係不可見的枚舉到任何深度
對完整性的信心未詳可根據索引範圍進行驗證

入門和程式碼導航

對於初次接觸大型程式碼庫的開發者來說,他們需要建立一個關於程式碼功能的心理模型:元件如何連接,資料如何在系統中流動,哪些程式是入口點,哪些是輔助工具,以及特定業務流程的執行路徑是怎樣的。這種模型建構練習主要是一個程式碼理解任務。文字搜尋可以幫助定位特定的字串,但它無法提供結構性的上下文資訊:它只能找到某個單字出現的位置,而無法解釋包含該單字的程式碼在系統中扮演的角色。

程式碼理解工具透過讓系統結構易於理解,從而加快新用戶上手速度。互動式呼叫圖顯示了哪些程式呼叫了哪些程式。資料流追蹤顯示了欄位的來源和最終去向。控制流視覺化顯示了哪些條件決定了哪些分支的執行。依賴關係圖顯示了哪些元件可以獨立修改,哪些元件需要與其他團隊協調。這些都不是文字搜尋的結果,而是程式碼理解工具執行結構分析的結果。如同在以下背景所考察的: 什麼是靜態程式碼分析透過結構化分析而不是手動閱讀來駕馭複雜性的能力,使團隊能夠在任何個人都無法完全理解的龐大系統中有效地工作。

識別死代碼和未使用元素

死程式碼是指已定義但從未執行的程式碼:從未被呼叫的函數、從未被執行的分支、已賦值但從未被讀取的變數。識別死程式碼是一項程式碼理解任務,需要建立完整的呼叫圖,並確定哪些已定義的元素沒有來自任何可達入口點的入站呼叫邊。文字搜尋無法識別死代碼,因為根據定義,死代碼沒有被任何地方引用。文字搜尋無法找到「無引用」這種字串。

對於已棄用函數的移除,識別死代碼至關重要。某些看似調用已棄用函數的元素本身可能就是死代碼:這些函數被編寫出來是為了調用已棄用函數,但自身從未被調用,因此不構成任何實際的依賴關係。區分實際的呼叫者和死的呼叫者需要進行與識別死代碼相同的呼叫圖分析。正如在以下上下文中所述: 必要的重構技巧靜態使用情況分析提供了足夠的洞察力,可以確定函數、標籤、段落或模組是否被調用,而這種分析只能透過結構調用圖的構建來實現,而不是透過文字出現次數的統計來實現。

安全與合規審計

安全合規審計需要追蹤敏感資料在系統中的流轉:識別個人識別資訊的儲存位置、哪些程式碼路徑可以存取這些資訊、存取控制檢查是否正確部署在通往敏感資料的每個執行路徑中,以及敏感資料是否會透過日誌、錯誤訊息或 API 回應洩漏到系統之外。這些都是資料流和控制流分析任務,而文字搜尋難以勝任。

對敏感欄位名稱進行文字搜尋可以找到包含該名稱的文件,但無法確定這些文件執行的是授權存取、未經授權存取還是根本沒有存取。它也無法確定在通往該欄位存取的執行路徑中是否存在存取控制檢查。此外,它也無法追蹤該欄位的值是否隨後被寫入日誌或傳回不應包含該值的 API 回應中。污點分析是一種資料流分析功能,它可以追蹤敏感值在系統中的流動,並識別它們可能到達哪些不受信任的輸出。這正是注重安全性的程式碼理解工具所提供的功能,也是文字搜尋無法取代的。

SMART TS XL 提升企業整體程式碼理解能力

SMART TS XL 這個系統基於這樣的前提:企業系統需要的是結構化理解,而非文字檢索。其軟體智慧平台能夠解析環境中每種語言和平台上的原始程式碼,為每種語言產生特定於該語言的抽象語法樹,並將這些樹解析成一個統一的跨語言圖,該圖表示整個系統的結構關係。 COBOL 程式、JCL 作業流程、Java 服務、.NET 應用程式、Python 腳本、SQL 模式、TypeScript 模組和配置工件都以節點和邊的形式表示在該圖中,而關係則以類型化連接的形式表達:呼叫、資料流、副本包含、模式參考和跨語言等效性。

該平台的企業級搜尋功能為程式碼理解任務提供了入口,但其運作方式與文字搜尋截然不同。搜尋結果依關係型別和工件結構組織,而非依字串出現次數組織。查詢欄位名稱會傳回定義、讀取、寫入、SQL 參考和副本包含等不同類別的結果類型,因此,開發人員詢問「哪些操作會寫入此欄位?」時,會得到精確的寫入關係,而不是包含所有出現該名稱的檔案的混合清單。這種搜尋結果的結構化組織方式反映了底層交叉引用模型,使開發人員能夠獲得所需的具體、可操作的信息,而無需手動篩選字串。

該平台的各項功能,包括影響分析、調用圖遍歷、控制流可視化和資料流追踪,均基於同一統一的結構模型。當開發者發現某個函數已被棄用時,呼叫圖會顯​​示層次結構中每一層級的所有呼叫者。當計劃進行模式變更時,影響分析會列舉所有語言中的所有呼叫者。當新入職的開發者需要了解批次流程時,控制流程視覺化功能可以清楚地展示執行路徑,而無需他們逐行閱讀數百行原始程式碼。正如在更廣泛的背景下所考察的那樣… 遺留程式碼庫的開發者體驗和DX指標程式碼的複雜性和結構的複雜性決定了可維護性,而能夠揭示這些結構屬性(而不僅僅是表面文字)的工具,正是使複雜系統能夠大規模管理的關鍵所在。

兩者之間的差異是什麼 SMART TS XL 文字搜尋的作用在於區分已解答的問題和已提出的問題。文字搜尋開啟了調查,而程式碼理解則完成了調查。

以探索代替理解的持續代價

將文字搜尋視為程式碼理解的替代品,其實際後果會在所有需要程式碼庫結構知識的開發任務中悄悄累積。任何依賴文字搜尋的影響評估都會遺漏數量不明的依賴項。任何在語言邊界處停止的欄位追蹤都會使系統的一部分不可見。任何統計字串出現次數而非分析呼叫圖可達性的死代碼識別都會產生誤報,並遺漏真正的死代碼。任何搜尋敏感欄位名稱而非追蹤資料流經執行路徑的安全審計,其提供的保證都是不完整且無法驗證的。

在一個小型、單一語言、頻繁修改的程式碼庫中,這些成本或許可以控制。開發人員擁有足夠的上下文資訊來準確篩選搜尋結果,團隊中的每個人都了解系統的邊界,並且人工檢查能夠快速彌補文字搜尋的不足,從而避免嚴重錯誤。然而,在一個使用多種語言、程式碼累積數十年、團隊結構複雜(導致沒有人能夠全面了解整個系統)的大型企業系統中,這些成本會倍增。遺漏的依賴關係會在生產環境中揭露出來。在會議室裡令人信心滿滿的影響評估,在發布過程中卻會引發意想不到的故障。涵蓋了所有字串的安全審計,卻遺漏了暴露敏感資料的資料流路徑。那些曾經存在於已離職開發人員腦海中的知識,也無法透過文字搜尋重建,因為他們所理解的結構關係從未被編碼到原始檔案的任何字串中。

從文字搜尋到程式碼理解的轉變並非一種工具取代另一種工具。文字搜尋在其適用的任務中仍然發揮作用:字串定位、快速定位、配置檢查和文件導航。程式碼理解則提供了文字搜尋無法實現的結構分析:呼叫圖、資料流追蹤、影響分析、死程式碼辨識和跨語言依賴關係解析。兩者運行在不同的抽象層次,回答不同類型的問題,並服務於不同的目的。將二者混淆的代價是:遺漏依賴關係、評估錯誤,以及由於對複雜系統的實際運作機制缺乏完整了解而進行重大更改所帶來的風險不斷累積。