---
# System prepended metadata

title: clean code
tags: [improve]

---

# clean code

###### tags: `improve`

## Objects and Data Structures
### Use getters and setters

TypeScript支持getter/setter語法，使用getter和setter從封裝行為的對像中訪問數據可能比僅在對像上查找屬性更好。以下是一些原因：


```typescript
type BankAccount = {
  balance: number;
  // ...
}

const value = 100;
const account: BankAccount = {
  balance: 0,
  // ...
};

if (value < 0) {
  throw new Error('Cannot set negative balance.');
}

account.balance = value;
```
示例使用了一個類型為BankAccount的對象，它包含了一個balance屬性和一些其他的屬性。在賦值之前，需要手動驗證值是否為負數，這樣做很麻煩，也容易出錯。

```typescript
class BankAccount {
  private accountBalance: number = 0;

  get balance(): number {
    return this.accountBalance;
  }

  set balance(value: number) {
    if (value < 0) {
      throw new Error('Cannot set negative balance.');
    }

    this.accountBalance = value;
  }

  // ...
}

// Now `BankAccount` encapsulates the validation logic.
// If one day the specifications change, and we need extra validation rule,
// we would have to alter only the `setter` implementation,
// leaving all dependent code unchanged.
const account = new BankAccount();
account.balance = 100;

```

使用了一個BankAccount類，它具有一個私有的accountBalance屬性和一個公共的getter和setter來訪問balance屬性。 setter驗證要設置的值是否為負，並在值為負時拋出錯誤。這有助於確保balance屬性始終是有效的值。

使用getter和setter可以確保對象的內部表示被封裝，任何對對象屬性的更改都被驗證，以確保它們是有效的，從而預防錯誤並提高代碼的可維護性。

1. 當我們想要超出獲取對象屬性時，我們無需查找和更改代碼庫中的每個訪問器。

```typescript
class User {
  private _name: string;

  constructor(name: string) {
    this._name = name;
  }

  get name(): string {
    return this._name.toUpperCase();
  }
}

const user = new User('John');
console.log(user.name); // output: "JOHN"
```

在上面的示例中，我們定義了一個User類，它具有一個私有的_name屬性和一個公共的getter來訪問該屬性。 getter將_name屬性的值轉換為大寫，並返回該值。這意味著我們可以使用user.name來獲取用戶的姓名，而無需查找和更改代碼庫中的每個訪問器。

2. 在進行設置時添加驗證非常簡單。

```typescript
class BankAccount {
  private _balance: number = 0;

  get balance(): number {
    return this._balance;
  }

  set balance(value: number) {
    if (value < 0) {
      throw new Error('Cannot set negative balance.');
    }

    this._balance = value;
  }
}

const account = new BankAccount();
account.balance = 100; // valid
account.balance = -50; // throws an error
```

在上面的示例中，我們定義了一個BankAccount類，它具有一個私有的_balance屬性和一個公共的getter和setter來訪問該屬性。 setter驗證要設置的值是否為負數，並在值為負數時拋出錯誤。這有助於確保_balance屬性始終是有效的值。

3. 封裝內部表示。

```typescript
class Person {
  private _name: string;
  private _age: number;

  constructor(name: string, age: number) {
    this._name = name;
    this._age = age;
  }

  get name(): string {
    return this._name;
  }

  set name(value: string) {
    this._name = value.trim();
  }

  get age(): number {
    return this._age;
  }

  set age(value: number) {
    if (value < 0) {
      throw new Error('Age cannot be negative.');
    }
    this._age = value;
  }
}

const person = new Person('John', 30);
person.name = '  Jane  ';
person.age = 25;

console.log(person.name); // output: "Jane"
console.log(person.age); // output: 25
```

在上面的示例中，我們定義了一個Person類，它具有一個私有的_name屬性和一個私有的_age屬性，以及公共的getter和setter來訪問這些屬性。 setter使用trim方法來刪除用戶名中的空格。這意味著我們可以使用person.name來獲取用戶的姓名，而無需關心姓名中是否包含空格。

4. 在獲取和設置時添加日誌記錄和錯誤處理非常容易。

```typescript
class Counter {
  private _count: number = 0;

  get count(): number {
    console.log('Getting count...');
    return this._count;
  }

  set count(value: number) {
    if (value < 0) {
      throw new Error('Count cannot be negative.');
    }
    console.log('Setting count...');
    this._count = value;
  }
}

const counter = new Counter();
counter.count = 10; // logs "Setting count..."
console.log(counter.count); // logs "Getting count..." and returns 10
```

在上面的示例中，我們定義了一個Counter類，它具有一個私有的_count屬性和一個公共的getter和setter來訪問該屬性。 getter和setter分別在獲取和設置屬性時記錄日誌。 setter還驗證要設置的值是否為負數，並在值為負數時拋出錯誤。這使得添加日誌記錄和錯誤處理變得非常容易。

5. 您可以惰性加載您對象的屬性，比如從服務器獲取它。

```typescript
class User {
  private _name: string | null = null;

  get name(): string {
    if (this._name === null) {
      // fetch name from server
      this._name = 'John';
    }
    return this._name;
  }
}

const user = new User();
console.log(user.name); // logs "John", fetches name from server first time
console.log(user.name); // logs "John", returns cached name second time
```

我們可以惰性加載用戶的屬性，例如從服務器獲取它，而不必在對象創建時立即獲取該屬性的值。這可以提高性能，並減少網絡請求。 提高 cache hit 減少 cache miss

```typescript
type Member = {
  id: string,
  name: string,
}

type Team = {
  name: string,
  members: Member[],
  currentMember: Member
}

function mustFind<T>(arr: T[], predicate: (t: T) => boolean) {
  const item = arr.find(predicate)
  if (item) return item
  throw new Error('item not find')
}


function problemGetMemberById(memberId: string): Team {
  const Members: Member[] = [
    { id: '1', name: 'Danny' },
    { id: '2', name: 'Alex' },
    { id: '3', name: 'Blob' },
  ]
  return {
    name: 'My_team',
    members: Members,
    currentMember: Members.find(item => item.id === memberId)
  }
}
function getMemberById(memberId: string): Team {
  const Members: Member[] = [
    { id: '1', name: 'Danny' },
    { id: '2', name: 'Alex' },
    { id: '3', name: 'Blob' },
  ]
  const currentMember = mustFind(Members, (item) => item.id === memberId)
  return {
    name: 'My_team',
    members: Members,
    currentMember
  }
}

function getMemberById2(memberId: string): Team {
  const Members: Member[] = [
    { id: '1', name: 'Danny' },
    { id: '2', name: 'Alex' },
    { id: '3', name: 'Blob' },
  ]
  return {
    name: 'My_team',
    members: Members,
    get currentMember() {
      const member = Members.find(i => i.id === memberId)
      if (member) return member
      throw new Error('Member not find')
    }
  }
}

```


### Make objects have private/protected members

```typescript!

//bad
class Circle {
  radius: number;
  
  constructor(radius: number) {
    this.radius = radius;
  }

  perimeter() {
    return 2 * Math.PI * this.radius;
  }

  surface() {
    return Math.PI * this.radius * this.radius;
  }
}

//good
class Circle {
  constructor(private readonly radius: number) {
  }

  perimeter() {
    return 2 * Math.PI * this.radius;
  }

  surface() {
    return Math.PI * this.radius * this.radius;
  }
}
```

在TypeScript中，支持使用public（默認）、protected和private訪問器來訪問類成員。

上面的示例中，我們定義了一個Circle類，用於計算圓的周長和麵積。在“Bad”示例中，我們將radius屬性設置為公共屬性，這意味著它可以被外部代碼直接訪問和更改。這違反了封裝的原則，因為我們希望將radius屬性的訪問限制在類內部。

在“Good”示例中，我們將radius屬性設置為只讀的私有屬性。這意味著我們可以在類內部訪問和更改它，但是外部代碼不能訪問或更改它。這符合封裝的原則，因為我們可以控制radius屬性的訪問權限，並防止外部代碼對它進行非法操作。


### Prefer immutability

```typescript

//bad
interface Config {
  host: string;
  port: string;
  db: string;
}

//good
interface Config {
  readonly host: string;
  readonly port: string;
  readonly db: string;
}
```
```typescript
//bad
const array: number[] = [ 1, 3, 5 ];
array = []; // error
array.push(100); // array will be updated

//good

const array: ReadonlyArray<number> = [ 1, 3, 5 ];
array = []; // error
array.push(100); // error
```

```typescript!
//bad
const config = {
  hello: 'world'
};
config.hello = 'world'; // value is changed

const array  = [ 1, 3, 5 ];
array[0] = 10; // value is changed

// writable objects is returned
function readonlyData(value: number) {
  return { value };
}

const result = readonlyData(100);
result.value = 200; // value is changed

//good
// read-only object
const config = {
  hello: 'world'
} as const;
config.hello = 'world'; // error

// read-only array
const array  = [ 1, 3, 5 ] as const;
array[0] = 10; // error

// You can return read-only objects
function readonlyData(value: number) {
  return { value } as const;
}

const result = readonlyData(100);
result.value = 200; // error
```

以下是 TypeScript 中的不可變性的 5 個重點：

1. 使用 `readonly` 關鍵字來標記只讀屬性
2. 使用 `ReadonlyArray<T>` 來創建只讀數組
3. 在函數中使用 `readonly` 關鍵字來標記只讀參數
4. 使用 `const` 斷言將對象標記為只讀
5. 使用 `as const` 將數組和對象字面量標記為只讀

下面給出一個示例代碼，演示瞭如何使用上述 5 個技術來實現不可變性：

```typescript
interface User {
  readonly id: number;
  readonly name: string;
  readonly age: number;
}

function createUser(id: number, name: string, age: number): User {
  return {
    id,
    name,
    age,
  } as const;
}

const users: ReadonlyArray<User> = [
  createUser(1, 'Alice', 25),
  createUser(2, 'Bob', 30),
  createUser(3, 'Charlie', 35),
];

function printUsers(users: ReadonlyArray<User>) {
  users.forEach((user) => {
    console.log(`ID: ${user.id}, Name: ${user.name}, Age: ${user.age}`);
  });
}

printUsers(users);

// Attempt to modify a property of a User object
users[0].name = 'Eve'; // Error: Cannot assign to 'name' because it is a read-only property.
```

在上面的代碼中，我們定義了一個只讀的 `User` 接口，它包含了 `id`、`name` 和 `age` 三個只讀屬性。我們還定義了一個 `createUser` 函數，它使用 `readonly` 關鍵字來標記返回的對象為只讀。我們使用 `as const` 將對象字面量強制轉換為只讀對象。

我們還定義了一個只讀的 `users` 數組，它包含了三個只讀的 `User` 對象。我們使用 `ReadonlyArray<User>` 來標記這個數組為只讀數組。

最後，我們定義了一個 `printUsers` 函數，它接受一個只讀的 `users` 數組作為參數，並輸出每個 `User` 對象的 `id`、`name` 和 `age` 屬性。我們嘗試修改 `users` 數組中第一個 `User` 對象的 `name` 屬性，但是 TypeScript 報告了一個錯誤，因為這個屬性是只讀的。


### type vs. interface

```typescript
//bad
interface EmailConfig {
  // ...
}

interface DbConfig {
  // ...
}

interface Config {
  // ...
}

//...

type Shape = {
  // ...
}

//good

type EmailConfig = {
  // ...
}

type DbConfig = {
  // ...
}

type Config  = EmailConfig | DbConfig;

// ...

interface Shape {
  // ...
}

class Circle implements Shape {
  // ...
}

class Square implements Shape {
  // ...
}
```
| Aspect | Type | Interface |
| --- | --- | --- |
| Can describe functions | ✅ | ✅ |
| Can describe constructors | ✅ | ✅ |
| Can describe tuples | ✅ | ✅ |
| Interfaces can extend it | ⚠️ | ✅ |
| Classes can extend it | 🚫 | ✅ |
| Classes can implement it (implements) | ⚠️ | ✅ |
| Can intersect another one of its kind | ✅ | ⚠️ |
| Can create a union with another one of its kind | ✅ | 🚫 |
| Can be used to create mapped types | ✅ | 🚫 |
| Can be mapped over with mapped types | ✅ | ✅ |
| Expands in error messages and logs | ✅ | 🚫 |
| Can be augmented | 🚫 | ✅ |
| Can be recursive | ⚠️ | ✅ |




### Can intersect another one of its kind:

```typescript
type A = {
  propA: string;
};

type B = {
  propB: number;
};

type C = A & B;

const c: C = {
  propA: 'hello',
  propB: 123,
};
```

### Can create a union with another one of its kind:

```typescript
type ResultType = number | string;

function processResult(result: ResultType) {
  if (typeof result === 'number') {
    console.log(`The result is a number: ${result}`);
  } else {
    console.log(`The result is a string: ${result}`);
  }
}

processResult(123);
processResult('hello');
```


### Can be used to create mapped types:

```typescript
type Person = {
  name: string;
  age: number;
};

type PersonRecord = {
  [K in keyof Person]: string;
}

const person: Person = { name: 'Alice', age: 30 };
const personRecord: PersonRecord = { name: 'Alice', age: '30' };
```


### Can be mapped over with mapped types:

```typescript
type Person = {
  name: string;
  age: number;
};

type PersonPartial = {
  [K in keyof Person]?: Person[K];
}

const person: Person = { name: 'Alice', age: 30 };
const personPartial: PersonPartial = { name: 'Alice' };

type PersonPartials<T> = {
  [k in keyof T]?:T[k]
}
```


### Expands in error messages and logs:

```typescript
type Person = {
  name: string;
  age: number;
};

function greet(person: Person) {
  console.log(`Hello, ${person.name}! You are ${person.age} years old.`);
}

const person = { name: 'Alice', age: '30' };
greet(person);
```
`person.age` 的类型错误。

### Can be augmented:

```typescript
interface Person {
  name: string;
  age: number;
}

interface Person {
  gender: string;
}

const person: Person = { name: 'Alice', age: 30, gender: 'female' };
```

### Can be recursive:

```typescript
type TreeNode<T> = {
  value: T;
  left?: TreeNode<T>;
  right?: TreeNode<T>;
};

const root: TreeNode<number> = {
  value: 1,
  left: {
    value: 2,
    left: { value: 4 },
    right: { value: 5 },
  },
  right: {
    value: 3,
    left: { value: 6 },
    right: { value: 7 },
  },
};
```





