ソロ開発の最適化方法論とおすすめnpmライブラリ

# この記事は何か

[俺たちの本当にやりたかったDevDay (2023/06/21)](https://connpass.com/event/282059/) イベントでの発表資料です。

***

# 自己紹介

- @akiyan
- https://www.akiyan.com/
- ゼロ年代のDMM => ベンチャー2社 => nanapi (現supership) => 有休消化中
- 個人受託や個人サービス
- EC・動画配信・ライブチャット・ブログ・SNS・アドネットワーク・メディア・掲示板コミュニティ・EC広告リスティング・マッチングアプリ etc
- CakePHP => Laravel(PHP) => express + sequelize

***

# 今日話すこと

- ソロ開発の最適化方法
- ソロ開発者におすすめな npm ライブラリ

***

# ソロ開発とは

- 可能な限り一人で開発するスタイル

***

***

# なぜこの話をするのか

- 組織開発論はたくさんある 
- 個人開発論はあんまりない
- 世の中の99%のプロダクトは一人で開発されている（要出典）
- 一人前提の生産性を重視してもいい
- なので話したい

***

# ソロ開発で大事なこと

- サーバーもフロントもぜんぶ書くから身軽であるべき
- 脳内メモリの消費を抑える
  - 複雑なインフラは組まない、増やさない
  - コード全体を把握できるようにする

***

# 脳内メモリの節約方法

- 言語を減らせ！
- レイヤーを減らせ！
- ファイルを減らせ！

***

# 言語を減らせ！

- 個人は nodejs だ！
- 一人だからフロントも書くことになるから脳内切り替えが減る
- たまにフロントとサーバーとどっちかわからなくなるけど、言語自体を切り替えるよりかなりマシだ
- じっさい、PHP + JS のコードを捨てて nodejs + JS に変えたらめちゃくちゃはかどるようになった

***

# レイヤーを減らせ！インフラ編

- スケール性とか考えなくていい。99%のプロダクトは1サーバーで動く（要出典）
- それ Docker でやる必要ある？ VM で十分では？
- RDB はフルマネージドを使え。一人でRDBの管理はつらい。
- KVS は使うな。RDBでやれ。RDBはKVS的に使うと意外に性能がでる。DBで困ったら初めてKVSだ

***

# レイヤーを減らせ！アプリケーション編

- 薄いフレームワークを使え
  - nodejs なら express だ。ファイルが少なく済む
- ディレクトリは浅くしろ
  - ツリー表示でもパス指定でも深いディレクトリは手数が増える
- モノレポにしろ
  - レポジトリ同士が依存するブランチをそれぞれのレポジトリで切りたいか？

***

# レイヤーを減らせ！プログラミング編

- 追いやすいコードを書け
- ライブラリにイベントフックがあっても使わなくて済むなら使うな
  - コードを追っても気づかない場所に書かれたコードとなり、書いたことを忘れる
- 例外もまじで例外だけに狭く絞って使え
  - どこからでもGOTOされる可能性のあるコードを一人で保守できると思うな

***

# ファイル減らせ！ その１

- ファイルを減らすとファイル切り替えの手間が減る（当然）
- grep せずともファイル内の移動で済むようになる
- 1ファイルに沢山書け
  - 設定ファイルとかもいちいち分類して沢山作るな。config.js に全部書け
  - でも dotenv は使え

***

# ファイル減らせ！ その２

- ユーザーコンテキストが1つならサーバーサイドは app.js だけでいい
- コンテキストが複数あるならコンテキストごとに分けるだけでいい
- 例えばECサイト
  - customer.js
  - tenant.js
  - admin.js

***

# 以上、脳内メモリの節約方法でした

***

# ソロ開発者におすすめな npm ライブラリ

- node-cron
- async-retry
- sequelize

***

# node-cron

- crontab や jenkins にするまでもないバッチ処理やポーリング処理を nodejs だけで書ける。イチオシ。PHPとかだとできない
- cron を増やすのが苦じゃなくなる

```js
var cron = require('node-cron');

cron.schedule('* * * * *', () => {
  console.log('running a task every minute');
});

cron.schedule('*/2 * * * *', () => {
  console.log('running a task every two minutes');
});
```

***

# async-retry

- 超シンプルにリトライさせられる

```js
await retry(async (bail) => {
  const res = await fetch('https://example.com');
  if (403 === res.status) {
    bail(new Error('Unauthorized'));
    return;
  }
  return await res.text();
}, {
  retries: 5,
});
```

***

# async-retry

- その他よく使う設定値
  - minTimeout
  - maxTimeout
  - onRetry (err, num)

***

# sequelize

- 定番ORM
- おすすめ機能と地雷機能の紹介

***

# ◎ DataTypes.VIRTUAL

- 仮想フィールド
- 便利すぎるので用法用量に注意

```js
isActive: {
  type: DataTypes.VIRTUAL,
  get() {
    return this.status == 1;
  }
},
statusText: {
  type: DataTypes.VIRTUAL,
  get() {
    return this.isActive ? '有効' : '無効';
  }
},
```

***

# ◎ scope

- scopes で where や attributes のまとめを複数定義できる。よく書くし深くなりがちな include も定義できる
- defaultScope も便利
  - toJSON するようなモデルは defaultScope で attributes を絞っておくと安全
  - unscoped() すれば defaultScope を外せる

***

# ◎ scope

```js
class User extends Model {
  ...
  scopes: {
    active() {
      return {
        where: {
          status: 1,
        },
      };
    }
  }
}
```

***

# ◎ scope

```js
// active かつ 1981年生まれ
const activeEightyOnes = User.scope('active').findAll({
  where: {
    birthYear: 1981,
  }
});
```

***

# ⚠ イベントフックは地雷

- beforeCreate
- beforeSave
- beforeDestroy
- afterCreate
- afterSave
- afterDestroy
- ...

***

# なぜイベントフックが地雷なのか

- 書いたことを忘れる
- 素直にコードを追うと実行されていることに気づけない
- 論理的に起きることではなく、物理的に起きることに対してフックしているのが問題

***

# 物理イベントにすべて共通な処理などない

- 例えば n 種類の save の前処理で共通処理があったときに beforeSave を使うとどうなるか
- n + 1 種類目にその beforeSave が合わなくて死ぬ
- 無理やり beforeSave 内で分岐していって beforeSave が膨れていく
- 他のイベントフックも使っていると、さらにわけがわからなくなる

***

# イベントフックせずにどうするか

- メソッドとして生やせ
- そこから共通処理の関数を呼べ
- さすればコードを追うだけで処理を把握できる

***

# ご清聴ありがとうございました！ 

***

# 余談

- nodejs歴は2年しかないのに偉そうなこと言ってすみませんでした
- YAGNI は超大事。YAGNI してないコードは認知負荷にやられる
- DRY も大事だが2つまでならコピペでいい。3つ目で関数をつくれ
  - ただし同じ処理だからって作るな。同じ役割なら作れ
- テストは書いたら捗るなら書け。生産性は一人だから全て把握できてる。自由にやれ