ตัวอย่างการค้นหาและทำความเข้าใจโค้ด ที่ไม่เคยรู้มาก่อน
Tags: elixir, programming
Published: 2020-12-28 06:11:32

เมื่อเราอยากรู้ว่ามันทำงานได้ยังไง ก็แงะโค้ดมันเลย จะพาดูว่าปกติผมแงะโค้ดเพื่อทำความเข้าใจจุดที่สงสัยยังไง

ด้วยความสงสัยว่าตอนสั่ง `mix run` หรือ `mix test` มันไปโหลด `config/runtime.exs` ตอนไหนเลยแงะโค้ดของ `mix` ดูจนได้คำตอบ

เวลาสั่ง `mix run` นั่นคือมันจะไปเรียก task ที่ชื่อว่า run ซึ่งเป็น task ที่ติดมากับตอนติดตั้ง elixir อยู่แล้ว ตัวโค้ดอยู่ที่ `elixir/lib/mix/lib/mix/tasks/run.ex` (elixir คือ directory ที่เราลง elixir เอาไว้ ต่างกันไปขึ้นอยู่กับว่าติดตั้ง elixir ด้วยอะไร บน OS ไหน)

กลไกของ mix task คือ module สำหรับ task ต้องมี function `run` ก็ไปแงะแถวนั้น ตอนแรกก็ยังไม่เห็นว่ามีส่วนไหนที่เกี่ยวข้องกับการโหลดไฟล์ `config/runtime.exs`

สิ่งที่พอจะทำได้คือลองค้นว่ามีไฟล์ไหนใน path `elixir/lib/mix/lib/mix` ที่อ้างถึง `config/runtime.exs` บ้าง

ค้นดูแล้วก็เจอไฟล์ที่น่าสงสัยตามรูป

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/v7ndwhcfncz9ret00c55.png)

`tasks/app.config.ex` น่าสงสัยสุดเพราะบรรทัดที่ค้นเจอบอกเลยว่า
> 9:  This is done by loading `config/runtime.exs` if one exists.

พอเปิดโค้ดในไฟล์ดูแล้วก็เจอโค้ดแบบนี้

```elixir
    config = Mix.Project.config()
    runtime = config[:config_path] |> Path.dirname() |> Path.join("runtime.exs")

    if File.exists?(runtime) do
      Mix.Tasks.Loadconfig.load_runtime(runtime)
    end
```

ชัดเลยว่า task นี้มันจะทำหน้าที่โหลดไฟล์ `config/runtime.exs` ถ้ามีไฟล์นี้อยู่ ส่วนโค้ดที่โหลดจริงๆอยู่ในฟังก์ชัน `Mix.Tasks.Loadconfig.load_runtime` ลองเปิดดูเจอโค้ดประมาณนี้

```elixir
  @doc false
  # Loads runtime configuration, they do not support imports, and are deep merged.
  def load_runtime(file) do
    config = Config.Reader.read!(file, env: Mix.env(), target: Mix.target(), imports: :disabled)
    Mix.ProjectStack.loaded_config(persist_apps(hydrate_apps(config), file), [])
    config
  end
```

เอาละเราคงไม่ขุดลึกไปกว่านี้ ณ จุดนี้เรารู้แล้วว่า task `app.config` ทำหน้าที่โหลด `config/runtime.exs` แต่ว่าตอนเราใช้งานเราเรียก `mix run` กับ `mix test` อ่ะไม่ได้เรียก `mix app.config` มันต้องมีอะไรเชื่อมโยงกัน

เราค้นหาต่อโดยลองค้นว่ามีไฟล์ในที่มี `app.config` อยู่ในไฟล์บ้าง แล้วก็เจอแบบนี้

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7h6wctau08f3b6ckp5zb.png)

และแล้วเราก็เจอว่าไฟล์ `tasks/app.start.ex` นั่นไง พอเปิดดูโค้ดรอบๆบรรทัดนั้นก็เจอแบบนี้

```elixir
@impl true
  def run(args) do
    Mix.Project.get!()
    Mix.Task.run("app.config", args)
```

ชัดเลยว่าทุกครั้งที่ task `app.start` ถูกเรียกนั้นมันจะเรียก `app.config` ให้ทำงานด้วย เราคงไม่ต้องไปแงะ `Mix.Task.run` แล้วเพราะชื่อก็ค่อนข้างชัดแล้วว่าเรียกแล้วมันจะไปสั่ง `app.config` ให้ทำงาน

ต่อไปก็หาต่อว่าแล้วใครมันเรียก `app.start` บ้างเหมือนเดิมค้น `app.start` ดูว่าเจอที่ไฟล์ไหนบ้าง แล้วก็เจอแบบนี้

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nbs3jrd7ccii73xtr69i.png)

นั่นไงและแล้วเราก็เจอว่า `tasks/run.ex` กับ `tasks/test.ex` มีการเรียก `app.start` ให้ทำงาน ลองเปิดดูโค้ดของทั้ง task `run` กับ `test` พบว่ามันไปเรียก `app.start` เวลาสอง task นี้ทำงานจริงๆด้วย 

สรุปคือ `run` และ `test` จะเรียก `app.start` แล้ว `app.start` เรียก `app.config` แล้ว `app.config` โหลด `config/runtime.exs` อีกที

จริงๆที่เขียนมาประเด็นไม่ได้อยู่ที่ว่าจะรู้ไปทำไปหรอก ใช้ mix ไม่ต้องรู้ก็ได้ว่ามันโหลด runtime.ex ยังไง แต่ประเด็นคือถ้าเราสงสัยการทำงาน เรามีโค้ดมันอยู่ ไม่ต้องกลัวที่จะขุดเพื่อจะเรียนรู้มัน เครื่องมือสำคัญในการลงลุยอ่านโค้ดในแต่ละครั้งคือ

- Document เพราะเราต้องรู้ก่อนว่าสิ่งต่างๆที่เราลงไปขุดหลักๆมันไว้ทำอะไร มีองค์ประกอบอะไรบ้าง เช่นตอนขุดก็ต้องอ่าน document ของ mix ว่ามันมีหลักการยังไง แบ่ง task ยังไง โค้ดของ task อยู่ที่ไหน
- Text search tool อันนี้สำคัญก็คือเอาไว้ค้นหาไฟล์ที่มีข้อความ หรือส่วนของโค้ดที่เราสนใจ ว่ามันถูกอ้างอิงหรือเรียกใช้ที่จุดไหน
- Editor แน่นอนเจอไฟล์แล้วเราก็ต้องเปิดดูโค้ดโดยรอบมัน หาความเชื่อมโยงต่ออีกที
- Paper and Pen กระดาษและปากกา เอาไว้จดโน้ต เอาไว้วาดรูปเพื่อเชื่อมโยงความสัมพันธ์ ของโค้ดแต่ละไฟล์ แต่ละ module ที่เราแกะ

<a href="https://www.buymeacoffee.com/weerasak" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>