在習慣 go mod 後重新學習 git submodule

前言 使用 Golang 作為主要開發已經有五年的時間。最近因工作需要接觸到 Python 專案,並且該專案使用 git submodule 的方式來引用共同函式庫 common-lib-python。對於長久使用 Golang 的 go mod 的我來說,submodule 是一個相對陌生的概念。藉這個機會撰寫一篇文章,整理並紀錄一下 git submodule 的用法,也作為未來的參考。 這篇文章會假設已經對 git 的基本操作有一定程度的了解,並著重在 submodule 的概念、使用情境以及與 Golang 的 go mod 的差異比較。文章內容會以下列流程來呈現: 建立一個新的 Python 專案 my-python-repo 建立一個 Python 模組 my-python-module 作為 submodule 在 my-python-repo 中使用 my-python-module 作為 submodule 模擬需求變更,同時修改 my-python-repo 與 my-python-module,並分別發送 PR 與 Golang 的 go mod 進行比較 建立 Python 專案與模組 先建立兩個新的 git repo,分別是 my-python-repo 與 my-python-module。 # 建立 my-python-repo mkdir my-python-repo cd my-python-repo git init touch main.py git add . git commit -m "Initial commit" # 建立 my-python-module cd .. mkdir my-python-module cd my-python-module git init touch my_module.py git add . git commit -m "Initial commit" # 將兩個專案 push 到 Github/Gitlab 上 my-python-repo 與 my-python-module 都已經是一個獨立的 git repo。 ...

Golang Composition over Inheritance

Golang Composition over Inheritance

Golang 是一門簡潔有力的程式語言,相較於其他程式語言,更傾向於使用組合(composition)而不是繼承(inheritance),語言設計之初更是沒有提供繼承的關鍵字,這種設計哲學讓 Golang 在現代軟體開發中脫穎而出。 繼承固然有其優點,但在建構複雜的物件關係時,容易產生過於龐大的繼承層級結構。這使得程式碼難以閱讀和維護,就像是一棵盤根錯節的大樹,牽一髮而動全身。 過深的繼承層級會導致知名的「脆弱基類問題」(fragile base class problem),使得程式碼難以修改和擴展。 組合則不同,它鼓勵建立小型、專注的 struct,然後像樂高積木一樣,將這些 struct 組合成更大的結構。這種方式讓程式碼模組化,更容易理解和修改。 彈性 Golang 的 type system 支援我們靈活地組合各種 struct。可以建立一個新的 struct,並在其中「嵌入」其他 struct 作為其欄位。 type Car struct { make string model string year int } type Driver struct { name string car Car } func main() { myCar := Car{"Toyota", "Camry", 2020} driver := Driver{"John", myCar} fmt.Println(driver.name) // 輸出: John fmt.Println(driver.car.make) // 輸出: Toyota } 在這個例子中,Driver 透過組合 Car 來建立更豐富的資料結構。Driver 「has-a」 Car,而不是 「is-a」 Car,這提供了更高的彈性,讓 Driver 可以更專注在自身的邏輯。 程式碼複用 組合促進了程式碼的複用。可以建立許多小型、可複用的 struct,然後將它們組合成各種不同的結構。 繼承也能實現程式碼複用,但它也可能導致不必要的耦合,使得程式碼難以修改,因為對父類別的修改可能會影響到所有子類別。 type Engine struct { power int fuelType string } type Wheels struct { count int material string } type Vehicle struct { engine Engine wheels Wheels brand string } func (v Vehicle) getBrand() string { return v.brand } func (v Vehicle) getEnginePower() int { return v.engine.power } 在這個例子中,Vehicle 透過組合 Engine 和 Wheels 來複用這兩個 struct 的欄位和功能。Vehicle 「has-a」 Engine and 「has-a」 Wheels,並可以新增自己的欄位和方法,例如 brand、getBrand() 和 getEnginePower()。這種方式讓 Vehicle 可以專注於自身的邏輯,同時又能複用 Engine 和 Wheels 的功能。 ...

使用 Templater 來提昇 Obsidian 的生產力

這篇文章不涉及 Obsidian 完整使用說明以及 Templater 完整使用教學,僅以一個 use case 來展現如何使用 Templater 來提昇 Obsidian 的使用效率。 前言: 工作日誌,是一個甜蜜的負擔,有些人是因為公司要求,開始寫工作日誌,逐漸流於形式。 最初我也是因為工作需要「彙報」,才開始寫工作日誌。直到開始跑 scrum,開始明白 standup meeting 的意義,開始在意如何讓 stackholder 放心把事情交給我做,於是 參考了《SCRUM:用一半的時間做兩倍的事》以及實際執行的經驗,以下是我認為良好的彙報需要注意的關鍵因素: 昨天的進度:簡要說明昨天完成的具體工作,重點在成果而非過程。 今天的計劃:說明今天的工作重點,聚焦在需要達成的具體目標。如果是要跨超過一天的工作,也需要設定一個預計能完成的時間。 遇到的障礙:明確指出當前的問題或挑戰,並說明需要的幫助。當然也沒有必要什麼事都等到 standup meeting 才提出問題,有障礙應當即時反應。 與其他團隊或成員的依賴:說明需要其他人或團隊支持的地方,確保協作順暢,例如後端需要寄送的 email 內容,需要前端協助產生 html 等等。 以終為始、以成果為導向:聚焦於交付成果的進展,而非細節,例如:目前已完成 80% 的登入模組開發,預計明天可以驗收。 詳細的舉例不是這篇文章的重點,有空可以再寫成其他文章 D: 上述幾個要點,在工作日誌中被我濃縮成 markdown 的格式: XXXX-XX-XX 昨日: - 昨日1 - 昨日2 瓶頸: - 瓶頸 今日: - 今日1 - 今日2 Obsidian 高中時接觸了 markdown,大學接觸了 Hackpad,畢業後延續這個習慣,使用 Hackmd 來管理自己的工作日誌。 隨著時間的增長,開始發現 Hackmd 無論是官方 hosting 的免費版與企業版,或是 self-hosted 的 CodeMD,在單一大檔案編輯文件時,都會有明顯的延遲。若我將工作日誌拆成多個月份,又不好管理。 在 2023 年轉換工作之際,開始尋找其他 markdown 文字編輯器。 ...

開源軟體的雙面刃:免費背後的隱藏成本

每日例行地開啟最愛的 Save for later,準備觀看有哪些部落格更新、產品釋出新版本時,一行醒目的提示告訴你「我們即將關閉伺服器」。 錯愕,這是 2024 年十月底每一位 Omnivore App 難民的心情。 一、開源軟體的普及與迷思 說到開源軟體,不外乎就是 free 或是 freedom 的軟體,可以說是免費的軟體,也可以說是自由的軟體。 早期的 LAMP Stack 相較於 Microsoft 的 Windows Server、IBM 或是 Oracle 付費的服務,每個組件都是免費的,廣泛受到 Geek 到大中小型企業的使用 同時,開源軟體顧名思義是開放原始碼,在授權許可下可以自由使用、修改、分享,許多人將開源與安全劃上等號。 二、開源軟體的安全現況 多雙眼睛的侷限 即便開放原始碼,所有人都能查看開放原始碼,如果有任何 bug 或是安全漏洞,都「應該」會被發現。 我使用「應該」一詞,正因為許多人(包含我)在安裝開源軟體時,就如同安裝閉源軟體一樣,並不會查看全部的程式碼,確認安全後才進行安裝。而是做一個「大家都看過了,應該很安全」的假設。 例如當你想用 Nginx 取代 Apache 時,可能僅會比較兩者的 feature 與 benchmark,並不會查看內部 tcp socket 是如何管理等等,或是 buffer overflow 時的錯誤處理機制。 開源是否暴露更多漏洞? ####安全問題的挑戰 當我們將軟體開源,等於是將所有安全問題也給暴露了出來。 舉例來說,假如我們 self-hosted 一個 NAS 服務,這個 NAS 使用指定的 port 進行 web 與 API 的存取,例如 :8787。 當一個 Hacker 看到該 NAS 的 Auth 並沒有任何 rate limit,就可以直接在 NAS 內進行 port scan 或是 brute force 攻擊,取得這個 NAS 服務的控制權。 ...

Golang 1.22 中 http routing 的改進

Golang 作為一個偏向 server 應用的程式語言,一般的 web server 並不會直接使用原生的 package net/http,而更多的使用 gin-gonic/gin 或是 gorilla/mux,後來也有 labstack/echo 以及 go-chi/chi 等等選擇,在效能、輕量、好維護、好擴充中,都能找到對應的 third party package,其中的原因不外乎是原生的 package 提供的功能過於簡潔。 好在 1.22 中,官方改進了 net/http 中對於多工器、路由,甚至出了一篇部落格,現在更可以「大膽的」直接使用 standard library。 Path Parameter 若要將應用的 Web API 定義成 RESTful,我們會使用 /資源/{資源唯一識別符}/子資源/{子資源唯一識別符} 來定義路徑。假如要獲取一個使用者的訂單,則會使用 GET /users/1/orders 來獲取。在 1.22 以前,我們只能定義到 /users,再自行解析往後的 path: http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) { subPath := strings.TrimPrefix(req.URL.Path, "/users/") if len(subPath) == 0 { xxx } else { ooo } ... }) 而在 1.22 中新增了 net.http 對 path parameter 的支持,我們可以直接使用 (*http.Request).PathValue("xxx") 來獲取: http.HandleFunc("/users/{user_id}", func(w http.ResponseWriter, r *http.Request) { userID := r.PathValue("user_id") ... }) 不過也帶來一些限制 ...

使用 TinyGo 與 Raspberry pi pico 實現溫濕度感測器

前言 作為嵌入式系統學習的微小專案,我決定使用 TinyGo 來實現一個簡單的溫濕度感測器。過去我大多使用 Arduino 作為微控制器(MCU)開發平台,但這次想嘗試使用 TinyGo 來進行單片機的學習。 TinyGo 是 Go 語言的一個子集,專門針對小型設備和微控制器進行了優化,使得我們可以在資源受限的硬體上運行 Go 程式。 全部的程式碼都可以到 omegaatt36/pico-bme280 中找到。 硬體選擇 Raspberry Pi Pico 我選擇了 Raspberry Pi Pico 作為本專案的主控板。Pico 是一款基於 RP2040 晶片的微控制器開發板,具有以下特點: 雙核 ARM Cortex-M0+ 處理器,時脈可達 133 MHz 264KB 的 SRAM 和 2MB 的板載閃存 支援 USB 1.1 主機和設備功能 低功耗睡眠和休眠模式 可編程 I/O(PIO)狀態機 30 個 GPIO 引腳 便宜 價格僅需要 5 美元,後繼款的 Pico 2 仍然維持 5 美元,還額外增加了 RISC-V 架構的支援,可以一次玩到兩種處理器架構。 Pico 的這些特性使其非常適合用於各種嵌入式專案,包括我們的溫濕度感測器。 BME280 感測器 最初我購買了 BMP280 感測器,但後來發現它只能測量溫度和氣壓,無法測量濕度。因此,我轉而選擇了 BME280 感測器,它可以同時測量溫度、濕度和氣壓。 BME280 是一款精確度高、體積小、待機功耗低的環境感測器。但當連續偵測時,通過的電流會導致 sensor 功耗上升,進而導致測量到過高的溫度。 ...

A smarter way to change directory: zoxide

在日常的開發工作中,我們經常需要在不同的目錄間切換。雖然 cd 命令已經足夠好用,但如果有一個更聰明的工具能記住我們最常用的目錄,並讓我們用最少的按鍵就能快速跳轉,那豈不是更棒? 這就是 zoxide 的用武之地。zoxide 是一個由 Rust 編寫的「更聰明的 cd 命令」,靈感來自 z 和 autojump。它會記住你最常使用的目錄,讓你只需輸入幾個字符就能快速跳轉。 zoxide 的主要特性 自動匹配: 不需要輸入完整路徑,zoxide 會根據輸入自動匹配最相關的目錄: 自動紀錄過去的目錄: zoxide 會記住你最常使用的目錄,讓你只需輸入幾個字符就能快速跳轉。這些資料可以使用 zoxide query 來查詢,或是 zoxide edit 來管理。 互動式選擇: 結合 fzf,可以互動式地選擇目標目錄 輕量快速: 用 Rust 編寫,啟動迅速,幾乎不會影響 shell 的啟動時間 安裝與配置 zoxide 的安裝可以參考 官方文件,我們就不多贅述了。 安裝完成後,我們需要在 shell 的配置文件中添加初始化命令。以 zsh 為例,在 ~/.zshrc 的末尾添加: eval "$(zoxide init zsh)" 重新打開終端或執行 source ~/.zshrc 後,zoxide 就可以使用了。 使用方法 zoxide 的基本用法非常直觀,經過自動執行 zoxide init zsh 後,我們可以使用 z 和 zi 自動匹配: ❯ ls boo bar baz ❯ z bo ❯ pwd /home/raiven/boo 即便沒有輸入完整路徑,也能夠自動匹配最相關的目錄 ❯ pwd /home/raiven/dev/omegaatt-blog ❯ z ❯ pwd /home/raiven ❯ z blog ❯ pwd /home/raiven/dev/omegaatt-blog 互動式選擇: ❯ zi ~ < 98/98(0) 296.0 /home/raiven/dev/omegaatt-blog 124.0 /home/raiven/dev/bookly 50.0 /home/raiven/dev 24.0 /home/raiven/dev/test 12.0 /home/raiven/ 這裡的 z 命令就像一個更聰明的 cd,而 zi 則是互動式版本。 ...

使用 Bitwarden 與自架後端 Vaultwarden 來管理密碼與 2FA Authenticator

使用 Bitwarden 與自架後端 Vaultwarden 來管理密碼與 2FA Authenticator

在尋覓有哪些 self-hosted 專案好玩時,偶然發現了 1password、LastPass 的開源替代方案,甚至後端資料庫能自架,決定架來用用看。 為什麼要自架?完全控制資料、隱私考量、或者純粹是學習和折騰 (符合 self-hosted 精神)。但控制權也意味著責任,特別是安全和備份。 這篇會專注在使用 Bitwarden 客戶端搭配輕量級的 Vaultwarden 後端。 使用 Bitwarden 來管理密碼 Bitwarden 是一款流行且功能強大的密碼管理工具,它提供了一個安全的方法來存儲和管理所有密碼。作為一個開源產品,Bitwarden 允許用戶選擇自行托管其服務,這意味著用戶可以在自己的服務器上運行 Bitwarden,從而更好地控制自己的數據安全。 Bitwarden 的特點 安全性: Bitwarden 使用端到端加密,確保只有您可以訪問您的密碼。 跨平台支持: 支持 Windows、macOS、Linux、Android 和 iOS。 易於使用: 提供直觀的用戶界面和簡單的操作流程。 開源: 開源,增加了透明度和安全性。 Bitwarden 同時支援基於時間的一次性密碼,讓 TOTP 也能自動填入。也能透過 github.com/scito/extract_otp_secrets 來提取 Google Authenticator 內的 2FA 資訊,儲存進 Bitwarden 中。 Bitwarden 開源了 client 與 server,在 server 端的選擇有以下: 使用 Bitwarden 提供的官方服務,又分為免費跟付費,但這個選擇就跟 1p 沒太多區別。 自架 Bitwarden 提供的 open source server,由於是使用 C# 與 mssql,吃的記憶體著實太多。 自架 Bitwarden 相容的後端,我採用的是 rust 實做的 Vaultwarden,搭配 sqlite,記憶體使用量與官方的 C# 不是一個量級的。 Bitwarden 架構 bitwarden 的 local storage 都是儲存加密後的密碼資料,不會使用明碼儲存,故上傳到 server 上的也僅僅是加密後的密碼資料。 ...

在 Firefox 上使用 PWA 將網頁應用安裝成 Desktop App

前言 開應用時,跨平台的桌面解決方案時常困擾開發者,在 Linux Desktop 上要支援多種桌面協議與桌面管理系統,更是複雜許多。時常會看到 Electron 等等基於 chromium 的技術。 當然也可以像是 zed.dev 這樣的 geek 精神,自己寫了一套跨平台的 GUI 框架,但多數新創公司可能沒有這些資源來實現。例如已經非常成熟的 Notion.so、Obsidian 等等都是基於 Electron 實現的。 我並非一個 anti-Electron 或是 deGoogle 的人,但試圖找到其他解決方案正式樂趣之所在。 PWA(Progressive Web App) 詳盡的 PWA 技術可以到 mozilla 的文件中查看,對我來說只要可以達到目的:可以在 Windows、Mac、Linux 上封裝成 Desktop Aplication 即可。 官方支援 mozilla 官方有一個文件針對「安裝網頁應用到桌面環境」的歷史講解,可以參考官方文件。 文中提到過去將網址「儲存」在桌面上的類似「書籤」的方法(SSB),以及與 PWA 的不同之處:PWA 的使用者資料是儲存在桌面環境中。 PWA Add-on 上述文件內有提到需要使用 Fan-maded 的 PWA Add-on 來安裝 PWA:Progressive Web Apps for Firefox by Filip Štamcar。 他是一個開源的 Firefox 擴充套件(filips123/PWAsForFirefox),具有完整的文件。 安裝過程 在 Firefox 瀏覽氣上安裝 PWA Add-on 瀏覽器會自動跳到設定頁面,首先會需要同意 EULA 接著會需要安裝 connector,需要透過該 connector 才能連結瀏覽器本身與 extension。 connector 由 Rust 編寫,使該 extension 可以在跨平台的桌面環境中管理 firefox 設定檔、runtime 等等未開放給 extension 存取的資源。 Windows: Windows 的 connector 除了手動安裝外,也推薦使用 winget 來安裝,類似於 apt/yum 的套件管理工具。 Linux Linux 的 connector 可以到 github release 上找到,例如我的作業系統是 OpenSUSE Tumbleweed,則可以下載 .rpm 檔案。 sudo zypper in ./firefoxpwa-2.12.1-1.x86_64.rpm Debian/Ubuntu(DEB): Redhat/Fedora/OpenSUSE(RPM): 接著會需要安裝不同於原生 Firefox 的 runtime,根據描述,會下載並由 connector 自動修改成支援 PWA 的 runtime。 到這邊已經完成 PWA extension 的安裝,可以嘗試使用 PWA 的方式來安裝 web app to desktop app。 首先需要打開你想要安裝成 Desktop App 的網頁,如果在不同的網頁嘗試設定,extension 會自動判斷該網頁 CORS Error,即便是 https://www.notion.so 與 https://notion.so 也屬於不同的 Origin。 接著點擊 PWAForFirefox 的圖示,可以看到已經安裝好的 extension。 接著一步一步的跟著指引設定好該 PWA 的設定。 完成,可以在應用程式選單內找到剛剛建立的 PWA 應用程式。 開啟後可以自動開啟網頁應用程式,並具有獨立的設定檔。 使用感受 三四年前在 Windows 上習慣使用 Notion Desktop 版本,在 Linux 上反而已經習慣要用網頁來打開,這次嘗試使用 PWA 後倒是經常忘記有將它安裝成 Desktop Application。 ...

透過內建的 pprof 工具來分析 Golang 發生的 memory leak

前言 某天下午,公司的 cronjob daemon 無預警的被 GCP OOM Kill 了,且程式碼沒有看出明顯的原因。 根據過去的經驗,local 開發時會使用 go tool pprof 來分析 CPU profile 或是 memory 與 trace 的問題,詳細可以參考 Go 官方文件。 由於我們的程式碼是一個基於 gin 的 http service,因此可以使用 gin 提供的 pprof 來快速建立 endpoint。 gin pprof gin 的 pprof package 提供了數個基於 net/http/pprof 的 endpoint,可以分別為: /: 基本的 pprof 的 static page,可以分析 CPU 與 memory 的問題。 /cmdline: 分析 command line 的問題。 /profile: 分析 CPU profile 的問題,可以透過 query string 來指定 CPU profile 的 duration。 /symbol: 分析 symbol table 的問題。 /trace: 分析 trace 的問題。 /goroutine: 分析 goroutine 的問題,這也是本文中重點查看的 endpoint。 /heap: 分析 heap 的問題。 可以在 http server 中加入以下程式碼來啟用 pprof: ...