[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"post-\u002Fblog\u002Fdebugging-legacy-code":3},{"id":4,"title":5,"body":6,"category":310,"date":311,"description":312,"draft":313,"extension":314,"meta":315,"navigation":316,"path":317,"readingTime":318,"seo":319,"stem":320,"tags":321,"__hash__":325},"blog\u002Fblog\u002Fdebugging-legacy-code.md","既有系統除錯思路：從重現問題到建立防護",{"type":7,"value":8,"toc":302},"minimark",[9,18,23,26,39,43,46,72,76,79,170,173,255,259,262,274,277,281,292,295,298],[10,11,12,13,17],"p",{},"接手既有系統時，最大的困難往往不是技術，而是「不確定改這裡會影響哪裡」。比起憑感覺修改，用固定的流程除錯更穩定：",[14,15,16],"strong",{},"重現、定位、歸因、修復、防範","，每一步都留下可以驗證的證據。",[19,20,22],"h2",{"id":21},"一重現先讓問題穩定發生","一、重現：先讓問題穩定發生",[10,24,25],{},"無法穩定重現的問題，修好了也無法證明修好。先把條件寫清楚：",[27,28,29,33,36],"ul",{},[30,31,32],"li",{},"哪個頁面、什麼操作順序、什麼資料。",[30,34,35],{},"只發生在特定瀏覽器、螢幕尺寸或登入身分嗎？",[30,37,38],{},"能不能縮小到最小重現範例（Minimal Reproduction）：拿掉不相關的程式碼，問題還在，代表範圍縮對了。",[19,40,42],{"id":41},"二定位用二分法縮小範圍","二、定位：用二分法縮小範圍",[10,44,45],{},"用 DevTools 把範圍切半，再切半：",[27,47,48,54,60,66],{},[30,49,50,53],{},[14,51,52],{},"Console","：先看有沒有錯誤或警告，它們常直接指出方向。",[30,55,56,59],{},[14,57,58],{},"Network","：確認請求有沒有送出、狀態碼、回傳資料是否符合預期。",[30,61,62,65],{},[14,63,64],{},"Elements","：看實際套用的 CSS，以及被劃掉的規則是被誰覆蓋。",[30,67,68,71],{},[14,69,70],{},"Sources","：在可疑位置設中斷點，或用「Event Listener Breakpoints」在特定事件發生時暫停。",[19,73,75],{"id":74},"三歸因用假設驗證不要憑猜測","三、歸因：用假設驗證，不要憑猜測",[10,77,78],{},"介面行為問題常見的原因，可以對照著檢查：",[80,81,82,98],"table",{},[83,84,85],"thead",{},[86,87,88,92,95],"tr",{},[89,90,91],"th",{},"現象",[89,93,94],{},"常見原因",[89,96,97],{},"驗證方式",[99,100,101,113,124,135,159],"tbody",{},[86,102,103,107,110],{},[104,105,106],"td",{},"彈窗點外面會立刻關閉，或關不掉",[104,108,109],{},"事件冒泡或捕獲順序，點擊被上層攔截",[104,111,112],{},"在 capture 階段記錄事件路徑",[86,114,115,118,121],{},[104,116,117],{},"元件值沒有更新",[104,119,120],{},"響應性失效（解構、非響應式物件）",[104,122,123],{},"在 DevTools 檢查該值是否仍是響應式",[86,125,126,129,132],{},[104,127,128],{},"樣式改了沒效果",[104,130,131],{},"選擇器優先權較低，或被框架樣式覆蓋",[104,133,134],{},"在 Elements 面板看被劃掉的規則",[86,136,137,140,148],{},[104,138,139],{},"浮層被遮住",[104,141,142,143,147],{},"stacking context 讓 ",[144,145,146],"code",{},"z-index"," 失效",[104,149,150,151,154,155,158],{},"檢查祖先元素是否有 ",[144,152,153],{},"transform","、",[144,156,157],{},"opacity"," 等",[86,160,161,164,167],{},[104,162,163],{},"偶發錯誤",[104,165,166],{},"非同步時序或請求競態",[104,168,169],{},"加上時間戳記記錄，比對順序",[10,171,172],{},"例如要確認是哪一層攔截了點擊，可以在 capture 階段觀察事件路徑：",[174,175,180],"pre",{"className":176,"code":177,"language":178,"meta":179,"style":179},"language-js shiki shiki-themes github-dark","document.addEventListener('click', (e) => {\n  console.log('capture:', e.target, e.composedPath())\n}, true)\n","js","",[144,181,182,219,242],{"__ignoreMap":179},[183,184,187,191,195,198,202,205,209,212,216],"span",{"class":185,"line":186},"line",1,[183,188,190],{"class":189},"s95oV","document.",[183,192,194],{"class":193},"svObZ","addEventListener",[183,196,197],{"class":189},"(",[183,199,201],{"class":200},"sU2Wk","'click'",[183,203,204],{"class":189},", (",[183,206,208],{"class":207},"s9osk","e",[183,210,211],{"class":189},") ",[183,213,215],{"class":214},"snl16","=>",[183,217,218],{"class":189}," {\n",[183,220,222,225,228,230,233,236,239],{"class":185,"line":221},2,[183,223,224],{"class":189},"  console.",[183,226,227],{"class":193},"log",[183,229,197],{"class":189},[183,231,232],{"class":200},"'capture:'",[183,234,235],{"class":189},", e.target, e.",[183,237,238],{"class":193},"composedPath",[183,240,241],{"class":189},"())\n",[183,243,245,248,252],{"class":185,"line":244},3,[183,246,247],{"class":189},"}, ",[183,249,251],{"class":250},"sDLfK","true",[183,253,254],{"class":189},")\n",[19,256,258],{"id":257},"四修復最小範圍並評估影響面","四、修復：最小範圍，並評估影響面",[10,260,261],{},"修改前先回答三個問題：",[263,264,265,268,271],"ol",{},[30,266,267],{},"這段程式碼還有哪些地方會用到？（全域搜尋、查看引用）",[30,269,270],{},"修改會改變哪些既有行為？",[30,272,273],{},"有沒有更小的改法？例如修元件本身，而不是在每個呼叫端各補一次。",[10,275,276],{},"既有系統通常沒有完整測試，修復後要手動走過「會受影響的流程」，而不只是驗證被回報的那一個情境。",[19,278,280],{"id":279},"五防範讓同樣的問題不再發生","五、防範：讓同樣的問題不再發生",[27,282,283,286,289],{},[30,284,285],{},"補一個能重現原問題的測試；沒有測試環境時，至少把重現步驟寫進 PR 說明。",[30,287,288],{},"能用工具擋下的，就收斂成規則，例如 ESLint 規則或元件的 props 限制。",[30,290,291],{},"把根因記錄在程式碼註解或文件中，寫的是「為什麼這樣做」，而不是「做了什麼」。",[19,293,294],{"id":294},"小結",[10,296,297],{},"除錯的重點是把「感覺」換成「證據」：能穩定重現、能說明原因、能驗證修復、能防止再發。流程固定下來，面對不熟悉的舊程式碼時也能一步一步縮小範圍，而不是到處試改。",[299,300,301],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":179,"searchDepth":221,"depth":221,"links":303},[304,305,306,307,308,309],{"id":21,"depth":221,"text":22},{"id":41,"depth":221,"text":42},{"id":74,"depth":221,"text":75},{"id":257,"depth":221,"text":258},{"id":279,"depth":221,"text":280},{"id":294,"depth":221,"text":294},"case","2026-10-29","面對非本人撰寫的舊程式碼時，如何透過清晰的最小重現範例、斷點排查與元件解耦逐步解決問題。",false,"md",{},true,"\u002Fblog\u002Fdebugging-legacy-code",7,{"title":5,"description":312},"blog\u002Fdebugging-legacy-code",[322,323,324],"Debugging","Legacy Code","DevTools","dKhjFex4bygbBuYZC85m1yXOyi68cnetrZEScQVjaME"]