エレガントなJSコードの書き方

エレガントなJSコードの書き方

変数

意味があり発音しやすい変数名を使用する

// 間違った書き方 const yyyymmdstr = moment().format("YYYY/MM/DD");

// 良い書き方 const currentDate = moment().format("YYYY/MM/DD");

同じ型の変数には同じ語彙を使用する

// getUserInfo() の書き方が間違っている
クライアントデータを取得します。
顧客レコードを取得します。

// getUser() を書く良い方法

検索可能な名前を使用する

私たちは書くよりも読むことの方が多いので、命名があまりにも恣意的だと​​、その後のメンテナンスが難しくなるだけでなく、コードを読む開発者にも悪影響を及ぼします。変数名は人間が読めるものにしてください。buddy.js や ESLint などのツールは、名前のない定数を識別するのに役立ちます。

// 悪い書き方 // 86400000 の目的は何ですか?
タイムアウトを設定します(blastOff、86400000);

// 良い書き方 const MILLISECONDS_IN_A_DAY = 86_400_000;
setTimeout(blastOff, 1日あたりミリ秒);

説明変数の使用

// 書き込みが間違っています const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\s]+(.+?)\s*(\d{5})?$/;
保存都市郵便番号(
  address.match(cityZipCodeRegex)[1],
  address.match(cityZipCodeRegex)[2]
);


// 適切な書き方 const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\s]+(.+?)\s*(\d{5})?$/;
const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
CityZipCode を保存します(都市、郵便番号);

推測を排除

暗黙の明示

// 構文が間違っています const locations = ["Austin", "New York", "San Francisco"];
場所.forEach(l => {
  メソッド
  その他の操作を実行します。
  // ...
  // ...
  // ...
  // ちょっと待って、「l」って何ですか?
  ディスパッチ(l);

// 良い書き方 const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(場所 => {
  メソッド
  その他の操作を実行します。
  // ...
  // ...
  // ...
  ディスパッチ(場所);
});

不必要なコンテキストを追加する必要はない

クラス名/オブジェクト名がすでに記載されている場合は、変数名で繰り返す必要はありません。

// 間違った書き方 const Car = {
  車種:「ホンダ」
  車種:「アコード」、
  車の色:「青」
};

関数paintCar(車) {
  car.carColor = "赤";
}
// 良い書き方 const Car = {
  メーカー:「ホンダ」
  モデル:「アコード」、
  色: 「青」
};

関数paintCar(車) {
  car.color = "赤";
}

論理OR(AND)演算の代わりにデフォルトのパラメータを使用する

// 間違った書き方 function createMicrobrewery(name) {
  const breweryName = name || "Hipster Brew Co.";
  // ...
}
// 良い実践例 function createMicrobrewery(name = "Hipster Brew Co.") {
  // ...
}

関数

関数パラメータ(理想的には2以下)

関数のパラメータの数を制限すると、関数のテストが容易になるため、非常に重要です。パラメータが 3 つ以上ある場合は、組み合わせ爆発が発生し、個々のパラメータごとに多数の異なるケースをテストする必要があります。

1 つまたは 2 つのパラメータが理想的であり、可能であれば 3 つのパラメータは避けてください。 さらに、統合する必要があります。ほとんどの場合、3 つ以上のパラメータをオブジェクトに置き換えることができます。

// 間違った書き方 function createMenu(title, body, buttonText, cancelable) {
  // ...
}

メニューを作成します("Foo", "Bar", "Baz", true);

// 良い書き方 function createMenu({ title, body, buttonText, cancelable }) {
  // ...
}

メニューを作成します({
  タイトル: 「Foo」、
  本文: "バー",
  ボタンテキスト: "Baz",
  キャンセル可能: true
});

関数は1つのことだけを行うべきである

これはソフトウェア エンジニアリングにおいて最も重要なルールです。関数が複数のことを実行する場合、関数の作成、テスト、および推論が難しくなります。関数を単一の操作に分離できると、リファクタリングが容易になり、コードがよりわかりやすくなります。

// 間違った書き方 function emailClients(clients) {
  クライアント.forEach(クライアント => {
    const clientRecord = database.lookup(client);
    クライアントレコードがアクティブである場合
      電子メール(クライアント);
    }
  });
}

// 良い書き方 function emailActiveClients(clients) {
  クライアントのフィルターを(isActiveClient).forEach(メール);
}

関数isActiveClient(クライアント) {
  const clientRecord = database.lookup(client);
  clientRecord.isActive() を返します。
}

関数名は何をするかを説明する必要があります

// 間違った書き方 function addToDate(date, month) {
  // ...
}

定数date = 新しいDate();

// 関数名から何を追加するのかわかりにくい addToDate(date, 1);

// 良い書き方 function addMonthToDate(month, date) {
  // ...
}

定数date = 新しいDate();
月を日付に追加します(1、日付);

関数は1つの抽象レベルのみを持つべきである

関数に複数の抽象化レベルがある場合、関数が実行する処理が多すぎるため、再利用性とテストの容易さのために関数を分割する必要があることを意味します。

// 関数の書き方の間違い parseBetterjsAlternative(code) {
  定数正規表現 = [
    // ...
  ];

  const ステートメント = code.split(" ");
  const トークン = [];
  REGEXES.forEach(REGEX => {
    ステートメント.forEach(ステートメント => {
      // ...
    });
  });

  定数ast = [];
  トークン.forEach(トークン => {
    // レックス...
  });

  ast.forEach(ノード => {
    // 解析...
  });
}

// 関数の書き方 parseBetterJSAlternative(code) {
  const トークン = tokenize(コード);
  const syntaxTree = parse(トークン);
  構文Tree.forEach(ノード => {
    // 解析...
  });
}

関数 tokenize(コード) {
  定数正規表現 = [
    // ...
  ];

  const ステートメント = code.split(" ");
  const トークン = [];
  REGEXES.forEach(REGEX => {
    ステートメント.forEach(ステートメント => {
      トークンをプッシュします(/* ... */);
    });
  });

  トークンを返します。
}

関数parse(トークン) {
  const 構文ツリー = [];
  トークン.forEach(トークン => {
    構文ツリーをプッシュします(/* ... */);
  });

  構文ツリーを返します。
}

重複コードを削除する

重複したコードを避けるようにしてください。重複したコードは良くありません。つまり、ロジックを変更する必要がある場合、多くの場所を変更する必要があります。

多くの場合、重複したコードが存在するのは、共通点が多く、わずかに異なるものが 2 つ以上あるためですが、それらの違いにより、多くの同じことを実行するのに 2 つ以上の個別の関数を記述する必要があります。 重複コードを削除するということは、1 つの関数/モジュール/クラスだけでこのさまざまなもののセットを処理する抽象化を作成することを意味します。

抽象化を正しく行うことは非常に重要なので、クラス セクションに記載されている SOLID 原則に従う必要があります。不適切な抽象化は重複したコードよりも悪影響を及ぼす可能性があるため、注意してください。とはいえ、適切な抽象化を作成できる場合は、それを実行してください。同じことを繰り返さないでください。そうしないと、1 つの変更を行うたびに複数の場所を更新しなければならないことに気付くでしょう。

デザインパターンの 6 つの原則は次のとおりです。

  • 単一責任の原則
  • オープンクローズ原則: オープンクローズ原則
  • リスコフの置換原則: リスコフの置換原則
  • デメテルの法則:
  • インターフェース分離の原則: インターフェース分離の原則
  • 依存逆転の原理

これら 6 つの原則の頭文字 (L が 2 つで 1 つ) を組み合わせると SOLID (solid) となり、これはこれら 6 つの原則を組み合わせて使用​​することで得られる、安定性、柔軟性、堅牢性を備えた設計を確立できるという利点を表しています。これら 6 つの設計原則を 1 つずつ見ていきましょう。

悪い文章

関数 showDeveloperList(開発者) {
  開発者.forEach(開発者 => {
    予想される給与を計算します。
    定数 experience = developer.getExperience();
    GithubLink を getGithubLink() に追加します。
    定数データ = {
      希望給与、
      経験、
      githubリンク
    };

    レンダリング(データ);
  });
}

関数 showManagerList(マネージャー) {
  マネージャー.forEach(マネージャー => {
    const 期待給与 = manager.calculateExpectedSalary();
    定数エクスペリエンス = manager.getExperience();
    const ポートフォリオ = manager.getMBAProjects();
    定数データ = {
      希望給与、
      経験、
      ポートフォリオ
    };

    レンダリング(データ);
  });
}

良い文章

関数 showEmployeeList(従業員) {
  従業員.forEach(従業員 => {
    期待される給与を計算します。
    定数の経験 = 従業員.getExperience();

    定数データ = {
      希望給与、
      経験
    };

    スイッチ(従業員タイプ){
      ケース「マネージャー」:
        data.portfolio = 従業員.getMBAProjects();
        壊す;
      ケース「開発者」:
        データ.githubLink = employee.getGithubLink();
        壊す;
    }

    レンダリング(データ);
  });
}

Object.assign を使用してデフォルトのオブジェクトを設定する

悪い文章

const メニュー構成 = {
  タイトル: null、
  本文: "バー",
  ボタンテキスト: null、
  キャンセル可能: true
};

関数createMenu(config) {
  config.title = config.title || "Foo";
  config.body = config.body || "バー";
  config.buttonText = config.buttonText || "バズ";
  config.cancellable =
    config.cancellable !== 未定義ですか? config.cancellable : true;
}

メニューを作成します(menuConfig);

良い文章

const メニュー構成 = {
  タイトル: 「注文」、
  // ユーザーは 'body' キーを含めませんでした
  ボタンテキスト: "送信",
  キャンセル可能: true
};

関数createMenu(config) {
  config = オブジェクト.assign(
    {
      タイトル: 「Foo」、
      本文: "バー",
      ボタンテキスト: "Baz",
      キャンセル可能: true
    },
    設定
  );

  // 設定は次のようになります: {title: "Order", body: "Bar", buttonText: "Send", cancelable: true}
  // ...
}

メニューを作成します(menuConfig);

関数の引数としてフラグを使用しない

このフラグは、この関数が複数のタスクを実行できることと、関数が 1 つのタスクを実行する必要があることをユーザーに伝えます。 関数がブール値に基づいて異なるコードパスに従う場合は、それらを分割します。

// 書き込みエラー function createFile(name, temp) {
  (一時)の場合{
    fs.create(`./temp/${name}`);
  } それ以外 {
    fs.create(名前);
  }
}

// 良い書き方 function createFile(name) {
  fs.create(名前);
}

関数createTempFile(名前) {
  ファイルを作成します(`./temp/${name}`);
}

副作用の回避(パート1)

関数が値を受け入れて別の値を返すこと以外何もしない場合は、その関数には副作用があります。 副作用としては、ファイルへの書き込み、グローバル変数の変更、または誤ってすべての資金を他人に送信してしまうことが考えられます。

悪い文章

名前を「ライアン・マクダーモット」にします。

関数splitIntoFirstAndLastName() {
  名前 = 名前.split(" ");
}

姓と名を分割します();

console.log(name); // ['Ryan', 'McDermott'];

良い文章

関数splitIntoFirstAndLastName(名前) {
  name.split(" "); を返します。
}

const name = "ライアン・マクダーモット";
const newName = splitIntoFirstAndLastName(名前);

console.log(name); // 'ライアン・マクダーモット';
console.log(newName); // ['Ryan', 'McDermott'];

副作用の回避(パート2)

JavaScript では、プリミティブ値は値で渡され、オブジェクト/配列は参照で渡されます。 オブジェクトと配列の場合、関数がカート配列に変更を加えると (たとえば、購入するアイテムを追加するなど)、そのカート配列を使用する他の関数もこの追加の影響を受けます。 それは素晴らしいことかもしれないが、悪いことかもしれない。 悪い状況を想像してみましょう:

ユーザーが「購入」ボタンをクリックすると、購入関数が呼び出され、ネットワーク要求が行われ、カート配列がサーバーに送信されます。ネットワーク接続が不良なため、購入機能はリクエストを継続的に再試行する必要があります。さて、ネットワーク リクエストが開始される前に、ユーザーが実際には欲しくないアイテムの [カートに追加] ボタンを誤ってクリックした場合はどうなるでしょうか。このような状況が発生し、ネットワーク リクエストが開始されると、購入関数は、誤って追加されたアイテムを送信します。これは、カート配列への参照があり、addItemToCart 関数によってその参照が追加されて変更されたためです。

良い解決策としては、addItemToCart が常にカート配列を複製し、それを編集してから複製を返すことです。これにより、カートによって参照される他の機能が変更の影響を受けなくなります。

このアプローチに関して注意すべき点が 2 つあります。

1. 入力オブジェクトを変更する必要がある場合もありますが、このプログラミング手法を採用すると、このような状況は非常にまれであり、ほとんどの場合は副作用なしで変換できることがわかります。

2. 大きなオブジェクトの複製は、パフォーマンスの面で非常にコストがかかる可能性があります。 幸いなことに、このプログラミング方法を高速化し、オブジェクトや配列を手動で複製するほどメモリを消費しない優れたライブラリが多数あるため、これは実際には大きな問題ではありません。

// 間違った書き方 const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

// 良い書き方 const addItemToCart = (cart, item) => {
  [...cart、{item、date: Date.now() }] を返します。
};

グローバル関数を書かない

グローバル変数を汚染することは、JS ではよくない習慣です。別のライブラリと競合する可能性があり、運用中に例外が発生するまで API のユーザーには何も残されないからです。 例を考えてみましょう。JS のネイティブ Array メソッドを拡張して、2 つの配列の違いを表示できる diff メソッドを追加したい場合はどうなるでしょうか。 Array.prototype に新しい関数を記述することは可能ですが、同じことを実行しようとする別のライブラリと競合する可能性があります。 他のライブラリが配列の最初の要素と最後の要素の違いを見つけるためにのみ diff を使用する場合はどうなるでしょうか? そのため、ES6 クラスを使用して、Array グローバルを単純に拡張する方がはるかに適切です。

// 間違った書き方 Array.prototype.diff = function diff(comparisonArray) {
  const ハッシュ = 新しい Set(比較配列);
  this.filter(elem => !hash.has(elem)) を返します。
};

// 良い書き方 class SuperArray extends Array {
  diff(比較配列) {
    const ハッシュ = 新しい Set(比較配列);
    this.filter(elem => !hash.has(elem)) を返します。
  }
}

命令型プログラミングの代わりに関数型プログラミングを使用する

JavaScript は Haskell のような関数型言語ではありませんが、関数型スタイルを持っています。関数型言語はより簡潔でテストが容易になります。可能であれば、このスタイルのプログラミングを優先するようにしてください。

悪い文章

const プログラマ出力 = [
  {
    名前: 「ボビーおじさん」
    コード行数: 500
  },
  {
    名前:「スージーQ」
    コード行数: 1500
  },
  {
    名前: 「ジミー・ゴスリング」
    コード行数: 150
  },
  {
    名前:「グレイシー・ホッパー」
    コード行数: 1000
  }
];

合計出力を 0 にします。

(i = 0 とします; i < programmerOutput.length; i++) {
  合計出力 += プログラマー出力[i].コード行;
}

良い文章

const プログラマ出力 = [
  {
    名前: 「ボビーおじさん」
    コード行数: 500
  },
  {
    名前:「スージーQ」
    コード行数: 1500
  },
  {
    名前: 「ジミー・ゴスリング」
    コード行数: 150
  },
  {
    名前: 「グレイシー・ホッパー」
    コード行数: 1000
  }
];

const totalOutput = プログラマー出力.reduce(
  (totalLines, output) => totalLines + output.linesOfCode、
  0
);

梱包条件

// 悪い書き方 if (fsm.state === "fetching" && isEmpty(listNode)) {
  // ...
}

// 良い書き方 function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

条件文の使用を避ける

// 書き込みが間違っている function isDOMNodeNotPresent(node) {
  // ...
}

DOMNodeが存在しない(ノード)の場合
  // ...
}

// 良い書き方 function isDOMNodePresent(node) {
  // ...
}

DOMNodePresent(ノード) の場合
  // ...
}

条件を多用しすぎない

それは不可能な仕事のように思えます。これを聞いて、ほとんどの人は「if 文なしで何かできるの?」と言います。その答えは、ポリモーフィズムを使用すれば、多くの状況で同じタスクを達成できるということです。

2 番目の質問は通常、「それは素晴らしいですが、なぜそうするのですか?」です。その答えは、上で説明した概念です。つまり、関数は 1 つのことだけを行う必要があります。 if ステートメントを含むクラスと関数がある場合、この関数が複数の処理を実行することをユーザーに伝えることになります。

悪い文章

クラス飛行機{
  // ...
  巡航高度を取得する() {
    スイッチ (this.type) {
      ケース「777」:
        this.getMaxAltitude() - this.getPassengerCount() を返します。
      「エアフォースワン」の場合:
        this.getMaxAltitude() を返します。
      ケース「セスナ」:
        this.getMaxAltitude() - this.getFuelExpenditure() を返します。
    }
  }
}

良い文章

クラス飛行機{
  // ...
}

クラス Boeing777 は Airplane を拡張します {
  // ...
  巡航高度を取得する() {
    this.getMaxAltitude() - this.getPassengerCount() を返します。
  }
}

クラスAirForceOneはAirplaneを拡張します{
  // ...
  巡航高度を取得する() {
    this.getMaxAltitude() を返します。
  }
}

クラス Cessna は Airplane を拡張します {
  // ...
  巡航高度を取得する() {
    this.getMaxAltitude() - this.getFuelExpenditure() を返します。
  }
}

型チェックを避ける

JavaScript は型指定がないため、関数は任意の型の引数を受け入れることができます。 時々、この自由度が気になって、関数の型チェックをしたいことがあります。 これを避ける方法はたくさんあります。 最初に考慮すべきことは、一貫性のある API です。

// 間違った書き方 function travelToTexas(vehicle) {
  if (車両インスタンス自転車) {
    車両.ペダル(this.currentLocation、新しい場所("texas"));
  } そうでない場合 (車両インスタンス Car) {
    車両.ドライブ(this.currentLocation、新しい場所("texas"));
  }
}

// 良い書き方 function travelToTexas(vehicle) {
  車両を移動します(this.currentLocation、新しい場所("texas"));
}

過度に最適化しない

最新のブラウザは実行時に多くの最適化作業を行います。多くの場合、最適化を行うと、ただ時間を無駄にしているだけです。最適化が不足している部分を確認するための優れたリソースは存在します。最適化が必要な領域をターゲットにするだけで済みます。

// 悪い習慣 // 古いブラウザでは、キャッシュされていない 'list.length' の各反復はコストがかかります // 'list.length' が再計算されるためです。最近のブラウザでは、これは (let i = 0, len = list.length; i < len; i++) { に最適化されています。
  // ...
}

// 良い書き方 for (let i = 0; i < list.length; i++) {
  // ...
}

以上がエレガントな JS コードの書き方の詳細です。エレガントな JS コードの詳細については、123WORDPRESS.COM の他の関連記事をご覧ください。

以下もご興味があるかもしれません:
  • js シンプルなネットワーク速度テスト方法の完全な例
  • Baidu と Google のスピードテストの JavaScript コードにアクセスする
  • JS 非同期コードユニットテストの魔法 Promise
  • ネイティブ js はフォームの定期的な検証を実装します (検証後にのみ送信)
  • JS での Reduce Fold Unfold の使用法の詳細な説明
  • JS WebSocketを使用して簡単なチャットを実装する方法
  • Vue での weixin-js-sdk の一般的な使用方法の詳細な説明
  • JSホモロジー戦略とCSRFの詳細な説明
  • JavaScript でネットワーク速度をテストする方法

<<:  MySQLは文字列関数のSQL文をインターセプトします

>>:  VMware での Ubuntu 16.04 イメージの完全インストール チュートリアル

推薦する

カルーセルバナーの自動回転効果を実現する純粋な CSS

さっそくコードを見てみましょう * { マージン: 0; パディング: 0; } 。容器 { マージ...

Gearman + MySQL による永続化操作例

この記事では、gearman+mysql メソッドを使用して永続化操作を実装します。ご参考までに、詳...

Linuxで現在のスクリプトの実際のパスを取得する方法

1. 現在のスクリプトの実際のパスを取得します。 #!/bin/bash if [[ $0 =~ ^...

CSS3マスクレイヤーのくり抜き効果を実現するさまざまな方法

この記事では、マスク レイヤーの中空化を実現する 4 つの方法を紹介します。みんなと共有し、自分用の...

アイデアのパッケージ化とクラウドサービスへのアップロードにおけるプロジェクトプロセスの分析

1つ。まず、アイデアとしてパッケージ化する必要があります。私はSpringbootフレームワークプロ...

MySQL データベース 8 - データベース内の関数の適用の詳細な説明

データベースの組み込み関数の使用この記事では、主に日付関数、文字列関数、数学関数など、データベースの...

HttpとHttpsの両方をサポートするNginxの詳細な設定

最近の Web サイトでは Https をサポートすることがほぼ標準機能となっており、Nginx は...

Vueタイムラインコンポーネントの使い方

この記事の例では、参考までにvueタイムラインコンポーネントの具体的な実装コードを共有しています。具...

Windows Server 2012 リモート デスクトップ ライセンス サーバーがライセンスを提供できず、リモート セッションが切断される

本日、会社の内部サーバーにログインしたところ、リモートアクセスができませんでした。エラー メッセージ...

背景画像に CSS3 変換を適用するためのソリューション

CSS 変換は便利ですが、背景画像にはまだ適用されていません。この投稿では、背景画像を回転させたい場...

MySQLインデックスの詳細

1. インデックスの原則インデックスは、列内の特定の値を持つ行をすばやく見つけるために使用されます。...

CocosCreatorでWeChatゲームを作成する方法

目次1. WeChatパブリックプラットフォームからWeChat開発者ツールをダウンロードする2. ...

nginxでの共有メモリの使用に関する詳細な説明

nginx プロセス モデルでは、トラフィック統計、トラフィック制御、データ共有などのタスクを完了す...

JavaScript データ型の詳細な説明

目次1. リテラル1.1 数値リテラル1.2 浮動小数点リテラル1.3 特別な値1.4 文字列リテラ...

Vue で配列パラメータを渡すための get / delete メソッド

フロントエンドとバックエンドがやり取りする場合、get または delete を介してバックエンドに...