写了3年TypeScript,总结几个让代码更干净的实用技巧

TypeScript代码质量最佳实践重构
老夫撸代码
老夫撸代码
-- 次浏览

最近在重构一个两年前的项目,打开代码的那一刻我差点想删库跑路。变量名叫data1data2,函数动不动就 100 多行,类型全是any...简直是灾难现场。

痛定思痛,我总结了一些在 TypeScript 项目中让代码更干净的实用技巧。这些都是我在实际项目中踩过坑后总结出来的,希望能帮大家避免写出"屎山"代码。

1. 起个好名字,别让人猜

这个真的太重要了。好的变量名就是最好的注释。

我之前写的垃圾代码:

// 这是什么鬼?
function calculate(a: number, b: number, c: number) {
  return a + b * c;
}

现在我会这样写:

function calculateOrderTotal(price: number, quantity: number, taxRate: number): number {
  const subtotal = price * quantity;
  const taxAmount = subtotal * taxRate;
  return subtotal + taxAmount;
}

看到区别了吗?第二个版本不用注释就知道在干什么。

还有一个小技巧:TypeScript 的接口命名,我现在直接用User而不是IUser。现代的做法更简洁。

2. 一个函数只做一件事

这个原则听起来简单,但实际项目中很容易违反。我之前经常写出这种"万能函数":

反面教材:

function fetchAndProcessUserData(userId: string) {
  // 1. 获取数据
  const response = fetch(`/api/users/${userId}`);
  // 2. 解析数据
  const user = response.json();
  // 3. 验证数据
  if (!user.name || !user.email) {
    throw new Error("Invalid user data");
  }
  // 4. 保存到数据库
  db.save(user);
}

这个函数做了 4 件事!调试的时候简直要命。

改进后:

async function fetchUserData(userId: string): Promise<User> {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
}

function validateUserData(user: User): boolean {
  return !!(user.name && user.email);
}

async function saveUser(user: User): Promise<void> {
  await db.save(user);
}

async function handleUserRequest(userId: string): Promise<void> {
  const user = await fetchUserData(userId);
  if (validateUserData(user)) {
    await saveUser(user);
  }
}

现在每个函数职责清晰,测试和维护都容易多了。

3. 别再用 any 了,好好利用 TypeScript 的类型系统

TypeScript 的类型系统是它最大的优势,但我发现很多人(包括以前的我)经常偷懒用any

用具体的类型,不要 any

any就像是给类型系统开了个后门,用多了就失去 TypeScript 的意义了。

善用联合类型和字面量类型

// 这样太宽泛了
type Status = string;

// 这样更精确,IDE还能自动补全
type OrderStatus = "pending" | "processing" | "shipped" | "delivered" | "cancelled";

function handleOrderStatus(status: OrderStatus) {
  // TypeScript会检查你是否处理了所有可能的状态
  switch (status) {
    case "pending":
      // ...
      break;
    case "processing":
      // ...
      break;
    // 如果漏了某个状态,TypeScript会提醒你
  }
}

定义清晰的数据结构

interface User {
  id: number;
  name: string;
  email: string;
  isActive: boolean;
}

interface Product {
  id: string;
  name: string;
  price: number;
}

这样定义数据模型,整个团队都知道数据长什么样。

4. 错误处理要优雅

以前我经常返回null或者错误码,现在我更倾向于抛出异常。TypeScript 可以帮我们定义自定义错误类型:

class NetworkError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "NetworkError";
  }
}

class ValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ValidationError";
  }
}

async function fetchData(): Promise<Data> {
  try {
    const response = await fetch("/api/data");
    if (!response.ok) {
      throw new NetworkError("Failed to fetch data");
    }
    return response.json();
  } catch (error) {
    if (error instanceof NetworkError) {
      // 处理网络错误
    }
    throw error;
  }
}

5. 写可测试的代码

这个我深有体会。以前写的代码测试起来特别痛苦,后来学会了依赖注入,世界都清净了。

难测试的代码:

class UserService {
  private db = new DatabaseClient(); // 直接依赖具体实现

  async getUser(id: number): Promise<User> {
    return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
  }
}

这样的代码测试时必须连真实数据库,太麻烦了。

可测试的代码:

interface IDatabase {
  query(sql: string): Promise<any>;
}

class UserService {
  constructor(private db: IDatabase) {} // 依赖抽象接口

  async getUser(id: number): Promise<User> {
    return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
  }
}

// 测试时可以轻松mock
const mockDb: IDatabase = {
  query: async (sql) => ({ id: 1, name: "Test User" }),
};
const userService = new UserService(mockDb);

总结

写了这么多年代码,我发现最重要的不是炫技,而是让代码容易理解和维护。TypeScript 给了我们很好的工具,关键是要善用。

记住几个要点:

  • 起好名字,让代码自己说话
  • 函数职责单一,便于测试和维护
  • 充分利用类型系统,别偷懒用 any
  • 优雅处理错误,定义清晰的异常类型
  • 通过依赖注入让代码可测试

这些技巧看起来简单,但在实际项目中坚持下来并不容易。不过一旦养成习惯,你会发现代码质量有质的提升。

最后说一句:代码是写给人看的,机器只是顺便执行一下。投入时间让代码更清晰,未来的你会感谢现在的自己。

关注微信公众号

微信公众号二维码

扫码关注获取:

  • • 最新技术文章推送
  • • 独家开发经验分享
  • • 实用工具和资源

💬 评论讨论

欢迎对《写了3年TypeScript,总结几个让代码更干净的实用技巧》发表评论,分享你的想法和经验