更新於 2024/10/14閱讀時間約 13 分鐘

閒談軟體設計:Developer eXperience

圖片來源:www.freepik.com

圖片來源:www.freepik.com

最近公司陸陸續續排入了 developer experience 的改進項目,自此,好像這樣,就變成了不再只是關注 UX 也在乎 DX 的好公司,但 DX 的定義是什麼?即便是找到了這段影片

大家真的都認同 DX = (Productivity + Impact + Satisfaction)Collaboration 這個公式嗎?或是認同這句話:

Optimizing DevEx is about creating a collaborative environment where developers can be their most productive, impactful, and satisfied.

但今天我不是要討論 Developer Experience 具體的做法,而是討論 Developer Experience 是否該對標的程式碼有影響?

講古時間

在開始討論我前先來講古一下,多古呢?我寫的第一行程式是在我國中二年級的電腦教室裡 (國小舅舅好像有教過我寫程式,但內容完全忘記了),用 QuickBasic 語言在 386 的電腦上寫簡單的 hello world,然後用以現在標準來看很醜的選單按執行,接著螢幕上就出現了 hello world。現在事後回想,第一次寫程式經驗不算差,因為 QB 在當時就提供了一個還不算差的 IDE,不需要一堆 command line 的指令,就可以完成編譯、連結與執行的動作。

上高中後,語言雖然從 QB 轉成 VisualBasic,但語法沒有太大的改變,但整個畫面變漂亮了,用 GUI 工具拖拉元件到視窗上,對著按鈕點兩下,就可以開始寫程式,當時也不知道那個函式是 MVC 中的 controller,在裡面寫個數十行的程式,程式可以動,做出來的 UI 也很漂亮,但... 那時的程式碼以現在的我來看:爛透了。啊~ 那時還有用 FrontPage/Dreamweaver 寫網頁,但這兩個工具產生出來的 HTML 根本是...,最後工具只是用來預覽。

上大學後,因為是電子工程系,初期寫的程式比較偏硬體,C (用 Turbo C)、組合語言 (用筆記本,真的是筆記本,然後下指令組譯)、HDL (算是有 IDE,但不太好用,加上 PLD 燒錄器不是每人一台,編譯完的檔案要用 3.5 吋磁片,複製到老師的那台電腦上,然後燒錄到 16v8 或 22v10 晶片上,再將晶片插到麵包版上測試),說真的,以現在的 DX 標準,體驗非常差勁,但當時除了組合語言,我其實蠻樂在其中的,看到 LED 燈以期望的方式閃爍,非常開心。

據說,現在電子系已經沒再用燒錄器了,全部都是軟體模擬。

中期寫的程式比較偏應用,寫過 Pascal (用 Delphi) 和 Java (用 JBuilder),那時用 Java applet 寫一個可以在網頁上畫畫的小畫家,和一個工程計算機,我對於我設計的 GUI 非常滿意,當時讓我痛苦的不是 GUI 或 IDE,而是沒有完整的資料結構與演算法知識下,解析輸入的式子,建立 AST (Abstract Syntax Tree) 並用後序的方式計算出結果,或是在小畫家程式裡支援 undo/redo。

後期開始接觸網頁和資料庫應用,用 PHP (用 Dreamweaver) 寫購物車,剛開始覺得很新鮮,Dreamweaver 幫忙做了幾件事:syntax highlight 和FTP 上傳 (佈署),但愈寫到後面愈不喜歡 PHP,因為要到執行時才發現語法錯誤真的是一件很花時間的事。

我先說,我沒有要戰 PHP,畢竟我寫 PHP 時的版本跟現在差太多了,我後續也沒有再研究 PHP,說不定現在 PHP 已經很好寫了,這只是當時的感想。

研究所應該是我工作之前,被要求寫最多程式碼的兩年吧!幾乎每門課都有要寫程式的作業。其中一門課,一個學期分七次作業,用 C++ 搭配 GTK 完成一個 UML Class Diagram Editor,作為助教,帶大學學弟妹用 Visual C++開發遊戲,自己卻是用 Eclipse + CDT 寫作業,那體驗根本是悲劇,再加上 C++ 要自己管理記憶體,寫作業時就是在不斷地處理 segment fault。但是,我很感激這兩年紮實的課程。

聊聊好的體驗

古遠故事說完了,我只是想說,好的體驗其實很簡單:讓學習的循環變快,就會有好的體驗。軟體開發這件事,是工程師在學習如何將 domain know how 轉成軟體的一個過程,學習本質上是不斷試錯,所以,你不會希望三個月後發現整合不起來,你不會希望半年後才知道規格錯了,你當然也不會希望幾分鐘後才知道語法錯了、變數名稱錯了,如果這個循環能愈快,工程師就愈有成就感。

因此就來聊聊幾個,我認為很有感的提升吧!第一個是記憶體管理,對於沒寫過 C/C++ 的人來說應該超級無感,但我當初開始學 Java,發現不用再寫 delete 時,那感動真的是無法形容,終於不用擔心漏掉 delete 會有 memory leak,多一次 delete 都會讓程式 crash 的窘境。

第二個是 incremental build,我忘記第一次體驗到 incremental build 是在哪個 IDE 了,早期的 IDE 是你按下執行,然後 console 會跑出一堆編譯的過程,然後告訴你哪裡錯了,比較差的 IDE 是你要去讀 console 的訊息,好一點的 IDE 會在編輯器上標示錯誤,但你還是得等。有了 incremental build 後,一邊打字 IDE 一邊進行編譯,馬上在錯誤的地方跑出紅色的蚯蚓底線,沒錯就是立即!那體驗真好。

第三個是 code autocomplete 或 code assistant,這裡不是指最近 AI 加持的版本,單純是 IDE 編寫程式時,跳出來的提示,在一個大型的專案中,工程師得將很多事情放入腦袋,小到變數和函式名稱,大到架構設計,所以看到工程師進入流的狀態時,千萬別去打斷他。有了 code assistant,至少可以讓腦袋去處理更有用的東西,而不是記住數十個變數或函式名稱。

第四個是 hot reload,在第一家公司上班時,同時開發後端與前端,即便有當時每修改一小端程式就要把 local 的 server 停掉重新啟動,好載入新版本的程式,同事推薦安裝 JRebel,它會偵測檔案的變動,自動載入變動的部分,省去相當多的時間,後來,Spring boot 也加了類似的功能,以及 React 等前端框架也都有類似的功能。

最後是自動化,同樣在第一家公司,當時 server 是由 IT 管理的 VM,上面裝好 J2EE 的 container server,所以每當要上版,就是要手動將編譯完並打包好的 WAR 檔 (不是 JAR 檔喔) 用 J2EE 的 container server 介面上傳,然後等待重新啟動應用,手動的步驟愈多,就愈容易出錯。後來,第二家公司完全由 Jenkins 完成所有的工作,那種 code 只要 push 上去就會自動佈署的感覺,就是非常好的體驗。

待商榷的體驗

上述我覺得好的體驗,大多不需要 production code 有對應的修改或調整,主要來自工具的優化,像是 IDE、CI/CD 等。但接下來的體驗,須對 production code 進行修改,甚至影響到撰寫,但帶來的體驗我覺得是有待商榷的。

假型別

這幾年都是用 JavaScript 寫後端,弱型別語言在初期可以有非常高的生產力,不用定義類別,變數不用宣告型別,它就是能動 (it works)。但當您的程式規模開始成長,開始進入維運,當團隊裡的人愈來愈多時,弱型別就成了一個包袱,這也是當初 TypeScript 發展的動機之一。

新人開始看不懂程式,為什麼同一個概念,在這裡有某個 property,在另一個地方沒有某個 property,您永遠不知道拿到的物件是完整版、簡化版還是特殊版。於是,奇怪的 bug 開始出現,然後得依賴單元測試或整合測試來確認本來編譯器能幫您檢查的事情。

為了讓新人好上手,於是團隊決定加入 JSDoc,於是花了大量的時間用 JSDoc 定義型別,剛開始是有幫助的,至少 Visual Studio Code 能提示您,您拿到的是某個型別的物件,但事實真的是這樣嗎?JavaScript 不會因為 JSDoc 就突然變成了強型別語言,您還是可能拿到預期外的物件,您還是得依賴測試確認編譯器能檢查的事情。

最後,您還可能得到多個版本的 JSDoc 以及過期的 JSDoc,因為,JSDoc 是文件不是程式。

少寫 Code

Java 這幾年試圖改變「囉嗦」的形象,其中最讓開發者煩躁的是定義一個類別後,您還需要為每個屬性定義 getter 和 setter,並覆寫 equalshashCode 函式,於是我在第一家公司時,同事推薦我使用 Lombok

在一開始確實帶來生產力的提升,因為只需要簡單的幾個 annotation,就會在 bytecode 層級自動生成上述的程式碼。沒錯,bytecode 層級,因此,不只 source code 需要 annotation,連編譯過程都需要 Lombok,非常深的耦合。

但問題是,很少人認真思考,為什麼一定要提供 getter 和 setter?如果一開始就提供 getter 和 setter,直接把屬性宣告為 public 不是更簡單?又或是為什麼一定要覆寫 equalshashCode 函式?一個 Entityequals 是根據 ID 還是屬性的值?那 Value Objectequals 呢?不用管,反正自動生成很快。

在 Java 17 引入 record 後,宣告 Value Object 確實變簡單了,很可惜,Entity 仍然沒有很好的支援 (record 是 immutable 物件)。

自動生成

剛剛的自動生成還是小的程式碼片段,為了少寫程式碼,很多工具還能自動生成整個 server 或是 client。前陣子,團隊試著在不同專案內部溝通上使用 tRPC,有型別支持且能自動生成 client (不用自己串接 API),能提升 DX。對我來說,這不是什麼新鮮事,Java RMI 就是類似的技術。

對了,對我來說,tRPC 產生的不是 RESTful API,不是 HTTP 加上 JSON 就是 RESTful API (參閱 閒談軟體設計:休息時間)。

在第一家公司時 (又是第一家公司,笑),當時用 multi-tier 架構,前端、服務和資料是獨立運行的 server,彼此之間靠 RESTful API 溝通,為了減輕負擔,當時使用了 Apache CXF 自動生成 client,服務提供者與使用者共用一個 interface 定義,使用者就能呼叫使用服務,不需要自己串接 API,相當方便。

那為什麼我覺得有待商榷呢?任何 RPC 的理想是封裝複雜的底層,在本地端提供一個函式可以呼叫遠端的服務,您不需在意也不用在意底層是怎麼實作的。即便如此,仍然會有一個底層的協定,但這個協定不是您能控制的,因此不適合作為公開的 API 使用,有幾個原因:

  • 為了實現序列化與反序列化,通常會和語言高度耦合,僅少數 RPC 技術有提供多語言支援,因此像 Java RMI 兩端都必須是 Java。
  • 即便使用 XML 或 JSON 格式作為中介,減少與特定語言的耦合,仍有一些型別需要特殊處理,例如:JSON 沒有原生的 Date
  • 即便少數 RPC 技術有提供多語言支援,也很不容易做到完全符合標的語言的 coding convention。像是之前串過一個服務商的 API,由於是使用 C# 開發,API 裡的屬性都是用 upper camel case 命名,自定義的抽象層至少可以轉成專案使用的 lower camel case 後再往外丟。
這邊要說一下,Apache CXF 可以用 JAX-RS 完全自訂出 RESTful API,包含 HTTP 動詞、path 變數、query 變數、header 變數等。

也就是說當您在意 API 的形式時,這類 RPC 技術不一定能幫助到您。實際上,除了自動生成 client 外,也有自動生成 server 的技術,但前提是,您的應用只是簡單的 CRUD 應用程式。

取捨

我不是說不能用 xx 技術,而是該怎麼取捨,如果不會影響 production code,問題不大。但如果會影響,那 DX 只是眾多考量的其中一個,對該技術的掌握度、對架構完整性的影響、是否公開等因素,都會是需要考量的,對我來說 DX 的優先度沒其他因素高,可能是我從以前就對自動生成的程式碼不感興趣吧!


後記

寫這篇文章時,突然想起了一個有趣的東西,高中時學長看到很多人都用兩個迴圈寫九九乘法表時,曾問:要不要試試只用一個迴圈寫九九乘法表?不知道有沒有人有興趣試試?

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.