[翻譯] TypeScript 寫給 C#/Java 工程師

前言

工作上有開發前端專案的需求,主流使用 JavaScript。
而聽說了一些強型別語言的優點,加上我有開發 C# 的經驗,我常常改用 TypeScript,
但實際上確常常覺得反而更笨重了,覺得開發上不太順暢。

一個是 node_module 的問題

相依性的管理十分麻煩,常常有新版的模組更新,但其相依的模組卻尚未更新。
運氣好等幾周就會有更新,運氣不好是模組的開發者已經不在維護,目前我的處理方式是使用 ncu ,
但也有其極限,而在其上花費的大量時間,反而拖累開發的速度。

TypeScript

另一個麻煩的點是使用 TypeScript ,雖然我有 C# 的經驗,也熟悉 JavaScript,
但是常常覺得開發仍然不順,直到閱讀官方的這篇文章,才稍解一些疑惑。
並稍作記錄如下。

翻譯

本文出處為TypeScript for Java/C# Programmers
我不會逐字翻譯,只針對我認為重要的觀念作筆記。

Class 的反思

在 C#/Java Class 是程式的基本單位,這類的程式語言我們稱之為 mandatory OOP
而在 TypeScript/JavaScript 當中 Function 才是程式語言的基本單位。
Function 可以自由的存在,而不需要寄生在 Class 之中。
這帶來了靈活性的優點,在思考 TypeScript 時,應以此作為考量。
因此,C#和 Java 的某些結構,如 Singleton 和 Static Class 在 TypeScript 中是不必要的。

Type 的反思

Nominal Reified Type Systems vs Structural System
C#/Java 使用 Nominal Reified Type Systems ,
這表示 C#/Java 程式中所使用的值或物件,一定會是 null 或基本型別(int、string、boolean 等…)或具體的 Class。
而在 TypeScript 中,類別只是個集合。
你可以這樣描述一個值同時可能是 string 或 number

1
2
3
let stringNumber: string | number;
stringNumber = 1;
stringNumber = "123";

同時在作型別的推導的時候, C#/Java 會有具體的型別,而 TypeScript 會使用結構作推導,
參考下面的例子,我們沒有給 obj 具體的型別,但是在結構上符合 Pointlike 與 Named 所擁有的屬性,
使得呼叫方法時的型別推導在 TypeScript 是合法的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Pointlike {
x: number;
y: number;
}
interface Named {
name: string;
}

function logPoint(point: Pointlike) {
console.log("x = " + point.x + ", y = " + point.y);
}

function logName(x: Named) {
console.log("Hello, " + x.name);
}

const obj = {
x: 0,
y: 0,
name: "Origin",
};

logPoint(obj);
logName(obj);

這樣的設計在 runtime 的時候,我們無法具體的知道型別是什麼,
下面的概念在 TypeScript 將不可行,
即使用 JavaScript 的 typeof 或 instanceof 你也只會拿到 “object”,而非具體的型別

1
2
3
4
// C#
static void LogType<T>() {
Console.WriteLine(typeof(T).Name);
}

即使如此,這樣的設計在編譯時期的檢查在實務上是足夠的,
另外兩個例子與長時間開發 C#/Java 的概念上可能會有所不同,

Empty Type

Typescript 允許設計無屬性的 Type,而基於 Structural System
你可以建立一個方法傳遞任何物件進去(而不是用 any 或 object 去定義傳入值)

1
2
3
4
5
6
7
8
class Empty {}

function fn(arg: Empty) {
// do something?
}

// No error, but this isn't an 'Empty' ?
fn({ k: 10 });

Identical Types

下面的程式不會發生錯誤,但是可能會讓 C#/Java 的開發者有點意外

1
2
3
4
5
6
7
8
9
10
11
12
class Car {
drive() {
// hit the gas
}
}
class Golfer {
drive() {
// hit the ball far
}
}
// No error?
let w: Car = new Golfer();

一樣基於 Structural System 的設計,有相同的屬性與方法簽章的兩個不同物件,
是允許這樣子的行為,而以官方的看法來說,實務上並不太容易發生這種情形(兩個不同的模型,卻有相同的方法、屬性等…)。

小結

以下是 C#/Java 與 TypeScript 的相同和不同之處:

相同之處:

  • C#/Java 和 TypeScript 都是物件導向編程語言。
  • C#/Java 和 TypeScript 都使用類(class)和物件(object)的概念。
  • C#/Java 和 TypeScript 都有靜態類型系統。

不同之處:

  • C#/Java 的類型系統基於類型聲明,而 TypeScript 的類型系統基於屬性的兼容性。
  • C#/Java 的類型在 runtime 是存在的,而 TypeScript 的類型在 runtime 時是不存在的。
  • C#/Java 類型之間的關係通過繼承關係或共同實現的接口來定義。而在 TypeScript 中,類型之間的關係是通過屬性的兼容性來定義。

心得

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

TypeScript 的類型設計讓我想起了鴨子測試,也讓我想起 golang 對 interface 的處理方式。
甚至想起一些對物種起源的探討,先有分類還是先有特性呢 ? 真實的世界分類反而是人類主觀強加上去的。
TypeScript 這樣的設計似乎比起強制性的類別設計(C#/Java),更貼近真實的世界。

參考

(fin)

[實作筆記] 不要用 Homebrew 安裝 nvm

前言

在開發 Node.js 應用程式時,我們可能需要在不同的 Node.js 版本之間進行切換。
在這種情況下,一個方便的解決方法是使用 Node Version Manager(nvm)。
而 Homebrew 則是一個流行的 macOS 套件管理工具。
在這篇文章中,我們將探討為什麼官方不建議使用 homebrew 安裝 nvm。

情況

首先,值得注意的是,nvm 官方文件明確表示不建議使用 Homebrew 安裝 nvm。
主要是因為 Homebrew 的安裝方式可能會干擾 nvm 的運作,導致一些奇怪的問題。
nvm 官方建議使用其提供的安裝方式,以確保 nvm 能夠正常運作。

那麼,標準的 nvm 安裝方式是什麼呢?
您可以在 nvm 的 GitHub 頁面上找到完整的安裝指南,這裡不再贅述。
簡而言之,您需要在終端機中運行一個安裝腳本,該腳本將安裝 nvm,並更新您的 shell 啟動腳本以使其能夠正常運行。

如果您仍然希望使用 Homebrew 安裝 nvm,可以參考一些額外的資源。
這裡有一篇詳細的文章,介紹了如何使用 Homebrew 安裝並配置 nvm。
不過,需要注意的是,這種方法仍然不是官方建議的方式。

實作

不管您選擇哪種方式安裝 nvm,一旦完成後,您需要在終端機中設置 nvm 的環境變數才能正常運行。
如果您希望在不關閉終端機窗口的情況下讓變數生效,可以使用以下命令:

1
source ~/.bashrc

如果您使用的是 zsh,則需要使用以下命令:

1
source ~/.zshrc

這將重新載入您的 shell 啟動檔案,使 nvm 環境變數生效。

參考:

(fin)

[AI 共筆] macOS Monterey 5000 Port 佔用原因與解方

前言

在 macOS Monterey 上如何處理開發伺服器使用端口被佔用的問題,
若使用其他 macOS 版本,請查看相關頁面。

問題

更新到最新的 macOS 作業系統時,發現我的前端網站,
顯示類似於「Port 5000 already in use」的訊息。

透過執行

1
lsof -i :5000

發現正在佔用該端口的進程名稱為 ControlCenter,這是一個原生的 macOS 應用程式。
即使你使用強制終止它,它還是會自動重新啟動。

處理方式

原來使用該端口的進程是 AirPlay 伺服器,
你可以在「系統偏好設定」 >「共享」中取消勾選「AirPlay Receiver」以釋放 5000 端口。

小結

網路大多的中文解法是關閉”隔空播放接收器”,在新版的 OS 找不到,可能是語言差異。
所幸找到英文的解法,順利停止佔用 port 的服務。

參考

(fin)

[AI 共筆] 自動重新加載 Node 專案工具比較

前言

在開發Node.js應用程序時,開發人員通常需要經常重新啟動應用程序來看到他們所做的更改。
這真的很煩,因為每次修改都需要重新啟動應用程序。
為了解決這個問題,開發人員可以使用許多自動重新加載工具,
例如 nodemon 和 pm2。

比較

首先,讓我們來比較 nodemon 和 pm2 自動重新加載功能。

nodemon

一個簡單易用的自動重新加載工具,可以在幾分鐘內完成安裝和設置。
它會監聽文件的更改,並在發現更改時自動重新加載應用程序。
此外,它還可以在命令行中顯示有用的日誌信息,例如錯誤和堆棧跟踪。
但是 nodemon 需要不斷監聽文件系統的更改,因此它會帶來一些性能開銷。
此外,它也不能用於在分佈式系統中部署 Node.js 應用程序。

pm2

pm2 是另一種自動重新加載工具,具有進程管理功能,例如重啟和停止應用程序,監聽應用程序的日誌等等。
它可以輕鬆地在分佈式系統中部署 Node.js 應用程序,並可以與其他 pm2 命令進行集成,
例如性能監測和自動縮放。 然而,相對於 nodemon,pm2 更為複雜,需要更多的配置和設置。
此外,某些 pm2 命令可能會影響系統的性能,例如性能監測和自動縮放。

小結

在開發 Node.js 應用程序時,自動重新加載工具是非常重要的。
如果開發人員只需要簡單的自動重新加載功能,並且不需要在分佈式系統中部署應用程序,
那麼 nodemon 是一個不錯的選擇。
如果需要進程管理功能和在分佈式系統中部署Node.js應用程序,
那麼 pm2 是一個更好的選擇。

參考

(fin)

[AI 共筆] tsconfig 中的 tsx 設定

前言

本篇是 AI Knows 的第一篇,ChatGPT 問世後,
在學習開發上,我認為又到了一個新的領域,
很多人開始作分享對 ChatGPT 的看法,在我看來還是社群時代的蹭流量作法。
更進一步的有人開始透過 AI 實作商業應用,商人果然是跑最快的。
另外一種,用嘴涮夢、涮 AI,開始賣書賣課的,我認為接近灰色地帶,就不予置評了。

問題

我正在替專案配置 EsLint 文件,過程當然直接請 AI 代勞,
但是有一些設置我不那麼清楚也懶得查文件。
就一樣也請教 AI 囉。

對話記錄

Q:tsconfig.json 中的 jsx 屬性為何?**

jsx 屬性是用來指定 TypeScript 如何處理 JSX 語法的選項。
JSX 是一種 JavaScript 語法擴展,允許在 JavaScript 代碼中直接使用
HTML 標籤來描述 UI 組件,使得開發 Web 應用程式更為方便。

在 tsconfig.json 中,jsx 屬性可以設置為以下值:

“react”:表示使用 React 的 JSX 語法。
“preserve”:表示將 JSX 語法保留為原始的 JSX 代碼。
“react-native”:表示使用 React Native 的 JSX 語法。
當設置為 “react” 時,TypeScript 會將 JSX 轉換為對應的 JavaScript 代碼,
這樣就可以在瀏覽器中運行。而當設置為 “preserve” 時,TypeScript 不會對
JSX 進行任何轉換,而是將其保留為原始的 JSX 代碼。

Q:為什麼我看到的設定是 “jsx”:”react-jsx”

在 TypeScript 4.1 之前,jsx 選項僅有兩個可選值,分別是 preserve 與 react。
其中 preserve 會保留 JSX 並將其轉換為對應的 React.createElement 函式,
而 react 則是直接轉換為對應的 JavaScript 代碼。

在 TypeScript 4.1 中,又新增了一個選項 react-jsx,用來代表 TypeScript
將會使用 @babel/plugin-transform-react-jsx 進行轉換,以此來與 Babel 兼容。

因此,如果你的 TypeScript 版本較新,通常會看到的 jsx 設定是 “jsx”: “react-jsx”,
表示 TypeScript 將會與 Babel 兼容。

小結

綜合以上問答,我們可以得知 JSX 是一種 JavaScript 語法擴展,讓開發 Web 應用程式更加方便,
而 tsconfig.json 中的 jsx 屬性用來指定 TypeScript 如何處理 JSX 語法的選項,
可以設置為 react、preserve 或 react-native 三種值,分別代表將 JSX 轉換為對應的 JavaScript 代碼、保留原始的 JSX 代碼或使用 React Native 的 JSX 語法。

而在 TypeScript 4.1 中,新增了 react-jsx 選項,用來與 Babel 兼容。
因此,如果 TypeScript 版本較新,通常會看到的 jsx 設定是 “jsx”: “react-jsx”。

寫在最後

通篇八成為 AI 牙慧,正確性未考究。

(fin)

[實作筆記] passport 與 passport-local 原始碼分析

前言

最近接觸的專案有一部份是 express 與登入機制有相關
使用的套件是 Passport
Passport 是這樣介紹自已的

Simple, unobtrusive authentication for Node.js
Passport is authentication middleware for Node.js. Extremely flexible and modular,
Passport can be unobtrusively dropped in to any Express-based web application.
A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more.

這裡我們不作使用教學,官方都有而且相當簡單。
passport.authenticate 本質上就是一個 middleware。
而 passport.use 會用來註冊各種 Strategy。
比如說 facebook login、google login 等…
更多可見這裡

談談 passport-local

開發的過程中,有小朋友反應,如果他不輸入帳密,
頁面就會直接轉導,無法看到錯誤訊(有關 req.flash 的部份,本文不會談到)。

1
2
3
4
5
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/users/login",
failureFlash: true,
});

當然我們很清楚有設定 failureRedirect
不過讓我們不得其解的是,我們寫的整個 LocalStrategy 並沒有被執行,連 log 都沒有

這裡要翻一下原碼
這個作者的專案我覺得很棒,大多數都有測項,
除了文件外,你可以直接看測項理解它的想法。
在 Strategy.prototype.authenticate 可以發現以下程式

1
2
3
4
5
6
if (!username || !password) {
return this.fail(
{ message: options.badRequestMessage || "Missing credentials" },
400
);
}

簡單的說,它預設回傳一個 400 錯誤,而我們又設定了一個錯誤轉導。
使用這類的第三方套件就怕這類的非預期行為,
好在這個套件的文件與測項開源且清楚,我們才能快速定位問題。

參考

(fin)

[實作筆記] .ssh 與 Dropbox

警語

這不是很安全的做法。因為 .ssh 的檔案通常包含了敏感的訊息,比如私鑰。
如果放到雲端服務(Dropbox)上,就會有風險。

為什麼要這樣作 ?

現在混合的工作型態,有時會需要用家中的電腦工作,
每次都要花時間重建或是搬移 ssh pub key 顯得有點麻煩
Dropbox 本身就是一個適合的資料同步工具,之前我也設定過 zshrcvimrc 的同步機制。
同理 .ssh 也可適用,但風險需要評估

實作

  1. 將你的 .ssh 內所有檔案移至 ~/Dropbox/.ssh
  2. 刪除 .ssh 資料夾
  3. 執行以下語法
1
ln -s ~/Dropbox/.ssh ~/.ssh

(fin)

[技術筆記] JSON.stringify 的參數

簡介 JSON.stringify()

JSON.stringify() 是 Javascript 提供的一個方法,
可以將 JavaScript 的值轉換成 JSON 字串(String)。

這方法需要 3 個參數

  1. value
    這個參數是必填的,會被轉成 JSON 字串

    1
    2
    3
    var obj = { name: "John", age: 30, city: "New York" };
    var myJSON = JSON.stringify(obj);
    //myJSON is 「{ "name":"John", "age":30, "city":"New York"}」
  2. replacer
    這個選擇性參數,會是一個陣列或是一個 function ,主要的目的可以過濾 value 的值。
    我們可以用 w3school 的範例說明一下
    如果我們傳入的是一個陣列,那它會像一個過濾器一樣,只篩選出陣列中的字串

    1
    2
    3
    var obj = { name: "John", age: 30, city: "New York" };
    var myJSON = JSON.stringify(obj, ["name", "city"]);
    //myJSON is 「{"name":"John","city":"New York"}」

    如果我們傳入的是一個 function,會將每一個參數丟入 function 之中(可以進行加工或裝飾)

    1
    2
    3
    4
    5
    6
    var obj = { name: "John", age: 30, city: "New York" };
    var myJSON = JSON.stringify(obj, (key, value) => {
    if (key.match(/(age|city)/)) return "*";
    return value;
    });
    //myJSON is 「{"name":"John","age":"*","city":"*"}」
  3. space 選擇性
    這個參數是為了可讀性

    1
    2
    3
    var obj = { name: "John", age: 30, city: "New York" };
    var myJSON = JSON.stringify(obj, null, 2);
    //myJSON is 「{ "name": "John", "age": 30, "city": "New York" }」

JSON.stringify(obj, null, 2);JSON.stringify(obj, null, 4);便是開發者常用的呼叫方式

Ref

[生活筆記] 2023 的展望與希望

引言

回顧過往的 Blog,我很少在作年度的計劃與回顧,
22 年末我個人發生比較大的變化。
甚至讓我斷了每個月都寫一篇 Blog 的紀錄…
下面就是我個人的回顧與反思。

工作相關

我不打算在這裡寫太多細節,畢竟事情都還在進行中,未來有機會再細講。
簡單講是我會離開 T 社,回顧一年,不論是支援前端 Flutter 或是後台 React 的開發,
又或是離職同事的 golang 專案,我自認盡力了,但結果可能不如人意。
好處是我學會了 React、Golang、K8s 與部份 GCP 的服務
— Flutter 不算有掌握,畢竟公司內的專案實務上並無法接觸 —
但是這樣的技能樹是我現在該點的嗎 ? 我心中總有點疑惑…

我的觀察

找工作的過程中,發現了很多,
太多的公司(不論新舊、大小)都不具備技術審核的能力,
這樣的公司都會希望找一個資深的工程師,來帶領團隊,而我似乎是他們的人選,
實際上,我不太確定我是否能作好管理,但是無止的追求新技術,也不是我目前的高優先選項,
我還是追求匠人精神,技術總是 2~3 年一個大迭代,但是產品不可能不停的翻新,
而使用的技術,其實對招募會有很大的影響,畢竟市場上的人才很多會技術導向,
開發、維運、招募之間取得平衡是一個持續不斷的挑戰。

跟很多開發者不一樣的地方,我開發的專案,
追求的是零知識的轉移,讓巴士因子儘可能與人脫勾,
說白話一點,我希望我開發的產品,透過工程的手段與必要的文件,
任何人都可以快速上手,只要搭配不同的組態,可以佈署到不同的環境上。
然後我很追求 XP 的開發精神,以前說蠻多的,以後也會再說,就不再佔本篇篇幅了。

面試感覺

如果要透過 Hunter,可以再看看他手上的機會,
如果都是人力銀行就找得到的機會,可以試試看自已投吧,
Hunter 或許可以談出比較高的薪水,但是我的感覺是在玩遊戲,
當我表達對工作有興趣,但是薪水可不可能再往上談的時候,講得很硬,
當我表達有其它的機會時,薪水就又都可以談了,無言…
至少 A 社與 S 社的 Hunter 我是不推的。

一般的工程師職務都會考 LeetCode,
沒刷過的人去刷吧,也不用卡太久,但是一定要把答案看懂,
常見的資料結構與演算法寫個一輪吧,
是不是需要上百題,就見人見智了。

未來的走向

首先,我最理想的狀態,是找到一份遠端的工作機會,如果這個工作穩定,
或許我會開始數位遊牧的生活,每隔幾年會到不同的國家作線上的工作。
不過我現在有的選項當中,並沒有這個項目

保持現狀

A 社是一個老牌的公司了,系統就是 10 年來的累積,
感覺有要作很多新東西,但是老系統會放不掉,薪資 N 有符合預期,
離家也不算太遠,可以說如果選擇了這份工作,
會最接近我的現況。

出國

另一個可能性是出國遊學,
與打工留學不同的是他沒有年齡限制,
剛好現在長輩健康、沒有家累,工作停下來一下,或許是我現階段需要的
缺點是這段時間不但沒有收入,半年內可能還會花費不少儲蓄。

主管職

另一間 A 社的工作機會是一個主管職,主要的技術棧也與我原本的不大相同。
但轉念一想,或許可以有更高的自由度與挑戰,
團隊的組成是年輕的新創團隊,2 個後端與 1 個前端,1 個全端與 1 個 AI 工程師。
有一種工程師的路走到了盡頭,是不是應該去接受這樣挑戰呢 ?

(fin)

[實作筆記] 設定 Vercel 的 DNS

前情提要

最近很紅的聊天 AI ChatGPT 開始出現各種應用了。
這基本上應驗了我 2010 年說的 「10 年內,軟體工程師不會被取代」的說法
雖然是否真的會取代工程師,我會另文寫寫我現在的看法,不過也算是敲響了一記警鐘。
各式各樣的應用也隨之而來。
此外文末記錄一下 heroku 停止免費服務後的可能方案。

Line Bot 與 chatGPT

最常見的應用就是 Line Bot 與 OpenAI GPT-3 API 進行串接。
來達到一個賴機器人能夠與你有一定程度的智能對話。
(不過實測看來 GPT-3 API 無法與 ChatGPT 的提供智能相比)

這類的應該短短幾周就出現了各式各樣的版本,
語言有 nodejs、python、C#

主要的步驟大概如下,

  1. 主程式 push 到 Github 上(大部份都很佛,可以直接 Clone)
  2. 到 OPEN AI 取得 API KEY
  3. 到 Line Developer 建立一個 Message API
  4. 取得 Line Channel Access Token 、Channel Secret
  5. 調整 Line 相關設定 ex: 停用自動回復
  6. 到 Vercel 申請免費伺服器並連結 GitHub 帳號
  7. Vercel 進行伺服器設定 webhook

即可完成

Vercel 這個服務也可以自定 domain,
當然買了 marsen.me 這個 domain 的我自然不會放棄裝專業自定義 domain 的機會
在 Vercal → 登入 → 找到你的專案 → Settings → Domains
輸入你的網域 subdomain.yourdoma.in

接下來去你的 DNS 服務商的設定表(我是用 cloudflare)加上一個 CNAME 的記錄,

Type Name Content
CNAME gptbot cname.vercel-dns.com

後記

這是我第一次使用 Vercel,沒什麼門檻,簡單好上手。
不過看到各方大神使用了各種免費服務,
想說稍微記錄一下這些服務,以因應 heroku 停止免費服務,
或許未來也會有有機會用到

uptimerobot

免費服務通常一段時間沒有用就會睡著,這是個自動喚醒它們的服務

免費部署 service 的方案

  • heroku(已停止免費方案)
  • Colab + Flask(Google 提供給 python 的雲端 solution)
  • fly.io
  • vercel
  • render

我還沒有用過這些服務,所以先作記錄,想要比較的話,可以使用 slant

參考

(fin)