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?

正確な `interface` がまだ決めきれないなら──`any` ではなく `extends` で「必要なものだけ」を約束する

Last updated at Posted at 2025-10-10

開発の現場には、不思議と「任せられる」人がいます。仕様が揺れていても、締切が迫っていても、彼らは慌てて断言しません。むしろこう言います。「いま確実に言えるのはここまで。残りは確認して、約束できる形にして持ち帰ります」。
型付けも同じです。全部わかったふりをする any ではなく、「ここだけは必要です」と明確に言える最小限を宣言する。そうすれば、曖昧さは減り、実装は進み、余計な事故も起きません。

その“最小限の約束”を、TypeScript では 制約付きジェネリクスで表せます。

any は便利だけど、約束がゼロ

// ❌ なんでも通るが、なんの保証もない
function doThing(value: any) {
  // コンパイルは通るが、実行時に落ちるかもしれない
  value.fullName.toUpperCase();
}

any は「今はとりあえず答える」ための言い切りです。会議のその場しのぎと同じで、後から信用コストを払いがち。

「型を固定」もやり過ぎると、相手の情報を落とす

// ✅ だが狭すぎる: 返り値から余分な情報が消える
function echoNarrow(x: { fullName: string }) {
  return x;
}

const obj = { fullName: "ok", extra: 123 } as const;
const r1 = echoNarrow(obj);
// r1 の型: { fullName: string }(extra が消えた)

会議で「この一点だけです」としかメモを取らないと、あとで“その他の文脈”が失われるのと同じ。

ちょうどよい中庸:T extends { fullName: string }

// ✅ 必要なものは保証しつつ、相手の情報は丸ごと保持
function echoWide<T extends { fullName: string }>(x: T): T {
  return x;
}

const obj = { fullName: "ok", extra: 123 } as const;
const r2 = echoWide(obj);
// r2 の型: { readonly fullName: "ok"; readonly extra: 123 }

「このプロパティは必須です」だけを約束し、その他は相手のまま。
まさに、「不用意に断言しないが、必要な約束は持ち帰らない」という姿勢です。

基本パターン

function useFullName<T extends { fullName: string }>(x: T) {
  const upper = x.fullName.toUpperCase(); // 安全
  // もし呼び出し元が extra を持っていれば、ここでも候補に出る
  // x.extra
}

useFullName({ fullName: "hi", extra: 42 });  // ✅
useFullName({ extra: 42 });                  // ❌ Property 'fullName' is missing

“持ち帰って”付加情報を足すユーティリティ

function stamp<T extends { id: string }>(input: T) {
  return { ...input, stampedAt: new Date() };
}

const payload = { id: "u1", role: "admin" };
const stamped = stamp(payload);
// { id: string; role: string; stampedAt: Date }

必要条件(id)だけ固める → あとは自然に広がる。
議事録の「確定事項」と同じ設計です。

Optional を許して、あとで確認する

「たぶんあるはず」な項目は Optional で受けて、使う前に“確認”します。

function readMaybe<T extends { fullName?: string }>(x: T) {
  if (!x.fullName) throw new Error("Missing fullName");
  return x.fullName.toUpperCase(); // 以降は安全に使える
}

型ガードで“確認済み”を型に反映する手もあります。

type WithFullName = { fullName: string };

function hasFullName<T extends { fullName?: unknown }>(x: T): x is T & WithFullName {
  return typeof (x as any).fullName === "string";
}

function demo<T extends { fullName?: unknown }>(x: T) {
  if (hasFullName(x)) {
    x.fullName.toUpperCase(); // ここでは T & { fullName: string }
  }
}

複数キーでも同じ発想で

function process<T extends { fullName: string; count: number }>(x: T) {
  x.fullName.toUpperCase();
  x.count.toFixed(0);
}

ユーティリティで汎用化もできます。

type With<K extends string, V> = Record<K, V>;

function byKey<K extends string, V, T extends With<K, V>>(x: T, key: K) {
  return x[key]; // V として扱える
}

keyof と組み合わせても、相手の情報を保つ

function pick<T extends { fullName: string }, K extends keyof T>(x: T, key: K) {
  return x[key]; // T[K]
}

const obj = { fullName: "x", count: 3 };
const v1 = pick(obj, "fullName"); // string
const v2 = pick(obj, "count");  // number

すぐ使えるスニペット集

1) 「必須キーだけ保証して通す」

export function ensureFullName<T extends { fullName: string }>(x: T) {
  // ここでは fullName が必ず使える
  return x.fullName.trim();
}

2) 「入力を保ったまま 1 フィールド付加」

export function withTag<T extends { id: string }>(x: T, tag: string) {
  return { ...x, tag };
}

3) 「Optional を受けてから確認」

export function must<T extends { fullName?: string }>(x: T) {
  if (!x.fullName) throw new Error("fullName is required at runtime");
  return x as T & { fullName: string };
}

4) 「配列から“必須が満たされているものだけ”を使う」

export function filterValid<T extends { fullName?: string }>(items: T[]) {
  return items.filter((i): i is T & { fullName: string } => !!i.fullName);
}

まとめ

  • any は「今は答えるけど、正確ではない」という無保証の言い切り。
  • T extends { fullName: X } は必要最小限の約束だけを固め、相手の情報を丸ごと残す
  • 仕様が揺れる局面、ユーティリティ、ビルダー、アダプタでは、この“中庸”が最も強い

正確な interface をいま決めきれないなら── any ではなく extends を。
約束できる最小限を明確にし、残りは誠実に“持ち帰る”。それが、型と実装とチームを前に進めます。

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?