スケルトンコード(skeleton code)は、関数やクラスの宣言だけを先に書き、中身の処理を未実装のまま残した「骨組み」のコードです。スケルトン実装、コードスケルトンとも呼ばれ、日本語では「骨組みのコード」とも言います。フレームワークが最初に生成するプロジェクトの雛形を指して使うこともあります。この記事では、Python・Java・C#・TypeScript・Go・Rustで書いたスケルトンを実際に実行したときのエラーと、OpenAPI Generator 7.26.0やSymfonyが生成するスケルトンの中身を示し、スタブやボイラープレートとの違い、未実装のまま出荷しないための書き方を整理します。
まとめ:スケルトンコードの意味と書き方の要点
- スケルトンコードは、関数名・引数・戻り値・クラス構成といった「形」だけを決め、処理を後から書く前提のコードです。
- もう1つの意味は、Laravelの
laravel/laravelやSymfonyのsymfony/skeletonのような、プロジェクト全体の雛形です。関数1つ分の形を決めるか、アプリケーション全体の最小構成を用意するかという範囲の違いで見分けます。 - 中身は空にせず、呼ばれたら例外で止まる形にします(Pythonは
raise NotImplementedError、C#はNotImplementedException、KotlinはTODO()、Rustはtodo!())。passや空のreturnは呼ばれても黙って通るため、未実装に気づけません。 - テスト用の偽物であるスタブ、毎回書く定型コードであるボイラープレートとは目的が違います。スケルトンは「本物の実装が入る場所」です。
- OpenAPI Generatorやprotoc-gen-go-grpcは、API定義からスケルトンを自動生成します。OpenAPI Generatorの生成ファイルに実装を書くなら、再生成で上書きされないよう先に除外設定を入れます。gRPCは生成された型を自分の実装に埋め込む方式なので、生成ファイル自体は編集しません。
スケルトンコードの意味:IT用語としての「骨組み」
skeletonは英語で骨格のことです。英語版Wikipediaは skeleton programming を「simple high-level program structures and so called dummy code」に基づくプログラミングの書き方と説明しています。日本語のIT用語辞典(e-Words)も「主要な制御構造や関数などの宣言や呼び出し」「プログラムの大枠のみを記述し、詳細な処理の記述を省いたもの」と定義しており、骨組みという意味は共通しています。
関数名・引数と戻り値の型・クラスの分け方は決まっていて、決まっていないのは処理の中身だけです。そのため静的型付き言語でもコンパイルは通り、呼び出す側のコードを先に書き始められます。
「スケルトン実装」「コードスケルトン」との呼び分け
「コードスケルトン」「スケルトンプログラム」は同じものを指します。「スケルトン実装」も多くは同じ意味で使われ、開発現場では「まずスケルトンだけ切ってPRを出す」のように、骨組みだけを先にレビューに出す場面で使われます。UMLツールのastahのマニュアルでも、クラス図からJava・C#・C++のコードを出力する機能を「スケルトンコードの作成」と呼んでいます。
ただしJavaの「スケルトン実装」には逆の意味もあります。Java SEのAPIドキュメント(日本語版)は、AbstractListを「Listインタフェースのスケルトン実装を提供」するクラスと説明しています。こちらはインタフェースの大半を実装済みの抽象クラス(英語ではskeletal implementation)で、変更不可能なリストなら get(int) と size() を書くだけで動きます。中身が未実装の骨組みではなく、共通部分が実装済みの土台を指す用法です。
プロジェクトの雛形を指す「スケルトン」
もう1つの意味は、新しいプログラムを作るときに最初に置く共通の骨組みです。通信用語の基礎知識(WDIC)は、スケルトンコードを「ある種のプログラムに共通するコード」の「骨組みとなる共通コードだけを取り出しておいたもの」と説明し、Visual C++などの開発環境が目的に応じたスケルトンコードを用意している例を挙げています。C言語の main 関数だけを書いたファイルのような、毎回同じになる書き出しがこれにあたります。
この意味の言葉は、今のフレームワークのパッケージ名に残っています。Laravelの laravel/laravel はPackagistの説明文が「The skeleton application for the Laravel framework.」で、Symfonyは公式のセットアップ手順で composer create-project symfony/skeleton を案内しています。2026年10月15日にComposer 2.10.3とPHP 8.5.8でSymfonyのスケルトンを作成し、PHPの組み込みサーバーで起動しました。
composer create-project symfony/skeleton:"8.1.*" sym
php sym/bin/console --version
# Symfony v8.1.9 (env: dev, debug: true)
# 組み込みサーバーは前面で動き続けるので、curlは別のターミナルで実行する
php -S 127.0.0.1:18931 -t sym/public
# 別のターミナルで
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:18931/
# 404(本文は「Welcome to Symfony!」の案内ページ)
生成されたのは src/Kernel.php、config/ 配下の設定、public/index.php、AIエージェント向けの AGENTS.md などで、src/Controller/ は空でした。ルートが1本も無いので / は404を返しますが、アプリケーション自体は起動しています。Rustの cargo new(cargo 1.98.0)と .NET の dotnet new console(SDK 10.0.401)も同じで、生成直後の main.rs は「Hello, world!」、Program.cs は「Hello, World!」を出力して終わりました。
ここで生成した3つのプロジェクトは、生成した時点で起動できました。次章以降で扱うのは関数1つ分の骨組みで、この記事では呼ばれたら例外やpanicで止まる書き方を勧めます。会話でどちらの意味かが曖昧なときは、「関数の形を決める話か」「アプリケーション全体の最小構成の話か」を確かめます。
分散オブジェクトの「スケルトン」は別の意味
「スケルトン it」で調べると、Java RMIやCORBAの文脈の「スタブとスケルトン」が出てきます。こちらはコードの骨組みではありません。Java RMIの仕様は、スケルトンを「a server-side entity that dispatches calls to the actual remote object implementation」と定義しています。つまりクライアント側のスタブから届いた呼び出しを、サーバー上の実際のオブジェクトへ振り分ける受け口のことです。
この意味のスケルトンは、Java 2 SDK 1.2で導入されたスタブのプロトコルにより、Java同士の環境では不要になりました。スタブとスケルトンを生成していた rmic コマンドもJDK 13で削除予定として非推奨になり、JDK 15で削除されています。いま「スケルトンコード」と言えば、上の2つの骨組みの意味です。
スタブ・ボイラープレート・ウォーキングスケルトンとの違い
スケルトンコードと混同されやすい用語は、目的で見分けます。
| 用語 | 目的 | 最終的に残るか |
|---|---|---|
| スケルトンコード | 本実装の形を先に決める | 中身を書いて本実装になる |
| スタブ(テスト用) | 依存先の代わりに決まった値を返す | テストコードにだけ残る |
| ボイラープレート | 毎回同じになる定型の記述 | そのまま残る |
| テンプレート・雛形 | 起動できる最小構成から始める | 書き換えて残る |
| サンプルコード | 使い方を見せる | 動く状態で配布される |
| 擬似コード | 処理の流れを説明する | コンパイルできず残らない |
| ウォーキングスケルトン | 端から端まで最小機能を通す | 肉付けして本番になる |
前節のとおり、雛形もスケルトンと呼ばれます。この表のスケルトンコードは関数単位の意味で、雛形との違いは骨組みを用意する範囲と目的です。スケルトンは関数やクラスの形を先に決めるためのもので、雛形はアプリケーション全体を起動できる状態から書き始めるためのものです。
いちばん紛らわしいのはスタブです。英語版Wikipediaも、オブジェクト指向でのスケルトンのダミー部分は「abstract method, a method stub or a mock object」にあたると書いており、言葉としては重なります。実務での線引きは「その関数が本番で呼ばれる予定か」です。テストのためだけに固定値を返す偽物はスタブ(テストダブル)で、詳しくはモックとスタブの違いとテストダブル5分類で扱っています。中身が後で入る本物の関数はスケルトンです。
ウォーキングスケルトン(Walking Skeleton)は、Alistair Cockburnが「a tiny implementation of the system that performs a small end-to-end function」と定義した考え方です。関数単位の空実装ではなく、画面からDBまで細い1本の機能を実際に動かす点が違います。スケルトンコードが「中身の無い骨」なら、ウォーキングスケルトンは「歩ける最小の骨格」です。
言語別のスケルトンの書き方と実行結果
中身の空け方は言語ごとに定番があります。2026年10月7日に手元で実行した結果をまとめます(Kotlinは実行環境を用意していないため、標準ライブラリのソースにある既定メッセージを載せています)。どれも呼ばれたら例外かpanicで処理を中断し、未実装であることを知らせる書き方です。ただし呼び出し側がcatchやrecoverで握りつぶせば先に進めてしまうので、テストで未実装の箇所を通すことと組み合わせます。
| 言語(実行環境) | 未実装の書き方 | 実行時の表示 |
|---|---|---|
| Python 3.14.6 | raise NotImplementedError |
NotImplementedError |
| Java(OpenJDK 25) | throw new UnsupportedOperationException() |
UnsupportedOperationException |
| C#(.NET SDK 10.0.401) | throw new NotImplementedException() |
The method or operation is not implemented. |
| TypeScript(Node v26.5.0) | throw new Error("Not implemented") |
Not implemented |
| Go 1.26.5 | panic("not implemented") |
panic: not implemented |
| Kotlin(stdlib) | TODO() |
An operation is not implemented. |
| Rust 1.98.0 | todo!() |
not yet implemented |
Python:NotImplementedErrorと抽象基底クラス
Pythonの公式ドキュメントは、NotImplementedErrorを「while the class is being developed to indicate that the real implementation still needs to be added」の場面で送出する例外としています。スケルトンのための例外だと公式に書いてあるわけです。インターフェースにあたる基底クラスは abc.ABC と @abstractmethod で作り、本体は ... にします。
from abc import ABC, abstractmethod
class PaymentGateway(ABC):
@abstractmethod
def charge(self, amount: int) -> str: ...
class StripeGateway(PaymentGateway):
def charge(self, amount: int) -> str:
raise NotImplementedError("StripeGateway.charge")
def refund(order_id: str) -> None:
pass # TODO
print(refund("A1")) # None と表示され、そのまま通る
StripeGateway().charge(100) # NotImplementedError: StripeGateway.charge
Python 3.14.6で実行すると、refund() は何の警告も無く None を返し、charge() だけが NotImplementedError: StripeGateway.charge で止まりました。抽象クラスを直接インスタンス化した場合は Can't instantiate abstract class PaymentGateway without an implementation for abstract method 'charge' という TypeError になります(Python 3.9.6では文言が「with abstract method charge」と短い)。
JavaとC#:UnsupportedOperationExceptionとNotImplementedException
Javaの標準ライブラリには「未実装」専用の例外がありません。慣習として UnsupportedOperationException を投げ、メッセージにTODOと書いて未実装だと分かるようにします。
public class Main {
interface Repository { String find(int id); }
static class UserRepository implements Repository {
@Override public String find(int id) {
throw new UnsupportedOperationException("TODO: UserRepository.find");
}
}
public static void main(String[] a) { new UserRepository().find(1); }
}
OpenJDK 25.0.4.1で java Main.java を実行すると、java.lang.UnsupportedOperationException: TODO: UserRepository.find と行番号付きのスタックトレースが出ました。ただし、この例外のJavadocは「Thrown to indicate that the requested operation is not supported.」です。本来は「対応していない操作」を表す例外なので、メッセージを空にすると、未実装なのか意図的な非対応なのか区別できなくなります。
C#には専用の NotImplementedException があります。.NET SDK 10.0.401で式形式のメソッド本体に => throw new NotImplementedException(); と書いて呼ぶと、The method or operation is not implemented. で停止しました。
TypeScriptとGo:Errorの送出とpanic
TypeScriptも専用の例外を持たないので、Error にメッセージを付けて投げます。非同期メソッドでは、呼び出し側でrejectされたPromiseとして受け取ることになります。
interface Mailer { send(to: string, body: string): Promise<void>; }
class SmtpMailer implements Mailer {
async send(to: string, body: string): Promise<void> {
throw new Error("Not implemented: SmtpMailer.send");
}
}
new SmtpMailer().send("[email protected]", "hi").catch(e => console.log(e.message));
// Not implemented: SmtpMailer.send
Goは panic("not implemented") が定番です。エラーを返す設計の関数では、return "", errors.New("not implemented") とエラー値で返す書き方もあります。エラー値は呼び出し側が無視すると素通りします。panicも recover で回復できますが、回復しない限りプログラムはその場で止まるので、未実装の目印としてはpanicのほうが見落としにくい書き方です。
KotlinとRust:TODO()とtodo!/unimplemented!
KotlinとRustは、未実装を表す仕組みを言語の標準ライブラリに持っています。Kotlinの TODO() は戻り値の型が Nothing で、常に NotImplementedError を投げます。標準ライブラリのソースでは、既定のメッセージが An operation is not implemented.、理由を渡すと An operation is not implemented: 理由 になります。戻り値の型に関係なく書けるので、fun total(): Int = TODO("集計処理") の1行でコンパイルが通ります。
Rustには todo!() と unimplemented!() の2つがあり、公式ドキュメントは違いを「todo! conveys an intent of implementing the functionality later」と説明しています。rustc 1.98.0で実行した表示は、todo!() が not yet implemented、unimplemented!() が not implemented でした。後で書くつもりの骨組みには todo!() を使います。
fn parse(_input: &str) -> Vec<String> {
todo!() // 実行すると panicked at ... not yet implemented
}
ツールでスケルトンを生成する方法
APIやクラスの定義が先にあるなら、スケルトンは手で書くより生成したほうが正確です。
OpenAPI Generator 7.26.0でのサーバースタブ生成
OpenAPI Generatorは、OpenAPIの定義ファイルからサーバー側のスケルトン(公式の呼び方はサーバースタブ)とクライアントを生成するツールです。2026年10月15日時点でもMaven Centralの最新版は7.26.0で、-g でジェネレーター名を指定します。GET /todos/{id} だけを持つ最小の定義から、Go向けを生成しました。
次の内容を api.yaml として保存します。
openapi: 3.0.3
info: {title: Todo API, version: 1.0.0}
paths:
/todos/{id}:
get:
operationId: getTodo
parameters:
- {name: id, in: path, required: true, schema: {type: integer}}
responses:
'200':
description: ok
content:
application/json:
schema: {$ref: '#/components/schemas/Todo'}
components:
schemas:
Todo:
type: object
properties: {id: {type: integer}, title: {type: string}}
CLIのJARはMaven Centralの org.openapitools:openapi-generator-cli から入手し、次のコマンドで生成します。生成先には go.sum が無いので、go mod tidy で依存(gorilla/mux)を取得してから go run . を実行すると、ポート8080で起動します。
java -jar openapi-generator-cli-7.26.0.jar generate -i api.yaml -g go-server -o gosrv
生成された go/api_default_service.go の中身が、スケルトンそのものです。
func (s *DefaultAPIService) GetTodo(ctx context.Context, id int32) (ImplResponse, error) {
// TODO - update GetTodo with the required logic for this service method.
// Add api_default_service.go to the .openapi-generator-ignore to avoid overwriting this service implementation when updating open api generation.
// TODO: Uncomment the next line to return response Response(200, Todo{}) or use other options such as http.Ok ...
// return Response(200, Todo{}), nil
return Response(http.StatusNotImplemented, nil), errors.New("GetTodo method not implemented")
}
このままビルドして起動し、curl -i http://localhost:8080/todos/1 を送ると、HTTP/1.1 501 Not Implemented と本文 "GetTodo method not implemented" が返りました。開発者が書くのは GetTodo の中身だけです。OpenAPI Generatorの対応言語と導入方法はOpenAPI Generatorとは|生成できるもの・仕組み・対応言語と導入方法で解説しています。API定義の書き方とSwagger UIでの確認はSwaggerのEditor・UIをDockerで動かす手順を参照してください。API定義の設計から生成したスケルトンの実装、既存システムとの接続までをまとめて任せたい場合は、API開発・システム連携の支援で定義の設計から引き受けています。
gRPCのUnimplemented埋め込み
gRPCのGo向けコード生成(protoc-gen-go-grpc)では、生成される Unimplemented<Service>Server がスケルトンの役割を持ちます。全メソッドが codes.Unimplemented を返す実装で、公式READMEは、サービスの実装は将来の互換性のためにこれを埋め込む必要があると説明しています。埋め込んでおけば、protoにメソッドが増えてもビルドは通り、未実装のメソッドはクライアントに Unimplemented のステータスで返ります。生成手順はgRPC Goの実装とprotoc-gen-go-grpcの生成手順で扱っています。
UMLツール・IDEからの生成
astahはクラス図からJava・C#・C++のスケルトンコードを出力できます。クラスや操作の定義はドキュメンテーションコメントとして出力され、上書き出力ではメソッドのボディブロックを残します。OpenAPI Generatorと違い、再生成で実装が消えない設計です。クラス図の書き方はクラス図の書き方と関連・多重度にまとめています。IDEの「インターフェースを実装」「オーバーライド」機能も、1クラス分のスケルトンを作る生成機能の一種です。ただしIntelliJ IDEAの既定テンプレート(Implemented Method Body)は return $DEFAULT_RETURN_VALUE; で、nullや0を返して黙って通ります。生成後に例外を投げる形へ書き換えるか、テンプレート自体を変えておきます。
スケルトンコードが役立つ場面
関数シグネチャの設計レビューと作業分担
スケルトンは、処理を書く前にインターフェースを固めたいときに使います。関数の分け方や引数の型を骨組みの段階でレビューすれば、指摘で直すのは数行です。実装後に責務の分け方を直すと、テストと呼び出し元まで書き換えが広がります。複数人で分担するときは、スケルトンが決めたシグネチャが担当者間の約束になり、前述のgRPCの Unimplemented<Service>Server を埋め込んでおけば、担当者の実装がそろう前でもサーバー全体のビルドは通ります。インターフェースで約束を表す考え方はインターフェースの意味と種類で詳しく扱っています。
オブジェクト指向では、処理の順序だけを親クラスに書き、各ステップを子クラスで実装させるテンプレートメソッドパターンも、スケルトンの考え方そのものです。英語版Wikipediaも、スケルトンプログラムの使い道としてこのパターンを挙げています(テンプレートメソッドパターンの構造と実装例)。
プログラミング課題の配布
プログラミング演習では、関数の宣言だけを入れたファイルを「スケルトンコード」として配り、学習者に中身を書かせる形式があります。例えば教育プラットフォームのeliceは、演習問題の作成マニュアルに「Writing Skeleton Code」という項目を設け、「Proper skeleton code makes it easier for students to code.」と書いています。配る側は関数名と引数を固定しておけば、採点用のテストを全員分そのまま流せます。
TDDの初期段階と未実装を確かめるテスト
テスト駆動開発では、テストを先に書くと、テスト対象の関数が存在せずコンパイルが通らない段階があります。ここで例外を投げるスケルトンを置くと、テストを「コンパイルは通るが失敗する状態」まで進められます。進め方はTDD(テスト駆動開発)のRed・Green・Refactorの回し方で解説しています。
スケルトンが未実装であることを確かめるテストを書いておき、実装したらそのテストを書き換えないとCIが落ちるようにする手法を、当サイトでは「スケルトンテスト」と呼んで解説しています。ここで落ちるのは「未実装のまま」のときではなく、「実装したのにテストを直していない」ときです。pytest 9.1.1なら、実装後に pytest.raises(NotImplementedError) のテストが DID NOT RAISE NotImplementedError で失敗します。書き方はスケルトンテストとは?未実装を検証するテストの書き方で詳しく解説しています。
未実装のまま出荷しないための書き方
スケルトンの事故は、未実装の関数が本番で呼ばれて初めて発覚するパターンです。
黙って成功する空実装の禁止
Pythonの pass、Javaの空の return null、Goの return "", nil は、未実装のまま呼ばれても正常終了に見えます。前述のPythonの例でも、refund() は None を返してそのまま通りました。スケルトンの中身は、呼ばれたら必ず止まる例外かpanicにしてください。pass を使ってよいのは、本当に何もしないのが正しい処理(イベントハンドラの既定動作など)だけです。
「未実装」と「非対応」の例外の使い分け
Pythonの公式ドキュメントは、NotImplementedErrorを「そもそもサポートしない操作」に使うべきではないと注記しています。.NETも、インターフェースのメンバーを意味のある形で実装できない場合は NotSupportedException を使うよう案内しており、NotImplementedException はtry/catchで処理せず呼び出し自体を消すべきエラーとしています。未実装を表す例外を握りつぶすcatchを書くと、スケルトンが残っていても気づけなくなります。なおKotlinの NotImplementedError は Exception ではなく Error の派生なので、catch (e: Exception) では捕まりません。
生成コードの再生成と残存TODOの検出
OpenAPI Generatorの生成コメントにあるとおり、実装を書いたファイルを .openapi-generator-ignore に入れないと、API定義を更新して再生成したときに実装が上書きされ、501を返すスケルトンに戻ります。再生成をCIで回している場合は特に、最初の実装コミットと同時に除外設定を入れます。なお同じファイルの先頭には // Code generated by OpenAPI Generator ... DO NOT EDIT. の行があり、Goのツールはこの行で生成ファイルと判定します。実装を書くならこの行を消すか、実装を別ファイルへ分けます。
残ったスケルトンは機械的に探せます。Rustならclippyの clippy::todo と clippy::unimplemented が検出しますが、どちらも既定では無効です。手元のclippy 0.1.98でも、オプション無しでは警告0件で、-W clippy::todo を付けて初めて「todo should not be present in production code」と出ました。CIで止めるなら cargo clippy -- -D clippy::todo -D clippy::unimplemented のように -D でエラーに格上げします。手元では todo!() を残したプロジェクトで同じ指摘がerrorとして出て、終了コードは101でした。他の言語では、リリース前のCIで NotImplementedError・NotImplementedException・TODO( と、Javaで例外メッセージに付けた "TODO: をgrepし、件数が0でなければ止めます。抽象基底クラスの正当な raise NotImplementedError は除外リストで外します。
よくある質問
スケルトンコードとは簡単に言うと何ですか?
関数やクラスの宣言だけを書き、中身の処理を後回しにした骨組みのコードです。形が決まっているので呼び出し側を先に書けますが、そのままでは目的の処理は動きません。LaravelやSymfonyが生成するプロジェクトの雛形(起動できる最小構成)を指して使うこともあります。
スケルトン実装とはどういう意味ですか?
スケルトンコードを書くこと、または書いたスケルトンそのものを指します。「スケルトン実装を先に出す」と言えば、中身を書く前に関数の分け方や型だけをレビューに出すという意味です。Javaのドキュメントでは、AbstractListのように大半を実装済みの抽象クラスを指す別の用法もあります。
スケルトンコードはコンパイルできますか?
できるように書くのが普通です。Javaなら例外を投げる1行、Kotlinなら TODO()、Rustなら todo!() を中身に置けば、戻り値の型を満たしてコンパイルが通ります。コンパイルが通る状態を保てば、呼び出し側のコードやテストを先に書き進められます。
スケルトンコードとスタブの違いは何ですか?
スケルトンは後で本物の処理が入る場所で、スタブはテストのために固定値を返す偽物です。ただしOpenAPI Generatorのように、生成されたサーバー側のスケルトンを「サーバースタブ」と呼ぶツールもあるので、文脈で判断します。
スケルトンスクリーンとは関係がありますか?
ありません。スケルトンスクリーンは、読み込み中に灰色の枠を表示する画面表現のことです。こちらはスケルトンスクリーンの実装コードと逆効果になる条件で解説しています。