最終更新: a few seconds agoRemove Netlify-related code from main (grafted, HEAD)

コーディングスタイル

概要

インデントなどのスタイルについては prettier を使って統一する。ものによってはさらに ESLint を補助的に使う。

Prettier の使い方 (Visual Studio Code)

  1. VSCode の prettier 拡張を入れる。

  2. 設定に以下のようなものを入れる。

    "editor.formatOnSave": true
    

    あるいはファイルタイプ別にする場合は以下のようにする。

    "[javascript]": {
      "editor.formatOnSave": true
    },
    "[typescript]": {
      "editor.formatOnSave": true
    }
    

prettier の設定は package.json に書かれており、以下のようなルールについてはファイル保存時に自動で守ってくれるようになる。

  • インデントはスペース 2 文字を使う。
  • コロンは省略せず行末に書く。
  • リストを区切るコンマは行頭ではなく行末に書く。
  • できるだけ 1 行 80 文字に収める。
  • 開き括弧と閉じ括弧を同じインデントレベルに収める。

行列演算のコードなどで、もし一時的に prettier の整形を無効にしたい場合は、そのコマンドの直前でソースコードのコメントとして // prettier-ignore をつける。

その他の遵守事項

  • ES2015 スタイルの import/export を使う。
  • ファイル名は default export するものと大文字小文字も含んで同一にする。例えば MyClass.jsMyClass を default export する。
  • ローカル変数は camelCase を使う。
  • 可能な限り const を使い、let は必要最小限に使う。var は使用しない。

モジュール

常に標準化された ES6 modules を使う。require() は使用禁止とし、動的にモジュールをロードする必要がある場合も dynamic な import() を使う。

JavaScript ファイルの拡張子は *.js とする。将来的に Node.js での ES6 modules は *.jsm という拡張子にすることが決定しているが、現時点ではこの拡張子は使わない。

クラス

Koa の設計思想がミドルウェア関数ベースなので、ES6 クラスは可能な限り使わない。クラスとクロージャの両方で実現可能な機能がある場合は、クロージャを使う。関連性のある関数をまとめる場合はモジュールと名前付きエクスポートを使う。

class と Interface

  • 基本的には、クラス継承ではなく、インターフェースと関数の組み合わせにより実現する。

    • 理由

      • インターフェースの方が汎用性が高く、実装の際の自由度が上がる。
      • JavaScript の場合、クロージャでクラスのようなことをよりシンプルに実現できる。
      • 関数ベースは、デコレータみたいなメタなプログラミングとの相性が良い。
      • JavaScript のクラスは言語仕様的に後付けなので、「カプセル化が甘い」「this の挙動がウザい」という明白な落とし穴がある。
      • 上記理由により、開発者にも好まれる傾向があり、現に有名なライブラリではクラスも継承もほぼ使われていない。
    • 注意

      • クラスの場合は宣言自体がほぼ型定義そのものでもあるのに対し、関数型の場合はインターフェースをしっかりやっておかないと何をしているのか分からなくなる危険性がある。
  • ただし、下記に該当するような場合は、クラスの使用も検討する。

    • 数値計算系などパフォーマンスがクリティカルな場合
      • クラスのメンバ参照は本質的に this へのある意味でフラットなプロパティ参照であるが、クロージャの場合はスコープを上に辿る処理が現時点ではやや重い。よって、ループで何百万回とメンバにアクセスして計算が起きる場所ではカプセル化を諦めてクラスにする方が望ましい。
      • 実装例
        • RawData
        • MultiRange
    • 多量のメソッドが必要かつ差異が微少で、綺麗な継承のヒエラルキーを構築でき、その全体を自分達でメンテナンスしていける場合
      • 実装例:
        • RawData , AnisotropicRawData , DicomVolume
      • 注意
        • 多重継承は全面禁止

型定義・型推論の利用方針

  • ほかで再利用される関数やクラスを宣言する時には可能な範囲で型を書く
  • 一時的な関数なら関数の戻り値の型すら宣言せずに TypeScript の推論に任せる
  • 関数やクラスを使う場面では変数名にいちいち型をつけず、可能な限り TypeScript の型推論を使う
  • 参考: http://udomomo.hatenablog.com/entry/2017/10/08/165321

非同期処理の書き方

データベースやネットワークへの I/O を伴う処理は、可能な限り非同期処理で実装する。

いわゆるコールバッグ地獄を避けるため、可能な限り async/await を使う。Promise は内部的に利用しているが、thencatch を直接書かない。例外として Promise.all() など async/await で実現不可能なものは Promise を明示的に使用してもよい。

Node.js 互換のコールバックスタイルの API は極力直接利用しない。これらを Promise に変換するのには pify モジュールを使うか、既に変換済みの NPM モジュール (fs-extra, glob-promise, etc) を使う。

import * as fs from 'fs';
fs.readFile('foo.txt', 'utf8', (err, data) => {
  if (err) triggerError();
});
// promisified-fs.js
import pify from 'pify';
import * as fs from 'fs';
export default pify(fs);

// somewhere.js
import fs from './promisified-fs';
const readFoo = async () => {
  const data = await fs.readFile('foo.txt', 'utf8');
};

new Promise を直接呼ぶのは最終手段。既存の Promise 非互換かつ Node 非互換のコールバックスタイルの API(例:setTimeout)を Promise 版に変換する時にのみ最低限使うこととし、できる限りこの変換処理は別モジュールに分離する。

// delay.js
export default function delay(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

// somewhere.js
import delay from './delay';
async function foo() {
  await delay(1000);
}