Code Generation

CHO WanGi·2024년 3월 4일

Flutter

목록 보기
22/27

GDSC HUFS에선 좋은 개발 관련 게시글을 공유하는 공간인 getit-recommendation 게시판이 있다.
(여기 올라온 글들은 GET IT라고 해서 메일로 매달 1일, 15일에 모아서 날아온다!)
거기에 이번에 RiverPod 연습을 하다가 Code Gen 관련해서
너무 좋은 글이 있어서 공유하게 되었고,

내 velog에도 나만의 정리를 하고 싶어서 글을 남겨본다.

코드 제너레이션이란?

코드 생성, 말 그대로 자동으로 코드를 생성해주는 것을 말함.

Code Gen in Flutter

https://riverpod.dev/ko/docs/essentials/first_request

RiverPod 연습을 위해 공식 홈페이지에 나와있는대로 연습을 하다가
난생 처음 보는 형태인

part 'activity.freezed.dart';
part 'activity.g.dart';
class Activity with _$Activity {
  factory Activity({
    required String key,
    required String activity,
    required String type,
    required int participants,
    required double price,
  }) = _Activity;

  /// JSON 객체를 [Activity] 인스턴스로 변환합니다.
  /// 이렇게 하면 API 응답을 형안정(Type-safe)하게 읽을 수 있습니다.
  factory Activity.fromJson(Map<String, dynamic> json) => _$ActivityFromJson(json);
}


라는 코드가 있길래 저게 뭔가 하고 찾아보니
Flutter 에서 사용하는 JSON 직렬화를 위한 json_serializable
데이터 모델 활용에 유용한 Freezed Code Gen 임을 알았다.

Code Gen, Before & After

원래 flutter 에서 API GET 통신을 할때, 데이터 모델은 보통 이런형식으로 짜여진다

class ProductInfo {
  final int id;
  final String category;
  final String title;
  final String description;
  final String product_image_data;
  final int user_id;

  ProductInfo({
    required this.id,
    required this.category,
    required this.title,
    required this.description,
    required this.product_image_data,
    required this.user_id,
  });

  factory ProductInfo.fromJson(Map<String, dynamic> json) {
    if (!json.containsKey('id') ||
        !json.containsKey('category') ||
        !json.containsKey('title') ||
        !json.containsKey('description')) {
      throw Exception('Invalid JSON data');
    }
    return ProductInfo(
      id: json["id"],
      category: json["category"],
      title: json["title"],
      description: json["description"],
      product_image_data: json["product_image_data"],
      user_id: json["user_id"],
    );
  }
}

이렇게 일반적으로 JSON 데이터를 직렬화/파싱 하는데,
Factory 생성자를 주로 이용한다.
생성자는 파라미터에 어떤 값을 넣어야 하는지 지정을 하니까 존재가 당연하지만,
factory 생성자로 만든 fromJson은 다시한번 속성을 넣어줘야하는 중복이 발생한다.

또한 flutter 에서 json 데이터를 사용하려면 Map 으로 바꾸어서 사용해야하는데,
json을 객체화 하기 위해 fromJson, 객체를 json으로 바꾸기위해 toJson 함수를 만들어서 사용한다.
이때 문제점은 내부 변수가 추가 되거나 삭제되면
위에 파라미터와 밑에 fromJson 함수 모두 수정 해야하는
아주 귀찮은 작업이 요구된다.

따라서 JsonSerializablefreezed같은 Code Gen을 사용하게 되면

import 'package:freezed_annotation/freezed_annotation.dart';

part 'product_info.freezed.dart';
part 'product_info.g.dart';


class ProductInfo with _$ProductInfo {
  factory ProductInfo({
    required int id,
    required String title,
    required String category,
    required String address,
    required String product_image_data,
    required String description,
  }) = _ProductInfo;

  factory ProductInfo.fromJson(Map<String, dynamic> json) =>
      _$ProductInfoFromJson(json);
}

코드만 작성하면 되고,
이후 flutter pub run build_runner build 혹은
계속해서 Code Gen 작업이 필요하다면 flutter pub run build_runner watch 명령어를 입력해주면

새로운 파일을 만들어주는데,

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'product_info.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

_$ProductInfoImpl _$$ProductInfoImplFromJson(Map<String, dynamic> json) =>
    _$ProductInfoImpl(
      id: json['id'] as int,
      title: json['title'] as String,
      category: json['category'] as String,
      address: json['address'] as String,
      description: json['description'] as String,
    );

Map<String, dynamic> _$$ProductInfoImplToJson(_$ProductInfoImpl instance) =>
    <String, dynamic>{
      'id': instance.id,
      'title': instance.title,
      'category': instance.category,
      'address': instance.address,
      'description': instance.description,
    };

g.dart 파일을 보면 Model을 생성하는 인스턴스가 생긴 것을 확인할 수 있다.
따라서 직접 코딩할 필요 없이 BE 와의 협업 과정에서 다른 데이터가 추가되거나 삭제하면,
데이터 모델 클래스 정의한 파일에서 수정하고 편리하게 다시 빌드하면 또 수정된 g.dart 파일이 생성된다.

아 참고로 Freezed 는 json-serializable 기능에 copy 기능, toString override, union 클래스 등 편의 기능을 추가로 더한 패키지 이다.
참고링크: https://blog.codefactory.ai/flutter/freezed/

Code Gen, 꼭 좋은걸까?

코드의 중복을 줄여주고, 개발자의 수고로움을 덜어낸다는 강력한 장점이 있지만,
세상사 모든 것이 그렇듯 단점도 존재한다.

  1. 코드를 알 수 없다.
    일단 내 코드가 아니라는 점이다.
    즉 개발자가 직접 만들지 않은 코드라는 것인데, 이는 협업시 상당한 모호함을 주게 된다.
    특히 이런 g.dart, freezed.dart 파일들은 .gitignore 에 올라가는 경우가 많기 때문에,
    협업하는 입장에선 이게 무슨 함수인지 알 길이 없다.
    또한 이를 사용하는 입장에서도 build 하기 전까지는 코드를 알 수 없다는 점이다.

  2. 강력한 의존성
    특정 Code Gen 에 의존할 수 밖에 없다.
    만약 갑작스러운 Code Gen의 지원 중단이 발생한다면?
    flutter 의 아주 유명한 http 통신 패키지인 dio 같은 사태가 발생한다면?
    개발자는 다시 이걸 한땀한땀 돌려내야 한다는 점이다.

  3. 커스터마이징
    1번과 연결되는 단점으로
    어떠한 방법으로 code 가 생성되는지 알 수 없기 때문에 개발 과정에서 커스터마이징이 필요하다면
    이것이 매우 어렵다는 단점이 있다.
    또한 Code Gen의 방식으로 커스터마이징을 진행했는데,
    Flutter Lint 와 충돌을 하는 등의 사례를
    참고 블로그에서 확인할 수 있었다.

  4. 매번 빌드가 필요하다
    Code Gen 의 결과물을 활용하려면 매번 빌드를 해줘야 한다는 단점이다.
    이 빌드를 진행하기 전까지는 문법상 경고가 계속 떠서,
    에디터의 장점인 자동완성 기능을 이용할 수 없다는 것도 문제가 된다.
    특히 Flutter를 다룬지 얼마 안된 내 입장에선 자동완성기능을 이용할 수 없다는 것이 큰 공포로 다가오기도 하였다.

  5. AI의 등장과 IDE 발달
    GitHub Copilot, Chat GPT 에게 Flutter 데이터 모델을 작성해줘
    한마디면 코드를 만들어주고 커스터마이징도 간편하게 할 수 있으며,
    또한 IDE 의 발달로
    Dart Data Class Generator같은 익스텐션들은
    위에서 사용한 Freezed 를 대체 할 수 있으며,
    Flutter Lint 와도 충돌하지 않는다는 장점을 갖고 있다.

결론

일단 이번 RiverPod 연습에만 사용할 것 같고,
RiverPod 의 원리만 깨닫는다면 굳이 사용할 것 같지는 않다.
왜냐하면, 개발의 생명은 협업이라고 생각한다.
그 협업의 관점에서 보았을 때,
나도 이 코드가 어떻게 만들어지는 지 모르고,
같이 작업하는 개발자분이 직관적으로 코드를 읽어낼 수 없다는 점이
앞에서 나열한 장점을 모두 덮어버리는 단점이라고 생각한다.

내 개인 플젝에는 알고는 가자는 느낌으로 사용해보겠으나,
다른 개발자들과 같이하는 프로젝트에서는
굳이 사용할 거 같지는 않다는 것이 내 결론.

참고

https://velog.io/@hwibinissuccess/Flutter%E3%85%A3JsonSerializableJson-%EC%A7%81%EB%A0%AC%ED%99%94

https://nx006.tistory.com/69

profile
제 Velog에 오신 모든 분들이 작더라도 인사이트를 얻어가셨으면 좋겠습니다 :)

0개의 댓글