快看,我的代碼能“自己說(shuō)話”!
開(kāi)發(fā)人員什么時(shí)候最崩潰?
別人我不知道,就我而言,要是我耗費(fèi)了幾個(gè)小時(shí)來(lái)研究代碼,試圖破譯它的目的,卻遲遲不得門路,真是恨不得找到寫(xiě)代碼的那個(gè)家伙,讓他回爐重造。
今天我們將在這篇文章中探討如何編寫(xiě)自文檔化的代碼,讓代碼自己會(huì)說(shuō)話。
什么是自文檔化的代碼?
自文檔化的代碼是以清晰、富有表現(xiàn)力的方式編寫(xiě)的代碼,無(wú)需大量的注釋和外部文檔,就能讓人理解代碼的目的和功能。
自文檔化代碼的特點(diǎn):
- 可讀性:代碼易于閱讀和理解,一目了然
- 富有表現(xiàn)力:清楚傳達(dá)代碼的意圖和目的
- 可維護(hù):代碼易于修改和更新,不會(huì)引入錯(cuò)誤和重大更改
自文檔化代碼的重要性
編寫(xiě)自文檔化代碼的好處:
- 開(kāi)發(fā)人員可以快速掌握代碼的目的和功能,減少理解和使用代碼庫(kù)所需的腦力勞動(dòng)。
- 新的團(tuán)隊(duì)成員可以更快地上手,因?yàn)樗麄儾恍枰貏e依賴外部文檔。
- 最大限度地減少團(tuán)隊(duì)成員之間的誤解,促進(jìn)對(duì)代碼庫(kù)的共同理解。
- 即使隨著時(shí)間的推移,維護(hù)和更新也依然方便簡(jiǎn)單,因?yàn)殚_(kāi)發(fā)人員可以快速理解現(xiàn)有代碼并做出明智的更改。
如何編寫(xiě)自文檔化的代碼
了解了什么樣的代碼是自文檔化的代碼之后,敲黑板,我們的重點(diǎn)來(lái)了,那么,怎么編寫(xiě)這樣可以“自己說(shuō)話”的代碼呢?
1.使用有意義的名字
使代碼自文檔化的最有效方法之一是對(duì)變量、函數(shù)、類和模塊使用有意義的名稱。
請(qǐng)看以下示例:
// Bad
const x = 5;
const y = 10;
const z = x + y;
// Good
const numberOfItems = 5;
const itemPrice = 10;
const totalCost = numberOfItems * itemPrice;
在Good示例中,變量名稱numberOfItems、itemPrice、totalCost清楚地傳達(dá)了用途,理解起來(lái)非常方便。
2. 編寫(xiě)小而精的函數(shù)
編寫(xiě)小而精的函數(shù)是自文檔化代碼的另一個(gè)關(guān)鍵。函數(shù)應(yīng)當(dāng)功能單一,并準(zhǔn)確命名以反映其目的。
例如:
// Bad
function processData(data: any): any {
// ...
// Lots of complex logic
// ...
return result;
}
// Good
function extractRelevantFields(data: Record<string, any>): Record<string, any> {
// ...
return relevantFields;
}
function applyBusinessRules(relevantFields: Record<string, any>): Record<string, any> {
// ...
return processedData;
}
function formatOutput(processedData: Record<string, any>): string {
// ...
return formattedResult;
}
通過(guò)將大函數(shù)分解為名稱更具描述性的小而精函數(shù),代碼明顯更可讀了。
3. 使用描述性的函數(shù)名稱和方法名稱
在命名函數(shù)和方法時(shí),使用描述性的名稱可以更加清楚地傳達(dá)其目的和操作。注意:應(yīng)盡量避免使用通用名稱,如handle()或process()這樣的寫(xiě)法。
請(qǐng)看示例:
// Bad
function handleInput(input: string): void {
// ...
}
// Good
function validateUserCredentials(username: string, password: string): boolean {
// ...
}
看到了嗎?描述性名稱validateUserCredentials清楚地表明了函數(shù)的作用?,F(xiàn)在,我們哪還需要額外的注釋?
4. 利用 TypeScript 的類型系統(tǒng)
TypeScript 強(qiáng)大的類型系統(tǒng)可以大大增強(qiáng)代碼的自文檔化。所以要懂得利用工具,借助 TypeScript 的功能,使代碼更具表現(xiàn)力,及早發(fā)現(xiàn)潛在的錯(cuò)誤。
例如:
- 接口和類型:使用接口和自定義類型來(lái)定義數(shù)據(jù)結(jié)構(gòu)的形狀。
interface User {
id: number;
name: string;
email: string;
}
function getUserById(id: number): User | undefined {
// ...
}
- 枚舉:利用枚舉來(lái)表示一組固定的值,提供了一種清晰易讀的方式來(lái)處理不同的方案。
enum PaymentStatus {
Pending = 'pending',
Completed = 'completed',
Failed = 'failed',
}
function processPayment(status: PaymentStatus): void {
// ...
}
- 類型推斷:盡量使用 TypeScript 推斷類型,因?yàn)榭梢跃?jiǎn)代碼。
// Bad
const count: number = 10;
const message: string = 'Hello, world!';
// Good
const count = 10;
const message = 'Hello, world!';
5. 使用強(qiáng)類型 ID
在 TypeScript 中處理 ID 時(shí),建議使用強(qiáng)類型 ID,不要直接上字符串和數(shù)字。強(qiáng)類型 ID 提供了額外的類型安全性。
實(shí)現(xiàn)強(qiáng)類型 ID 的一種方法是使用不透明的類型:
// user.ts
export type UserId = string & { readonly __brand: unique symbol };
export function createUserId(id: string): UserId {
return id as UserId;
}
// post.ts
export type PostId = string & { readonly __brand: unique symbol };
export function createPostId(id: string): PostId {
return id as PostId;
}
這里我們使用強(qiáng)類型 ID,確保了UserId只分配給需要UserId的屬性和函數(shù),PostId只分配給需要PostId的屬性和函數(shù)。
function getUserById(userId: UserId): User | undefined {
// ...
}
const userId = createUserId('user-123');
const postId = createPostId('post-456');
getUserById(userId); // No error
getUserById(postId); // Error: Argument of type 'PostId' is not assignable to parameter of type 'UserId'.
強(qiáng)類型的 ID 有助于在編譯時(shí)捕獲潛在錯(cuò)誤,使代碼更具表現(xiàn)力和自文檔化。
但是,與使用簡(jiǎn)單的字符串類型相比,強(qiáng)類型 ID確實(shí)會(huì)引入一些開(kāi)銷。所以我們需要根據(jù)項(xiàng)目的需求和規(guī)模權(quán)衡利弊。
6. 增強(qiáng)團(tuán)隊(duì)凝聚力
團(tuán)隊(duì)工作的時(shí)候,建立一致性至關(guān)重要,不然你有你的標(biāo)準(zhǔn),我有我的約定,程序還怎么跑得起來(lái)?
關(guān)于一致性,有一個(gè)非常重要的方面是命名約定。最好的做法是建立一個(gè)風(fēng)格指南,定義變量、函數(shù)、類和其他實(shí)體的命名方式。
例如,可以使用類似這樣的術(shù)語(yǔ):
- get:檢索 API 或數(shù)據(jù)源中的單個(gè)值。
- list:檢索 API 或數(shù)據(jù)源中的一組值。
- patch:部分更新現(xiàn)有實(shí)體和對(duì)象。
- upsert:更新現(xiàn)有實(shí)體,如果不存在,則插入新實(shí)體。
統(tǒng)一執(zhí)行術(shù)語(yǔ)可以確保整個(gè)代碼庫(kù)的一致性。
例如:
function getUser(userId: UserId): Promise<User> {
// ...
}
function listUsers(): Promise<User[]> {
// ...
}
function patchUser(userId: UserId, updatedData: Partial<User>): Promise<User> {
// ...
}
怎么樣,是不是明顯更易于大家理解了呢。
除了命名約定之外,還可以為代碼庫(kù)的其他方面制定準(zhǔn)則,例如文件和文件夾結(jié)構(gòu)、注釋、錯(cuò)誤處理、測(cè)試和代碼格式等。
在團(tuán)隊(duì)中工作時(shí),我們有時(shí)候可能不習(xí)慣或者不贊同正在執(zhí)行的某個(gè)約定。但是,重要的是要記住,一致性和協(xié)作對(duì)于項(xiàng)目的成功至關(guān)重要。
即使你有不同的偏好或編碼風(fēng)格,也應(yīng)該遵守約定。從而保持一個(gè)有凝聚力的代碼庫(kù),減少混淆。
7. 復(fù)雜場(chǎng)景使用 JSDoc 或 TSDoc
雖然自文檔化的代碼非常優(yōu)秀,但是不可否認(rèn)的是,在某些情況下該上文檔就得上文檔,例如如果遇到復(fù)雜的算法和復(fù)雜的業(yè)務(wù)邏輯,你不寫(xiě)注釋,簡(jiǎn)直就不給后來(lái)人活路。
在這種情況下,可以考慮使用 JSDoc 或 TSDoc 來(lái)提供清晰簡(jiǎn)潔的文檔。
/**
* Calculates the Fibonacci number at the given position.
*
* @param {number} position - The position of the Fibonacci number to calculate.
* @returns {number} The Fibonacci number at the specified position.
*/
function fibonacci(position: number): number {
if (position <= 1) {
return position;
}
return fibonacci(position - 1) + fibonacci(position - 2);
}
通過(guò) JSDoc 或 TSDoc,我們可以為復(fù)雜的代碼提供額外的上下文和說(shuō)明,而不會(huì)使代碼庫(kù)變得亂糟糟。
結(jié)論
編寫(xiě)自文檔化的代碼是每個(gè)開(kāi)發(fā)人員都應(yīng)該努力掌握的一門藝術(shù)。
通過(guò)有意義的名稱、小而精的函數(shù)、 TypeScript 的類型系統(tǒng)以及明智地使用文檔,我們可以創(chuàng)建可讀的、富有表現(xiàn)力的和可維護(hù)的代碼。
自文檔化的代碼,可以減少對(duì)外部文檔的依賴,使我們的開(kāi)發(fā)工作更輕松。
所以,下次寫(xiě)代碼的時(shí)候,花點(diǎn)時(shí)間考慮如何使其更具自文檔性。相信我,未來(lái)的你自己和隊(duì)友都會(huì)感謝你!聽(tīng)我說(shuō),謝謝你,因?yàn)橛心?,溫暖了四季?/p>