Try-Catch คือโครงสร้างการดักจับข้อยกเว้นที่ช่วยให้สามารถเรียกใช้โค้ดที่อาจเป็นอันตรายในบล็อกที่ป้องกันและจัดการข้อผิดพลาดได้อย่างถูกต้องโดยไม่ทำให้โปรแกรมหยุดทำงานกะทันหัน บล็อก try มีโค้ดที่อาจโยนข้อยกเว้น catch จะดักจับมันและดำเนินการลอจิกการกู้คืน ตาม Apple Swift Documentation (2026) บล็อก finally จะทำงานไม่ว่าจะมีการโยนข้อยกเว้นหรือไม่ก็ตาม เพื่อรับประกันการปลดปล่อยทรัพยากร
ประเด็นสำคัญ
Try-Catch คือโครงสร้างการจัดการข้อยกเว้นเชิงโครงสร้างพื้นฐานที่มีอยู่ในภาษาโปรแกรมมิ่งสมัยใหม่ส่วนใหญ่ ประกอบด้วยสามบล็อก: 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 ที่เหมาะสม ภาษาสมัยใหม่ค้นหา catch ที่มีประเภทตรงกับประเภทของข้อยกเว้นที่ถูกโยนโดยใช้ กลไกการจับคู่ประเภท (type matching)
หากพบ 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 โดยกำหนดสามกรณี: noConnection, serverError พร้อมรหัส และ timeout ฟังก์ชัน fetchUser ถูกทำเครื่องหมายด้วย throws: ตรวจสอบการเชื่อมต่อก่อน จากนั้นดำเนินการตามคำขอและแยกวิเคราะห์ ในบล็อก do-catch สาม catch จัดการสถานการณ์ที่แตกต่างกัน: กรณีเฉพาะ noConnection, ประเภททั่วไป NetworkError และข้อผิดพลาดอื่นๆ ทั้งหมด ซึ่งช่วยให้แสดงข้อความที่แตกต่างกันให้ผู้ใช้เห็นตามประเภทของปัญหา
Kotlin สืบทอด try-catch-finally จาก Java แต่เพิ่มความแตกต่างที่สำคัญ: ใน 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 พร้อมการบันทึก วิธีการนี้ช่วยให้ผู้เรียกจัดการข้อผิดพลาดโดยไม่ต้องใช้ข้อยกเว้น — ผ่านนิพจน์ when บนประเภท Result ซึ่งสะดวกเป็นพิเศษใน Jetpack Compose สำหรับการแสดงสถานะ UI ที่แตกต่างกัน (Loading, Success, Error) ผ่าน StateFlow และ collectAsState
Dart รองรับ try-catch-finally ด้วยไวยากรณ์คล้ายกับ Java แต่เพิ่ม on clause สำหรับกรองตามประเภทข้อยกเว้นโดยไม่ต้องระบุตัวแปร ซึ่งสะดวกเมื่อไม่จำเป็นต้องใช้ข้อยกเว้นเอง — เพียงแค่ประเภทของมันก็เพียงพอแล้ว Dart ยังรองรับบล็อก 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 ซึ่งนำไปสู่การรั่วไหลของหน่วยความจำ บั๊กที่ซ่อนอยู่ หรือพฤติกรรมที่ไม่เหมาะสมของแอปพลิเคชัน มาดูปัญหาที่พบบ่อยที่สุดห้าประการในการพัฒนาแอปมือถือ
catch ว่างเปล่า เป็นหนึ่งในแนวปฏิบัติที่แย่ที่สุด ข้อยกเว้นถูกกลืนกิน แอปพลิเคชันยังคงทำงานในสถานะที่ไม่ถูกต้อง และนักพัฒนาไม่รู้เกี่ยวกับปัญหา อย่างน้อยควรบันทึกข้อยกเว้นเสมอ ใน Kotlin ใช้ catch(e: Exception) { Log.e(...) }, ใน Swift — catch { print($0) } ใน Dart catch ที่ยอมรับได้ขั้นต่ำควรเรียก debugPrint หรือเขียนไปยัง Crashlytics
การดักจับข้อยกเว้นทั้งหมดผ่าน catch (Exception e) โดยไม่แยกตามประเภทจะซ่อนข้อผิดพลาดที่ไม่คาดคิด — NullPointerException, OutOfMemoryError, StackOverflowError ดักจับเฉพาะประเภทที่คุณคาดหวังและสามารถจัดการได้ สำหรับสิ่งอื่นๆ ปล่อยให้แพร่กระจายขึ้นไป ในการพัฒนาแอปมือถือ catch เฉพาะสำหรับ IOException, TimeoutException, AuthException ให้ข้อความที่มีความหมายมากขึ้นแก่ผู้ใช้
ทรัพยากร — ไฟล์, ซ็อกเก็ต, เคอร์เซอร์ฐานข้อมูล, แอนิเมชัน — ควรถูกปลดปล่อยใน finally หรือใน use-block (AutoCloseable) นักพัฒนามักลืมปิดทรัพยากรเมื่อเกิดข้อยกเว้น ซึ่งนำไปสู่การรั่วไหล ใน Kotlin ใช้ .use { } สำหรับทรัพยากร Closeable, ใน Swift — defer { }, ใน Dart — await using จากแพ็คเกจ async บล็อก finally รับประกันการปลดปล่อยแม้เมื่อมีการโยนข้อยกเว้นภายใน catch
ในโค้ดอะซิงโครนัส try-catch ไม่สามารถดักจับข้อยกเว้นจากเธรดอื่นได้ ใน Kotlin Coroutines ใช้ CoroutineExceptionHandler หรือ SupervisorJob ใน Swift async/await — do-catch ภายใน Task ใน Flutter — runZonedGuarded สำหรับการดักจับระดับโลก การละเลยกฎนี้เป็นสาเหตุของความล้มเหลวที่ยากจะทำซ้ำในโปรดักชัน
การจัดการข้อยกเว้นไม่ควรบล็อกอินเตอร์เฟซผู้ใช้อย่างไม่มีกำหนด แสดงข้อความเฉพาะให้ผู้ใช้เห็นและให้โอกาสพวกเขาลองดำเนินการอีกครั้ง Snackbar พร้อมปุ่ม Retry ใน Kotlin/Compose, UIAlertController พร้อม action ใน Swift, SnackBar พร้อม action ใน Flutter — UX ที่เพียงพอขั้นต่ำสำหรับข้อผิดพลาดเครือข่ายหรือเซิร์ฟเวอร์ หลีกเลี่ยงไดอะล็อกทั่วไป “เกิดข้อผิดพลาด” โดยไม่มีตัวเลือกการกู้คืน
คำถามที่พบบ่อย
Try-catch ใช้ข้อยกเว้นและการคลายสแต็กสำหรับการจัดการข้อผิดพลาด ซึ่งอาจมีต้นทุนด้านประสิทธิภาพสูงเมื่อมีข้อผิดพลาดจำนวนมาก Result Type เป็นประเภทคอนเทนเนอร์ (Success หรือ Failure) ที่จัดการผ่านการจับคู่รูปแบบ (pattern matching) โดยไม่ต้องคลายสแต็ก ซึ่งมีประสิทธิภาพมากกว่าสำหรับข้อผิดพลาดที่คาดการณ์ได้
Finally จำเป็นถ้าบล็อก try เปิดทรัพยากร (ไฟล์, ซ็อกเก็ต, เคอร์เซอร์) ที่ต้องปิด หากไม่มีการเปิดทรัพยากร finally ไม่จำเป็น ในภาษาสมัยใหม่ ใช้ AutoCloseable/use/defer สำหรับการปิดทรัพยากรอัตโนมัติโดยไม่ต้องใช้ finally use-block ใน Kotlin และ Swift แทนที่ finally สำหรับออบเจกต์ Closeable
ในการไหลปกติ (ไม่มีข้อยกเว้น) try-catch แทบไม่มีผลกระทบต่อประสิทธิภาพ — JVM และคอมไพเลอร์ Swift ปรับกรณีนี้ให้เหมาะสม แต่เมื่อมีการโยนข้อยกเว้น การคลายสแต็กจะเกิดขึ้น ซึ่งอาจใช้เวลา 10–100 μs ขึ้นอยู่กับความลึกของสแต็ก อย่าใช้ข้อยกเว้นสำหรับการควบคุมการไหล — นี่เป็นรูปแบบที่ไม่ดี
ในคอรูน ใช้ try-catch ภายใน coroutineScope หรือ CoroutineExceptionHandler สำหรับการดักจับระดับโลก SupervisorJob ป้องกันการยกเลิกคอรูนแม่เมื่อคอรูนลูกล้มเหลว สำหรับ launch ใช้ CoroutineExceptionHandler สำหรับ async — try-catch รอบ await()
หลาย catch ดีกว่า: โค้ดอ่านเป็นเส้นตรง แต่ละบล็อกจัดการข้อยกเว้นหนึ่งประเภท catch เดียวกับ if-else ดูแลรักษายากกว่าและพลาดข้อยกเว้นประเภทใหม่ได้ง่าย ใน Swift หลาย catch เป็นข้อบังคับสำหรับการจัดการ enum Error ที่ครอบคลุม ใน Kotlin ไม่มีข้อจำกัด แต่แนวปฏิบัติที่ดีที่สุดคือ catch แยกตามประเภท
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม