🦕

deno lint plugin で例外処理に関するルールを作った

に公開

do から始まる関数は try catch しないといけない、という deno-lint-plugin を作ってみた。

動機

  • Gemini/Claude は例外設計が下手くそ
    • 全部 try catch して握り潰す
    • 明示的に例外を表現したい
  • neverthrow はやりすぎ
  • TS の型シグネチャは例外が現れないので難しい

ネタ元: vscode の doGet~

vscode のコードを読んだ時に見たパターンで、do から始まる関数は例外を吐くことが明示されてて、たしかにわかりやすかった。

async function doGetLogs(fileService: IFileService, logs: ILogFile[], curFolder: URI, logsHome: URI): Promise<void> {
  ...
}

https://github.com/microsoft/vscode/blob/9b4e21695e2b905d293544dcb583fae2ef8ec7c0/src/vs/platform/log/browser/log.ts#L36

もっというと Java の検査例外

public void throwableMethod(String name) throws Exception {...}

deno-lint-plugin で実装してみた

https://jsr.io/@mizchi/deno-lint-plugin-do-try@0.0.2

{
  "lint": {
    "plugins": ["jsr:@mizchi/deno-lint-plugin-do-try"]
  }
}

2 つのルールを実装。

  • do-try/require-try-catch-for-do-functions
    • do から始まる関数の呼び出しは、必ず try catch で囲む必要がある
  • do-try/throw-needs-do-prefix
    • throw を含む関数名は do~ である必要がある。

eslint でないのは、単に deno-lint-plugin が作ってみたかったから...

async function doSomething() {}
const obj = { doSomething };

// NG
doSomething();
// NG
await doSomething();
// NG
await obj.doSomething();

try {
  const run = async () => {
    // NG
    await doSomething();
  };
  await run();
} catch (error) {
  console.error("An error occurred:", error);
}

try {
  // OK
  doSomething();
} catch (error) {
  console.error("An error occurred:", error);
}

// NG
// deno-lint-ignore no-unused-vars
function throwableFunction() {
  if (Math.random() > 0.5) {
    throw new Error("Random error");
  }
  return "data";
}

// OK
// deno-lint-ignore no-unused-vars
function doThrowableFunction() {
  if (Math.random() > 0.5) {
    throw new Error("Random error");
  }
  return "data";
}

実行結果

$ deno lint examples/main.ts
error[do-try/require-try-catch-for-do-functions]: Function 'doSomething' starts with 'do', so it must be wrapped in a try-catch block
 --> /home/mizchi/sandbox/deno-plugin/examples/main.ts:4:1
  |
4 | doSomething();
  | ^^^^^^^^^^^^^


error[do-try/require-try-catch-for-do-functions]: Function 'doSomething' starts with 'do', so it must be wrapped in a try-catch block
 --> /home/mizchi/sandbox/deno-plugin/examples/main.ts:5:1
  |
5 | await doSomething();
  | ^^^^^^^^^^^^^^^^^^^


error[do-try/require-try-catch-for-do-functions]: Function 'doSomething' starts with 'do', so it must be wrapped in a try-catch block
 --> /home/mizchi/sandbox/deno-plugin/examples/main.ts:6:1
  |
6 | await obj.doSomething();
  | ^^^^^^^^^^^^^^^^^^^^^^^


error[do-try/require-try-catch-for-do-functions]: Function 'doSomething' starts with 'do', so it must be wrapped in a try-catch block
  --> /home/mizchi/sandbox/deno-plugin/examples/main.ts:10:5
   |
10 |     await doSomething();
   |     ^^^^^^^^^^^^^^^^^^^


error[do-try/throw-needs-do-prefix]: Function 'throwableFunction' contains throw statement, so it must start with 'do'
  --> /home/mizchi/sandbox/deno-plugin/examples/main.ts:24:1
   |
24 | function throwableFunction() {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
25 |   if (Math.random() > 0.5) {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
26 |     throw new Error("Random error");
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
27 |   }
   | ^^^
   |
28 |   return "data";
   | ^^^^^^^^^^^^^^^^
   |
29 | }
   | ^

真面目にプロダクションで使ったわけじゃないが、自分の意図は表現できてるので、微修正しながら使っていく。

おまけ: 作り方

まず AI にこいつを読ませる。

https://docs.deno.com/runtime/reference/lint_plugins/

あとは Few Shots で valid なコードと invalid なコードを与えて、夕飯食べ終わったらできてた。

Discussion