Post

[Capstone-Start] Dart ํ•™์Šต โ€” Generics, Sealed Class, freezed

[Capstone-Start] Dart ํ•™์Šต โ€” Generics, Sealed Class, freezed

๐Ÿ“… ์ผ์‹œ: 2026. 05. 21

๐Ÿ’ฌ ๋‚ด์šฉ: Generics์™€ Dart 3์˜ sealed class, freezed ์ฝ”๋“œ ์ƒ์„ฑ์œผ๋กœ ๋งŒ๋“  Result<T>์™€ AppError ์ฝ”๋“œ ํšŒ๊ณ 


์ฝ”์–ด ์ธํ”„๋ผ์˜ ์—๋Ÿฌ ๋ชจ๋ธ ๋ถ€๋ถ„์„ ํ•™์Šต ๋…ธํŠธ๋กœ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์œผ๋กœ๋Š” core/errors/์— ๋งŒ๋“ค์–ด ๋‘” AppError sealed ํด๋ž˜์Šค์™€ Result<T> ํƒ€์ž…์ด Dart Generics์™€ Dart 3์˜ sealed class, ๊ทธ๋ฆฌ๊ณ  freezed ์ฝ”๋“œ ์ƒ์„ฑ์ด๋ผ๋Š” ์„ธ ๊ฐ€์ง€ ๊ธฐ๋Šฅ์„ ํ•ฉ์ณ ๋งŒ๋“  ๊ฒฐ๊ณผ๋ฌผ์ด๋ผ, ๊ฐ ์š”์†Œ๋ฅผ ์ •๋ฆฌํ•ด ๋‘ก๋‹ˆ๋‹ค.

๊ฒ€์ƒ‰ํ•ด์„œ ๋“ค์–ด์˜ค์‹  ๋ถ„๋“ค๊ป˜ ๋„์›€์ด ๋  ๋งŒํ•œ ์ˆœ์„œ๋กœ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  1. Generics โ€” bounded์™€ variance
  2. sealed class (Dart 3)
  3. freezed ํŒจํ‚ค์ง€ โ€” ์™œ ์“ฐ๋Š”๊ฐ€
  4. ์‹ค์ œ ์ฝ”๋“œ: AppError + Result<T>
  5. ํšŒ๊ณ 

1. Dart์˜ Generics

C++ ํ…œํ”Œ๋ฆฟ์ด๋‚˜ Java/Kotlin์˜ generic์ฒ˜๋Ÿผ ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ ์–ธํ•ด ์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. Dart์˜ generic์€ invariant๊ฐ€ ๊ธฐ๋ณธ์ด๊ณ , ํ‚ค์›Œ๋“œ๋กœ covariance๋ฅผ ์•ฝํ™”์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ๋‹ค๋ฅธ ์–ธ์–ด์™€ ๋น„์Šทํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์‚ฌ์šฉ

1
2
3
4
5
6
7
class Box<T> {
  Box(this.value);
  final T value;
}

final intBox = Box<int>(42);
final stringBox = Box<String>('hello');

Bounded generics โ€” extends

ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ƒํ•œ์„ ๋‘˜ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Java์˜ T extends Comparable<T>์™€ ๊ฐ™์€ ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.

1
2
3
4
5
6
T maxOf<T extends Comparable<T>>(T a, T b) {
  return a.compareTo(b) >= 0 ? a : b;
}

maxOf(3, 5);          // OK โ€” int๋Š” Comparable<int>
maxOf('apple', 'b');  // OK โ€” String์€ Comparable<String>

Generic ํ•จ์ˆ˜์™€ ์ถ”๋ก 

ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ํ˜ธ์ถœ ์‹œ์ ์— ์ธ์ž๋กœ๋ถ€ํ„ฐ ์ถ”๋ก ๋ฉ๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์œผ๋กœ ๋ฐ•๋Š” ๊ฒฝ์šฐ๋Š” ์ถ”๋ก ์ด ์•ˆ ๋  ๋•Œ๋งŒ์ž…๋‹ˆ๋‹ค.

1
2
3
4
List<T> wrap<T>(T value) => [value];

final ints = wrap(1);        // List<int>๋กœ ์ถ”๋ก 
final strings = wrap<String>(null as dynamic); // ๋ช…์‹œ ํ•„์š”

2. Sealed Class โ€” Dart 3์˜ Sum Type

sealed๋Š” Dart 3์—์„œ ์ •์‹ ๋„์ž…๋œ ํ‚ค์›Œ๋“œ๋กœ, ํ•˜์œ„ ํด๋ž˜์Šค๋ฅผ ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(ํŒŒ์ผ) ์•ˆ์—์„œ๋งŒ ์ •์˜ํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค. Kotlin์˜ sealed class, Rust์˜ enum(๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” enum), TypeScript์˜ discriminated union๊ณผ ๊ฐ™์€ sum type ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.

์™œ ํ•„์š”ํ•œ๊ฐ€

์ผ๋ฐ˜ ์ถ”์ƒ ํด๋ž˜์Šค๋Š” ์™ธ๋ถ€์—์„œ ์ž„์˜๋กœ ์ƒ์†๋  ์ˆ˜ ์žˆ์–ด์„œ, ๋ชจ๋“  ๊ฒฝ์šฐ๋ฅผ ํ•œ ์ž๋ฆฌ์—์„œ ๋ง๋ผํ•˜๋Š” ํŒจํ„ด ๋งค์นญ์ด ์ปดํŒŒ์ผ๋Ÿฌ ์ฐจ์›์—์„œ ๋ณด์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. sealed๋Š” ํ•˜์œ„ ์ข…๋ฅ˜๊ฐ€ ๋‹ซํ˜€ ์žˆ์–ด switch๋กœ ๋ถ„๊ธฐํ•  ๋•Œ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ exhaustiveness(๋ชจ๋“  ์ผ€์ด์Šค๋ฅผ ๋‹ค ์ฒ˜๋ฆฌํ–ˆ๋Š”์ง€)๋ฅผ ๊ฒ€์‚ฌํ•ด ์ค๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
sealed class Shape {}
class Circle extends Shape { final double r; Circle(this.r); }
class Square extends Shape { final double a; Square(this.a); }

double area(Shape s) => switch (s) {
  Circle(:final r) => 3.14 * r * r,
  Square(:final a) => a * a,
  // ์ผ€์ด์Šค๋ฅผ ๋น ๋œจ๋ฆฌ๋ฉด ์ปดํŒŒ์ผ ์—๋Ÿฌ
};

์—ฌ๊ธฐ์„œ switch (s) { ... } ํ˜•ํƒœ๋Š” switch expression์ž…๋‹ˆ๋‹ค(๋‚ด์ผ ๋ณ„๋„ ๊ธ€๋กœ ์ •๋ฆฌํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค). ํ•ต์‹ฌ์€ Circle/Square ๋‘ ์ผ€์ด์Šค๋ฅผ ๋ชจ๋‘ ๋‹ค๋ฃจ์ง€ ์•Š์œผ๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋ง‰์•„์ค€๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

3. freezed โ€” ์™œ ์“ฐ๋Š”๊ฐ€

AppError ๊ฐ™์€ sealed ๊ณ„์ธต์„ ์ง์ ‘ ์†์œผ๋กœ ์งœ๋ฉด ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

  • ๊ฐ ๋ณ€ํ˜•(variant)๋งˆ๋‹ค ๋ณ„๋„ ํด๋ž˜์Šค ์„ ์–ธ
  • == / hashCode ์ง์ ‘ ๊ตฌํ˜„
  • copyWith ์ง์ ‘ ๊ตฌํ˜„
  • toString ๊ฐ€๋…์„ฑ์šฉ override
  • JSON ์ง๋ ฌํ™”๊ฐ€ ํ•„์š”ํ•˜๋ฉด fromJson/toJson

freezed ํŒจํ‚ค์ง€๋Š” ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๋ฉด ์œ„์˜ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ๋ฅผ ๋ชจ๋‘ ์ฝ”๋“œ ์ƒ์„ฑ์œผ๋กœ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. ์บก์Šคํ†ค์—์„œ๋Š” ๋„๋ฉ”์ธ ๋ชจ๋ธ๊ณผ ์ƒํƒœ(state) ํ‘œํ˜„ ๊ฑฐ์˜ ๋ชจ๋‘์— freezed๋ฅผ ์ ์šฉํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค.

์˜์กด์„ฑ ์ถ”๊ฐ€

1
2
3
4
5
6
7
8
dependencies:
  freezed_annotation: ^3.0.0
  json_annotation: ^4.9.0

dev_dependencies:
  build_runner: ^2.4.13
  freezed: ^3.0.0
  json_serializable: ^6.8.0

*_annotation์€ ๋Ÿฐํƒ€์ž„ ์˜์กด์„ฑ(์–ด๋…ธํ…Œ์ด์…˜ ํด๋ž˜์Šค๋งŒ ๋“ค์–ด ์žˆ์Œ), ๋ณธ์ฒด๋Š” dev_dependencies๋กœ ๋“ค์–ด๊ฐ€์„œ ๋นŒ๋“œ ์‹œ์ ์—๋งŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์ฝ”๋“œ ์ƒ์„ฑ ์‹คํ–‰

1
dart run build_runner build --delete-conflicting-outputs

*.g.dart, *.freezed.dart ํŒŒ์ผ์ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. watch ๋ชจ๋“œ(build_runner watch)๋กœ ๋‘๋ฉด ํŒŒ์ผ์ด ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ์ž๋™ ์žฌ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.

4. ์‹ค์ œ ์ฝ”๋“œ โ€” AppError + Result<T>

์–ด์ œ ์ง  core/errors/์˜ ํ•ต์‹ฌ ๋‘ ํƒ€์ž…์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋‘ ํƒ€์ž… ๋ชจ๋‘ sealed + generics + freezed์˜ ์กฐํ•ฉ์ž…๋‹ˆ๋‹ค.

AppError โ€” ๋„๋ฉ”์ธ ์—๋Ÿฌ sealed union

1
2
3
4
5
6
7
8
9
10
11
import 'package:freezed_annotation/freezed_annotation.dart';
part 'app_error.freezed.dart';

@freezed
sealed class AppError with _$AppError {
  const factory AppError.network(String message) = NetworkError;
  const factory AppError.notFound(String resource) = NotFoundError;
  const factory AppError.unauthorized() = UnauthorizedError;
  const factory AppError.unknown(Object error, [StackTrace? trace]) =
      UnknownError;
}

๊ฐ factory๋Š” ๋ณ„๋„์˜ variant๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. NetworkError, NotFoundError, UnauthorizedError, UnknownError๊ฐ€ ๋ชจ๋‘ AppError์˜ ํ•˜์œ„ ํƒ€์ž…์ด๊ณ , freezed๊ฐ€ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

ํ˜ธ์ถœ๋ถ€์—์„œ๋Š” ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ณ€ํ˜•์„ ํŒจํ„ด ๋งค์นญ์œผ๋กœ ๋ถ„๊ธฐํ•ฉ๋‹ˆ๋‹ค.

1
2
3
4
5
6
String describe(AppError e) => switch (e) {
  NetworkError(:final message) => '๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜: $message',
  NotFoundError(:final resource) => '$resource์„(๋ฅผ) ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค',
  UnauthorizedError() => '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค',
  UnknownError(:final error) => '์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜: $error',
};

์ƒˆ variant๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์ด switch๊ฐ€ ์ปดํŒŒ์ผ๋Ÿฌ ์—๋Ÿฌ๋กœ ๋ง‰ํ˜€์„œ ๋ชจ๋“  ์‚ฌ์šฉ์ฒ˜๋ฅผ ๊ฐ•์ œ๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ˆ„๋ฝ์„ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์žก์•„์ฃผ๋Š” ํŒจํ„ด์ด๋ผ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

Result<T> โ€” Either์˜ ์Šฌ๋ฆผ ๋ฒ„์ „

Either / Result ํŒจํ„ด์€ ํ•จ์ˆ˜ํ˜• ์–ธ์–ด์—์„œ ์ž์ฃผ ๋ณด๋Š” โ€œ์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจ๋ฅผ ๊ฐ’์œผ๋กœ ํ‘œํ˜„โ€ ํŠธ๋ฆญ์ž…๋‹ˆ๋‹ค. Dart์—๋Š” ํ‘œ์ค€ Result ํƒ€์ž…์ด ์—†์–ด์„œ, sealed + generics๋กœ ์ง์ ‘ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
sealed class Result<T> {
  const Result();
}

class Ok<T> extends Result<T> {
  const Ok(this.value);
  final T value;
}

class Err<T> extends Result<T> {
  const Err(this.error);
  final AppError error;
}

์—ฌ๊ธฐ์„œ T๋Š” generic ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ์ž…๋‹ˆ๋‹ค. Result<User>, Result<List<Meal>>์ฒ˜๋Ÿผ ์ž„์˜์˜ ๋„๋ฉ”์ธ ํƒ€์ž…์„ ๋‹ด์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Controller(Riverpod notifier)์—์„œ์˜ ์‚ฌ์šฉ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class DietController extends StateNotifier<AsyncValue<DietDay>> {
  DietController(this._repository) : super(const AsyncLoading());

  final DietRepository _repository;

  Future<void> load() async {
    state = const AsyncLoading();
    final result = await _repository.fetchToday();
    state = switch (result) {
      Ok(:final value) => AsyncData(value),
      Err(:final error) => AsyncError(error, StackTrace.current),
    };
  }
}

switch๋กœ ๋‘ ์ผ€์ด์Šค๋ฅผ ํ•œ ์ž๋ฆฌ์—์„œ ๋ถ„๊ธฐํ•˜๋‹ˆ, repository๊ฐ€ ๋˜์งˆ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ controller์—์„œ ๋ช…์‹œ์ ์œผ๋กœ ๋‹ค๋ฃจ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. try/catch๋กœ ํ’€๋ฉด ์–ด๋””์„œ ์–ด๋–ค ์˜ˆ์™ธ๊ฐ€ ์–ด๋””๋กœ ํ˜๋Ÿฌ๊ฐ€๋Š”์ง€ ์ถ”์ ์ด ์–ด๋ ค์šด๋ฐ, sealed + Result๋กœ ๋ช…์‹œํ•˜๋ฉด ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ํ•œ๋ˆˆ์— ๋ณด์ž…๋‹ˆ๋‹ค.

5. ํšŒ๊ณ  โ€” sum type์ด ํ—ฌ์Šค์ผ€์–ด ๋„๋ฉ”์ธ์— ์ž˜ ๋งž๋Š” ์ด์œ 

์บก์Šคํ†ค์˜ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ๋ณด๋ฉด sum type์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋“ฑ์žฅํ•˜๋Š” ์ž๋ฆฌ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

  • ์šด๋™ ์ข…๋ฅ˜ โ€” ์œ ์‚ฐ์†Œ / ๊ทผ๋ ฅ / ์ŠคํŠธ๋ ˆ์นญ (์„ธ ๊ฐ€์ง€๊ฐ€ ๋ชจ๋‘ ๋‹ค๋ฅธ ํ•„๋“œ๋ฅผ ๊ฐ€์ง)
  • ์•Œ๋ฆผ ์ข…๋ฅ˜ โ€” ์‹์‚ฌ / ์šด๋™ / ์ •๊ธฐ ์ ๊ฒ€ (๊ฐ ํŽ˜์ด๋กœ๋“œ๊ฐ€ ๋‹ค๋ฆ„)
  • ๊ฑด๊ฐ• ์œ„ํ—˜๋„ โ€” ์ •์ƒ / ๊ฒฝ๊ณ„ / ์œ„ํ—˜ (๊ตฌ๊ฐ„๋ณ„ ๋ฉ”์‹œ์ง€ ๋‹ค๋ฆ„)
  • ๋ถ„์„ ๊ฒฐ๊ณผ ์ƒํƒœ โ€” Pending / Success / Failure

์ด ๋ชจ๋“  ์ผ€์ด์Šค๋ฅผ String ํ•„๋“œ ํ•œ ์ค„๋กœ ํ‘œํ˜„ํ•˜๋ฉด, ์ฝ”๋“œ ๊ณณ๊ณณ์— if (kind == 'cardio') ...๊ฐ€ ํฉ์–ด์ง€๋ฉด์„œ ์ƒˆ ์ข…๋ฅ˜๋ฅผ ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ ๋ฌด์—‡์„ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๋Š”์ง€๊ฐ€ ํ•œ๋ˆˆ์— ์•ˆ ๋ณด์ž…๋‹ˆ๋‹ค. sealed class๋กœ ๋ชจ๋ธ๋งํ•˜๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๋น ๋œจ๋ฆฐ ๊ณณ์„ ์ฝ• ์ง‘์–ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ๋„๋ฉ”์ธ์ด ํ™•์žฅ๋  ๋•Œ ๋ฆฌ์Šคํฌ๊ฐ€ ๋‚ฎ์•„์ง‘๋‹ˆ๋‹ค.

6. ์ •๋ฆฌ / ๋‹ค์Œ ํ•™์Šต ๊ณ„ํš

์˜ค๋Š˜ ์ •๋ฆฌํ•œ ํ•ต์‹ฌ์€ ๋‹ค์Œ ์„ธ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

  1. Generics๋Š” invariant๊ฐ€ ๊ธฐ๋ณธ์ด๊ณ , extends๋กœ ์ƒํ•œ์„ ๋‘˜ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Result<T>์ฒ˜๋Ÿผ ์ž„์˜์˜ ๋„๋ฉ”์ธ ํƒ€์ž…์„ ๋‹ด๋Š” ์ปจํ…Œ์ด๋„ˆ ๋งŒ๋“ค ๋•Œ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.
  2. sealed class๋Š” Dart 3์˜ sum type์ด๊ณ , switch ๋ถ„๊ธฐ์—์„œ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ exhaustiveness๋ฅผ ๊ฒ€์‚ฌํ•ด ์ค๋‹ˆ๋‹ค. ๋„๋ฉ”์ธ ์—๋Ÿฌ / ์•Œ๋ฆผ ์ข…๋ฅ˜ / ์šด๋™ ์นดํ…Œ๊ณ ๋ฆฌ ๊ฐ™์€ ๋‹ซํžŒ ์ง‘ํ•ฉ ํ‘œํ˜„์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.
  3. freezed๋Š” sealed + immutable data class + copyWith + ๋™๋“ฑ์„ฑ ๋น„๊ต๋ฅผ ํ•œ ๋ฒˆ์— ์ž๋™ ์ƒ์„ฑํ•ด ์ค๋‹ˆ๋‹ค. ์บก์Šคํ†ค์˜ ๋ชจ๋“  ๋„๋ฉ”์ธ ๋ชจ๋ธ์— ์ ์šฉํ•  ๊ณ„ํš์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ ํ•™์Šต์€ ์˜ค๋Š˜ ์ฝ”๋“œ์— ์‚ด์ง ๋“ฑ์žฅํ•œ switch expression๊ณผ ํŒจํ„ด ๋งค์นญ โ€” Dart 3์—์„œ ์ƒˆ๋กœ ๋“ค์–ด์˜จ ๊ธฐ๋Šฅ๋“ค์„ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ •๋ฆฌํ•˜๋ ค ํ•ฉ๋‹ˆ๋‹ค. ์บก์Šคํ†ค ํ›„๋ฐ˜์— ๋ณธ๊ฒฉ์ ์œผ๋กœ ์“ฐ๊ฒŒ ๋  ๊ฑฐ๋ผ ๋ฏธ๋ฆฌ ํ•™์Šต ๋…ธํŠธ๋กœ ๋‚จ๊ฒจ ๋‘˜ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.


๐Ÿ“Œ ์š”์•ฝ

AppError sealed์™€ Result<T> generic ํƒ€์ž…์„ ๋งŒ๋“ค๋ฉด์„œ ํ•™์Šตํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. Generics๋Š” ์ž„์˜ ๋„๋ฉ”์ธ ํƒ€์ž…์„ ๋‹ด๋Š” ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋งŒ๋“ค ๋•Œ ์“ฐ๊ณ , sealed class๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ exhaustiveness๋ฅผ ๊ฒ€์‚ฌํ•ด ์ฃผ๋Š” ๋‹ซํžŒ ํ•ฉ ํƒ€์ž…์ด๋ฉฐ, freezed๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ํ•œ ์ค„๋กœ immutable data class + copyWith + ๋™๋“ฑ์„ฑ์„ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์„ธ ๊ฐ€์ง€๋ฅผ ํ•ฉ์น˜๋ฉด ํ—ฌ์Šค์ผ€์–ด ๋„๋ฉ”์ธ์˜ ๋‹ซํžŒ ๋ณ€ํ˜•๋“ค(์—๋Ÿฌยท์šด๋™ ์ข…๋ฅ˜ยท์œ„ํ—˜๋„ ๋“ฑ)์„ ์•ˆ์ „ํ•˜๊ฒŒ ๋ชจ๋ธ๋งํ•  ์ˆ˜ ์žˆ๊ณ , ์ƒˆ ๋ณ€ํ˜•์„ ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์‚ฌ์šฉ์ฒ˜๋ฅผ ๊ฐ•์ œ๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.

This post is licensed under CC BY 4.0 by the author.