Pull Request を作るのに一時間以上かかったら捨ててる

一時間は大体の目安でちゃんと測ってない.

  • PR の作成に時間がかかるときは, 何らか良くないことが起きている可能性が高い
    • 試行錯誤を繰り返している
    • 変更の規模が過大になっている
  • 良くないことが起きているなら, そのまま続けて余計なコストをかけるよりも捨てた方が良い
    • 試行錯誤を繰り返していたなら, そこまでは勉強か練習だと思って捨てる
    • 変更の規模が過大になっていたなら, 分割して作り直すために捨てる
  • 捨てることを躊躇わない
    • 時間をかけるほど引き返しづらくなるので, 一時間くらいで打ち切る
    • 一時間の手戻りなら一時間で取り返せる. なんなら知識が増えている分もっと短くて済む
    • 思ったより進んでいなくても, そのまま思っていたところまで進めようとはせずに諦めて捨てる. とにかく良くないことが起きている可能性が高いので立ち止まるべきで, 思ったところまで進められる (良くない状態に陥らなくなる) 状態は立ち止まって反省した先にある
    • いいから捨てろ. 大抵はコードそのものよりもコードを書くことで得た知識の方が圧倒的に価値があるので, コードを捨てるのは大したことじゃない
  • 全ての作業を一時間で終わらせるという話ではない
    • N 時間を一回でやっているところを一時間を一回にするのではなく, 一時間を N 回やるという話
  • 品質で手を抜くという話でもない
    • 一時間で品質が低い状態までしか辿り着けなかったとしたら, そこで完成とするのではなく, 一旦捨てて品質が高い状態に辿り着けるような進め方を考えるという話
    • 低品質で作る → 高品質化する のように作業を分けるのも, 結局後者が実行されないことがあるので理想的ではない. 高品質で作る → 高品質で作る と作業を分けられた方が良くて, そうできるようになるためには実際にこのように分けようとしてみるしかない

Node.js パッケージから自分自身を参照する

例えばサンプルコードを同梱したい場合や, コンパイラのセルフホストのような場合など, Node.js パッケージの内部でそのパッケージ自身を参照したくなることがあります. あるか? このときの方法がいくつかあるので軽くまとめます.

ここでは例として以下のようなディレクトリ構造になっている mypackage パッケージを仮定して, examples/ 以下のサンプルコードからパッケージ自身を参照することを考えます. CommonJS と ES modules で挙動が異なるので, どちらの形式であるかを明示するために拡張子には常に .cjs.mjs を使います.

+-- package.json
+-- lib/
|   \-- index.cjs
\-- examples/
    +-- greet.cjs
    \-- greet.mjs

package.jsonlib/index.cjs はひとまずこういう感じ.

{
  "name": "mypackage",
  "main": "lib/index.cjs"
}
/* lib/index.cjs */
"use strict";

function greet(name) {
  return `Hello, ${name}!`;
}

module.exports = { greet };

package.json のあるディレクトリを参照する

CommonJS の場合は package.json のあるディレクトリを require すると, そのパッケージ自身 (より正確には main フィールドに指定されたファイル) を参照できます (参考: Node.js のドキュメント, 特に LOAD_AS_DIRECTORY あたり).

/* examples/greet.cjs */
"use strict";

const { greet } = require("..");

console.log(greet("Alice"));
$ node examples/greet.cjs
Hello, Alice!

ただし ES modules の場合は, このようにディレクトリを import することは禁止されています.

/* examples/greet.mjs */
import { greet } from "..";

console.log(greet("Alice"));
$ node examples/greet.mjs
node:internal/errors:497
    ErrorCaptureStackTrace(err);
    ^

Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '/path/to/mypackage/' is not supported resolving ES modules imported from /path/to/mypackage/examples/greet.mjs

自分自身の名前で参照する

package.jsonexports フィールドがあれば, 自身のパッケージ名を使って自分を参照できます (参考: Node.js のドキュメント). この方法であれば CommonJS と ES modules の両方で動作します.

{
  "name": "mypackage",
  "main": "lib/index.cjs",
  "exports": "./lib/index.cjs"
}
/* examples/greet.mjs */
import { greet } from "mypackage";

console.log(greet("Alice"));
/* examples/greet.mjs */
import { greet } from "mypackage";

console.log(greet("Alice"));
$ node examples/greet.cjs
Hello, Alice!
$ node examples/greet.mjs
Hello, Alice!

exports を追加することに問題がなければ1, パッケージをユーザーと同じ方法で参照できてわかりやすいので, 基本的にはこれがおすすめ.

自分自身の別のバージョンを参照する

コンパイラが自身をセルフホストする場合など, ビルドツールの開発では依存関係に自身の安定版を追加して, それを使って自身のビルドを行うといったことがあります (microsoft/TypeScript が好例).

package.jsonexports フィールドがない場合は, 以下のように自分自身を依存関係に追加するだけで, mypackage という名前で自身の安定版を参照できるようになります.

{
  "name": "mypackage",
  "version": "1.1.0",
  "main": "lib/index.cjs",
  "devDependencies": {
    "mypackage": "^1.0.0"
  }
}

ところが exports フィールドがある場合は, 上で説明した自分自身を名前で参照する挙動が優先されて, mypackage という名前では安定版ではなく開発中の自分自身を参照することになってしまいます.

この場合は npm, yarn, pnpm といった各種パッケージマネージャーが共通して提供しているエイリアス機能を使って, 安定版を別の名前 (例えば mypackage-stable など) でインストールして, 名前の重複を回避してやるとよいです.

{
  "name": "mypackage",
  "version": "1.1.0",
  "main": "lib/index.cjs",
  "exports": "./lib/index.cjs",
  "devDependencies": {
    "mypackage-stable": "npm:mypackage@^1.0.0"
  }
}

  1. exports フィールドがあると, そこで指定されているファイル以外は外部から参照できなくなる点には注意.