Sejak saya mula membangun di Golang, saya tidak begitu menggunakan debugger. Sebaliknya saya secara naif menambah pernyataan fmt.Cetak di mana-mana untuk mengesahkan kod saya ?. Walaupun kenyataan cetak dan log mungkin juga merupakan naluri penyahpepijatan pertama anda, ia sering gagal apabila berurusan dengan asas kod yang besar dan kompleks, dengan gelagat masa jalan yang canggih dan (sudah tentu!) isu konkurensi kompleks yang kelihatan mustahil untuk dihasilkan semula.
Selepas mula mengerjakan projek yang lebih kompleks (seperti ini: https://github.com/cloudoperators/heureka) saya terpaksa memaksa diri saya untuk melihat dengan lebih mendalam pada delve (penyahpepijat Golang) dan melihat apa yang ditawarkan oleh Emacs berinteraksi dengannya. Walaupun ekosistem Go menawarkan alat penyahpepijatan yang sangat baik, menyepadukannya ke dalam aliran kerja pembangunan yang selesa boleh menjadi mencabar.
Dalam siaran ini saya akan menghuraikan gabungan hebat Emacs, Delve dan dape. Bersama-sama, alatan ini mencipta pengalaman penyahpepijatan yang meniru (dan selalunya mengatasi) IDE tradisional, sambil mengekalkan fleksibiliti dan kebolehlanjutan yang Emacs terkenal.
Inilah yang anda boleh jangkakan:
Dalam siaran ini saya menganggap anda sudah mempunyai beberapa pengalaman Emacs dan kini cara mengkonfigurasi pakej dan menulis coretan Elisp kecil. Saya secara peribadi menggunakan straight.el sebagai pengurus pakej, minimal-emacs.d sebagai konfigurasi Emacs vanila minimum (bersama-sama dengan penyesuaian saya sendiri), dape sebagai klien penyesuai nyahpepijat dan eglot sebagai klien LSP saya.
Untuk pengguna Emacs 29, eglot terbina dalam. Lihat mengkonfigurasi eglot untuk gopls dan beberapa tetapan gopls yang lebih maju. Kita tambah dape dulu:
(use-package dape :straight t :config ;; Pulse source line (performance hit) (add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line) ;; To not display info and/or buffers on startup ;; (remove-hook 'dape-start-hook 'dape-info) (remove-hook 'dape-start-hook 'dape-repl))
Dan mod pergi:
(use-package go-mode :straight t :mode "\.go\'" :hook ((before-save . gofmt-before-save)) :bind (:map go-mode-map ("M-?" . godoc-at-point) ("M-." . xref-find-definitions) ("M-_" . xref-find-references) ;; ("M-*" . pop-tag-mark) ;; Jump back after godef-jump ("C-c m r" . go-run)) :custom (gofmt-command "goimports"))
Pasang Delve dan gopls, pelayan LSP:
# Install Delve go install github.com/go-delve/delve/cmd/dlv@latest # Install gopls go install golang.org/x/tools/gopls@latest
Selain itu, saya mempunyai banyak alatan lain yang saya gunakan dari semasa ke semasa:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest go install github.com/onsi/ginkgo/v2/ginkgo@latest go install -v golang.org/x/tools/cmd/godoc@latest go install -v golang.org/x/tools/cmd/goimports@latest go install -v github.com/stamblerre/gocode@latest go install -v golang.org/x/tools/cmd/gorename@latest go install -v golang.org/x/tools/cmd/guru@latest go install -v github.com/cweill/gotests/...@latest go install -v github.com/davidrjenni/reftools/cmd/fillstruct@latest go install -v github.com/fatih/gomodifytags@latest go install -v github.com/godoctor/godoctor@latest go install -v github.com/haya14busa/gopkgs/cmd/gopkgs@latest go install -v github.com/josharian/impl@latest go install -v github.com/rogpeppe/godef@latest
Kemudian anda perlu mengkonfigurasi pakej Emacs yang sepadan:
(use-package ginkgo :straight (:type git :host github :repo "garslo/ginkgo-mode") :init (setq ginkgo-use-pwd-as-test-dir t ginkgo-use-default-keys t)) (use-package gotest :straight t :after go-mode :bind (:map go-mode-map ("C-c t f" . go-test-current-file) ("C-c t t" . go-test-current-test) ("C-c t j" . go-test-current-project) ("C-c t b" . go-test-current-benchmark) ("C-c t c" . go-test-current-coverage) ("C-c t x" . go-run))) (use-package go-guru :straight t :hook (go-mode . go-guru-hl-identifier-mode)) (use-package go-projectile :straight t :after (projectile go-mode)) (use-package flycheck-golangci-lint :straight t :hook (go-mode . flycheck-golangci-lint-setup)) (use-package go-eldoc :straight t :hook (go-mode . go-eldoc-setup)) (use-package go-tag :straight t :bind (:map go-mode-map ("C-c t a" . go-tag-add) ("C-c t r" . go-tag-remove)) :init (setq go-tag-args (list "-transform" "camelcase"))) (use-package go-fill-struct :straight t) (use-package go-impl :straight t) (use-package go-playground :straight t)
Tiada sebab tertentu mengapa saya menggunakan dape sebagai ganti dap. Apabila saya masih menggunakan MinEmacs ia adalah sebahagian daripadanya dan saya baru terbiasa dengannya. Seperti yang dinyatakan dalam dokumentasi:
- Dape tidak menyokong fail launch.json, jika konfigurasi setiap projek diperlukan gunakan dir-locals dan dape-command.
- Dape meningkatkan ergonomik dalam penimbal mini dengan membenarkan pengguna mengubah suai atau menambah entri PLIST pada konfigurasi sedia ada menggunakan pilihan.
- Tiada sihir, tiada pembolehubah khas seperti ${workspaceFolder}. Sebaliknya, fungsi dan pembolehubah diselesaikan sebelum memulakan sesi baharu.
- Cuba untuk membayangkan bagaimana konfigurasi penyesuai nyahpepijat akan dilaksanakan dalam Emacs jika vscode tidak pernah wujud.
Jika anda pernah bekerja dengan VSCode, anda sudah tahu bahawa ia menggunakan launch.json untuk menyimpan profil penyahpepijatan yang berbeza:
(use-package dape :straight t :config ;; Pulse source line (performance hit) (add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line) ;; To not display info and/or buffers on startup ;; (remove-hook 'dape-start-hook 'dape-info) (remove-hook 'dape-start-hook 'dape-repl))
Anda mempunyai medan/sifat berbeza yang mengikut halaman ini anda boleh tweak dalam konfigurasi penyahpepijatan anda:
Property | Description |
---|---|
name | Name for your configuration that appears in the drop down in the Debug viewlet |
type | Always set to "go". This is used by VS Code to figure out which extension should be used for debugging your code |
request | Either of launch or attach. Use attach when you want to attach to an already running process |
mode | For launch requests, either of auto, debug, remote, test, exec. For attach requests, use either local or remote |
program | Absolute path to the package or file to debug when in debug & test mode, or to the pre-built binary file to debug in exec mode |
env | Environment variables to use when debugging. Example: { "ENVNAME": "ENVVALUE" } |
envFile | Absolute path to a file containing environment variable definitions |
args | Array of command line arguments that will be passed to the program being debugged |
showLog | Boolean indicating if logs from delve should be printed in the debug console |
logOutput | Comma separated list of delve components for debug output |
buildFlags | Build flags to be passed to the Go compiler |
remotePath | Absolute path to the file being debugged on the remote machine |
processId | ID of the process that needs debugging (for attach request with local mode) |
Sekarang mari kita praktikkan pengetahuan kita dengan menyahpepijat aplikasi sebenar yang melaksanakan API REST.
Contoh kami ialah REST API untuk pengurusan tugasan dengan struktur berikut:
(use-package dape :straight t :config ;; Pulse source line (performance hit) (add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line) ;; To not display info and/or buffers on startup ;; (remove-hook 'dape-start-hook 'dape-info) (remove-hook 'dape-start-hook 'dape-repl))
Mari kita lihat komponen teras.
Tugas mewakili model domain teras kami:
(use-package go-mode :straight t :mode "\.go\'" :hook ((before-save . gofmt-before-save)) :bind (:map go-mode-map ("M-?" . godoc-at-point) ("M-." . xref-find-definitions) ("M-_" . xref-find-references) ;; ("M-*" . pop-tag-mark) ;; Jump back after godef-jump ("C-c m r" . go-run)) :custom (gofmt-command "goimports"))
Stor Tugas mengendalikan operasi data dalam memori kami:
# Install Delve go install github.com/go-delve/delve/cmd/dlv@latest # Install gopls go install golang.org/x/tools/gopls@latest
API mendedahkan titik akhir berikut:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest go install github.com/onsi/ginkgo/v2/ginkgo@latest go install -v golang.org/x/tools/cmd/godoc@latest go install -v golang.org/x/tools/cmd/goimports@latest go install -v github.com/stamblerre/gocode@latest go install -v golang.org/x/tools/cmd/gorename@latest go install -v golang.org/x/tools/cmd/guru@latest go install -v github.com/cweill/gotests/...@latest go install -v github.com/davidrjenni/reftools/cmd/fillstruct@latest go install -v github.com/fatih/gomodifytags@latest go install -v github.com/godoctor/godoctor@latest go install -v github.com/haya14busa/gopkgs/cmd/gopkgs@latest go install -v github.com/josharian/impl@latest go install -v github.com/rogpeppe/godef@latest
Berikut ialah pelaksanaan pelayan:
(use-package ginkgo :straight (:type git :host github :repo "garslo/ginkgo-mode") :init (setq ginkgo-use-pwd-as-test-dir t ginkgo-use-default-keys t)) (use-package gotest :straight t :after go-mode :bind (:map go-mode-map ("C-c t f" . go-test-current-file) ("C-c t t" . go-test-current-test) ("C-c t j" . go-test-current-project) ("C-c t b" . go-test-current-benchmark) ("C-c t c" . go-test-current-coverage) ("C-c t x" . go-run))) (use-package go-guru :straight t :hook (go-mode . go-guru-hl-identifier-mode)) (use-package go-projectile :straight t :after (projectile go-mode)) (use-package flycheck-golangci-lint :straight t :hook (go-mode . flycheck-golangci-lint-setup)) (use-package go-eldoc :straight t :hook (go-mode . go-eldoc-setup)) (use-package go-tag :straight t :bind (:map go-mode-map ("C-c t a" . go-tag-add) ("C-c t r" . go-tag-remove)) :init (setq go-tag-args (list "-transform" "camelcase"))) (use-package go-fill-struct :straight t) (use-package go-impl :straight t) (use-package go-playground :straight t)
Jom tengok fungsi utama kami:
{ "name": "Launch file", "type": "go", "request": "launch", "mode": "auto", "program": "${file}" }
Mari mulakan pelayan:
taskapi/ ├── go.mod ├── go.sum ├── main.go ├── task_store.go └── task_test.go
Kini daripada terminal lain buat tugas baharu:
import ( "fmt" ) type Task struct { ID int `json:"id"` Title string `json:"title"` Description string `json:"description"` Done bool `json:"done"` }
Jawapan:
type TaskStore struct { tasks map[int]Task nextID int } func NewTaskStore() *TaskStore { return &TaskStore{ tasks: make(map[int]Task), nextID: 1, } }
Mari kita lihat sama ada kita boleh mengambilnya:
// CreateTask stores a given Task internally func (ts *TaskStore) CreateTask(task Task) Task { task.ID = ts.nextID ts.tasks[task.ID] = task ts.nextID++ return task } // GetTask retrieves a Task by ID func (ts *TaskStore) GetTask(id int) (Task, error) { task, exists := ts.tasks[id] if !exists { return Task{}, fmt.Errorf("task with id %d not found", id) } return task, nil } // UpdateTask updates task ID with a new Task object func (ts *TaskStore) UpdateTask(id int, task Task) error { if _, exists := ts.tasks[id]; !exists { return fmt.Errorf("task with id %d not found", id) } task.ID = id ts.tasks[id] = task return nil }
Jawapan:
package main import ( "encoding/json" "fmt" "net/http" ) // Server implements a web application for managing tasks type Server struct { store *TaskStore } func (s *Server) handleCreateTask(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var task Task if err := json.NewDecoder(r.Body).Decode(&task); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } createdTask := s.store.CreateTask(task) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(createdTask) } func (s *Server) handleGetTask(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } id := 0 fmt.Sscanf(r.URL.Query().Get("id"), "%d", &id) task, err := s.store.GetTask(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(task) }
Di bawah ialah beberapa ujian unit (ditulis dalam Ginkgo) untuk Kedai Tugas:
package main import ( "log" "net/http" ) func main() { store := NewTaskStore() server := &Server{store: store} http.HandleFunc("/task/create", server.handleCreateTask) http.HandleFunc("/task/get", server.handleGetTask) log.Printf("Starting server on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }
go build -o taskapi *.go ./taskapi 2024/11/14 07:03:48 Starting server on :8080
Dalam Emacs saya kemudiannya akan memanggil ginkgo-run-this-container seperti yang ditunjukkan dalam tangkapan skrin ini:
Untuk menyahpepijat API Tugas kami, kami mempunyai pendekatan berikut:
Berikut ialah pilihan untuk jenis permintaan yang berbeza:
request | mode | required | optional |
---|---|---|---|
launch | debug | program | dlvCwd, env, backend, args, cwd, buildFlags, output, noDebug |
test | program | dlvCwd, env, backend, args, cwd, buildFlags, output, noDebug | |
exec | program | dlvCwd, env, backend, args, cwd, noDebug | |
core | program, corefilePath | dlvCwd, env | |
replay | traceDirPath | dlvCwd, env | |
attach | local | processId | backend |
remote |
Berikut ialah profil penyahpepijatan pertama kami untuk .dir-locals.el:
(use-package dape :straight t :config ;; Pulse source line (performance hit) (add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line) ;; To not display info and/or buffers on startup ;; (remove-hook 'dape-start-hook 'dape-info) (remove-hook 'dape-start-hook 'dape-repl))
? Anda mungkin mahu menggunakan nilai yang berbeza untuk perintah-cwd. Dalam kes saya, saya mahu memulakan penyahpepijat dalam direktori yang pada masa ini bukan projek. default-directory ialah pembolehubah yang menyimpan direktori kerja untuk penimbal semasa yang anda sedang masuk.
Mulakan nyahpepijat:
Selepas memulakan penyahpepijat dengan profil ini, anda seharusnya melihat dalam penimbal dape-repl:
(use-package go-mode :straight t :mode "\.go\'" :hook ((before-save . gofmt-before-save)) :bind (:map go-mode-map ("M-?" . godoc-at-point) ("M-." . xref-find-definitions) ("M-_" . xref-find-references) ;; ("M-*" . pop-tag-mark) ;; Jump back after godef-jump ("C-c m r" . go-run)) :custom (gofmt-command "goimports"))
Perhatikan bahawa kami tidak menentukan sebarang perduaan/fail untuk nyahpepijat (kami mempunyai :program "." dalam .dir-locals.el). delve akan secara automatik membina binari sebelum ia melancarkan aplikasi:
# Install Delve go install github.com/go-delve/delve/cmd/dlv@latest # Install gopls go install golang.org/x/tools/gopls@latest
Mari tambahkan profil untuk menyambung ke sesi penyahpepijatan sedia ada:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest go install github.com/onsi/ginkgo/v2/ginkgo@latest go install -v golang.org/x/tools/cmd/godoc@latest go install -v golang.org/x/tools/cmd/goimports@latest go install -v github.com/stamblerre/gocode@latest go install -v golang.org/x/tools/cmd/gorename@latest go install -v golang.org/x/tools/cmd/guru@latest go install -v github.com/cweill/gotests/...@latest go install -v github.com/davidrjenni/reftools/cmd/fillstruct@latest go install -v github.com/fatih/gomodifytags@latest go install -v github.com/godoctor/godoctor@latest go install -v github.com/haya14busa/gopkgs/cmd/gopkgs@latest go install -v github.com/josharian/impl@latest go install -v github.com/rogpeppe/godef@latest
Sekarang mari kita mulakan penyahpepijat pada CLI:
(use-package ginkgo :straight (:type git :host github :repo "garslo/ginkgo-mode") :init (setq ginkgo-use-pwd-as-test-dir t ginkgo-use-default-keys t)) (use-package gotest :straight t :after go-mode :bind (:map go-mode-map ("C-c t f" . go-test-current-file) ("C-c t t" . go-test-current-test) ("C-c t j" . go-test-current-project) ("C-c t b" . go-test-current-benchmark) ("C-c t c" . go-test-current-coverage) ("C-c t x" . go-run))) (use-package go-guru :straight t :hook (go-mode . go-guru-hl-identifier-mode)) (use-package go-projectile :straight t :after (projectile go-mode)) (use-package flycheck-golangci-lint :straight t :hook (go-mode . flycheck-golangci-lint-setup)) (use-package go-eldoc :straight t :hook (go-mode . go-eldoc-setup)) (use-package go-tag :straight t :bind (:map go-mode-map ("C-c t a" . go-tag-add) ("C-c t r" . go-tag-remove)) :init (setq go-tag-args (list "-transform" "camelcase"))) (use-package go-fill-struct :straight t) (use-package go-impl :straight t) (use-package go-playground :straight t)
Kini dalam Emacs anda boleh melancarkan dape dan pilih profil go-attach-taskapi:
Dalam senario ini aplikasi sudah berjalan tetapi anda mahu lampirkan penyahpepijat padanya. Mula-mula lancarkan aplikasi:
{ "name": "Launch file", "type": "go", "request": "launch", "mode": "auto", "program": "${file}" }
Ketahui ID prosesnya (PID):
taskapi/ ├── go.mod ├── go.sum ├── main.go ├── task_store.go └── task_test.go
Mari tambah satu lagi profil nyahpepijat:
import ( "fmt" ) type Task struct { ID int `json:"id"` Title string `json:"title"` Description string `json:"description"` Done bool `json:"done"` }
Kami memerlukan fungsi pembantu:
type TaskStore struct { tasks map[int]Task nextID int } func NewTaskStore() *TaskStore { return &TaskStore{ tasks: make(map[int]Task), nextID: 1, } }
Sekarang saya memulakan penyahpepijat:
Jika saya menghantar permintaan POST seperti ini:
// CreateTask stores a given Task internally func (ts *TaskStore) CreateTask(task Task) Task { task.ID = ts.nextID ts.tasks[task.ID] = task ts.nextID++ return task } // GetTask retrieves a Task by ID func (ts *TaskStore) GetTask(id int) (Task, error) { task, exists := ts.tasks[id] if !exists { return Task{}, fmt.Errorf("task with id %d not found", id) } return task, nil } // UpdateTask updates task ID with a new Task object func (ts *TaskStore) UpdateTask(id int, task Task) error { if _, exists := ts.tasks[id]; !exists { return fmt.Errorf("task with id %d not found", id) } task.ID = id ts.tasks[id] = task return nil }
Penyahpepijat harus berhenti secara automatik pada titik putus yang ditetapkan:
Keupayaan untuk menyahpepijat ujian di Golang adalah penting. Untuk menjalankan ujian ginkgo saya menggunakan ginkgo-mode yang mempunyai beberapa ciri:
Dan sebagai output saya dapat:
(use-package dape :straight t :config ;; Pulse source line (performance hit) (add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line) ;; To not display info and/or buffers on startup ;; (remove-hook 'dape-start-hook 'dape-info) (remove-hook 'dape-start-hook 'dape-repl))
Ini ialah konfigurasi asas untuk menyahpepijat ujian Ginkgo:
(use-package go-mode :straight t :mode "\.go\'" :hook ((before-save . gofmt-before-save)) :bind (:map go-mode-map ("M-?" . godoc-at-point) ("M-." . xref-find-definitions) ("M-_" . xref-find-references) ;; ("M-*" . pop-tag-mark) ;; Jump back after godef-jump ("C-c m r" . go-run)) :custom (gofmt-command "goimports"))
Jika saya memilih profil nyahpepijat go-test-ginkgo, saya sepatutnya dapat menyahpepijat ujian:
Kini konfigurasi agak statik dan oleh itu anda tidak boleh prapilih ujian unit / bekas. Kita perlu entah bagaimana menjadikan parameter -ginkgo.focus dinamik:
# Install Delve go install github.com/go-delve/delve/cmd/dlv@latest # Install gopls go install golang.org/x/tools/gopls@latest
Selepas itu Jika saya melihat pembolehubah dape-configs, saya akan melihat nilai ini:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest go install github.com/onsi/ginkgo/v2/ginkgo@latest go install -v golang.org/x/tools/cmd/godoc@latest go install -v golang.org/x/tools/cmd/goimports@latest go install -v github.com/stamblerre/gocode@latest go install -v golang.org/x/tools/cmd/gorename@latest go install -v golang.org/x/tools/cmd/guru@latest go install -v github.com/cweill/gotests/...@latest go install -v github.com/davidrjenni/reftools/cmd/fillstruct@latest go install -v github.com/fatih/gomodifytags@latest go install -v github.com/godoctor/godoctor@latest go install -v github.com/haya14busa/gopkgs/cmd/gopkgs@latest go install -v github.com/josharian/impl@latest go install -v github.com/rogpeppe/godef@latest
Selepas memulakan penyahpepijat (dengan profil ujian fokus-debug) dalam penimbal dape-repl saya mendapat:
(use-package ginkgo :straight (:type git :host github :repo "garslo/ginkgo-mode") :init (setq ginkgo-use-pwd-as-test-dir t ginkgo-use-default-keys t)) (use-package gotest :straight t :after go-mode :bind (:map go-mode-map ("C-c t f" . go-test-current-file) ("C-c t t" . go-test-current-test) ("C-c t j" . go-test-current-project) ("C-c t b" . go-test-current-benchmark) ("C-c t c" . go-test-current-coverage) ("C-c t x" . go-run))) (use-package go-guru :straight t :hook (go-mode . go-guru-hl-identifier-mode)) (use-package go-projectile :straight t :after (projectile go-mode)) (use-package flycheck-golangci-lint :straight t :hook (go-mode . flycheck-golangci-lint-setup)) (use-package go-eldoc :straight t :hook (go-mode . go-eldoc-setup)) (use-package go-tag :straight t :bind (:map go-mode-map ("C-c t a" . go-tag-add) ("C-c t r" . go-tag-remove)) :init (setq go-tag-args (list "-transform" "camelcase"))) (use-package go-fill-struct :straight t) (use-package go-impl :straight t) (use-package go-playground :straight t)
?Perhatikan bahawa hanya "1 daripada 5 spesifikasi" (❶) dijalankan, bermakna ginkgo hanya memfokuskan pada bekas yang telah kami tentukan (❷).
Sepanjang pengalaman penyahpepijatan saya, saya telah menghargai beberapa amalan terbaik:
Atas ialah kandungan terperinci Menguasai Penyahpepijatan Golang dalam Emacs. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!