Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

just-cliでzsh関数をまとめる、簡単に選べるようにする

こんな感じで選択でき、troubleshoot kill-port 8080 みたいな感じで実行できる。

モチベ: 似ているローカル関数が増えてきた

最近、トラブルシューティング用のzsh関数を乱造しているのだが(shoot-kill-portみたいな)、増えすぎて流石に視認性が悪くなってきた。どれが何かコメントなしでは分からない(上の例はサンプルで作ったものなのでパッと見でギリ分かるが)。

just-cliを使ってローカル関数をまとめる

やり方はカンタン。以下のようなレシピファイルを用意してエイリアスを設定するだけ。

alias troubleshoot="just -f $HOME/just/troubleshoot.just --"

troubleshoot.justの中身

# ポートを使用中のプロセスを終了する
kill-port port:
    lsof -ti:{{port}} | xargs -r kill

# ローカルDBを作り直して初期データを投入する
reset-db:
    docker compose down db
    docker volume rm app_db_data || true
    docker compose up -d db
    pnpm db:migrate
    pnpm db:seed

# コンテナ内でシェルを起動する
shell service:
    docker compose exec {{service}} sh

# 使えるコマンド一覧を表示する
[default]
help:
  @just -f {{justfile()}} --list

これで補完とかも効くようになる(justのセットアップはしておく必要ある)。

コマンドを指定しなければhelpが表示される(↓)。

余談

just-cli、ずっと前から存在は知ってたんだけど、「ちょっといいmakeじゃねえか」の気持ちが拭えずに使ってなかった。今回zsh関数をまとめるものとして再定義してようやく腹落ちした。本来のユースケースっぽい、プロジェクトローカルの関数を定義する用途としてはどうなんですかね。

AI時代のエンジニアが目指すべき"テックリード"について

In short: 「解くべき課題から逆算して技術を使えるエンジニア」を目指せ

AI時代のエンジニアはテックをリードする人ではなく、 テックでリードする人 を目指すべきだ。どこの会社にも、掲げるミッションを達成するために解くべき課題というのがあるはず。 それらを見極め、技術を使って解決していく のだ。

背景: 技術力の重心が変化してきている

これまでは 高度なコーディング能力に需要があった (知識、判断力、発想力、発信力)。よりスマートな方法を思いつけるか、脆弱性やバグに事前に気が付けるか。登壇して知識を発信するのも含まれる。つまり テック領域をリードする役割 だ。

しかし、AI全盛の今、この価値の希少性は以前より低下した。特に実装コスト(調査、学習、コード生成など)が激減した。

結果、 「何を作るか」の重要性が相対的に上がった。 求められる価値の比重が「実装の巧拙」から「どの課題を解くべきかを見極め、それを解決すること」に移ってきている。

AI時代に求められる課題解決力とは何か?

AIによって調査、学習、実装のコストが大きく下がった。その結果、以前より少ない人数で仮説検証やプロトタイピングを回せるようになった。

これによってエンジニアが課題発見から実装まで現実的なコストで一気通貫で担えるようになった。この状況において、 これから重視されるのは技術を使って解くべき課題を解決する力だ。技術を使って、会社やプロダクトが目指す方向を実現する力とも言い換えられる。

ただし、これは PdMやPMになろうという話ではない。 課題を正しく理解するためにユーザー調査や要求整理を行うことはあるが、それらはあくまでエンジニアリングによる課題解決の解像度を上げる手段だ。

また、単に課題を解決するというのも違う。 会社の掲げるビジョン、ミッションに沿った課題を優先的に解決していくことが大事。例えばユーザーの要求は、必ずしもそれらとつながるとは限らない。

まとめ: 「テックをリードする人」から「テックでリードする人」へ

これまでは課題を決める人と実装する人という分業が多かった。しかし、AIによって実装コストが激減した結果、課題発見からプロトタイピングまで1人で現実的に回せるようになった。

だからこそ、PdMでもPMでもなく、"エンジニアとして"技術を武器に課題解決まで担う価値が高まっている。 今の時代に目指すべきテックリードは「解くべき課題から逆算して技術を使えるエンジニア」だ。

URLはどこまで変なことができるのか?

ドットが使える程度は本当に気軽なもので、もっと変なことができてしまうよという話

前提: URLの扱いはアプリによって異なる

そもそも、実は URLをどう扱うかはアプリによって異なる。 例えばブラウザはかなり扱いが緩い(WHATWG準拠)。反対に、curlなどは比較的硬い(RFC準拠)。

  • RFC: 理想ベースで規定された仕様。 硬め。
  • WHATWG: ブラウザ等ですでに行われていた壊れた使い方を前提に規定された仕様。 緩め。

今回は(面白いから)WHATWGの話メインでいく。

変1: そもそもほぼなんでも受け入れる

URLのpath部分はかなり強力な正規化がかけられるため、基本的に 変なことをしようとしてもエンコードされて終わり になる。

https://example.com/こんにちは😀 (日本語や絵文字 → 単にエンコードされて終わり)
https://example.com/改
行
あ
り(改行を含んだパターン → エンコードされて終わり)
https://example.com/\uD800 (サロゲートペアの前半だけ → 単にエンコードされて終わり。というかそもそもUTF-8として入れられない)
https://example.com/\0null (NULL文字 → /%00null とエンコードされて終わり)

確認用のJS。これをdevtoolsとかで打つと確認できる↓

console.log(
  new URL('https://example.com/こんにちは😀').href,
  new URL(`https://example.com/改


り`).href,
  new URL('https://example.com/\uD800').href,
  new URL('https://example.com/\0null').href,
);

このように、ドットどころか改行だろうがNULL文字だろうが受け入れてしまう。懐が広すぎる。ガバガバ。

変2: バックスラッシュをスラッシュとして扱う

これさっき知ってなんでやねんって思ったんだけど、WHATWGではOK。当該仕様

つまり https:\\example.com\path\to\content は単に https://example.com/path/to/content と解釈される。

変3: http:example.comでもOK

実はスラッシュ2つは なくてもアクセスできる。 https:example.com でok。当該仕様

new URL('https:example.com').href == 'https://example.com/'

そも論: URLがvalidであるかとどう扱うかは別

そもそも、URLがvalidか どうかと サーバーがどのようにURLを扱うか別の問題。

例えばSPAを考えると分かりやすい。SPAの場合、どんなURLが来てもサーバーは同じ内容 を返し、フロントでルーティングする。

実際に変なサーバーアプリを作ってみよう。ディレクトリの区切り文字としてドットを使うアプリを書く。例えば /user.entries.post でアクセスするとサーバー内の /user/entries/post のファイルが返ってくる (なお、拡張子もスラッシュに変換されるので、実用性は皆無)。

const express = require('express');
const fs = require('fs/promises');
const path = require('path');

const app = express();

app.get('/*path', async (req, res) => {
  const filePath = path.join(__dirname, req.params.path[0].replaceAll('.', '/'));

  res.send(await fs.readFile(filePath, 'utf8'));
});
app.listen(3000);

反転した文字列で受け付けるサーバーも書いてみる (/elif/ot/htap/path/to/file)

app.get('/*path', async (req, res) => {
  const reversedPath = req.params.path.map(s => [...s].reverse().join('')).reverse().join('/');
  const filePath = path.join(__dirname, reversedPath);

  res.send(await fs.readFile(filePath, 'utf8'));
});
app.listen(3000);

こういう変なことだってやろうと思えばできるのだ。

まとめ: URLにドットを入れられる程度は序章に過ぎない

このように、実は URLって結構変な書き方をしても受け入れられてしまう し、サーバーがURLをどのように解釈するかは自由となっている。こうしてみると、ドットを入れられるなんてのは自明に思えてくる。

安定ソートはフィルタリングである

const arr = [5, 2, 7, 8, 1, 4];

arr.sort((a, b) => (b % 2) - (a % 2));

console.log(arr) // [5, 7, 1, 2, 8, 4]

実質的にpartitionになっている。あたりまえ体操。

でも、単にpartitionしたいだけならO(N)でできるのであんまり意味はない。

function partition<T>(
  arr: T[],
  cond: (x: T) => boolean,
): [T[], T[]] {
  const t: T[] = [], f: T[] = [];
  for (const x of arr) (cond(x) ? t : f).push(x);
  return [t, f];
}
partition(arr, a => a % 2 === 1); // [[ 5, 7, 1 ], [ 2, 8, 4 ]]

ソートと同時にpartitionしたいときはたまにあるので、覚えておくと時々役に立つかもしれない