Clean Code Typescript - 8. Xử lý lỗi

Ném ra các lỗi là một ý tưởng tốt. Điều đó có nghĩa chương trình của bạn sẽ phát hiện được khi có lỗi gì đó xảy ra với chương trình

Clean Code Typescript - 8. Xử lý lỗi


Clean Code Typescript - 8. Xử lý lỗi

Ném ra các lỗi là một ý tưởng tốt. Điều đó có nghĩa chương trình của bạn sẽ phát hiện được khi có lỗi gì đó xảy ra với chương trình và chương trình sẽ được dừng lại ngay tại ngăn xếp hiện tại của nó, tiến trình sẽ bị hủy (trong Node), và nó sẽ thông báo cho bạn ở cửa sổ dòng lệnh với thông tin của ngăn xếp.

Luôn sử dụng kiểu Error khi muốn thể hiện một lỗi

JavaScript cũng như TypeScript cho phép bạn throw mọi đối tượng. Một Promise cũng có thể trả về thất bại (rejected) với mọi đối tượng là lý do thất bại.
Nên sử dụng cú pháp throw với kiểu là Error. Nên làm như vậy bởi vì, các lỗi của bạn có thể được bắt bằng cú pháp catch ở những đoạn code có level cao hơn. Nó sẽ là rất khó hiểu cho bạn nếu bạn chỉ bắt được một chuỗi ở đó và nó gây ra Việc khó khăn khi gỡ lỗi.
Vì lý do tương tự, khi muốn thông báo một Promise bị thất bại, bạn nên dùng kiểu Error.


Chưa tốt:
function calculateTotal(items: Item[]): number {
  throw 'Not implemented.';
}

function get(): Promise<Item[]> {
  return Promise.reject('Not implemented.');
}
Tốt:
function calculateTotal(items: Item[]): number {
  throw new Error('Not implemented.');
}

function get(): Promise<Item[]> {
  return Promise.reject(new Error('Not implemented.'));
}

// or equivalent to:

async function get(): Promise<Item[]> {
  throw new Error('Not implemented.');
}
Lợi ích của việc sử dụng kiểu Error (và các mở rộng của nó) là nó được hỗ trợ bởi cú pháp try/catch/finally và đặc biệt tất cả các lỗi sẽ có thuộc tính stack, đó là thứ sẽ giúp ích rất nhiều khi bạn gỡ lỗi chương trình. Ngoài ra có một cách khác giúp không sử dụng cú pháp throw - Luôn trả về các đối tượng thể hiện lỗi có tùy chỉnh. TypeScript còn làm điều này dễ dàng hơn. Xem xét ví dụ sau:
type Result<R> = { isError: false, value: R };
type Failure<E> = { isError: true, error: E };
type Failable<R, E> = Result<R> | Failure<E>;

function calculateTotal(items: Item[]): Failable<number, 'empty'> {
  if (items.length === 0) {
    return { isError: true, error: 'empty' };
  }

  // ...
  return { isError: false, value: 42 };
}
Bài viết này giải thích chi tiết về ý tưởng trên, hãy tham khảo.


Đừng bỏ qua các lỗi bắt được

Không làm gì với những lỗi có thể bắt được không giúp ích gì cho việc sửa lỗi hoặc có những hành động tương ứng với những lỗi đó. Việc bạn thể hiện lỗi bằng console.log sẽ không tốt hơn việc không làm gì, nó có thể bị lẫn trong một biển các thứ được in ra ở cửa sổ lệnh. Nếu bạn đặt đoạn mã của mình trong một đoạn try/catch, điều đó có nghĩa là bạn phải chuẩn bị cho việc một lỗi sẽ xảy ra ở đó và bạn phải có kế hoạch cho việc đó.

Chưa tốt:
try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}

// hoặc thậm chí tệ hơn

try {
  functionThatMightThrow();
} catch (error) {
  // bỏ qua lỗi
}
Tốt:
import { logger } from './logging'

try {
  functionThatMightThrow();
} catch (error) {
  logger.log(error);
}

Đừng bỏ trường hợp Promise bị lỗi

Lý do tương tự như phần trên, bạn không nên bỏ qua việc bắt các lỗi của Promise bằng try/catch.

Chưa tốt :
getUser()
  .then((user: User) => {
    return sendEmail(user.email, 'Welcome!');
  })
  .catch((error) => {
    console.log(error);
  });
Good:
import { logger } from './logging'

getUser()
  .then((user: User) => {
    return sendEmail(user.email, 'Welcome!');
  })
  .catch((error) => {
    logger.log(error);
  });

// hoặc sử dụng cú phát `async/await`:

try {
  const user = await getUser();
  await sendEmail(user.email, 'Welcome!');
} catch (error) {
  logger.log(error);
}