🍝

JSDoc 駆動開発なんてどうでしょう?

2023/12/09に公開

株式会社アイデミーの土屋です。
Lab Bank という化学業界の研究室向け SaaS 開発と、
Modeloy という DX プロジェクトの伴走支援をしています。

https://labbank.jp/
https://www.modeloy.ai/

はじめに

これは自ら茹で上げたスパゲッティコードに、
見事に絡まってしまったことに対する懺悔記事です。

背景

今回「Excel で行なっている業務をデスクトップアプリ化」するという要件があり、業務工程として下記のようなフローになってました。

かつ、各工程で

type Sample = {
  A: number;
  B: number;
  C: number;
  D: number;
  E: number;
}[];

のような、「同じ名前だが工程によって値が違う」ものを組み合わせる必要がありました。
実装に関して どの工程がどのような値を返しているか?という問題に加え、
Excel内のこのセルの値はどこから計算されているものなのか?を同時に意識する必要がありました。

自分自身が計算ロジックに対してのメンタルモデルができていなかったことに加え、
諸事情であまり時間がなかった焦りもあり、
「とりあえず実装だ〜!」(カタカタカタ)
「うぉ〜、計算合わないだと?!」
(ログを仕込む)
「この値はどこから引っ張ってくるんだっけ?」
(該当する Excel から計算 を探す)
「え〜っと、ということは」(スッコココ)

という悪循環で、かなり時間を溶かしてしまいました。。

そこで、解決方法を考えたところ、
最近読んだ世界一流エンジニアの思考法の「いきなり手を動かさない」という話と
読みやすいコードのガイドラインの「コードの概要を説明することで、コードを読む時間を短縮する」という話を思い出しました。

そこで、2 つを組み合わせて、
「実装の前に先に JSDoc を記載するJSDoc駆動開発をすると良いのでは?」
と思いつきました。

※一般的な名称ではなく、自分がそう呼んでいるだけです。

https://www.amazon.co.jp/世界一流エンジニアの思考法-牛尾-剛/dp/4163917683
https://www.amazon.co.jp/読みやすいコードのガイドライン-持続可能なソフトウェア開発のために-石川-宗寿/dp/429713036X/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=1702083583&sr=8-1

どんな場面で向いていそうか?

JSDoc駆動開発は、下記のような場面で向いていそうです。

  • ロジックが複雑
  • 複数人で開発する
  • メンタルモデルができていない分野での実装
  • 参照するドキュメントが多い
  • 変数名だけで細かいニュアンスを伝えるのが難しい

デメリットとして、

  • JSDoc を記載する初期コスト
  • コードに加え JSDoc もメンテナンスするコスト

などが挙げられますが、
特に上記に該当する場合はメリットの方が大きくなりそうです。

まずは今まで書かなかった言い訳をあげておく

これまでも、
「JSDoc などの適切なコメントを書くことで、
チームメンバーに対してだけでなく自分自身もコードを読む負担を減らせる」
ということは頭では理解していました。

それでもなぜか後回しにしてしまい、結果としていい進捗が出せませんでした。
自分が記事を見直したときに胸を痛めるよう、
まず言い訳を晒しあげます。

時間がない

天の声「コメントを書かなかったことで余計時間を無くしたのはどこのどいつや!急がば回れなんや!」

実装中にコメントを考えるのが面倒

天の声「確かに実装中にコメントを考えて書くのは負担に感じるけど、ほんならコーディングの前に書いといてしまえばええがな!」

書き方がわからない

天の声「安心せい、今回書き方を整理するから次からはばっちしや!」

ということで、早速 JSDoc のドキュメントを読んでみます。
https://jsdoc.app/

(うっ、英語がいっぱいでちょっと読みづら so)
天の声「きえぇぇい!!」

JSDoc 駆動開発の流れ

  1. まずは前提条件や、やりたいことを整理する
  2. 図やテキストで実装する際のメンタルモデルを作る
  3. TODO リストを作成する
  4. JSDoc を書く!
    • TODO リストを関数に落とし込み、JSDoc を書いてしまいます。
    • この時点では関数の中身は実装していないため、もし関数を移動したくなっても比較的身軽に移動できるはずです。
    • GitHub Copilot のGenerate Docsを使うのも良いかもしれません。
  5. 実装する
    • あとは関数の中身を実装していきます
    • 事前に TODO リストレベルで実装内容を整理しているため、関数が大きくなりすぎることを防げます

よく使いそうな JSDoc のまとめ

いざ JSDoc を書くときに無駄な脳のリソースを使わないように、
あらかじめよく使いそうな JSDoc をまとめておきます。

@param

一番よく使うと思います。
引数についての説明を書きます。

/**
 * 記事を書く
 * @param platform - プラットフォーム名
 * @param author - 執筆者
 */
function writeArticle(platform: string, author: string): string {
  return `${platform}${author}が記事を書きました。`;
}

You can add a hyphen before the description to make it more readable. Be sure to include a space before and after the hyphen.

とある通り、-をつけると見やすくなるよとありますが、
ハイフンをつける場合に前後のスペースを入れる以外は特に決まりはなさそうです。

https://jsdoc.app/tags-param


@returns

戻り値についての説明を書きます。
@returnsでも、省略形の@returnでもどちらでも良いようです。

/**
 * 記事を書く
 *
 * @return 記事を誰がどこで書いたか
 */
function writeArticle(platform: string, author: string): string {
  return `${platform}${author}が記事を書きました。`;
}

ちなみにジェネレーター関数の場合は、@yieldsを使うようです。

/**
 * ジェネレーター
 * @generator
 * @param i - 初期値
 * @yields ジェネレーターの現在の値
 */
function* generator(i) {
  yield i;
  yield i + 10;
}

https://jsdoc.app/tags-returns
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/function*


{@link}

指定した名前パスまたは URL へのリンクを作成します。

  • URL のリンクを貼る場合

{@link url|文字列の形式で書くと、マークダウンの[文字](url)のようにリンクを貼ることができます。

/**
 * この記事は{@link https://qiita.com/advent-calendar/2023/aidemy|Aidemy Advent Calendar 2023}の記事です
 */
function writeAdventCalendar() {
  console.log("writeAdventCalendar");
}

呼び出し元からは

のように表示されます。

  • オブジェクトへのリンクを貼る場合
type AdventCalendar = Record<"zenn" | "qiita", number>;

/**
 * {@link Record} 形式のオブジェクトを受け取る
 */
function writeAdventCalendar(adventCalender: AdventCalendar) {
  const platforms = Object.keys(adventCalender);
  platforms.forEach((platform) => {
    console.log(`${platform} Advent Calendar ${adventCalender[platform]}`);
  });
}
writeAdventCalendar({ zenn: 2023, qiita: 2022 });

  • 特定のクラスへのリンクを貼る場合
class AdventCalendar {
  // {@link クラス名.プロパティ} と書くと、このプロパティへのリンクになる
  zenn: number;
  qiita: number;

  // 省略
}

/**
 * {@link AdventCalendar} アドベントカレンダークラスへのリンク
 * {@link AdventCalendar.zenn} zennプロパティへのリンク
 * {@link AdventCalendar.qiita} qiitaプロパティへのリンク
 */
function writeAdventCalendar(adventCalender: AdventCalendar) {
  const platforms = Object.keys(adventCalender);
  platforms.forEach((platform) => {
    console.log(`${platform} Advent Calendar ${adventCalender[platform]}`);
  });
}

writeAdventCalendar(new AdventCalendar());

https://jsdoc.app/tags-inline-link
https://jsdoc.app/about-namepaths


@see

特定のオブジェクトへや、ドキュメントを参照する際に使います。
前述の{@link}と組み合わせることもできます。

/**
 * @see {@link https://zenn.dev/zenn/articles/markdown-guide|Zennでのマークダウン記法}
 * @see ぼくがかんがえたさいきょうのマークダウン記法.xlsx
 */
function writeAdventCalendar() {
  console.log("writeAdventCalendar");
}

writeAdventCalendar();

https://jsdoc.app/tags-see

@example

使用方法や、実行結果の例を記載します。

/**
 * 記事を書く
 *
 * @example
 * const platform = "Zenn";
 * const author = "ツチノコ";
 * const article = writeArticle(platform, author);
 * console.log(article);
 * // Zennでツチノコが記事を書きました。
 *
 *
 *
 */
function writeArticle(platform: string, author: string): string {
  return `${platform}${author}が記事を書きました。`;
}

https://jsdoc.app/tags-example

おわりに

懺悔から生まれたアイデアでした。
プロジェクトやチームの特性にもよると思いますが、
しばらくはこのスタイルで開発してみようと思います。

p.s.
パスタは茹でる前に塩を入れると、
パスタの吸水量が少なくなりコシが出るそうです。

アイデミーでは一緒に働く仲間を募集中です!

https://aidemy.co.jp/

Aidemy Tech Blog

Discussion