3 Years of TypeScript: Practical Tips for Clean Code

TypeScriptclean codecode qualitybest practices
Lafu Code
Lafu Code
-- views

Recently, I was refactoring a project from two years ago, and when I opened the code, I almost wanted to delete the repo and run away. Variable names like data1, data2, functions with 100+ lines, types all set to any... it was a disaster scene.

After some painful reflection, I've summarized some practical tips for writing cleaner TypeScript code. These are all lessons learned from stepping on landmines in real projects, and I hope they can help you avoid writing "spaghetti code."

1. Give Good Names, Don't Make People Guess

This is really crucial. Good variable names are the best comments.

My previous garbage code:

// What the hell is this?
function calculate(a: number, b: number, c: number) {
  return a + b * c;
}

How I write it now:

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

See the difference? The second version is self-explanatory without comments.

Here's a small tip: For TypeScript interface naming, I now use User directly instead of IUser. The modern approach is cleaner.

2. One Function, One Job

This principle sounds simple, but it's easy to violate in real projects. I used to write these "god functions" all the time:

Bad example:

function fetchAndProcessUserData(userId: string) {
  // 1. Fetch data
  const response = fetch(`/api/users/${userId}`);
  // 2. Parse data
  const user = response.json();
  // 3. Validate data
  if (!user.name || !user.email) {
    throw new Error("Invalid user data");
  }
  // 4. Save to database
  db.save(user);
}

This function does 4 things! Debugging was a nightmare.

After improvement:

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);
  }
}

Now each function has a clear responsibility, making testing and maintenance much easier.

3. Stop Using any, Leverage TypeScript's Type System Properly

TypeScript's type system is its biggest advantage, but I've found many people (including my past self) often get lazy and use any.

Use Specific Types, Not any

any is like opening a backdoor in the type system. Use it too much and you lose the point of TypeScript.

Make Good Use of Union Types and Literal Types

// This is too broad
type Status = string;

// This is more precise, and IDE can auto-complete
type OrderStatus = "pending" | "processing" | "shipped" | "delivered" | "cancelled";

function handleOrderStatus(status: OrderStatus) {
  // TypeScript will check if you've handled all possible states
  switch (status) {
    case "pending":
      // ...
      break;
    case "processing":
      // ...
      break;
    // If you miss a state, TypeScript will remind you
  }
}

Define Clear Data Structures

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

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

By defining data models this way, the entire team knows what the data looks like.

4. Handle Errors Gracefully

I used to return null or error codes frequently, but now I prefer throwing exceptions. TypeScript can help us define custom error types:

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) {
      // Handle network error
    }
    throw error;
  }
}

5. Write Testable Code

I have deep experience with this. My old code was extremely painful to test, but after learning dependency injection, the world became much cleaner.

Hard-to-test code:

class UserService {
  private db = new DatabaseClient(); // Direct dependency on concrete implementation

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

Testing this code requires connecting to a real database, which is too troublesome.

Testable code:

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

class UserService {
  constructor(private db: IDatabase) {} // Depends on abstract interface

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

// Easy to mock during testing
const mockDb: IDatabase = {
  query: async (sql) => ({ id: 1, name: "Test User" }),
};
const userService = new UserService(mockDb);

Summary

After writing code for so many years, I've found that the most important thing isn't showing off skills, but making code easy to understand and maintain. TypeScript gives us great tools; the key is to use them well.

Remember these key points:

  • Give good names, let code speak for itself
  • Keep functions single-purpose for easier testing and maintenance
  • Fully leverage the type system, don't get lazy with any
  • Handle errors gracefully, define clear exception types
  • Make code testable through dependency injection

These tips seem simple, but sticking to them in real projects isn't easy. However, once you develop the habit, you'll find a qualitative improvement in code quality.

Final thought: Code is written for humans to read; machines just happen to execute it. Invest time in making code clearer, and your future self will thank your present self.


If this article helped you, feel free to share it with more developer friends. Let's write better code together!

Follow WeChat Official Account

WeChat Official Account QR Code

Scan to get:

  • • Latest tech articles
  • • Exclusive dev insights
  • • Useful tools & resources

💬 评论讨论

欢迎对《3 Years of TypeScript: Practical Tips for Clean Code》发表评论,分享你的想法和经验