iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
佛心分享-IT 人自學之術

學過一點 React,卻被撈進 C#:新手用 AI 硬啃 MVC 企業專案的 3 個月實錄系列 第 24

Day 24|程式停下來之後看什麼?變數、Locals、Watch 和 Call Stack

  • 分享至 

  • xImage
  •  

上一篇終於把中斷點下下去了~

F9
下 Breakpoint

F5
開始 Debug

F10
往下一行

F11
進入方法

https://ithelp.ithome.com.tw/upload/images/20260813/20181499MdxRS1yeRe.png

然後 Visual Studio 真的停住了。

結果我又出現新的問題:

好。

它停了。

然後我要看什麼?

程式停在 Breakpoint 之後,其實才是 Debug 真正開始的地方。

這時除了黃色那一行程式碼之外,我還可以去看:

目前變數是多少?

物件裡面有哪些資料?

資料在哪一步開始變錯?

程式到底是從哪個方法一路跑到這裡的?

Visual Studio 也有幾個專門拿來看這些資訊的「除錯視窗」:

Locals
區域變數

Watch
監看式

Call Stack
呼叫堆疊

這幾個視窗通常要先讓程式跑到 Breakpoint,進入「偵錯暫停狀態」之後,再從:

偵錯
↓
視窗
↓
Locals / Watch / Call Stack

開啟。

https://ithelp.ithome.com.tw/upload/images/20260813/20181499HFSQ9zQP7S.png

第一次看到一堆:

Locals
Watch
Call Stack
Autos
Immediate Window

真的很容易資訊過量。

但新手 Debug 不用一次全部會。

我目前最常用的其實就是:

直接看變數

Locals
看目前有哪些變數和值

Watch
盯著特定資料看它怎麼變

Call Stack
看程式是怎麼一路跑到這裡

這篇就用一個「畫面資料沒有正常顯示」的情境,實際看看這幾個工具怎麼用。

先準備一段要 Debug 的程式

假設現在畫面上的:

部門名稱

沒有顯示。

Controller:

public IActionResult Detail(int id)
{
    var model = _userService.GetDetail(id);

    return View(model);
}

Service:

public UserDetailViewModel GetDetail(int id)
{
    var user = _userRepository.GetById(id);

    var model = new UserDetailViewModel
    {
        Name = user.Name,
        Email = user.Email,
        DepartmentName = user.DepartmentName
    };

    return model;
}

View:

@model UserDetailViewModel

<h1>@Model.Name</h1>

<p>@Model.Email</p>

<p>@Model.DepartmentName</p>

現在問題是:

Name 有顯示

Email 有顯示

DepartmentName 卻是空的

如果只看 View,我只知道:

@Model.DepartmentName

沒有資料。

但真正的問題可能出現在很多地方:

Database
↓
Repository
↓
Entity / DTO
↓
Service
↓
ViewModel
↓
Controller
↓
View

所以這次不要猜。

直接 Debug。

第一個先看:目前變數是多少?

假設我在 Controller:

public IActionResult Detail(int id)
{
    var model = _userService.GetDetail(id);

    return View(model);
}

這一行下中斷點:

var model = _userService.GetDetail(id);

接著按:

F5

然後操作到:

/User/Detail/10

Visual Studio 停下來。

這時候最簡單的方法,就是直接把滑鼠移到:

id

上面。

可能會看到:

id = 10

這馬上就能確認:

網址傳進來的 id
是不是我預期的值?

假設我原本以為正在查:

id = 10

結果 Debug 發現:

id = 0

那問題可能根本還沒進到 Service 或資料庫。

而是前面的:

Route
網址參數
參數名稱
表單資料

就已經出問題了。

物件也可以直接展開看

不只是:

int
string
bool

這種簡單變數可以看。

物件也可以。

例如先按:

F10

把這行執行完:

var model = _userService.GetDetail(id);

現在程式停在:

return View(model);

這時把滑鼠移到:

model

上面。

可能會看到:

model
├─ Name = "Amy"
├─ Email = "amy@example.com"
└─ DepartmentName = null

這個資訊就很重要了。

因為現在已經知道:

Controller 準備傳給 View 的 model

DepartmentName 就已經是 null

所以問題不太可能只是:

View 沒有把 DepartmentName 顯示出來

因為 View 根本沒有拿到值。

這時候就要往前找:

Service
Repository
Database

這也是我現在 Debug 很重要的一個觀念:

先找到資料從哪一步開始不對

而不是看到哪裡就改哪裡。

Locals 是什麼?

當程式停在 Breakpoint 時,可以開:

Locals
區域變數

從:

偵錯
↓
視窗
↓
區域變數

開啟。

Locals 可以先理解成:

把目前這個執行位置附近可以使用的區域變數和值列給我看。

假設現在停在:

public UserDetailViewModel GetDetail(int id)
{
    var user = _userRepository.GetById(id);

    var model = new UserDetailViewModel
    {
        Name = user.Name,
        Email = user.Email,
        DepartmentName = user.DepartmentName
    };

    return model;
}

Locals 裡可能會看到:

id
user
model
this

物件還可以繼續展開。

例如:

user
├─ Id = 10
├─ Name = "Amy"
├─ Email = "amy@example.com"
└─ DepartmentName = "資訊部"

接著看:

model
├─ Name = "Amy"
├─ Email = "amy@example.com"
└─ DepartmentName = null

蛤?

這時候問題範圍就已經縮小很多了。

因為:

user.DepartmentName

明明有:

資訊部

但:

model.DepartmentName

卻是:

null

代表問題很可能發生在:

Entity / DTO
↓
ViewModel

這段資料轉換。

例如實際程式可能漏寫:

DepartmentName = user.DepartmentName

Locals 最適合看什麼?

我會把 Locals 理解成:

現在停在這個方法裡,

幫我把目前附近可以看的變數
全部攤出來。

所以它很適合拿來看:

方法收到的參數

查詢後拿到的物件

ViewModel 轉換前後的資料

bool 狀態

List 有幾筆資料

例如:

var users = _userRepository.GetUsers();

執行完後,可以在 Locals 展開:

users

看:

Count = 0

還是:

Count = 20

這比只看程式碼猜:

應該有查到吧?

可靠很多。

Locals 和滑鼠直接看變數有什麼差?

其實兩個都是在看:

目前執行時的資料

差別主要在使用方式。

滑鼠移到變數:

快速看某一個值

Locals:

一次看目前很多變數

如果我只是想知道:

id 現在是多少?

直接把滑鼠移過去就很方便。

但如果方法裡同時有:

id
user
department
model
result

很多東西要一起比較。

Locals 就會比較好用。

Watch 又是什麼?

下一個很實用的是:

Watch
監看式

可以從:

偵錯
↓
視窗
↓
監看式
↓
監看式 1

開啟。

Watch 和 Locals 最大的不同是:

Locals
Visual Studio 幫我列出目前有哪些變數

Watch
我自己指定「我想一直看哪些東西」

例如現在我只關心:

id

user.DepartmentName

model.DepartmentName

就可以把它們加進 Watch。

接著按:

F10
F11

一步一步執行。

Watch 就會一直顯示這些值目前是多少。

Watch 很適合追「資料什麼時候變掉」

例如:

var user = _userRepository.GetById(id);

var model = new UserDetailViewModel
{
    Name = user.Name,
    Email = user.Email,
    DepartmentName = user.DepartmentName
};

ProcessUser(model);

return model;

假設:

剛建立 model 時

DepartmentName = "資訊部"

但是執行:

ProcessUser(model);

之後突然變成:

DepartmentName = null

如果 Watch 一直放著:

model.DepartmentName

就很容易發現:

是在 ProcessUser(model)
執行之後才變掉。

那接下來就可以:

F11
↓
進入 ProcessUser()

繼續追。

所以我會把 Watch 想成:

把重要變數釘在旁邊

讓我一路看著它怎麼變

Watch 不只能放變數

Watch 也可以放一些運算式。

例如:

users.Count

或:

model == null

也可以直接看:

user.DepartmentName

所以 Debug List 時,不一定要每次都把整個物件一層一層展開。

可以直接監看:

users.Count

例如:

執行查詢前
users.Count = 0

執行查詢後
users.Count = 15

就會很清楚。

初學階段先會看:

變數

物件屬性

Count

其實就很夠用了。

Locals 和 Watch 到底怎麼選?

我自己會這樣記:

Locals
=
現在這個方法裡「有什麼」
Watch
=
我現在「特別想盯什麼」

例如剛進一個陌生方法:

先看 Locals

因為可能連目前有哪些變數都不知道。

等到發現:

DepartmentName 很可疑

再把:

user.DepartmentName

model.DepartmentName

加進 Watch。

接著一路追。

Call Stack 又是什麼?

這個我一開始覺得最抽象。

Call Stack

中文叫:

呼叫堆疊

名字聽起來很可怕。

但我後來用最簡單的方法理解:

我現在為什麼會跑到這個方法?是從哪裡一路被呼叫進來的?

例如現在 Breakpoint 停在:

UserRepository.GetById()

可能已經一路:

Controller
↓
Service
↓
Repository

追到有點忘記自己在哪裡。

這時打開:

偵錯
↓
視窗
↓
呼叫堆疊

可能會看到:

UserRepository.GetById(int id)

UserService.GetDetail(int id)

UserController.Detail(int id)

意思就是:

現在停在:

UserRepository.GetById()

↑

它是被:

UserService.GetDetail()

呼叫

↑

而 UserService.GetDetail()

又是被:

UserController.Detail()

呼叫

所以 Call Stack 可以先理解成:

這一次程式實際走進來的呼叫路線

Call Stack 實際有什麼用?

假設專案裡有一個方法:

GetUser()

但很多地方都會使用它。

例如:

UserController
ReportService
AdminController
BackgroundJob

現在我想知道:

這一次到底是誰呼叫 GetUser()?

這時 Call Stack 就很好用。

前面有學過:

Shift + F12

可以找:

哪些地方有參考這個方法

但那代表:

哪些地方「可能」會使用它

Call Stack 則是在 Debug 執行當下告訴我:

這一次實際是從哪裡一路呼叫進來

所以可以簡單記:

Shift + F12
↓
誰有可能呼叫我?
Call Stack
↓
這一次到底是誰呼叫我?

這兩個用途不一樣。

Call Stack 還可以幫我看回上一層

假設現在停在:

UserRepository.GetById()

Call Stack 裡看到:

UserRepository.GetById()

UserService.GetDetail()

UserController.Detail()

就可以查看上一層:

UserService.GetDetail()

或再上一層:

UserController.Detail()

這在:

F11
F11
F11

一路鑽進很多方法之後很好用。

至少可以知道:

我到底是怎麼跑到這裡的?

用這幾個工具一起抓「DepartmentName 為什麼是空的」

現在把它們真的串起來。

問題:

畫面的 DepartmentName 沒有顯示

第一步:Controller 下 Breakpoint

public IActionResult Detail(int id)
{
    var model = _userService.GetDetail(id);

    return View(model);
}

先確認:

id = 10

正常。

第二步:F11 進 Service

public UserDetailViewModel GetDetail(int id)
{
    var user = _userRepository.GetById(id);

    ...
}

現在開始看:

id

user

user.DepartmentName

第三步:Repository 查完資料

執行:

var user = _userRepository.GetById(id);

結果看到:

user.DepartmentName = "資訊部"

代表:

資料至少有查回來

所以這時不用急著跑去改 SQL。

第四步:建立 ViewModel

假設實際程式變成:

var model = new UserDetailViewModel
{
    Name = user.Name,
    Email = user.Email
};

注意,這裡漏了:

DepartmentName = user.DepartmentName

再看:

user.DepartmentName
= "資訊部"

model.DepartmentName
= null

問題就抓到了。

資料是在:

Entity / DTO
↓
ViewModel

轉換這一步掉的。

不是:

View

也不是:

Database

這就是 Debug 很重要的「切問題」

以前看到:

畫面 DepartmentName 空白

可能會同時懷疑:

HTML?

Razor?

Controller?

Service?

SQL?

Database?

範圍超大。

但 Debug 之後可以一層一層確認:

Database 查出來有值嗎?
↓
有

Repository 回來有值嗎?
↓
有

Service 裡 user 有值嗎?
↓
有

轉成 model 後還有嗎?
↓
沒有

問題範圍縮小

所以真正要找的是:

最後一個「還正常」的位置

+

第一個「開始不正常」的位置

問題通常就在這兩個位置之間。

這個觀念比單純記住:

F10
F11

更重要。

再看一個 List 查不到資料的例子

例如:

public UserListViewModel GetList()
{
    var users = _userRepository.GetUsers();

    var model = new UserListViewModel
    {
        Users = users
    };

    return model;
}

結果畫面顯示:

查無資料

我可以看:

users

users.Count

model.Users.Count

Repository 執行完:

users.Count = 0

那問題可能要往:

Repository
SQL
查詢條件
Database

繼續找。

如果:

users.Count = 20

但:

model.Users.Count = 0

那問題比較可能在:

資料轉換

如果:

model.Users.Count = 20

Controller 也拿到 20 筆。

但 View 還是顯示:

查無資料

這時才往:

View

繼續找。

這樣每一步都有證據。

如果變數是 null,我會先問哪三件事?

看到:

null

先不要急著補:

if (xxx != null)

我現在會先問:

1. 它本來就允許是 null 嗎?

2. 它應該在哪一步被賦值?

3. 前一個步驟有沒有資料?

例如:

var user = _userRepository.GetById(id);

結果:

user = null

我就會先確認:

id 是多少?

↓

Repository 查詢條件是什麼?

↓

資料庫真的有這筆資料嗎?

而不是直接在下一行寫:

if (user == null)
{
    return null;
}

當然正式程式本來就可能需要 null 防護。

但 Debug 時第一件事情還是:

先搞清楚它為什麼是 null

Autos 要學嗎?

Debug 時可能還會看到:

Autos

它會自動列出 Visual Studio 判斷目前附近可能有用的變數。

它不是不能用。

只是我目前比較常使用:

Locals

Watch

Call Stack

所以新手階段不用看到:

Autos
Locals
Watch
Call Stack
Immediate
Modules
Threads

就覺得:

這些全部都要會才能 Debug?

不用。

目前先把:

直接看變數

Locals

Watch

Call Stack

用熟,就已經非常夠用了。

我現在實際 Debug 的順序

如果今天畫面資料不對,我大概會這樣做:

第一步
在我認為的入口下 Breakpoint

↓

第二步
確認 Request 傳進來的參數
例如 id、model

↓

第三步
F10 / F11 往下執行

↓

第四步
用 Locals 看目前有哪些資料

↓

第五步
把重要欄位加入 Watch

↓

第六步
比較方法執行前後的值

↓

第七步
不知道自己怎麼跑到這裡
看 Call Stack

↓

第八步
找到:

最後正常的位置

+

第一個異常的位置

這時問題範圍通常就已經縮小很多。

F12、Breakpoint、Locals、Watch、Call Stack 現在可以串起來了

前幾天學的東西其實不是分開的。

現在可以組成一套真正的追程式流程。

先找程式:

F12
↓
這個方法寫在哪裡?

確認會不會執行:

Breakpoint
↓
這次真的有跑進來嗎?

看目前資料:

滑鼠變數提示 / Locals
↓
現在值是多少?

持續追關鍵資料:

Watch
↓
這個值在哪一步開始改變?

看執行路線:

Call Stack
↓
這次是從哪個方法一路進來的?

這幾個工具一起使用之後,比單純盯著程式碼猜有用很多。

這篇先記住什麼?

程式停在 Breakpoint 之後,不是只盯著黃色那一行發呆。

我現在主要會看四件事。

第一個:

直接看變數

這個參數現在是多少?

這個物件是不是 null?

第二個:

Locals

目前這個執行位置附近
有哪些區域變數和值?

第三個:

Watch

我現在最關心哪些資料?

一路執行時它們怎麼變?

第四個:

Call Stack

我現在為什麼會跑到這裡?

這一次是誰一路呼叫進來的?

真正 Debug 時,我想找的其實是:

資料最後一次正常是在哪裡?

第一次開始錯又是在哪裡?

例如:

Repository
DepartmentName = "資訊部"

↓

Service 轉 ViewModel

↓

ViewModel
DepartmentName = null

這樣就知道:

不是整個系統都壞掉

而是這兩個步驟之間出問題

對我來說,這才是開始真正會 Debug 的感覺。

不是看到問題後一直猜:

是不是這裡?

是不是那裡?

而是:

停下來

看資料

一步一步縮小範圍

接下來還有另一種很常遇到的情況。

不是:

資料怪怪的

而是程式直接:

Build Failed

或執行到一半跳出:

Exception

但 Visual Studio 裡又有:

Error List

Output

Exception

到底應該先看哪一個?

下一篇:

Day 25|Visual Studio 報錯到底先看哪裡?Error List、Output 和 Exception 怎麼分?


上一篇
Day 23|程式到底有沒有跑到這裡?用中斷點追執行流程
系列文
學過一點 React,卻被撈進 C#:新手用 AI 硬啃 MVC 企業專案的 3 個月實錄24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言