네트워크 요청 응답 상황
- 성공
- 실패(네트워크 연결 문제, 타임아웃, 요청 데이터 문제 등등)
try-catch 문
- 기본적인 예외 처리이다
- 런타임 에러뿐만 아니라 논리적인 오류나 예외 상황에 대한 처리를 하기에는 부족하다
Result 클래스
- Result 클래스는 성공시에는 데이터를, 실패시에는 에러 정보(예: Exception, String)를 담는 객체를 정의한다
- sealed 클래스는 타입 봉인 효과를 가진다 (enum 하고 비슷한 효과 + 다른 객체의 참조를 가질 수 있다)
사용법
Future<Result<List<Photo>>> fetch(String query);
기본 Result
sealed class Result<T> {
factory Result.success(T data) = Success;
factory Result.error(Exception e) = Error;
}
class Success<T> extends Result<T> {
final T data;
Success(this.data);
}
class Error<T> extends Result<T> {
final Exception e;
Error(this.e);
}
freezed를 사용한 Result
part 'result.freezed.dart'
@freezed
sealed class Result<T> with _$Result<T> {
const factory Result.success(T data) = Success;
const factory Result.error(Exception e) = Error;
}
원하는 에러 타입 정의
part 'result.freezed.dart'
@freezed
sealed class Result<D, E> with _$Result<D, E> {
const factory Result.success(D data) = Success;
const factory Result.error(E e) = Error;
}
enum을 사용하여 에러 타입 정의
enum NetworkError {
requestTimeOut,
unknown,
}
void main() async {
final repository = PhotoRepositoryImpl(PhotoApiImpl())
final result = await repository.getPhotos();
switch(result) {
case Success<List<Photo>, NetworkError>():
print(result.data);
case Error<List<Photo>, NetworkError>():
switch(result.error) {
case .requestTimeOut:
print('타임아웃')
case .unknown:
print('알 수 없는 에러')
}
}
}
장점
- enum과 동일하게 switch문과 조합하여 모든 처리를 강제할 수 있다
- 타입 안정성이 향상된다
- 에러 처리가 강제된다 (컴파일러가 모든 케이스 처리를 강제함)
- 에러 타입의 명확한 문서화가 가능하다
- try-catch문 사용을 줄일 수 있다
- 옵셔널(Nullable) 타입 사용이 감소된다
- 비즈니스 로직과 에러처리 로직을 분리할 수 있다
- 케이스별 테스트가 용이하다