【良いコードを書くための Tips】よりコードを綺麗にする編


# 概要

https://qiita.com/Yuki-TU/items/f2bb9b8146f490ef25d8　

の続きです。例題はTypeScriptで記載しております。


# 目次
1. 導入
2. 命名規則
3. **より綺麗にする**(<-ここ)

# 3. より綺麗にする方法

## フォーマッタの使用

- js
  - js-beautify (Visual Studio Code)
  - eslint --fix
  - prettier
- python
  - autopep8
  - yapf
  - black

---

### Prettier

設定できる値

- タブの文字数
- シングルクォーテーションかダブルクォーテーション
- 文字横幅数
- 文終わりにセミコロンをつけるか

など

vscode では、保存とともに自動整形してくれる機能がある(要拡張機能インストール)

---

## メソッド化

重複をメソッド化してきれいにする

```ts
outputFullName("taro", "yamada");
outputFullName("genta", "yamada");
outputFullName("yuta", "kato");

function outputFullName(under: string, top: string) {
  console.log(`${top} ${under}`);
}
```

---

### メソッド化による恩恵

- コードの可読性が向上
- 変更容易性
  - 出力が console.log から alert に変更なってもすぐ変更可
- テストが容易

---

## リンターの導入

- リンターとは、プログラムの静的解析ツール
- ルールに則って、良くない記述をしているコードを指摘してくれる
- TypeScriptではeslintなどが使われる
    - 適用するルールはAirbnbなどが有名


```ts
// ルールによっては、console.logの利用は良くないよと指摘される
// WARN: 
console.log("hhh");
```


---

## 一貫性のある並び順

- import 等の並び順は意味のある並び順にする
    - アルファベット順
    - 自作モジュール, オープンソースモジュール

```ts
// react
import React, { Suspense } from "react";
import ReactDOM from "react-dom";

// material-ui
import AccessAlarmIcon from "@mui/icons-material/AccessAlarm";
import Button from "@mui/material/Button";
import ThreeDRotation from "@mui/icons-material/ThreeDRotation";

// 自作モジュール
import { Address } from "./Address";
import { Email } from "./Email";
import { PhoneNumber } from "./PhoneNumber";
import { Shop } from "./Shop";
import { User } from "./User";
```

vscodeでは、import文を綺麗にしてくれるショートカットがある

---

## 宣言はブロック化

インスタンスプロパティの宣言はブロックにしてコメントにすると見やすい

```ts
class PurchaseHIstory {
  // ユーザ情報
  private address: Address;
  private phoneNumber: PhoneNumber;
  private userName: UserName;

  // 購入情報
  private numberOfPurchasedItems: PurchasedItem;
  private itemInformation: ItemInformation;

  // スケージュール関連
  private purchaseDate: PurchaseDate;
  private paymentDate: PaymentDate;
}
```

---

## 段落にわける

段落にわけてコメントにすると可読性向上

```ts
suggestNewFriends(user: User){
    // 友達がいなければ、新しい友達は提案しない
    if(user.friendNumer()) {
        return;
    }

    // ユーザの友達のメールアドレスをい取得
    const friends = user.friends();
    friends.forEach((friend: User) => {
        friendEmails.push(friend.email);
    })

    // また友達になっていないユーザを探す
    const suggestedFriendsEmail = searchFriendsOfFriends(friendEmails);
    const suggestedFriends = loadUserName(suggestedFriendsEmail);

    // まだ友達になっていないユーザを画面に表示
    outputFriendsEmail(suggestedFriends);
}
```


## コメントすべきではない

コードをそのまま説明しているコメントは意味がないコメント

```ts
suggestNewFriends(user: User){
    // 友達がいなければ早期リターン
    if(user.friendNumer()) {
        return;
    }
}

// ユーザを登録する
resisterUser(user);
```

---

## コメントすべきではない

- なぜや捕捉情報を意識してコメントをつける
    - コードだけでは表現できない情報をコメントで残す

```ts
suggestNewFriends(user: User){
    // 友第がいなければ、新しい友達は提案しない
    // 友第の友達より提案する仕様となっているため
    if(user.friendNumer()) {
        return;
    }
}

// データベースに対してユーザ登録
resisterUser(user);
```

---

## ひどい名前はコメントをつけず名前を変える

優れたコード > ひどいコード + コメント

```ts
//bad
// 指定ユーザを登録解除する。ユーザは削除しない
destoryUser(user);

// good
unresisterUser(user);
```

---

## コードの欠陥にコメント

各欠陥に応じてコメントとして残す

```ts
// TODO: 一時的にテスト用の値を利用、後で修正
// FIXME: 既知の不具合がある
// HACK: あまりいい方法ではない
```

vscode に TODO をハイライト・リスト表示してくれる拡張機能がある

[TODO Highlight](https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight)

---

## 定数にもコメント

なぜその値なのか、取りうる価はをコメント

```ts
// 10アイテムを登録する（仕様）
const MAX_RESISTER_ITEMS = 10;
```

---

## 読み手の立場になり質問されそうなところをコメント

- コミット、プッシュする前に確認

```ts
document.addEventListener("keydown", (event) => {
    if (event.key !== undefined) {
        // IE以外の端末はkeyを使用してプッシュキーを判断
        isHandled = this.keybordEventHundlerForKey(event.key);
        return;
    }
    if (event.keyCode !== undefined) {
        // IEではkeyが使用できないためkeyCodeを使用
        isHandled = this.keybordEventHundlerForKeyCode(event.keyCode);
        return;
    }
}
```

---

## ファイル、クラスに全体像をコメント

クラス、ファイルの冒頭に全体像を**高いレベル**でコメント

TypeScript では JSDocs 等で残す

```ts
/**
 * ユーザーに関するクラス
 * ユーザに関する処理はすべてこのクラスに記述している
 * @class
 */
class User {
  /**
   * ユーザーをアプリサーバーを介してDBに登録する
   * @param {User} user - ユーザ
   * @return {boolean} 登録結果 (true: 登録成功, false: 登録失敗)
   */
  async resisterUser(user: User) {
    try {
      await axios.post(url, user);
      return true;
    } catch (error) {
      console.error(error);
      return false;
    }
  }
}
```

---

## 曖昧な言葉を避ける

```ts
// bad(それは、キャッシュ？データ？)
// データをキャッシュに入れる。そのサイズをチェックする。

// ok
// データをキャッシュに入れる。キャッシュに入れるデータのサイズをチェックする。
```

---

## なぜを書く

- なぜそのコードを追加しているのかが、コード上で判断できなければ追記する
- 第三者が見てもすぐ理解できることを意識

```ts
document.addEventListener("keydown", (event) => {
    if (event.key !== undefined) {
        // IE以外の端末はkeyを使用してプッシュキーを判断(MDN参照)
        isHandled = this.keybordEventHundlerForKey(event.key);
        return;
    }
    if (event.keyCode !== undefined) {
        // IEではkeyが使用できないためkeyCodeを使用(MDN参照)
        isHandled = this.keybordEventHundlerForKeyCode(event.keyCode);
        return;
    }
}
```

---

## 高いレベルのコメントを残す

- より仕様書に近いような高いレベルのコメントを残す
- 具体例を載せる


```ts
// bad
const numbers = [21, -333, 4, 0, 447, 32, 3, 612, 6, -1, 1, 14, -9, 11, -59];
// 値をソートする
numbers.sort((comparedValue, otherComparedValue) => {
  if (comparedValue > otherComparedValue) {
    return 1;
  }
  if (comparedValue < otherComparedValue) {
    return -1;
  }
  return 0;
});

// good
const numbers = [21, -333, 4, 0, 447, 32, 3, 612, 6, -1, 1, 14, -9, 11, -59];
// 配列の数値を小さい順にソートする
// 例）[3, 9, -1, -3] -> [-3, -1, 3, 9]
numbers.sort((comparedValue, otherComparedValue) => {
  if (comparedValue > otherComparedValue) {
    return 1;
  }
  if (comparedValue < otherComparedValue) {
    return -1;
  }
  return 0;
});
```
---

## 中間変数を削除

早期リターンを利用することで中間変数を削除する

```ts
// bad
function searchRepliesDate(messageId: MessageId, targetId: UserId): string {
  const allReplies = Messages.objects.select(messageId);
  let date = "";
  allReplies.forEach((reply: Reply) => {
    if (reply.userId === targetid) {
      date = reply.date;
    }
  });
  if (date === "") {
    return "見つかりませんでした。";
  }
  return date;
}

// good
function searchRepliesDate(messageId: MessageId, targetId: UserId) {
  const allReplies = Messages.objects.select(messageId);
  allReplies.forEach((reply: Reply) => {
    if (reply.userId === targetid) {
      return reply.date;
    }
  });
  return "見つかりませんでした。";
}
```

---

## スコープを小さくする

- なるべくインスタンスプロパティは定義しない
- インスタンスプロパティは、クラス内では自由に使えるためミニグローバル変数と呼ばれる

```ts
// bad
class UserService {
  private userName: UserName;
  async resisterUser(user: User) {
    // userNameはこのメソッドのみで利用
    this.userName = user.userName;
  }
}

// good
class UserService {
  async resisterUser(user: User) {
    const userName = user.userName;
  }
}
```

---

## 定義の位置を下げる
- 変数の定義は使用する直前に定義する
- 最初からすべての変数を知るとそれらを考えて以降の処理を読むのはナンセンス

```ts
// bad
function viewFilteredReplies(messageId: MessageId) {
  const rootMessage = Messages.objects.get(messageId);
  const allReplies = Messages.objects.select(messageId);
  let filteredReplies = [];

  rootMessage.viewCount++;
  rootMessage.lastViewTime = parseDate(Date.now());
  rootMessage.save();

  allReplies.forEach((reply: Reply) => {
    if (reply.spamVotes <= MAX_SPAM_VOTES) {
      filteredReplies.push(reply);
    }
  });
  return filteredReplies;
}

// good
function viewFilteredReplies(messageId: MessageId) {
  // メッセージ閲覧情報の更新
  const rootMessage = Messages.objects.get(messageId);
  rootMessage.viewCount++;
  rootMessage.lastViewTime = parseDate(Date.now());
  rootMessage.save();

  // スパム報告が限度以下のメッセージ抽出
  const allReplies = Messages.objects.select(messageId);
  let filteredReplies = [];
  allReplies.forEach((reply: Reply) => {
    if (reply.spamVotes <= MAX_SPAM_VOTES) {
      filteredReplies.push(reply);
    }
  });
  return filteredReplies;
}
```

---

## 変数は一度だけ書き込む

- 変更が可能な変数を扱うと途中で値が変わってもエラーがないため、バグの原因になりやすい
- 途中で値を変えられる let はなるべく使用せず、const, readonly をできるだけ利用する

```ts
// bad
function calculatePrice(unitPrice: number, quantity: number) {
  let price = unitPrice * quantity;
  if (price < 3000) {
    price += 500;
  }
  price = price * taxRate();
  return price;
}

// good
function calculatePrice(unitPrice: number, quantity: number) {
  const basePrice = unitPrice * quantity;
  if (basePrice < 3000) {
    const shippingCost = 500;
    itemPrice = (basePrice + shippingCost) * taxRate();
    return itemPrice;
  }
  const itemPrice = basePrice * taxRate();
  return itemPrice;
}
```





---

## 3. まとめ

- フォーマッターツールの導入
- メソッド化できれいにする
- 並び順に意味を持たせる
- 宣言のブロック化として、空行とコメント
- コードの段落化として、空行とコメント
- コメントすべきではないこと
  - コードからすぐ読み取れるもの
  - ひどいコードはコメントではなくコードを修正 
- 記録すべき自分の考え
  - コードの欠陥を`TODO: 後でやる`等で残す
  - 定数にもコメント
- 読み手の立場になってコメント
  - コードを読んだ人がなぜ？と思うところはコメント
  - 全体像を高いレベルで残す
  - 人によって受け取り方が異なる曖昧な言葉は避ける
- なぜを意識して書く
  - 第三者を意識
  - 自分だけわかれば良いはダメ
- 高いレベルのコメントを書く
  - 仕様書に近いコメントを書く
- 邪魔な変数を削除する
- 変数のスコープをできるだけ小さくする
- 一度だけ書き込む変数を使う