檢視原始碼 偵錯

Elixir 中有許多方法可以偵錯程式碼。在本章中,我們將介紹一些較常見的方法。

IO.inspect/2

IO.inspect(item, opts \\ []) 在偵錯中真正有用的原因,在於它會傳回傳遞給它的 item 參數,而不會影響原始程式碼的行為。我們來看一個範例。

(1..10)
|> IO.inspect()
|> Enum.map(fn x -> x * 2 end)
|> IO.inspect()
|> Enum.sum()
|> IO.inspect()

列印

1..10
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
110

如你所見,IO.inspect/2 讓你可以「監控」程式碼中幾乎任何地方的值,而不會改變結果,這讓它在管線中非常有用,就像上述情況一樣。

IO.inspect/2 也提供使用 label 選項來裝飾輸出的功能。標籤會列印在受監控的 item 之前

[1, 2, 3]
|> IO.inspect(label: "before")
|> Enum.map(&(&1 * 2))
|> IO.inspect(label: "after")
|> Enum.sum

列印

before: [1, 2, 3]
after: [2, 4, 6]

IO.inspect/2binding/0 一起使用也很常見,後者會傳回所有變數名稱及其值

def some_fun(a, b, c) do
  IO.inspect binding()
  ...
end

some_fun/3:foo"bar":baz 呼叫時,它會列印

[a: :foo, b: "bar", c: :baz]

請分別參閱 IO.inspect/2Inspect.Opts,以進一步瞭解這個函式,並閱讀所有支援的選項。

dbg/2

Elixir v1.14 引入了 dbg/2dbg 類似於 IO.inspect/2,但特別針對偵錯量身打造。它會列印傳遞給它的值並傳回它(就像 IO.inspect/2 一樣),但它也會列印程式碼和位置。

# In my_file.exs
feature = %{name: :dbg, inspiration: "Rust"}
dbg(feature)
dbg(Map.put(feature, :in_version, "1.14.0"))

上述程式碼會列印以下內容

[my_file.exs:2: (file)]
feature #=> %{inspiration: "Rust", name: :dbg}
[my_file.exs:3: (file)]
Map.put(feature, :in_version, "1.14.0") #=> %{in_version: "1.14.0", inspiration: "Rust", name: :dbg}

當我們在討論 IO.inspect/2 時,我們提到了它在放置於 |> 管線步驟之間時很有用。dbg 做得更好:它了解 Elixir 程式碼,因此它會在管線的每個步驟中列印值。

# In dbg_pipes.exs
__ENV__.file
|> String.split("/", trim: true)
|> List.last()
|> File.exists?()
|> dbg()

這段程式碼印出

[dbg_pipes.exs:5: (file)]
__ENV__.file #=> "/home/myuser/dbg_pipes.exs"
|> String.split("/", trim: true) #=> ["home", "myuser", "dbg_pipes.exs"]
|> List.last() #=> "dbg_pipes.exs"
|> File.exists?() #=> true

儘管 dbg 提供了 Elixir 建構周邊的便利性,如果你想要在除錯時執行程式碼和設定中斷點,你將需要 IEx

中斷點

在使用 IEx 時,你可以傳遞 --dbg pry 作為選項,以在 dbg 呼叫所在處「停止」程式碼執行。

$ iex --dbg pry

現在,呼叫 dbg 會詢問你是否要審查現有程式碼。如果你接受,你將能夠直接從 IEx 存取所有變數,以及程式碼中的匯入和別名。這稱為「審查」。在審查階段執行時,程式碼執行會停止,直到呼叫 continuenext。請記住,你隨時可以在專案的背景下執行 iex,方法是 iex -S mix TASK

dbg 呼叫要求我們變更我們打算除錯的程式碼,並且具有有限的逐步執行功能。幸運的是,IEx 也提供了一個 IEx.break!/2 函式,它允許你在任何 Elixir 程式碼上設定和管理中斷點,而無需修改其原始碼。

dbg 類似,一旦達到中斷點,程式碼執行就會停止,直到呼叫 continuenext。但是,break!/2 無法存取被除錯程式碼中的別名和匯入,因為它作用於編譯後的成品,而不是原始碼。

觀察器

對於除錯複雜系統,跳到程式碼是不夠的。有必要了解整個虛擬機器、程序、應用程式,以及設定追蹤機制。幸運的是,這可以在 Erlang 中透過 :observer 來達成。在你的應用程式中

$ iex
iex> :observer.start()

缺少相依性

在專案內執行 iex 時,透過 iex -S mixobserver 將無法作為相依性使用。為此,你將需要在之前呼叫下列函式

iex> Mix.ensure_application!(:wx)
iex> Mix.ensure_application!(:runtime_tools)
iex> Mix.ensure_application!(:observer)
iex> :observer.start()

如果上述任何呼叫失敗,以下是可能發生的事情:某些套件管理員預設安裝精簡版 Erlang,不含用於 GUI 支援的 WX 繫結。在某些套件管理員中,您可能可以用更完整的套件取代無頭 Erlang(在 Debian/Ubuntu/Arch 中尋找名為 erlangerlang-nox 的套件)。在其他管理員中,您可能需要安裝一個單獨的 erlang-wx(或類似名稱)套件。

有討論要改善此體驗,以利於未來版本。

上述內容將開啟另一個圖形使用者介面,提供許多窗格,讓您完全了解並瀏覽執行時期和專案。

我們在 Mix 與 OTP 指南的動態監督章節中,探索一個實際專案 中的觀察者。這是除錯技術之一,Phoenix 框架用於在單一機器上達成 200 萬個連線

如果您使用 Phoenix 網路框架,它會隨附 Phoenix LiveDashboard,一個用於生產節點的網路儀表板,提供類似於觀察者的功能。

最後,請記住您也可以透過在 IEx 中直接呼叫 runtime_info/0 來取得執行時期資訊的迷你概觀。

其他工具和社群

我們僅觸及 Erlang VM 提供的皮毛,例如

  • 除了觀察者應用程式之外,Erlang 還包含一個 :crashdump_viewer 來檢視崩潰傾印

  • 與作業系統層級追蹤器整合,例如 Linux 追蹤工具組DTRACESystemTap

  • 微狀態會計衡量執行時期在短時間間隔內花費在多項低階工作上的時間

  • Mix 隨附許多在 profile 名稱空間下的工作,例如 cproffprof

  • 對於更進階的使用案例,我們推薦優秀的 Erlang in Anger,它以免費電子書的形式提供

除錯愉快!