昨天的做法解決了監控和樹狀搜尋兩個不相關的流程攪和在一起的問題,然而卻衍生了更多的問題 !
想像我們除了監控以外,還有錯誤處理、身分驗證、序列化反序列化...等好多個模組,而且每個函式之間會一層又一層的呼叫,如果繼續採用昨天的方式會造成像是下面這樣的可怕結果
const fnA = (monitor: (result: any) => void) => {
//....
};
const fnB = (logger: (result: any) => void) => {
//....
};
const fnC = (serializer: (result: any) => string) => {
//....
};
const fnAll = (
monitor: (result: any) => void,
logger: (result: any) => void,
serializer: (result: any) => string
) => {
fnA(monitor);
fnB(logger);
fnC(serializer);
};
const fnBeforeFnAll = (
monitor: (result: any) => void,
logger: (result: any) => void,
serializer: (result: any) => string
) => {
fnAll(monitor, logger, serializer);
};
隨著所需要的外部模組越來越多,要傳遞下去的 callback function 也越來越多,而且每一層都要傳遞,每一層都要手動加上一大堆的型別宣告,這在實務上真的不可行!
不要懷疑,我真的有在工作上處理過這樣的程式碼
負責的管理層過於理想化,而毫不在乎實務上會遇到的問題
太痛苦了 QAQ
我們實際上可以透過裝飾器模式來解決這些實務上的問題
interface MyNode {
_tag: "Node";
id: string;
text: string;
childrenIds: readonly string[];
}
interface NodeNotFoundError {
_tag: "NodeNotFoundError";
id: string;
}
// (1) 實際進行監控的邏輯
const monitor = (result:any)=>{
if(result instanceof Promise){
result.then(
(r)=>{
if(r?._tag === "NodeNotFoundError") console.error(`get id "${r.id}" failed, skip this node`);
else console.log(`get id "${r.id}" complete`)
}
)
}
return result
}
// (2) 將監控邏輯包裝成 class method decorator
function Monitor<Fn extends (...args: any[]) => any>(
fn: Fn, // (2-1) 目標 method 可以是任意函式
context: ClassMethodDecoratorContext // (2-2) 函示相關資訊,例如函式名稱
) {
function monitoredFn( // (2-3) 建立一個包含監控邏輯的、包裝好的 function
this: ThisParameterType<Fn>,
...args: Parameters<Fn>
): ReturnType<Fn> {
return monitor(fn(args))
}
return monitoredFn; // (2-4) 回傳新的 function
}
class NodeService {
@Monitor // (3) 插入監控邏輯
async getNode(id: string): Promise<MyNode | NodeNotFoundError> {
const map: Record<string, MyNode> = {
a: { _tag: "Node", id: "a", text: "a", childrenIds: ["a1", "a2", "a3"] },
a1: { _tag: "Node", id: "a1", text: "a", childrenIds: ["a11"] },
a11: { _tag: "Node", id: "a11", text: "a", childrenIds: [] },
a2: { _tag: "Node", id: "a2", text: "a", childrenIds: [] },
a3: { _tag: "Node", id: "a3", text: "a", childrenIds: [] },
};
if (map[id]) return map[id];
else return { _tag: "NodeNotFoundError", id };
}
}
const ns = new NodeService();
ns.getNode("ab")
最後,不知道大家有沒有發現,以上流程其實就是在做粗糙的 AOP - 切面導向程式設計。