TypeScriptの過剰プロパティチェックを習得し、ランタイムエラーを防止してオブジェクトの型安全性を高め、堅牢で予測可能なJSアプリを構築する方法を解説します。
TypeScriptの過剰プロパティチェック:オブジェクトの型安全性を強化する
現代のソフトウェア開発、特にJavaScriptの世界では、コードの完全性と予測可能性を確保することが最も重要です。JavaScriptは非常に高い柔軟性を提供しますが、その一方で予期せぬデータ構造やプロパティの不一致により、ランタイムエラーを引き起こすことがあります。ここでTypeScriptが真価を発揮します。静的型付け機能によって、本番環境で問題が表面化する前に多くの一般的なエラーを検出できるのです。TypeScriptの強力でありながら、時に誤解されがちな機能の一つが、過剰プロパティチェックです。
この記事では、TypeScriptの過剰プロパティチェックを深く掘り下げ、それが何であるか、なぜオブジェクトの型安全性にとって重要なのか、そしてより堅牢で予測可能なアプリケーションを構築するためにそれを効果的に活用する方法について説明します。さまざまなシナリオ、よくある落とし穴、そしてベストプラクティスを探求し、世界中の開発者がその背景に関わらず、この重要なTypeScriptのメカニズムを使いこなせるよう支援します。
中心概念の理解:過剰プロパティチェックとは何か?
TypeScriptの過剰プロパティチェックの核心は、型が明示的に許可していない余分なプロパティを持つオブジェクトリテラルを、変数に代入することを防ぐコンパイラのメカニズムです。簡単に言えば、オブジェクトリテラルを定義し、特定の型定義(インターフェースや型エイリアスなど)を持つ変数に代入しようとした際に、そのリテラルが定義された型で宣言されていないプロパティを含んでいる場合、TypeScriptはコンパイル時にそれをエラーとして検出します。
基本的な例で説明しましょう:
interface User {
name: string;
age: number;
}
const newUser: User = {
name: 'Alice',
age: 30,
email: 'alice@example.com' // エラー: オブジェクトリテラルは既知のプロパティのみ指定できます。プロパティ 'email' は型 'User' に存在しません。
};
このスニペットでは、`name`と`age`という2つのプロパティを持つ`User`という`interface`を定義しています。`email`という追加のプロパティを持つオブジェクトリテラルを作成し、`User`型として型付けされた変数に代入しようとすると、TypeScriptは即座に不一致を検出します。`email`プロパティは`User`インターフェースで定義されていないため、「過剰な」プロパティとなります。このチェックは、特にオブジェクトリテラルを代入に使用する場合に実行されます。
なぜ過剰プロパティチェックは重要なのか?
過剰プロパティチェックの重要性は、データと期待される構造との間の契約を強制する能力にあります。これらはいくつかの重要な方法でオブジェクトの型安全性に貢献します:
- タイポやスペルミスの防止: JavaScriptのバグの多くは、単純なタイポから発生します。`age`に値を代入するつもりが、誤って`agee`と入力してしまった場合、過剰プロパティチェックがこれを「スペルミス」のプロパティとして検出し、`age`が`undefined`になったり欠落したりする潜在的なランタイムエラーを防ぎます。
- API契約の遵守を保証: 特定の形状のオブジェクトを期待するAPI、ライブラリ、または関数とやり取りする際に、過剰プロパティチェックは、それらの期待に準拠したデータを渡していることを保証します。これは、大規模な分散チームやサードパーティのサービスと統合する場合に特に価値があります。
- コードの可読性と保守性の向上: オブジェクトの期待される構造を明確に定義することで、これらのチェックはコードを自己文書化しやすくします。開発者は、複雑なロジックを遡ることなく、オブジェクトが持つべきプロパティを迅速に理解できます。
- ランタイムエラーの削減: 最も直接的な利点は、ランタイムエラーの削減です。本番環境で`TypeError`や`undefined`へのアクセスエラーに遭遇する代わりに、これらの問題はコンパイル時エラーとして表面化するため、修正がより簡単かつ安価になります。
- リファクタリングの促進: コードをリファクタリングしてインターフェースや型の形状を変更すると、過剰プロパティチェックがオブジェクトリテラルがもはや準拠していない可能性のある箇所を自動的にハイライトし、リファクタリングプロセスを効率化します。
過剰プロパティチェックはいつ適用されるのか?
TypeScriptがこれらのチェックをどのような特定の条件下で実行するかを理解することが重要です。これらは主に、オブジェクトリテラルが変数に代入されたり、関数の引数として渡されたりする場合に適用されます。
シナリオ1:オブジェクトリテラルを変数に代入する
上記の`User`の例で見たように、余分なプロパティを持つオブジェクトリテラルを型付けされた変数に直接代入すると、チェックがトリガーされます。
シナリオ2:オブジェクトリテラルを関数に渡す
関数が特定の型の引数を期待している場合に、過剰なプロパティを含むオブジェクトリテラルを渡すと、TypeScriptはそれをエラーとして検出します。
interface Product {
id: number;
name: string;
}
function displayProduct(product: Product): void {
console.log(`Product ID: ${product.id}, Name: ${product.name}`);
}
displayProduct({
id: 101,
name: 'Laptop',
price: 1200 // エラー: 型 '{ id: number; name: string; price: number; }' の引数を型 'Product' のパラメーターに割り当てることはできません。
// オブジェクトリテラルは既知のプロパティのみ指定できます。プロパティ 'price' は型 'Product' に存在しません。
});
ここで、`displayProduct`に渡されたオブジェクトリテラルの`price`プロパティは、`Product`インターフェースがそれを定義していないため、過剰なプロパティとなります。
過剰プロパティチェックが適用*されない*のはいつか?
これらのチェックが回避される時を理解することは、混乱を避け、代替戦略が必要になるかもしれない時を知る上で同様に重要です。
1. 代入にオブジェクトリテラルを使用しない場合
オブジェクトリテラルではないオブジェクト(例:既にオブジェクトを保持している変数)を代入する場合、過剰プロパティチェックは通常回避されます。
interface Config {
timeout: number;
}
function setupConfig(config: Config) {
console.log(`Timeout set to: ${config.timeout}`);
}
const userProvidedConfig = {
timeout: 5000,
retries: 3 // この 'retries' プロパティは 'Config' にとって過剰なプロパティです
};
setupConfig(userProvidedConfig); // エラーなし!
// userProvidedConfig には余分なプロパティがありますが、チェックはスキップされます
// なぜなら、直接渡されるオブジェクトリテラルではないからです。
// TypeScriptは userProvidedConfig 自体の型をチェックします。
// もし userProvidedConfig が Config 型で宣言されていたら、エラーはもっと早い段階で発生していたでしょう。
// しかし、'any' やより広い型として宣言されている場合、エラーは先送りされます。
// 回避を示すより正確な方法:
let anotherConfig;
if (Math.random() > 0.5) {
anotherConfig = {
timeout: 1000,
host: 'localhost' // 過剰なプロパティ
};
} else {
anotherConfig = {
timeout: 2000,
port: 8080 // 過剰なプロパティ
};
}
setupConfig(anotherConfig as Config); // 型アサーションとバイパスのためエラーなし
// 重要なのは、'anotherConfig' が setupConfig への代入時点でオブジェクトリテラルではないということです。
// もし 'Config' 型の中間変数があれば、最初の代入は失敗していたでしょう。
// 中間変数の例:
let intermediateConfig: Config;
intermediateConfig = {
timeout: 3000,
logging: true // エラー: オブジェクトリテラルは既知のプロパティのみ指定できます。プロパティ 'logging' は型 'Config' に存在しません。
};
最初の`setupConfig(userProvidedConfig)`の例では、`userProvidedConfig`はオブジェクトを保持する変数です。TypeScriptは`userProvidedConfig`全体が`Config`型に準拠しているかを確認します。`userProvidedConfig`自体に対しては、厳密なオブジェクトリテラルチェックを適用しません。もし`userProvidedConfig`が`Config`と一致しない型で宣言されていた場合、その宣言または代入時にエラーが発生します。この回避が起こるのは、オブジェクトが関数に渡される前に既に作成され、変数に代入されているためです。
2. 型アサーション
型アサーションを使用することで過剰プロパティチェックを回避できますが、これはTypeScriptの安全保証を上書きするため、慎重に行うべきです。
interface Settings {
theme: 'dark' | 'light';
}
const mySettings = {
theme: 'dark',
fontSize: 14 // 過剰なプロパティ
} as Settings;
// 型アサーションのため、ここではエラーは発生しません。
// TypeScriptに「このオブジェクトはSettingsに準拠していると信じてくれ」と伝えています。
console.log(mySettings.theme);
// console.log(mySettings.fontSize); // もしfontSizeが実際に存在しなければ、これはランタイムエラーを引き起こします。
3. 型定義でのインデックスシグネチャまたはスプレッド構文の使用
インターフェースや型エイリアスが明示的に任意のプロパティを許可している場合、過剰プロパティチェックは適用されません。
インデックスシグネチャの使用:
interface FlexibleObject {
id: number;
[key: string]: any; // 任意の文字列キーと任意の値を許可する
}
const flexibleItem: FlexibleObject = {
id: 1,
name: 'Widget',
version: '1.0.0'
};
// 'name'と'version'はインデックスシグネチャによって許可されているため、エラーはありません。
console.log(flexibleItem.name);
型定義でのスプレッド構文の使用(チェックを直接回避するためにはあまり使われず、互換性のある型を定義するためによく使われます):
直接的な回避策ではありませんが、スプレッド構文を使うと既存のプロパティを組み込んだ新しいオブジェクトを作成でき、その新しく形成されたリテラルに対してチェックが適用されます。
4. `Object.assign()` またはスプレッド構文によるマージ
`Object.assign()`やスプレッド構文(`...`)を使ってオブジェクトをマージする場合、過剰プロパティチェックの挙動は異なります。それは、結果として形成されるオブジェクトリテラルに適用されます。
interface BaseConfig {
host: string;
}
interface ExtendedConfig extends BaseConfig {
port: number;
}
const defaultConfig: BaseConfig = {
host: 'localhost'
};
const userConfig = {
port: 8080,
timeout: 5000 // BaseConfigに対しては過剰なプロパティですが、マージ後の型では期待されています
};
// ExtendedConfigに準拠する新しいオブジェクトリテラルへのスプレッド
const finalConfig: ExtendedConfig = {
...defaultConfig,
...userConfig
};
// 'finalConfig'が'ExtendedConfig'として宣言されており
// プロパティが一致するため、これは通常問題ありません。チェックは'finalConfig'の型に対して行われます。
// それが失敗するシナリオを考えてみましょう:
interface SmallConfig {
key: string;
}
const data1 = { key: 'abc', value: 123 }; // ここでは'value'が余分です
const data2 = { key: 'xyz', status: 'active' }; // ここでは'status'が余分です
// 余分なプロパティを許容しない型に代入しようとしています
// const combined: SmallConfig = {
// ...data1, // エラー: オブジェクトリテラルは既知のプロパティのみ指定できます。プロパティ 'value' は型 'SmallConfig' に存在しません。
// ...data2 // エラー: オブジェクトリテラルは既知のプロパティのみ指定できます。プロパティ 'status' は型 'SmallConfig' に存在しません。
// };
// このエラーは、スプレッド構文によって形成されたオブジェクトリテラルが
// 'SmallConfig'に存在しないプロパティ('value', 'status')を含んでいるために発生します。
// もしより広い型を持つ中間変数を作成した場合:
const temp: any = {
...data1,
...data2
};
// そしてSmallConfigに代入すると、最初のリテラル作成時の過剰プロパティチェックは回避されますが、
// tempの型がより厳密に推論される場合、代入時の型チェックは依然として発生する可能性があります。
// ただし、tempが'any'の場合、'combined'への代入までチェックは行われません。
// スプレッド構文と過剰プロパティチェックの理解を深めましょう:
// チェックは、スプレッド構文によって作成されたオブジェクトリテラルが
// より具体的な型を期待する変数に代入されたり、関数に渡されたりする時に発生します。
interface SpecificShape {
id: number;
}
const objA = { id: 1, extra1: 'hello' };
const objB = { id: 2, extra2: 'world' };
// SpecificShapeが'extra1'や'extra2'を許可しない場合、これは失敗します:
// const merged: SpecificShape = {
// ...objA,
// ...objB
// };
// これが失敗する理由は、スプレッド構文が実質的に新しいオブジェクトリテラルを作成するからです。
// もしobjAとobjBに重複するキーがあった場合、後のものが優先されます。コンパイラは
// この結果のリテラルを見て、'SpecificShape'と照合します。
// これを機能させるには、中間ステップか、より寛容な型が必要になるかもしれません:
const tempObj = {
...objA,
...objB
};
// ここで、tempObjにSpecificShapeにないプロパティがある場合、代入は失敗します:
// const mergedCorrected: SpecificShape = tempObj; // エラー: オブジェクトリテラルは既知のプロパティのみ指定できます...
// 重要なのは、コンパイラが形成されるオブジェクトリテラルの形状を分析するということです。
// そのリテラルがターゲットの型で定義されていないプロパティを含んでいる場合、エラーになります。
// スプレッド構文と過剰プロパティチェックの典型的なユースケース:
interface UserProfile {
userId: string;
username: string;
}
interface AdminProfile extends UserProfile {
adminLevel: number;
}
const baseUserData: UserProfile = {
userId: 'user-123',
username: 'coder'
};
const adminData = {
adminLevel: 5,
lastLogin: '2023-10-27'
};
// ここで過剰プロパティチェックが関係してきます:
// const adminProfile: AdminProfile = {
// ...baseUserData,
// ...adminData // エラー: オブジェクトリテラルは既知のプロパティのみ指定できます。プロパティ 'lastLogin' は型 'AdminProfile' に存在しません。
// };
// スプレッドによって作成されたオブジェクトリテラルには 'lastLogin' がありますが、これは 'AdminProfile' には存在しません。
// これを修正するには、'adminData'が理想的にはAdminProfileに準拠するか、過剰なプロパティが処理されるべきです。
// 修正されたアプローチ:
const validAdminData = {
adminLevel: 5
};
const adminProfileCorrect: AdminProfile = {
...baseUserData,
...validAdminData
};
console.log(adminProfileCorrect.userId);
console.log(adminProfileCorrect.adminLevel);
過剰プロパティチェックは、スプレッド構文によって作成された結果のオブジェクトリテラルに適用されます。この結果のリテラルがターゲットの型で宣言されていないプロパティを含んでいる場合、TypeScriptはエラーを報告します。
過剰なプロパティを処理するための戦略
過剰プロパティチェックは有益ですが、含めたい、または異なる方法で処理したい余分なプロパティがある正当なシナリオも存在します。以下は一般的な戦略です:
1. 型エイリアスまたはインターフェースでのRestプロパティ
型エイリアスやインターフェース内でRestパラメータ構文(`...rest`)を使用して、明示的に定義されていない残りのプロパティをキャプチャできます。これは、これらの過剰なプロパティを認識し、収集するためのクリーンな方法です。
interface UserProfile {
id: number;
name: string;
}
interface UserWithMetadata extends UserProfile {
metadata: {
[key: string]: any;
};
}
// または、より一般的には型エイリアスとRest構文を使用:
type UserProfileWithMetadata = UserProfile & {
[key: string]: any;
};
const user1: UserProfileWithMetadata = {
id: 1,
name: 'Bob',
email: 'bob@example.com',
isAdmin: true
};
// 'email'と'isAdmin'はUserProfileWithMetadataのインデックスシグネチャによってキャプチャされるため、エラーはありません。
console.log(user1.email);
console.log(user1.isAdmin);
// 型定義でRestパラメータを使用した別の方法:
interface ConfigWithRest {
apiUrl: string;
timeout?: number;
// 他のすべてのプロパティを 'extraConfig' にキャプチャ
[key: string]: any;
}
const appConfig: ConfigWithRest = {
apiUrl: 'https://api.example.com',
timeout: 5000,
featureFlags: {
newUI: true,
betaFeatures: false
}
};
console.log(appConfig.featureFlags);
`[key: string]: any;`のようなインデックスシグネチャを使用することは、任意の追加プロパティを処理するための慣用的な方法です。
2. Rest構文による分割代入
オブジェクトを受け取り、特定のプロパティを抽出しつつ残りを保持する必要がある場合、Rest構文による分割代入は非常に価値があります。
interface Employee {
employeeId: string;
department: string;
}
function processEmployeeData(data: Employee & { [key: string]: any }) {
const { employeeId, department, ...otherDetails } = data;
console.log(`Employee ID: ${employeeId}`);
console.log(`Department: ${department}`);
console.log('Other details:', otherDetails);
// otherDetailsには、'salary'や'startDate'など、
// 明示的に分割代入されなかったプロパティが含まれます。
}
const employeeInfo = {
employeeId: 'emp-789',
department: 'Engineering',
salary: 90000,
startDate: '2022-01-15'
};
processEmployeeData(employeeInfo);
// employeeInfoに最初から余分なプロパティがあったとしても、
// 関数のシグネチャがそれを受け入れる場合(例:インデックスシグネチャの使用)、
// 過剰プロパティチェックは回避されます。
// もしprocessEmployeeDataが厳密に'Employee'として型付けされ、employeeInfoに'salary'があった場合、
// employeeInfoが直接渡されるオブジェクトリテラルであればエラーが発生します。
// しかし、ここではemployeeInfoは変数であり、関数の型が余分なプロパティを処理します。
3. すべてのプロパティを明示的に定義する(既知の場合)
追加される可能性のあるプロパティがわかっている場合、最善のアプローチはそれらをインターフェースや型エイリアスに追加することです。これにより、最も高い型安全性が得られます。
interface UserProfile {
id: number;
name: string;
email?: string; // オプショナルなemail
}
const userWithEmail: UserProfile = {
id: 2,
name: 'Charlie',
email: 'charlie@example.com'
};
const userWithoutEmail: UserProfile = {
id: 3,
name: 'David'
};
// UserProfileにないプロパティを追加しようとすると:
// const userWithExtra: UserProfile = {
// id: 4,
// name: 'Eve',
// phoneNumber: '555-1234'
// }; // エラー: オブジェクトリテラルは既知のプロパティのみ指定できます。プロパティ 'phoneNumber' は型 'UserProfile' に存在しません。
4. 型アサーションのための`as`の使用(注意して)
前述の通り、型アサーションは過剰プロパティチェックを抑制することができます。これは控えめに、そしてオブジェクトの形状について絶対的な確信がある場合にのみ使用してください。
interface ProductConfig {
id: string;
version: string;
}
// これが外部ソースやより厳密でないモジュールから来ると想像してください
const externalConfig = {
id: 'prod-abc',
version: '1.2',
debugMode: true // 過剰なプロパティ
};
// 'externalConfig'が常に'id'と'version'を持ち、それをProductConfigとして扱いたい場合:
const productConfig = externalConfig as ProductConfig;
// このアサーションは`externalConfig`自体に対する過剰プロパティチェックを回避します。
// しかし、オブジェクトリテラルを直接渡した場合は:
// const productConfigLiteral: ProductConfig = {
// id: 'prod-xyz',
// version: '2.0',
// debugMode: false
// }; // エラー: オブジェクトリテラルは既知のプロパティのみ指定できます。プロパティ 'debugMode' は型 'ProductConfig' に存在しません。
5. 型ガード
より複雑なシナリオでは、型ガードが型を絞り込み、条件付きでプロパティを処理するのに役立ちます。
interface Shape {
kind: 'circle' | 'square';
}
interface Circle extends Shape {
kind: 'circle';
radius: number;
}
interface Square extends Shape {
kind: 'square';
sideLength: number;
}
function calculateArea(shape: Shape) {
if (shape.kind === 'circle') {
// TypeScriptはここで'shape'がCircleであることを知っている
console.log(Math.PI * shape.radius ** 2);
} else if (shape.kind === 'square') {
// TypeScriptはここで'shape'がSquareであることを知っている
console.log(shape.sideLength ** 2);
}
}
const circleData = {
kind: 'circle' as const, // リテラル型の推論のために 'as const' を使用
radius: 10,
color: 'red' // 過剰なプロパティ
};
// calculateAreaに渡される際、関数のシグネチャは'Shape'を期待します。
// 関数自体は正しく'kind'にアクセスします。
// もしcalculateAreaが直接'Circle'を期待していてcircleDataを
// オブジェクトリテラルとして受け取った場合、'color'が問題になります。
// 特定のサブタイプを期待する関数での過剰プロパティチェックを説明しましょう:
function processCircle(circle: Circle) {
console.log(`Processing circle with radius: ${circle.radius}`);
}
// processCircle(circleData); // エラー: 型 '{ kind: "circle"; radius: number; color: string; }' の引数を型 'Circle' のパラメーターに割り当てることはできません。
// オブジェクトリテラルは既知のプロパティのみ指定できます。プロパティ 'color' は型 'Circle' に存在しません。
// これを修正するには、分割代入するか、circleDataにより寛容な型を使用します:
const { color, ...circleDataWithoutColor } = circleData;
processCircle(circleDataWithoutColor);
// または、circleDataをより広い型を含むように定義します:
const circleDataWithExtras: Circle & { [key: string]: any } = {
kind: 'circle',
radius: 15,
color: 'blue'
};
processCircle(circleDataWithExtras); // これで動作します。
よくある落とし穴とその回避方法
経験豊富な開発者でさえ、過剰プロパティチェックに不意を突かれることがあります。以下はよくある落とし穴です:
- オブジェクトリテラルと変数の混同: 最もよくある間違いは、チェックがオブジェクトリテラルに特有のものであると認識していないことです。最初に変数に代入し、その変数を渡すと、チェックはしばしば回避されます。常に代入のコンテキストを覚えておきましょう。
- 型アサーション(`as`)の過度な使用: 便利ではありますが、型アサーションを過度に使用するとTypeScriptの利点が損なわれます。チェックを回避するために頻繁に`as`を使用していることに気づいた場合、それはあなたの型やオブジェクトの構築方法に改善の余地があることを示しているかもしれません。
- 期待されるすべてのプロパティを定義していない: 多くの潜在的なプロパティを持つオブジェクトを返すライブラリやAPIを扱っている場合は、必要なプロパティを型でキャプチャし、残りはインデックスシグネチャやRestプロパティを使用するようにしてください。
- スプレッド構文の不適切な適用: スプレッド構文が新しいオブジェクトリテラルを作成することを理解してください。この新しいリテラルがターゲットの型に対して過剰なプロパティを含んでいる場合、チェックが適用されます。
グローバルな考慮事項とベストプラクティス
グローバルで多様な開発環境で作業する場合、型安全性に関する一貫したプラクティスを遵守することが重要です:
- 型定義の標準化: 特に外部データや複雑なオブジェクト構造を扱う際には、チームがインターフェースと型エイリアスの定義方法について明確な理解を持っていることを確認してください。
- 規約の文書化: インデックスシグネチャ、Restプロパティ、または特定のユーティリティ関数を通じて、過剰なプロパティを処理するためのチームの規約を文書化してください。
- 新しいチームメンバーの教育: TypeScriptやプロジェクトに新しい開発者が、過剰プロパティチェックの概念と重要性を理解していることを確認してください。
- 可読性の優先: できるだけ明示的な型を目指してください。オブジェクトが固定のプロパティセットを持つことを意図している場合は、データの性質が本当にそれを必要としない限り、インデックスシグネチャに頼るのではなく、明示的に定義してください。
- リンターとフォーマッターの使用: ESLintとTypeScript ESLintプラグインのようなツールを設定して、コーディング標準を強制し、オブジェクトの形状に関連する潜在的な問題を検出できます。
結論
TypeScriptの過剰プロパティチェックは、堅牢なオブジェクトの型安全性を提供する能力の基礎となるものです。これらのチェックがいつ、なぜ発生するのかを理解することで、開発者はより予測可能でエラーの少ないコードを書くことができます。
世界中の開発者にとって、この機能を受け入れることは、ランタイムでの驚きを減らし、共同作業を容易にし、より保守しやすいコードベースを意味します。小規模なユーティリティを構築している場合でも、大規模なエンタープライズアプリケーションを構築している場合でも、過剰プロパティチェックを習得することは、間違いなくあなたのJavaScriptプロジェクトの品質と信頼性を向上させるでしょう。
重要なポイント:
- 過剰プロパティチェックは、特定の型を持つ変数に代入されたり、関数に渡されたりするオブジェクトリテラルに適用されます。
- タイポを検出し、API契約を強制し、ランタイムエラーを削減します。
- 非リテラルの代入、型アサーション、およびインデックスシグネチャを持つ型ではチェックは回避されます。
- 正当な過剰プロパティを適切に処理するためには、Restプロパティ(`[key: string]: any;`)や分割代入を使用します。
- これらのチェックの一貫した適用と理解は、グローバルな開発チーム全体でより強力な型安全性を促進します。
これらの原則を意識的に適用することで、TypeScriptコードの安全性と保守性を大幅に向上させ、より成功したソフトウェア開発の成果につなげることができます。