반응형
try-catch문이란?
프로그램 실행 중 발생할 수 있는 오류(예외)를 처리하기 위한 구문
예상치 못한 상황이 발생했을 때 프로그램이 강제로
종료되는 것을 방지하고, 적절한 대응을 할 수 있게 해준다
기본 구조
try
{
// 예외가 발생할 수 있는 코드
}
catch (예외종류)
{
// 예외가 발생했을 때 실행할 코드
}
finally
{
// 예외 발생 여부와 관계없이 항상 실행되는 코드
}
예외처리를 사용하는 이유
1. 프로그램의 안정성 확보
public class FileManager : MonoBehaviour
{
// 예외처리가 없는 경우
public void SaveWithoutTryCatch(string data)
{
// 파일 저장 실패시 프로그램이 강제 종료될 수 있음
File.WriteAllText("save.txt", data);
}
// 예외처리가 있는 경우
public void SaveWithTryCatch(string data)
{
try
{
File.WriteAllText("save.txt", data);
}
catch (IOException ex)
{
// 오류 발생해도 프로그램은 계속 실행
Debug.LogError("저장 실패: " + ex.Message);
ShowErrorPopup();
}
}
}
2. 오류 원인 파악과 디버깅
try
{
ProcessGameData();
}
catch (Exception ex)
{
// 스택 트레이스를 통해 오류 발생 위치와 원인을 정확히 파악
Debug.LogError($"에러 메시지: {ex.Message}");
Debug.LogError($"스택 트레이스: {ex.StackTrace}");
}
try-catch문 사용 예시
public class SaveManager : MonoBehaviour
{
public void LoadGameData()
{
try
{
// 파일을 읽으려고 시도
string data = File.ReadAllText("savedata.json");
Debug.Log("데이터 로드 성공!");
}
catch (FileNotFoundException)
{
// 파일이 없을 때
Debug.LogError("저장된 데이터를 찾을 수 없습니다.");
}
catch (IOException ex)
{
// 파일을 읽는 도중 오류 발생
Debug.LogError($"파일 읽기 오류: {ex.Message}");
}
finally
{
// 성공하든 실패하든 실행됨
Debug.Log("로드 시도 완료");
}
}
}
예외 처리시 if문, switch문을 사용 안하는 이유
1. 가독성
// ==== if문 사용 ====
public void LoadGameData()
{
if (!File.Exists("save.txt"))
{
Debug.LogError("파일이 없습니다.");
return;
}
string fileContent = File.ReadAllText("save.txt");
if (string.IsNullOrEmpty(fileContent))
{
Debug.LogError("파일이 비어있습니다.");
return;
}
PlayerData data = JsonUtility.FromJson<PlayerData>(fileContent);
if (data == null)
{
Debug.LogError("데이터 변환 실패");
return;
}
}
// ==== try-catch 사용 ====
public void LoadGameData()
{
try
{
string fileContent = File.ReadAllText("save.txt");
PlayerData data = JsonUtility.FromJson<PlayerData>(fileContent);
ProcessPlayerData(data);
}
catch (FileNotFoundException)
{
Debug.LogError("파일이 없습니다.");
}
catch (JsonException)
{
Debug.LogError("잘못된 데이터 형식입니다.");
}
}
2.예외상황 전파
// ==== if문 사용 - 오류를 매번 체크해야 함 ====
public bool Method1()
{
if (!ValidateData())
return false;
return true;
}
public bool Method2()
{
if (!Method1())
return false;
return true;
}
// ==== try-catch 사용 - 오류는 자동으로 상위로 전파됨 ====
public void Method1()
{
if (!ValidateData())
throw new ValidationException();
}
public void Method2()
{
try
{
Method1();
}
catch (ValidationException ex)
{
// 필요한 지점에서 한 번만 처리
HandleError(ex);
}
}
3.처리 가능한 상황의 범위
// ==== switch문 - 예상 가능한 상황만 처리 ====
switch (errorCode)
{
case 404:
HandleNotFound();
break;
case 500:
HandleServerError();
break;
default:
HandleUnknown();
break;
}
// ==== try-catch - 예상치 못한 상황도 처리 가능 ====
try
{
ConnectToServer();
}
catch (HttpRequestException ex)
{
// 모든 종류의 네트워크 오류 처리
HandleNetworkError(ex);
}
catch (Exception ex)
{
// 예상치 못한 모든 오류 처리
HandleUnexpectedError(ex);
}
예외처리는 주로 예상치 못한 오류 상황이나 외부 리소스 접근 시에 사용하고,
if문이나 switch문은 일반적인 프로그램 로직의 흐름 제어에 사용하는 것이 바람직하다
주요 예외 타입
System.Exception : 모든 예외의 기본 클래스
ArgumentException : 잘못된 인수를 메서드에 전달할 때
NullReferenceException : null 객체를 참조할 때
IndexOutOfRangeException : 배열의 범위를 벗어난 접근
FileNotFoundException : 파일을 찾을 수 없을 때
IOException : 입출력 관련 오류
FormatException : 문자열 형식이 잘못되었을 때
실제 활용
세이브 데이터 로드
public class GameDataManager : MonoBehaviour
{
[Serializable]
public class PlayerData
{
public int level;
public float health;
}
public PlayerData LoadPlayerData()
{
string path = Application.persistentDataPath + "/playerData.json";
try
{
// 파일에서 데이터 읽기
string jsonData = File.ReadAllText(path);
// JSON을 객체로 변환
PlayerData data = JsonUtility.FromJson<PlayerData>(jsonData);
if (data == null)
throw new System.Exception("데이터 변환 실패");
return data;
}
catch (FileNotFoundException)
{
Debug.Log("저장된 데이터가 없습니다. 새 데이터를 생성합니다.");
return new PlayerData();
}
catch (Exception ex)
{
Debug.LogError($"데이터 로드 중 오류 발생: {ex.Message}");
return null;
}
}
}
리소스 로드
public class ResourceLoader : MonoBehaviour
{
public GameObject LoadPrefab(string prefabPath)
{
try
{
GameObject prefab = Resources.Load<GameObject>(prefabPath);
if (prefab == null)
throw new System.ArgumentException($"프리팹을 찾을 수 없음: {prefabPath}");
return Instantiate(prefab);
}
catch (System.ArgumentException ex)
{
Debug.LogError($"프리팹 로드 실패: {ex.Message}");
return null;
}
catch (Exception ex)
{
Debug.LogError($"알 수 없는 오류: {ex.Message}");
return null;
}
}
}
파일 저장 시스템
public class SaveSystem : MonoBehaviour
{
public void SaveGame()
{
string savePath = Application.persistentDataPath + "/save.json";
try
{
// 1. 데이터 준비
GameData data = PrepareGameData();
if (data == null)
{
Debug.LogError("데이터 준비 실패");
return; // 예외를 발생시키지 않고 일반적인 흐름 제어
}
// 2. 파일 저장 (예외가 발생할 수 있는 작업)
string jsonData = JsonUtility.ToJson(data);
File.WriteAllText(savePath, jsonData);
}
catch (UnauthorizedAccessException ex)
{
// 구체적인 예외 처리
Debug.LogError("저장 권한이 없습니다: " + ex.Message);
ShowErrorToUser("저장 권한이 없습니다. 관리자 권한으로 실행해주세요.");
}
catch (IOException ex)
{
// 더 일반적인 입출력 예외 처리
Debug.LogError("저장 실패: " + ex.Message);
ShowErrorToUser("게임 저장에 실패했습니다. 디스크 공간을 확인해주세요.");
}
catch (Exception ex)
{
// 가장 일반적인 예외 처리
Debug.LogError("예상치 못한 오류: " + ex.Message);
ShowErrorToUser("알 수 없는 오류가 발생했습니다.");
}
}
private void ShowErrorToUser(string message)
{
// UI를 통해 사용자에게 오류 메시지 표시
}
private GameData PrepareGameData()
{
// 게임 데이터 준비 로직
return null;
}
}
주의 사항
1. 예외는 더 구체적인 것부터 처리해야 한다
public void LoadGameData()
{
try
{
string data = File.ReadAllText("save.json");
ProcessData(data);
}
// ==== 잘못된 순서 ====
catch (Exception ex) // 모든 예외를 잡아버림
{
Debug.Log("오류 발생");
}
catch (FileNotFoundException ex) // 절대 실행되지 않음!
{
Debug.Log("파일을 찾을 수 없음");
}
// ==== 올바른 순서 ====
catch (FileNotFoundException ex) // 구체적인 예외 먼저
{
Debug.Log("파일을 찾을 수 없음");
}
catch (Exception ex) // 일반적인 예외는 나중에
{
Debug.Log("오류 발생");
}
}
2. 성능 고려
public class ItemChecker
{
// ==== 나쁜 예: 예외처리를 통한 로직 처리 ====
public bool ContainsItem_Bad(List<string> inventory, string itemName)
{
try
{
// First()는 찾지 못하면 예외를 발생시킴
inventory.First(item => item == itemName);
return true;
}
catch
{
return false; // 예외가 발생하면 false 반환
}
}
// ==== 좋은 예: 일반적인 조건문 사용 ====
public bool ContainsItem_Good(List<string> inventory, string itemName)
{
// Any()는 조건에 맞는 항목이 있는지 확인만 하고 예외를 발생시키지 않음
return inventory.Any(item => item == itemName);
}
}
예외를 발생시키고 잡는 과정이 필요해서 성능이 좋지 않기에
프로그램 로직 흐름 제어는 일반적인 조건문을 사용하는 것이 좋다
3. 예외 다시 던지기(Re-throwing)
public class DataManager : MonoBehaviour
{
// ==== 나쁜 예: 스택 트레이스 정보가 손실됨 ====
public void ProcessData_Bad()
{
try
{
LoadData();
}
catch (Exception ex)
{
Debug.Log("오류 발생");
throw ex; // 새로운 예외를 생성하여 던짐
}
}
// ==== 좋은 예: 원래 예외의 정보가 유지됨 ====
public void ProcessData_Good()
{
try
{
LoadData();
}
catch (Exception ex)
{
Debug.Log("오류 발생");
throw; // 원래 예외를 그대로 다시 던짐
}
}
private void LoadData()
{
// 데이터 로드 로직...
}
}
반응형
'유니티 > 문법' 카테고리의 다른 글
[Unity] 유니티C#) Struct ( 구조체 ) (1) | 2025.01.17 |
---|---|
[Unity] 유니티 C#) Casting ( 형변환 ) (0) | 2025.01.16 |
[Unity] 유니티 C#) LINQ ( Language Integrated Query ) (1) | 2025.01.14 |
[Unity] 유니티 C#) 전처리문 (Preprocessor Directives) (0) | 2025.01.13 |
[Unity] 유니티 C#) Event ( 이벤트 ), Action ( 액션 ) (0) | 2025.01.08 |