본문 바로가기
에러 해결방법

[트러블슈팅] Unhandled exception: LateInitializationError: Field '___' has not been initialized.(Feat. Dart 비동기처리)

by 개발짜 2024. 11. 7.

간단한 배경 설명

Dart 를 이용해서 간단한 게임 프로그램을 작성하고 있었다. 게임을 시작하면 사용자에게 캐릭터의 이름을 입력받고 몬스터와 전투를 한다. 캐릭터나 몬스터의 객체는 텍스트 파일을 읽어서 체력이나 공격력 등의 속성을 지정하여 새롭게 만들어진다.

 

문제의 시작...

기존에는 startGame 이라는 메서드를 만들어 앞에서 말한 파일 읽기+객체 생성+전투 를 한 번에 처리했으나, 메서드의 길이가 너무 길어지고 가독성이 좋지 않아 코드 분리를 시도했다. 새로운 메서드에 파일 읽기+객체 생성 구현부를 이동하여 startGame 에서 호출할 생각이었다. 하지만 메서드 분리를 하자마자 오류가 발생하는데...

 

기존 코드 실행 시 나타나야 할 문구
메서드 분리를 하자마자 엉망진창으로 나타나는 오류

 

나는 코드의 수정 없이 그대로 들어내서 void 타입의 setBeforeBattle() 이라는 새로운 메서드를 만들어 생명을 불어넣어주었다. startGame() 에서 전투를 시작하기 전 setBeforeBattle() 메서드를 호출하도록 변경한 후 실행을 했다. 기존과 동일하게 이름을 입력하라는 문구에 입력 값을 넣고 엔터를 치자마자 LateInitializationError 오류를 뱉어낸다.

 

문제

Unhandled exception: LateInitializationError: Field '___' has not been initialized.

위와 같은 오류는 Late 타입의 변수가 초기화되지 않은(혹은 객체가 생성되지 않은) 채 변수를 사용했기 때문에 발생한 예외이다. exception 내용은 알겠는데 왜..? 어디서 초기화가 안 되었다는 건지 이해가 안 갔다. 다음 코드는 문제가 발생하는 변수의 선언부와 메서드의 일부이다.

 

late Character character;
...
void startGame() {
  setBeforeBattle();

  print('게임을 시작합니다!');
  character.showStatus();
  ...
}

 

void setBeforeBattle() async {
  late String? name;
  do {
    stdout.write('캐릭터의 이름을 입력하세요(한글, 영문 대소문자만 가능합니다): ');
    name = stdin.readLineSync();
  } while (!regexp.hasMatch(name!));
  int stamina = 0;
  int attack = 0;
  int defense = 0;

  try {
    var lines = File(characterFilePath)
        .openRead()
        .transform(utf8.decoder)
        .transform(CsvToListConverter());
    await for (var line in lines) {
      stamina = line[0];
      attack = line[1];
      defense = line[2];
    }
    // 캐릭터 생성
    character = Character(name, stamina, attack, defense);
    ...
}

 

setBeforeBattle() 내에서 character 변수가 초기화되기 전에 character.showStatus() 메서드가 호출됐기 때문에 해당 오류가 발생했다. 

 

그렇다면 왜? startGame() 에서 모든 기능을 수행할 때는 정상적이었는데, 메서드를 분리해서 호출할 때는 오류가 날까? 그것은 바로 async 키워드로 setBeforeBattle() 비동기 함수가 되었기 때문이다. startGame() 내에서는 모든 코드가 순차적으로(동기적으로) 진행이 되는 반면 코드를 분리하여 setBeforeBattle() 메서드를 호출할 때는 해당 메서드의 모든 내용이 실행되기 전에 print() 문 이후의 명령이 실행(비동기적으로)된다. 운이 좋게 setBeforeBattle() 메서드가 빠르게 진행됐으면 해당 오류가 발생하지 않았을수도 있었겠지만, 파일 입출력과 새로운 객체의 생성에는 많은 리소스를 요구하기 때문에 피해갈 수 없었을 것이다.

 

해결

async 키워드가 있는 setBeforeBattle() 메서드는 비동기로 호출되기 때문에 해당 메서드가 끝날때까지 기다려주는 작업을 해야한다. Future 에 void 타입을 감싸서 await 키워드로 메서드를 동기화시키면 된다.

 

void startGame() async {
  await setBeforeBattle();

  print('게임을 시작합니다!');
  character.showStatus();
  ...
}

 

Future<void> setBeforeBattle() async {
    ...
    // 캐릭터 생성
    character = Character(name, stamina, attack, defense);
    ...
}

 

해당 문제를 겪고 나서 느낀 점

처음 setBeforeBattle() 작성할 때는 async 를 붙이지 않았었다. 파일을 읽어올 때 await for(~) 키워드 때문에 컴파일 오류가 나서 async 키워드를 사용했던 것 같다. Dart 의 비동기 관련 강의는 여러 번 듣고 이해도 했다고 생각했다.(강의 내용 자체는 이해가 가니까...) 비동기 처리를 하는 것을 해본 적이 별로 없어서 익숙치 않다고만 생각했었다. 심지어 void 타입이니 빨간 줄이 안생기기도 해서 실행을 하기 전까지는 코드가 잘못 됐는줄도 몰랐다. 왜 setBeforeBattle() 메서드가 비동기로 작동하는지, 어떻게 동기화 하는지 찾아가면서 해보니까 나는 그냥 몰랐던 것 이었다.... 이번 일을 계기로 비동기 처리에 대한 내용을 정리해보고 응용해야겠다는 생각이 들었다.

댓글