# 코드리뷰 - Flutter (2022.09.07)

# 메인 이슈

# 독립적인 모듈로 복잡한 시스템을 조각내기

# 스파게티 코드를 부축이는 데이터 공유

# 메시지가 아닌 속성으로 대화하는 객체들

# 상속보다는 위임을 선택해야 하는 이유

  • 무조건 나쁘다고는 할 수 없지만 단순히 중복코드를 제거하기 위해서 상속을 사용하지 마세요.

# 어디까지 코드를 감춰야 할까요?

# 기타 이슈

# 문자열 분기 및 중복 코드

# as is

if (ok) {
    // 정상 처리 코드
} else if (authResult.message == '권한이 부여되지 않았습니다. 모든 권한을 부여해주세요.') {
    CommonUtils.toast(authResult.message!);
    Get.offNamed(AppRoutes.insta_pro_access_error);
} else if (authResult.message == '해당 인스타그램 프로페셔널 계정에...'){
    CommonUtils.toast(authResult.message!);
    Get.offNamed(AppRoutes.insta_pro_facebook_account_error);
} else if (authResult.message == '이미 가입된 인스타그램 프로페셔널 계정입니다.') {
    CommonUtils.toast(authResult.message!);
    Get.back();
} else {
    CommonUtils.toast('알 수 없는 오류가 발생했습니다. 다시 시도해주시거나 고객 센터에...');
    Get.offNamed(AppRoutes.insta_pro_unknown_error);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# to be

if (authResult.errorCode != 0) {
    CommonUtils.toast(authResult.message);
    Get.offNamed(_getRouterPathFromErrorCode(authResult.errorCode));
    return;
}

// 정상 처리 코드
1
2
3
4
5
6
7

# 분기문의 블럭이 너무 길어서 코드 흐름을 알기 어려움

# as is

return showContent ? Container(
    child: Column(
        children: <Widget>[
            _divider(),
            Container(
                margin: EdgeInsets.only(top: h(20), right: w(32)),
                child: Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                        InkWell(
                            child: Row(
                                children: [
                                    Text('컨텐츠 확인하기', style: ...),
                                    Icon(LineAwesomeIcons.angle_right, ...)
                                ],
                            ),
                            onTap: () async {
                                if (...) {
                                    launchUrlString(...);
                                }
                            },
                        ),
                    ],
                ),
            ),
        ],
    ),
) : Container();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# to be

if (!showContent) return Container();

return Container(
    child: Column(
        children: [
            _divider(),
            Container(
                margin: EdgeInsets.only(top: h(20), right: w(32)),
                child: Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                        InkWell(
                            child: Row(
                                children: [
                                    Text('컨텐츠 확인하기', style: ...),
                                    Icon(LineAwesomeIcons.angle_right, ...)
                                ],
                            ),
                            onTap: () async {
                                if (...) {
                                    launchUrlString(...);
                                }
                            },
                        ),
                    ],
                ),
            ),
        ],
    ),
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 비효율적인 코드 #1

# as is

Widget _getPage(int index) {
    if (index == 0) {
        return _page1();
    } else if (index == 1) {
        return _page2();
    } else if (index == 2) {
        return _page3();
    } else {
        return Container();
    }
}
1
2
3
4
5
6
7
8
9
10
11

# to be

Widget _getPage(int index) {
    if (index > 2) retun Container();

    const _pages = [_page1(), _page2(), _page3()];
    return _pages[index];
}
1
2
3
4
5
6

# 실제 적용된 코드

Widget _getPage(int index) {
    const titles = [
        "<b>인스타그램\n메뉴 > 설정 > 계정 > 프로페셔널...",
        "<b>프로필 편집 > 페이지</b>를\n눌러 주세요.",
        "<b>기존 페이지 연결</b>을 눌러주세요.\n페이지가...",
        "연결이 잘 되었다면 <b>프로필 편집</b> 화면에\n다음과...",
        "<b>연동하러 가기</b>를 눌러 안내에 맞춰\n진행해 주세요."
    ];
    return Column(
        children: [
            Align(
                alignment: Alignment.centerLeft,
                child: StyledText(text: titles[index],
                    style: TextStyle(fontSize: sp(40)),
                    tags: {"b": StyledTextTag(...))}
                ),
            ),
            SizedBox(height: h(32)),
            Image.asset('.../{index + 1}.png'),
        ],
    );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 비효율적인 코드 #2

# as is

Widget _signinButton() {
    if (_signinEmailEnabled && _signinPasswordEnabled) {
        return mainButton(
            title: "로그인",
            onPressed: () async {
            final String email = this._emailController.text.trim();
            final String password = this._passwordController.text.trim();

            DeviceUtils.closeKeyboardLayer();
            await this._model.signin(email: email, password: password);
            }
        );
    } else {
        return mainButton(
            title: "로그인",
            backgroundColor: UiConfig.primaryDisabledColor,
            onPressed: () {
            }
        );
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# to be

Widget _signinButton() {
    const canSignin = _signinEmailEnabled && _signinPasswordEnabled;
    return mainButton(
        title: "로그인",
        backgroundColor: canSignin ? null : UiConfig.primaryDisabledColor,
        onPressed: () {
            if (!canSignin) return;

            DeviceUtils.closeKeyboardLayer();
            final String email = _emailController.text.trim();
            final String password = _passwordController.text.trim();
            _model.signin(email: email, password: password);
        }
    );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# null safe issue 그리고 ...

# as is

Text('${this._campaign!.brand!.brand_name}',
    style: TextStyle(color: UiConfig.secondaryFontColor, fontSize: sp(24))
);

var str = _campaignApply?.posting_url as String;
1
2
3
4
5

# to be 1

Text(_campaign?.brand?.brand_name ?? "",
    style: TextStyle(color: UiConfig.secondaryFontColor, fontSize: sp(24))
);

var str = _campaignApply?.posting_url ?? "";
1
2
3
4
5

# to be 2

Text(_campaign.brand.brand_name,
    style: TextStyle(color: UiConfig.secondaryFontColor, fontSize: sp(24))
)
1
2
3
  • 상황에 따라서 객체 내부에서 필요한 조치를 하여 외부에서는 신경쓰지 않도록 한다.

# 필요없는 코드

# as is

onPressed: () async {
    Get.dialog(Center(child: CircularProgressIndicator()));
    await _model.connectInstagramProfessional(_fromWhere == 'fromSignup');
}
1
2
3
4

# to be

onPressed: () async {
    Get.dialog(Center(child: CircularProgressIndicator()));
    _model.connectInstagramProfessional(_fromWhere == 'fromSignup');
}
1
2
3
4