-
Flutter 모델 생성 코드는 소스가 아니라 산출물입니다AI Agent/flutter 2026. 7. 4. 15:00728x90반응형

손으로 고친 .g.dart가 증발한 날 서버 응답에 필드 하나가
null로 와서 앱이 죽었습니다.
급한 김에 저는*.g.dart파일을 열어서,fromJson안에 있는 파싱 줄에 직접?? ''를 박아 넣었습니다.
앱은 살아났고, 저는 만족하며 커밋했습니다.
딱 다음 빌드까지였습니다.flutter pub run build_runner build를 한 번 돌리자, 제가 손으로 넣은?? ''가 흔적도 없이 사라졌습니다.
앱은 다시 죽었습니다.
저는 잠깐 제 눈을 의심했습니다.
분명 고쳤는데, 파일을 다시 여니 원래대로 돌아가 있었습니다.처음엔 저장이 안 됐나 싶어 다시 열어 확인했습니다.
아니었습니다.
커밋 로그에도 제 수정이 분명히 남아 있었습니다.
그런데도 파일 내용은 옛날 그대로였습니다.
Git이 아니라 도구가 파일을 덮어쓴 거라는 걸, 그 순간엔 상상도 못 했습니다.
저는 파일을 "제가 고치는 것"이라고만 생각했지, "누가 다시 써내는 것"이라고는 생각해본 적이 없었으니까요.생성 파일은 소스가 아니라 스냅샷입니다
json_serializable이나 freezed 같은 도구는 제가 쓴 모델 클래스를 읽고, 거기서 파싱 코드를 뽑아
*.g.dart에 써넣습니다.
그러니까 그 파일은 제가 짜는 소스가 아니라, 도구가 만든 산출물입니다.
마지막 생성 시점의 사진 한 장인 셈입니다.사진을 아무리 예쁘게 포토샵해도, 다음에 셔터를 누르면 원래 피사체대로 다시 찍힙니다.
제가*.g.dart를 손으로 고친 건, 딱 그렇게 사진 위에 낙서한 거였습니다.
build_runner를 다시 돌리는 순간 낙서는 지워지고, 도구는 원본 모델만 보고 새 사진을 찍습니다.
고쳐야 할 곳은 산출물이 아니라 원본, 그러니까 모델 클래스였습니다.@JsonSerializable() class UserDto { const UserDto({required this.id, this.name = ''}); // 기본값은 여기서 줍니다 final String id; @JsonKey(defaultValue: '') // null 방어도 원본 모델에 선언합니다 final String name; factory UserDto.fromJson(Map<String, dynamic> json) => _$UserDtoFromJson(json); Map<String, dynamic> toJson() => _$UserDtoToJson(this); }@JsonKey로 원본에 방어를 선언하니, build_runner를 다시 돌려도*.g.dart에 그 처리가 매번 다시 새겨졌습니다.
산출물에 낙서하는 게 아니라, 산출물을 만드는 규칙을 고친 겁니다.진짜 지옥은 머지에서 왔습니다
이 착각이 혼자 짠 코드였으면 그냥 웃고 넘겼을 겁니다.
문제는 팀이 같이 쓸 때였습니다.*.g.dart를 커밋에 포함해두니, 두 사람이 각자 모델을 조금씩 바꾸고 각자 build_runner를 돌리자, 생성 파일 수백 줄이 서로 다르게 다시 써졌습니다.
머지하는데 충돌이 파일마다 터졌습니다.
정작 사람이 쓴 코드는 몇 줄 안 겹치는데, 기계가 뽑은 줄에서 충돌이 산더미였습니다.그 충돌을 손으로 풀 이유가 없다는 걸 그때 알았습니다.
어차피 build_runner 한 번이면 원본에 맞게 다시 생성될 파일인데, 그걸 사람이 줄 맞춰가며 병합하고 있었던 겁니다.
산출물을 소스처럼 다뤘으니 당연한 고생이었습니다.한번은 충돌을 반쯤 잘못 풀어서, 한 사람의 필드 변경이 생성 파일에서 누락된 채 머지된 적도 있었습니다.
원본 모델은 멀쩡한데*.g.dart만 어긋난 상태였죠.
다행히 다음 빌드에서 다시 생성되며 맞춰졌지만, 그 사이에 "왜 필드가 파싱이 안 되지" 하고 또 원인을 엉뚱한 데서 찾았습니다.
산출물을 신뢰의 대상으로 두면, 이렇게 원본과 산출물이 어긋난 틈에서 시간이 샙니다.커밋할 것과 다시 만들 것을 나눕니다
정리하고 나니 기준은 단순했습니다.
사람이 쓰는 모델은 커밋하고 리뷰하고,*.g.dart는 필요할 때 다시 만들면 되는 것으로 분리했습니다.
팀에 따라 생성 파일을 아예.gitignore에 넣고 CI에서 매번 생성하기도 하고, 커밋하되 "이건 손대지 말 것"이라고 못을 박기도 합니다.
어느 쪽이든 핵심은 같습니다.
그건 고칠 파일이 아니라 다시 찍을 사진이라는 것.이 기준이 서니 리뷰도 편해졌습니다.
PR에*.g.dart변경이 수백 줄 껴 있어도, 그 줄들을 한 줄씩 읽지 않습니다.
원본 모델이 바뀌었으면 생성 파일도 당연히 바뀔 뿐이니까요.
제가 눈여겨볼 건 사람이 쓴 모델 몇 줄이고, 생성 파일은 그 모델과 아귀가 맞는지만 봅니다.
봐야 할 것과 안 봐도 되는 것이 갈리니, 리뷰에 드는 힘도 그만큼 줄었습니다.한 가지 조심할 게 더 있었습니다.
좁게 다시 만들려고--delete-conflicting-outputs를 붙였다가, 생각보다 많은 파일이 함께 지워져서 잠깐 놀란 적이 있습니다.
지워져도 어차피 다시 생성되긴 하는데, 그 순간엔 심장이 철렁합니다.
다음에 또*.g.dart에 손이 가려고 하면, 저는 파일을 열기 전에 그게 소스인지 사진인지부터 물어볼 겁니다.원문 기준
원문 기준: https://dart.dev/tools/build_runner
확인일: 2026-07-01.
build_runner 플래그는 버전에 따라 동작이 달라질 수 있어서, 올리기 전에 옵션 설명을 다시 봅니다.728x90반응형'AI Agent > flutter' 카테고리의 다른 글
Positioned 애니메이션이 프레임을 태우는 이유 (0) 2026.07.05 화면 밖으로 나간 Flutter 상태가 언제 살아남는지 정해야 합니다 (0) 2026.07.04 context는 전역 변수가 아니라 트리 안의 위치입니다 (0) 2026.07.04 Hot reload는 상태를 보존하지만 모든 변경을 반영하지 않습니다 (0) 2026.07.03 Flutter 터치는 콜백이 아니라 Gesture Arena에서 결정됩니다 (0) 2026.07.03