<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>LSP on Tarragon</title><link>https://tarrragon.github.io/blog/tags/lsp/</link><description>Recent content in LSP on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Wed, 04 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/lsp/index.xml" rel="self" type="application/rss+xml"/><item><title>LSP 優先開發策略方法論 - 語意化程式碼操作的最佳實踐</title><link>https://tarrragon.github.io/blog/record/lsp-%E5%84%AA%E5%85%88%E9%96%8B%E7%99%BC%E7%AD%96%E7%95%A5%E6%96%B9%E6%B3%95%E8%AB%96-%E8%AA%9E%E6%84%8F%E5%8C%96%E7%A8%8B%E5%BC%8F%E7%A2%BC%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AF%A6%E8%B8%90/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/record/lsp-%E5%84%AA%E5%85%88%E9%96%8B%E7%99%BC%E7%AD%96%E7%95%A5%E6%96%B9%E6%B3%95%E8%AB%96-%E8%AA%9E%E6%84%8F%E5%8C%96%E7%A8%8B%E5%BC%8F%E7%A2%BC%E6%93%8D%E4%BD%9C%E7%9A%84%E6%9C%80%E4%BD%B3%E5%AF%A6%E8%B8%90/</guid><description>&lt;p>有一次我們在追蹤一個介面的所有實作位置，習慣性地用 &lt;code>grep -r &amp;quot;implements SomeInterface&amp;quot;&lt;/code> 搜整個專案。等了大約 45 秒，拿到一堆夾雜字串字面值、註解、測試假資料的結果，還需要手動過濾。&lt;/p>
&lt;p>後來改用 LSP 的 &lt;code>goToImplementation&lt;/code>，不到 50 毫秒，精確乾淨。&lt;/p>
&lt;p>那個體驗讓我們開始想：我們平時到底在做什麼？&lt;/p></description><content:encoded><![CDATA[<p>有一次我們在追蹤一個介面的所有實作位置，習慣性地用 <code>grep -r &quot;implements SomeInterface&quot;</code> 搜整個專案。等了大約 45 秒，拿到一堆夾雜字串字面值、註解、測試假資料的結果，還需要手動過濾。</p>
<p>後來改用 LSP 的 <code>goToImplementation</code>，不到 50 毫秒，精確乾淨。</p>
<p>那個體驗讓我們開始想：我們平時到底在做什麼？</p>
<h2 id="文字搜尋的根本問題">文字搜尋的根本問題</h2>
<p><code>grep</code> 不理解語意，只做字串匹配。問題有三個：</p>
<p>結果包含噪音。<code>BookRepository</code> 這個名稱會出現在程式碼、字串字面值、文件、測試 mock、JSON 設定檔——grep 全部回傳，你自己過濾。</p>
<p>結果缺乏結構。你拿到的是一行行文字，不是符號的定義位置、型別資訊或呼叫關係，需要再做二次處理。</p>
<p>它很慢。大型 Codebase 裡全域搜尋很容易超過幾十秒。</p>
<p>LSP 真正理解程式碼的語義結構。問「這個函式在哪裡被呼叫」，它給出的是精確的呼叫位置，不是字串匹配的猜測結果。</p>
<h2 id="核心原則lsp-能做的不要用-grep">核心原則：LSP 能做的，不要用 grep</h2>
<p>每次操作程式碼之前，先問自己：<strong>這是語意操作，還是文字操作？</strong></p>
<p>語意操作包括：找定義、追引用、理解型別、分析呼叫鏈。這些用 LSP 或對應的語言 MCP 工具。文字搜尋是最後備援，不是第一選項。</p>
<p>決策流程很直接：</p>
<ul>
<li>分析誰呼叫了某個函式 → <code>callHierarchy</code> / <code>incomingCalls</code></li>
<li>找介面的所有實作 → <code>goToImplementation</code></li>
<li>追蹤符號定義來源 → <code>goToDefinition</code> 或 <code>resolve_workspace_symbol</code></li>
<li>查型別簽名和文件 → <code>hover</code> / <code>mcp__dart__hover</code></li>
<li>重構前影響分析 → <code>findReferences</code></li>
</ul>
<p>只有 LSP 工具無法使用，或需要搜尋非程式碼內容（設定檔、文件、log），才退回 Grep 或 Glob。</p>
<h2 id="效能差距有多大">效能差距有多大</h2>
<p>我們實際測量過：</p>
<ul>
<li>查找引用：LSP 約 50ms，grep 約 45 秒（900 倍差距）</li>
<li>跳轉到定義：LSP 約 10ms，文字搜尋約 5 秒</li>
<li>符號概覽：LSP 約 20ms，手動解析約 10 秒</li>
</ul>
<p>用 AI 工具輔助開發時還有另一個面向：token 消耗。LSP 的 <code>findReferences</code> 輸出是結構化位置列表，大約 100 到 500 個 token。同一個查詢用 grep，可能消耗 3000 到 10000 個 token。</p>
<p>速度、成本、結果的清晰度，全都差這麼多。</p>
<h2 id="工具對應">工具對應</h2>
<p>以 Dart/Flutter 環境為例：</p>
<table>
  <thead>
      <tr>
          <th>需求</th>
          <th>工具</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>懸停查看型別</td>
          <td><code>mcp__dart__hover</code></td>
      </tr>
      <tr>
          <td>工作區尋找符號</td>
          <td><code>mcp__dart__resolve_workspace_symbol</code></td>
      </tr>
      <tr>
          <td>查看函式簽名</td>
          <td><code>mcp__dart__signature_help</code></td>
      </tr>
      <tr>
          <td>整個專案靜態分析</td>
          <td><code>mcp__dart__analyze_files</code></td>
      </tr>
  </tbody>
</table>
<p>Dart MCP 無法使用時，退到 Serena MCP：<code>get_symbols_overview</code>、<code>find_symbol</code>、<code>find_referencing_symbols</code>。Serena 輸出更豐富，但消耗更多 token。</p>
<p>Grep 和 Glob 是最後備援，用在搜尋非程式碼內容，或完全沒有 LSP/MCP 的環境。</p>
<h2 id="一個實踐範例">一個實踐範例</h2>
<p>重構 <code>BookMetadataService</code>，想知道修改 <code>fetchMetadata</code> 方法會影響哪些地方。</p>
<p>文字搜尋：<code>grep -r &quot;fetchMetadata&quot; lib/</code>，結果包含真實呼叫、字串常數、測試 stub，手動過濾後才能確認影響範圍，大約一兩分鐘。</p>
<p>LSP：對 <code>fetchMetadata</code> 的定義位置執行 <code>findReferences</code>，50 毫秒內拿到所有真實呼叫位置，每個結果附帶精確檔案路徑和行號，沒有噪音。</p>
<p>不只是快，更重要的是對結果的信心不同。文字搜尋的結果需要懷疑，LSP 的結果可以直接信任。</p>
<h2 id="落地這個習慣">落地這個習慣</h2>
<p>每次伸手去打 <code>grep</code> 之前，先停一秒問自己：</p>
<ul>
<li>追蹤符號定義 → 有沒有先用定義跳轉？</li>
<li>做重構 → 有沒有先用 <code>findReferences</code> 分析影響範圍？</li>
<li>查型別或文件 → 有沒有用 <code>hover</code>？</li>
<li>分析呼叫關係 → 有沒有用 <code>callHierarchy</code>？</li>
</ul>
<p>這不是流程負擔，就是一個習慣檢查點。</p>
<h2 id="結論">結論</h2>
<p>文字搜尋給我們的是「字串在哪裡出現」，LSP 給我們的是「這個符號語義上與什麼相關」。</p>
<p>這個差異在小專案不明顯。但在中大型 Codebase 裡，它決定了重構的安全性，也決定了每天工作有多流暢。</p>
<p>讓語言伺服器做它擅長的事，認知資源才能留給真正需要思考的問題。</p>]]></content:encoded></item></channel></rss>