编写自文档代码
作为开发者,我们都曾浪费过数小时盯着一段代码,试图解读它的用途,并琢磨原作者究竟在想什么。在软件开发的世界里,项目易主,代码库快速更新,编写自文档化的代码不仅仅是锦上添花,更是必需的。
在本文中,我们将探索编写不言自明的代码的艺术,减少对外部文档的依赖,并使您和您的开发人员同事的生活更轻松。
什么是自文档代码?
自文档代码是以清晰、富有表现力和有意的方式编写的代码,使其目的和功能易于理解,而无需大量注释或外部文档。
这是关于编写以下代码:
- 可读性:一目了然、易于阅读和理解的代码
- 富有表现力:代码能够清晰地传达其意图和目的
- 可维护:代码易于修改和更新,不会引入错误或造成重大更改
为什么自文档代码很重要
编写自文档代码有几个好处:
-
减少认知负荷:当代码不言自明时,开发人员可以快速掌握其目的和功能,从而减少理解和使用代码库所需的脑力劳动。
-
更快的入职:当代码库是自文档时,新团队成员可以更快地上手,因为他们不需要过度依赖外部文档或大量的知识转移会议。
-
改进的协作:自文档代码有助于团队成员之间更好地协作,因为它可以最大限度地减少误解并促进对代码库的共同理解。
-
增强的可维护性:当代码是自文档时,随着时间的推移,它更容易维护和更新,因为开发人员可以快速理解现有代码并做出明智的更改。
编写自文档代码的技巧
让我们探索一些编写自文档代码的实用技巧,重点关注 TypeScript:
1. 使用有意义的名字
让代码自文档化最有效的方法之一是为变量、函数、类和模块使用有意义的名称。请考虑以下示例:
// Bad
const x = 5;
const y = 10;
const z = x + y;
// Good
const numberOfItems = 5;
const itemPrice = 10;
const totalCost = numberOfItems * itemPrice;
在“好”的例子中,变量名清楚地传达了它们的用途,使代码更具可读性和不言自明性。
2. 编写简短、专注的函数
编写简短、专注的函数是自文档化代码的另一个关键方面。函数应该具有单一职责,并且命名应准确反映其用途。例如:
// 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;
}
通过将大型函数分解为具有描述性名称的较小、集中的函数,代码变得更具可读性和自文档性。
3. 使用描述性的函数和方法名称
命名函数和方法时,请使用能够清晰表达其目的和操作的描述性名称。避免使用诸如handle()
或 之类的通用名称process()
。应选择能够描述函数功能的名称。例如:
// Bad
function handleInput(input: string): void {
// ...
}
// Good
function validateUserCredentials(username: string, password: string): boolean {
// ...
}
描述性名称validateUserCredentials
清楚地说明了函数的作用,而无需额外的注释。
4. 利用 TypeScript 的类型系统
TypeScript 强大的类型系统可以极大地增强代码的自文档性。利用 TypeScript 的功能,您可以提升代码的表达能力,并尽早发现潜在的错误。例如:
- 接口和类型:使用接口和自定义类型来定义数据结构的形状,使代码更具可读性和不言自明性。
interface User {
id: number;
name: string;
email: string;
}
function getUserById(id: number): User | undefined {
// ...
}
- 枚举:利用枚举来表示一组固定的值,提供一种清晰易读的方式来处理不同的场景。
enum PaymentStatus {
Pending = 'pending',
Completed = 'completed',
Failed = 'failed',
}
function processPayment(status: PaymentStatus): void {
// ...
}
- 类型推断:尽可能让 TypeScript 推断类型,因为它可以减少冗长程度并使代码更具可读性。
// Bad
const count: number = 10;
const message: string = 'Hello, world!';
// Good
const count = 10;
const message = 'Hello, world!';
5. 使用强类型 ID
在 TypeScript 中使用 ID 时,请考虑使用强类型 ID,而不是简单的字符串或数字。强类型 ID 可以提供额外的类型安全性,并使代码更具自文档性。
实现强类型 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;
}
通过使用强类型 ID,您可以确保 UserId 只能分配给需要 UserId 的属性或函数,并且 PostId 只能分配给需要 PostId 的属性或函数。
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'.
强类型 ID 有助于在编译时捕获潜在错误,并使代码更具表现力和自文档性。但是,与使用简单的字符串类型相比,它们确实会带来一些开销。请根据项目的需求和规模权衡利弊。
6. 在团队中建立一致性并设定期望
作为团队的一员,在代码库上工作时,保持一致性和设定清晰的期望至关重要。这有助于确保每个人都保持一致并遵循相同的约定,从而使代码更具可读性和可维护性。
一致性的一个重要方面是命名约定。建立一个样式指南,定义变量、函数、类和其他实体的命名方式。例如,考虑以下术语及其含义:
get
:从 API 或数据源检索单个值。list
:从 API 或数据源检索一组值。patch
:部分更新现有实体或对象。upsert
:更新现有实体,如果不存在则插入新实体。
通过定义这些术语及其用法,您可以确保整个代码库的一致性。例如:
function getUser(userId: UserId): Promise<User> {
// ...
}
function listUsers(): Promise<User[]> {
// ...
}
function patchUser(userId: UserId, updatedData: Partial<User>): Promise<User> {
// ...
}
一致性有助于提高代码库的可预测性,并让所有团队成员更容易理解。除了命名约定之外,还要考虑为代码库的其他方面制定指导原则,例如文件和文件夹结构、注释、错误处理、测试和代码格式。
在团队中工作时,您可能并不总是认同所有强制执行的约定。然而,务必记住,一致性和协作对于项目的成功至关重要。即使您有不同的偏好或编码风格,遵守约定的约定也有助于维护代码库的凝聚力,并减少其他团队成员之间的困惑。
7. 在复杂场景中使用 JSDoc 或 TSDoc
虽然自文档化代码应该是主要目标,但在某些情况下,例如复杂的算法或错综复杂的业务逻辑,可能需要额外的文档。在这种情况下,可以考虑使用 JSDoc 或 TSDoc 来提供清晰简洁的文档。
/**
* 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);
}
通过使用 JSDoc 或 TSDoc,您可以为复杂的代码提供额外的上下文和解释,而不会使代码库本身变得混乱。
结论
编写自文档化的代码是一门艺术,每个开发者都应该努力掌握。通过利用有意义的名称、精简而专注的函数、TypeScript 的类型系统以及合理的文档,您可以创建可读性强、富有表现力且易于维护的代码。
请记住,我们的目标是编写能够自我解释的代码,减少对外部文档的依赖,并简化其他开发人员的工作。所以,下次编写代码时,请花点时间思考如何使其更具自文档性。未来的你和你的队友都会感谢你!
另外,我还要毫不掩饰地打个广告🔌。如果你在敏捷开发团队工作,并且使用工具进行在线会议,比如计划扑克或回顾会议,不妨看看我的免费工具Kollabe!
文章来源:https://dev.to/mattlewandowski93/writing-self-documenting-code-4lga