0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

#200 スキルシートの情報の持ち方を見直す 〜 複数の出力パターンに耐えるデータ設計

0
Posted at

はじめに

以前、社内スキルシート管理の一環として
markdown形式のデータを「プレゼン向けのスキルシート」として出力する仕組みを作りました。

このPoCは一通り動くところまで完成していたのですが、
デザインパターンを踏襲して「もっと細かい粒度でのスキルシートも出力できるようにする」という追加要件を立てることにしました。

単純に「そのパターン用に入力と出力を増やせばいい」という話にはしたくなかったので、
情報の持ち方を見直し、複数の出力パターンに耐えられる設計へ作り直すことにしました。

そこで、以下の3点を設計見直しの目標として位置付けました。

  1. 出力パターンは複数あっても、元データは一元管理する
  2. 人間が計算しなくてよい部分は、静的処理で自動算出する
  3. 非エンジニアでも修正しやすい形式・管理方法にする

本記事では、この設計をどのような考え方で組み立てたのか、実際に見直した箇所を交えながら整理していきます。

なお、具体的な案件情報やスコアリングロジックの詳細など、社内固有の情報には触れません。
設計の考え方そのものに焦点を当てています。

1. PoCの構成と、そこにあった課題

最初に作ったPoCは、以下のような1エンジニア1ファイルのmarkdownでした。

## 技術スキル

### フロントエンド

| スキル | 年数 | 習熟度(1-5) |
|---|---|---|
| React | 3.0 | 5 |
| Next.js | 3.0 | 5 |

見出しと表でセクションを区切り、その表をプログラム側でパースしてHTMLに流し込む、という構成です。
「経験年数」も「習熟度」も、この時点では人間が自分で計算して埋める数値でした。

これは一見シンプルですが、次のような課題が見えてきました。

  • 表の構造に強く依存したパース処理になり、見出しや列を変えると壊れやすい
  • 経験年数・習熟度は自己申告の数値で、案件データと突き合わせても整合性を検証できない
  • 出力パターンを増やそうとすると、同じ情報を別の形式でもう一度書く必要がある

より細かい粒度の出力パターンに対応したデータを新しく作ろうとしたとき、

  • このmdファイルにさらに詳細な項目を積み増していく
  • 別ファイルとして分けて二重管理する

などの案もありましたが、保守コストなどを考えるとどちらも良い方針とは思えず、採用を見送りました。

そこで状況を整理していくと「出力パターンをどう増やすか」ではなく、
そもそも「増やせる状態になっていない」という前段の課題が見えてきました。

なぜyamlに寄せたのか

見直しにあたって、データの持ち方をmarkdownからyamlに変更しました。
理由は、上に挙げた3つの目標それぞれに対して、yamlの方が都合が良かったからです。

■ 一元管理・自動算出のしやすさについて

yamlであればキー・値・配列・ネストが構造として明示的なので、パーサ経由で安全に読み込めます。

表の行や見出しの文言に依存する脆いパース処理から離れられますし、数値・日付・真偽値といった型も素直に扱えるので、そこから経験年数や習熟度を計算する処理も書きやすくなります。

■ 非エンジニアでも編集しやすい形式について

一見yamlの方がハードルが高いようにも見えますが、次のように運用でカバーしています。

  • キー名の変更は禁止し、「触っていい場所」を明確に区切る
  • 空欄にしたい項目は削除せず、値だけ空にするルールに統一する
  • コメントで記入例・選択肢をその場に書き、別ドキュメントの参照を最小限にする

これらのルールを添えたテンプレートを用意しておけば、mdの表を直接編集するよりも軽い負荷で運用できます。

結果として、「構造化されたデータとして扱いやすい」ことと「非エンジニアでも編集できる」ことは、両立しやすい組み合わせだったと思います。
今回の見直しでは、この一元管理された元データから「複数の出力パターンをどう作り分けるか」という設計に進んでいきます。

2. 「事実」と「解釈」を分ける

前章で、PoCの課題のひとつに「経験年数・習熟度は自己申告の数値で、案件データと突き合わせても整合性を検証できない」という点を挙げました。

見直しにあたって、まずこの「習熟度」や「経験年数」といった数値を、そもそも人間が直接入力する項目から外すことにしました。
代わりに人間が入力するのは、案件ごとに「その工程で何を実施したか」という、もっと小さい単位の事実だけです。

tasks:
  implementation:
    APIエンドポイント実装: true
    バッチ処理実装: false
    レビュアー実績: false

習熟度や経験年数、担当工程の関与度合いといった値は、すべてこの事実データから出力側が計算して導き出します。

なぜ分離したか

理由は単純で、同じ情報を2箇所に持つと、
どちらかが更新されずに実態とズレる、などの不整合が生じる可能性があるからです。

案件側には実際に何をやったかの記録があるのに、プロフィール側の習熟度は更新し忘れたまま古い値が残る、というのはよくある話です。
人間が「入力する」箇所を最小単位の事実だけに絞れば、そもそもズレようがなくなります。

また、「人間は事実だけを入力する」という点については、「恣意性を外部に切り出す」ということも意識しました。

習熟度をどう判定するか、担当工程をどう集計するかというロジックには、当然ある程度の恣意性が残ります。
ですが、その恣意性を「入力する値」側に持たせてしまうと、判定基準を見直すたびに、全員に入力し直しを依頼することになります。

一方、恣意性を「出力側のロジック」に寄せておけば、判定基準を変えたいときはコードを直して再計算するだけで済みます。事実データそのものは変わっていないので、入力のやり直しは発生しません。

恣意性は消えるわけではなく、置き場所が変わるだけです。
ただ、その置き場所によって「基準を見直すコスト」が大きく変わってきます。

3. 出力パターンの軸を整理する

前章で、案件ごとの事実データ(実施したタスクのbooleanなど)を一箇所にまとめました。
ここでは、実際にどうやって複数の出力パターンを作り分けるか、という話に入ります。

プレゼン向けと詳細粒度のスキルシートのように、異なる出力パターンを設けようとする意図としては、「詳しく見せる」の中身が違うことが挙げられます。

  • プレゼン向け:担当工程や環境がパッと見たときに把握できることが重要。現場レベルの細かい情報まで載せるとノイズになる。
  • 詳細粒度:タスク単位の実施内容など、より踏み込んだ観点を見せたい。「その人がこれまで何をしてきたのか」に具体的なイメージを持たせる。

同じ「詳しく」でも、何を目的とする資料かによって適切な粒度が異なります。

当初は1つの軸にまとめる案も検討しましたが、「詳細粒度だから詳しく見せたい」という都合と
「この案件を主要案件として扱うのか」という表示範囲の判定を、1つの値が請け負うことになり、意味が混在してしまいます。

そこで、出力の軸を2つに分けることにしました。

  • 表示範囲の軸:主要な案件として厚く見せるか、経歴として一行で済ませるか
  • 出力ターゲットの軸:プレゼン向けに見せるか、詳細粒度で見せるか

この2軸は独立していて、掛け合わせると次のようなパターンになります。

プレゼン向け 詳細粒度
厚く見せる 担当工程・環境をひと通り表示 タスク単位の実施内容まで表示
一行で済ませる 概要と環境のみ 担当工程を粗くまとめて表示

なお、「厚く見せるかどうか」の判定基準は、プレゼン向けでも詳細粒度でも同じ値を使っています。
案件データ側に「主要な案件として扱うかどうか」というフラグを1つ持たせておけば、どちらの出力を作るときもそのフラグを参照するだけで済むからです。

4. データ側に拡張性を持たせるコスト

前章で、「誰に見せるか」「どこまで詳しく見せるか」という表示の軸を整理しました。
ここからは、データの中身側の話に入ります。

技術スキルを分類する項目のひとつに、「ツールのカテゴリ」があります。
使っているツールが「AI系のツールか、それ以外か」を分けて表示したい、という要件です。

これを設計するとき、選択肢は大きく2つありました。

  • カテゴリをあらかじめ決めた選択肢(AI/その他、など)に固定する
  • カテゴリを自由な文字列として持たせ、出力側はその値をそのまま使う

今回は後者を選びました。

tools:
  - name: "Claude Code"
    category: "AI"
  - name: "Git/GitHub"
    category: "その他"

出力側では、このcategoryの値をそのままカテゴリ名として使い、「AI」と書かれていれば「AIツール」、それ以外の値であれば「〇〇ツール」という見出しを動的に組み立てます。

この設計のメリットは、新しいカテゴリが増えてもコードを直す必要がない点です。
たとえば「ドキュメント」という値を書けば、何も変更しなくても「ドキュメントツール」という区分が自動的に増えます。
将来どんなカテゴリが必要になるか、あらかじめ全部を見積もっておく必要がありません。

表記ゆれとどう向き合うか

一方で、この設計には意識しておきたい点もあります。

カテゴリを自由な文字列にした時点で、表記のゆれをコード側でチェックできなくなります。
「AI」と書くつもりが「Ai」や「ai」になっていても、プログラムはエラーを出しません。
ただ、意図したカテゴリには集計されず、別のカテゴリとして扱われてしまいます。

「コードで値を縛らない」ということは、「表記を揃える」という作業を人間側に寄せることとも言えます。

この状態にどう対応するかも、あわせて決めておく必要があります。
今回は、将来的には「表記ゆれを検知する仕組み」を作ることを前提に、それが整うまでは「エラーにはならないが期待通りには表示されない」という状態を許容する、という判断にしました。
厳密にチェックする仕組みを今すぐ作るコストと、多少の表記ゆれが起きても実害が大きくない現状の運用規模を比べて、後者を選んだ形です。

同じ構図は、技術名を分類する別の場所にもあります。
ある技術名が「言語」に該当するかどうかを判定する仕組みも、あらかじめ用意した一覧表と名前が完全一致するかどうかで判定しています。
一覧にない表記で書かれた技術名は、静かに別のカテゴリに残り続けます。

コードでの検証を手放した分は、表記統一のルールやレビューといった形で、運用側に負担として残ります。
今回はこの負担を「許容できる範囲」と判断しましたが、自由にしておけば後で困らない、というほど単純な話でもありません。

次章では、これを踏まえたうえで、あえてコード側に固定した箇所について見ていきます。

5. あえて固定化した判断

前章では、値を自由な文字列にしたことで、拡張はしやすくなった一方、表記ゆれのようなリスクを運用側に残すことになる、という話をしました。
では、すべてを自由な値にしたかというと、そうではありません。
ここでは「あえて固定にした箇所」を見ていきます。

何を固定にしたか

技術スキルを分類する区分(バックエンド、フロントエンド、といった大枠)と、案件ごとに「その工程で何を実施したか」を記録するチェックリストの項目は、どちらも固定にしました。

区分もチェックリストの項目も、増やそうと思えばいくらでも増やせます。
ですが、増やすたびに次のようなコストが発生します。

  • 区分を増やせば、過去にさかのぼって既存データをどの区分に振り分けるか判断し直す必要がある
  • チェックリストの項目を増やせば、テンプレート・出力側のロジック・すでに入力済みのデータ、すべてに影響が及ぶ

こうした「構造そのもの」に関わる変更は、都度対応するコストが大きいため、最初から少数の区分にとどめる方針にしました。

固定にしたのは「箱」であって「中身」ではない

ただ、区分やチェックリストという構造を固定にしたからといって、そこに書ける情報の幅まで狭めたわけではありません。

areas:
  backend:
    technologies: [TypeScript, Express.js]

たとえば技術領域の区分自体は固定でも、その中に書く技術名は自由な文字列のリストです。
新しい技術が出てきても、区分を増やさずにこのリストへ追記するだけで済みます。

チェックリストについても同様です。
項目自体は固定ですが、そこにない取り組みは自由記述欄でアピールできる設計にしてあります。

points: |
  チェックリストにない取り組みも、ここで自由に書けます。

案件種別のような固定の選択肢にも、あわせて自由記述を許可しています。
あらかじめ決めた選択肢に当てはまらないケースについては、無理に既存の選択肢へ押し込めず、自由記述側で拾う想定です。

自由記述は受け皿であり、判断材料でもある

この自由記述欄には、もうひとつ役割を持たせています。

チェックリストにない取り組みが、いろんな案件の自由記述に何度も同じような形で書かれるようになったとします。
それは「この取り組みは、もう例外ではなく、チェックリストの項目に加えるべきタイミングが来た」というシグナルだと捉えます。

固定にした構造は、将来的にも「触らない」と決めたわけではありません。
自由記述側にどんな内容が積み上がっていくかを見ながら、「構造を更新するかどうかを判断する」という運用を前提にしています。

つまり自由記述欄は、単なる例外の受け皿ではなく、「次にチェックリストへ昇格させるべき項目」を運用の中から見つけるための場所でもあります。
ここでコストをかけて構造を変えるかどうかを判断できるので、最初から全部を見積もって構造に組み込んでおく必要がなくなります。

この自由度にも、2種類ある

ここで前章の話に少し戻ります。
「値の側を自由にする」という選択には、前章で見たような表記ゆれのリスクが伴う、という内容でした。
では、今回固定にした箱の中身も、同じリスクを抱えているのでしょうか。

答えは、場所によって違います。

技術名のリストのように「プログラム側がその値を見て分類やマッチングに使う」場合は、前章と同じ話がそのまま当てはまります。
当然、書き方が揺れれば、意図した通りには扱われません。

一方、チェックリストにない取り組みを書く自由記述欄のように、「人間が読むだけでプログラムが値を解釈しない」場合は、この話は当てはまりません。
表記が多少揺れていても、読む側が困るほどでなければ実害は少ないからです。

同じ「値を自由にする」という選択でも、その値をプログラムが読むのか、人間が読むだけなのかによって、抱えるリスクの種類が変わります。

まとめると

固定にするかどうかを決める基準は、大きく2段階で考えました。

  • 構造そのもの(区分やチェックリストの項目)が将来変わりそうかどうか
    • → 変わりそうでも、変更コストが大きい場所は、自由記述側にいったん吸収させておき、同じ内容が積み上がってきたタイミングで初めて構造を改修する
  • その構造の中で扱う値の種類が増えそうかどうか
    • → 増えそうな場所は、型を緩くしておいて、コードを変えずに吸収できるようにする

変化を受け止める場所を、「構造」から「値」の側に一度寄せておき、その値側の実績を見てから構造を更新するかどうかを判断する、という選択をしました。

おわりに

本記事では、スキルシートの元データをmarkdownからyamlに移行するにあたって、どんな考え方でデータの持ち方を設計し直したかを見てきました。

どこまでを固定するのか、自由度の範囲内として吸収させるのか、また将来的に対応する余地とするか、運用実態を加味しながら設計することができました。

4章で触れた表記ゆれのチェックは、今回はまだ仕組み化できていません。
レーベンシュタイン距離を使った類似度判定で、ゆるく検知できる仕組みから作ってみたいと思っています。

以上です。最後まで閲覧いただきありがとうございます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?