Try-Catchは、潜在的に危険なコードを保護されたブロックで実行し、プログラムをクラッシュさせることなくエラーを適切に処理できるようにする例外キャッチ構造です。tryブロックには例外をスローする可能性のあるコードが含まれ、catchはそれをキャッチして復旧ロジックを実行します。Apple Swift Documentation (2026)によると、finallyブロックは例外がスローされたかどうかに関係なく実行され、リソースの解放を保証します。
重要なポイント
Try-Catchは、ほとんどの最新プログラミング言語に存在する基本的な構造化例外処理構造です。3つのブロックで構成されます:try(危険なコードの実行試行)、catch(例外のキャッチと処理)、オプションのfinally(終了処理)。この構造のアイデアは、ビジネスロジックをエラー処理ロジックから分離し、コードをより読みやすく予測可能にすることです。
この概念はC++でtry/catchとして最初に実装され、その後Java、C#、Swift、Kotlin、Dart、Python、JavaScriptなどの言語に採用されました。各言語は独自の機能を追加しています:Swiftではcatchブロックは網羅的でなければならず、Kotlinではtry-catchは式になり得、Dartではストリームリソースにfinallyが必須です。違いはあるものの、基本原則は同じです:エラーはグローバルではなく、発生箇所にできるだけ近い場所で処理されます。
Try-Catchの使用はモバイル開発において特に重要です。ネットワーク損失、サーバーの誤った応答、メモリ不足などの外部要因が常に発生するからです。適切な例外処理はアプリケーションのクラッシュを防ぎ、正しいUXを保証します。ユーザーは突然のアプリ終了ではなくエラーメッセージを目にします。Google Android Kotlin Style Guide (2026)によると、例外をスローする可能性のあるすべての関数は、try-catchで処理するか、シグネチャでthrowsを宣言する必要があります。
try-catchの実行メカニズムはスタックアンワインディング(stack unwinding)に基づいています。tryブロック内でthrow演算子(またはシステムエラーの結果)によって例外がスローされると、通常の実行フローは直ちに中断されます。実行は適切なcatchブロックを探してコールスタックを1レベル上に移動します。最新の言語では、型マッチングメカニズム(type matching)を使用して、スローされた例外の型に一致するcatchを検索します。
一致するcatchが見つかれば、その本体が実行され、その後try-catch-finally構造全体の後に実行が継続されます。catchが見つからない場合、例外はスタックをさらに上昇し、より高いレベルで処理される可能性があります — モバイルアプリケーションではグローバルハンドラがユーザーにエラーダイアログを表示します。例外がどこでも処理されない場合、アプリケーションはクラッシュします。したがって、すべての可能な例外タイプの適切な処理はアプリケーションの安定性にとって極めて重要です。
fun readUserData(): User {
return try {
val response = api.fetchUser()
parseUser(response)
} catch (e: IOException) {
logError("ネットワークエラー", e)
throw AppException("データの読み込みに失敗しました")
} catch (e: JsonParseException) {
logError("解析エラー", e)
return User.default()
} finally {
closeLoadingIndicator()
}
}
コードはまずAPIリクエストを実行し、レスポンスを解析しようとします。IOException(ネットワーク問題)が発生した場合、例外はログに記録され、AppExceptionとして再スローされます。JsonParseExceptionが発生した場合 — デフォルトユーザーが返されます。finallyブロックはローディングインジケータを非表示にし、画面上のUIコンポーネントのリークを防ぎます。
Swiftでは、エラー処理はErrorプロトコル(以前はErrorType)を通じて実装されます。Errorに準拠する任意の型は、throw演算子によってスローできます。エラーをスローする可能性のある関数は、シグネチャにthrowsキーワードがマークされます。そのような関数の呼び出しには、tryプレフィックス(明示的なtry-catch用)、try?(オプショナル結果)、またはtry!(エラー処理なしの強制実行)が必要です。
enum NetworkError: Error {
case noConnection
case serverError(code: Int)
case timeout
}
func fetchUser(id: Int) throws -> User {
guard isConnected() else {
throw NetworkError.noConnection
}
let data = try performRequest(path: "/users/\(id)")
return try decodeUser(from: data)
}
do {
let user = try fetchUser(id: 42)
updateUI(user)
} catch NetworkError.noConnection {
showOfflineAlert()
} catch let error as NetworkError {
showError("ネットワーク " + error.localizedDescription)
} catch {
showGenericError()
}
enum NetworkErrorはErrorプロトコルを実装し、3つのケースを定義します:noConnection、コード付きのserverError、timeout。fetchUser関数はthrowsでマークされています:最初に接続を確認し、次にリクエストと解析を実行します。do-catchブロックでは、3つのcatchが異なるシナリオを処理します:特定のnoConnectionケース、一般的なNetworkError型、その他すべてのエラー。これにより、問題の種類に応じてユーザーに異なるメッセージを表示できます。
KotlinはJavaからtry-catch-finallyを継承しましたが、重要な違いを追加しました:Kotlinではtry-catchは式(expression)であり、文(statement)ではありません。つまり、tryブロックまたはcatchブロックの結果を変数に代入できます。tryブロックの最後の式が成功時の結果になり、catchの最後の式がエラー時の結果になります。エラーがいずれのcatchでも処理されない場合、例外はスタックを上昇します。
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
}
fun loadData(): Result<List<Item>> {
return try {
val response = api.getItems()
Result.Success(response.toList())
} catch (e: HttpException) {
Log.e("HTTP ", e)
Result.Error(e)
} catch (e: IOException) {
Log.e("ネットワーク ", e)
Result.Error(e)
}
}
この例では、sealed class Resultが成功レスポンスまたはエラーをラップします。loadData関数はtry-catchを式として使用します:成功時はResult.Successを返し、HttpExceptionまたはIOException時はログ付きのResult.Errorを返します。このアプローチにより、呼び出し元は例外なしでエラーを処理できます — Result型に対するwhen式を使用します。これはJetpack ComposeでStateFlowとcollectAsStateを通じて異なるUI状態(Loading、Success、Error)を表示するのに特に便利です。
DartはJavaと同様の構文でtry-catch-finallyをサポートしますが、変数を指定せずに例外タイプでフィルタリングするためのon句が追加されています。これは例外自体が必要ない場合に便利です — その型の事実だけが重要です。Dartは2つのパラメータを持つcatchブロックもサポートします:例外オブジェクトとStackTraceで、完全な呼び出しチェーンの記録に役立ちます。
import 'dart:io';
import 'dart:convert';
class UserRepository {
Future<User> fetchUser(String id) async {
try {
final client = HttpClient();
final request = await client.getUrl(
Uri.parse('https://api.example.com/users/$id')
);
final response = await request.close();
final body = await response.transform(utf8.decoder).join();
return User.fromJson(json.decode(body));
} on SocketException catch (e, stackTrace) {
log("No internet", e, stackTrace);
throw AppException("Connection failed");
} on FormatException {
throw AppException("Invalid response format");
} finally {
client.close();
}
}
}
SocketExceptionは詳細なログ記録のために例外オブジェクトとStackTraceの両方でキャッチされ、その後AppExceptionとして再スローされます。FormatExceptionは変数なしでキャッチされます — レスポンス形式が不正であることを知れば十分です。finallyブロックはHttpClientを閉じることを保証し、ソケットリークを防ぎます。Flutterでは、このアプローチはWidgetテストにとって特に重要です。State.initStateで未処理の例外が発生すると、テストセッション全体がクラッシュするからです。
経験豊富な開発者でも、try-catchを使用する際にメモリリーク、隠れたバグ、アプリケーションの不適切な動作を引き起こす間違いを犯します。モバイル開発における最も一般的な5つの問題を見てみましょう。
空のcatchは最悪のプラクティスの1つです。例外が飲み込まれ、アプリケーションは不正な状態で動作を続け、開発者は問題を知ることができません。常に少なくとも例外をログに記録してください。Kotlinではcatch(e: Exception) { Log.e(...) }、Swiftではcatch { print($0) }を使用します。Dartでは、最低限許容されるcatchはdebugPrintを呼び出すか、Crashlyticsに書き込む必要があります。
型を区別せずにcatch (Exception e)ですべての例外をキャッチすると、予期しないエラー(NullPointerException、OutOfMemoryError、StackOverflowError)が隠れてしまいます。予想して処理できる型のみをキャッチしてください。それ以外は上位への伝播を許可します。モバイル開発では、IOException、TimeoutException、AuthExceptionに対する特定のcatchがユーザーにより意味のあるメッセージを提供します。
リソース(ファイル、ソケット、DBカーソル、アニメーション)はfinallyまたはuseブロック(AutoCloseable)で解放する必要があります。開発者は例外発生時にリソースを閉じるのを忘れがちで、リークの原因になります。KotlinではCloseableリソースに.use { }、Swiftではdefer { }、Dartではasyncパッケージのawait usingを使用します。finallyブロックはcatch内で例外がスローされた場合でも解放を保証します。
非同期コードでは、try-catchは他のスレッドからの例外をキャッチしません。Kotlin CoroutinesではCoroutineExceptionHandlerまたはSupervisorJobを使用します。Swift async/awaitではTask内のdo-catchを使用します。FlutterではグローバルキャッチにrunZonedGuardedを使用します。このルールを無視すると、本番環境で再現が困難なクラッシュの原因になります。
例外処理はユーザーインターフェースを無期限にブロックしてはいけません。ユーザーに具体的なメッセージを表示し、操作を再試行する機会を提供してください。Kotlin/ComposeのRetryボタン付きSnackbar、SwiftのUIAlertController、FlutterのSnackBar — ネットワークエラーやサーバーエラーに対する最小限のUXです。回復オプションのない一般的な“エラーが発生しました”ダイアログは避けてください。
よくある質問
Try-catchは例外とスタックアンワインディングを使用してエラーを処理しますが、多数のエラーを扱う場合にパフォーマンスが低下する可能性があります。Result Typeはコンテナ型(SuccessまたはFailure)で、スタックアンワインディングなしでパターンマッチングによって処理され、予期されるエラーに対してより効率的です。
Finallyは、tryブロックが閉じる必要のあるリソース(ファイル、ソケット、カーソル)を開く場合に必須です。リソースを開かない場合、finallyは不要です。最新の言語では、finallyなしで自動リソースクローズを行うためにAutoCloseable/use/deferを使用します。KotlinとSwiftのuseブロックは、Closeableオブジェクトに対してfinallyを置き換えます。
通常のフロー(例外なし)では、try-catchは実質的にパフォーマンスに影響しません — JVMとSwiftコンパイラがこのケースを最適化します。ただし、例外がスローされるとスタックアンワインディングが発生し、スタックの深さに応じて10–100 μsかかる可能性があります。フロー制御に例外を使用しないでください — これはアンチパターンです。
コルーチンでは、グローバルキャッチにcoroutineScope内のtry-catchまたはCoroutineExceptionHandlerを使用します。SupervisorJobは子コルーチンが失敗したときに親コルーチンがキャンセルされるのを防ぎます。launchにはCoroutineExceptionHandler、asyncにはawait()の周囲にtry-catchを使用します。
複数のcatchが推奨されます:コードが直線的に読み取れ、各ブロックが1つの例外タイプを処理します。if-elseの1つのcatchは保守が難しく、新しい例外タイプを見逃しやすいです。Swiftでは、enum Errorの網羅的な処理に複数のcatchが必須です。Kotlinに制限はありませんが、ベストプラクティスはタイプごとに個別のcatchです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。