320x100
320x100

왜 이 글을 쓰게 되었는가

이게뭐약 프로젝트를 진행하면서 프론트엔드(애플리케이션)을 개발하는 팀원들이 기능을 추가하는데 너무 오래걸렸다.
앱 규모가 크지 않기 때문에 UI와 로직을 추가하는데 길어야 일주일을 생각했지만 한 달이 넘게 진행되는 경우가 많았다.
아무리 사회인이라서 바쁘다고 해도 내가 설계부터 구현까지 3일 ~ 7일 걸리는 것을 생각하면 너무 오래걸렸다.
처음에는 실력 부족이라고 생각했는데, 얘기를 들어보니 코드 베이스가 잘못된 것이었다.

 

 

 

 

 

어떤 문제들이 있었는가

 

애플리케이션의 코드 구조를 보니 아래와 같은 문제가 있었다.

 

  • 모든 UI의 레이아웃이 한 코드 파일에서 관리되고 있었다.
  • 비즈니스 로직 코드와 UI 코드가 얽혀있었다.
  • 단독 함수가 되어야하는 유틸리티 함수끼리 서로 호출하여 비즈니스 로직 함수처럼 작동하고 있었다.
  • 아토믹 패턴을 애매하게 사용하여 파일과 모듈의 구조가 불명확 했다.
  • 책임이 뒤섞여있어서 코드 수정 시 사이드 이펙트가 많이 발생했다.
  • API 호출과 DB 쿼리 로직이 섞여있는 등 끔찍한 모습이 보였다.
  • 데이터 가공 로직이 비즈니스 로직과 결합되어 있어서 코드 재활용을 할 수 없었다.
  • 파일명, 함수명, 변수명에 대한 컨벤션이 지켜지지 않았다.

 

 

 

 

코드 베이스가 이렇게 된 이유

  • 빠른 개발을 위한 임시 코드가 누적되었다.
  • 명확한 아키텍처 없이 기능 위주로 확장하며 개발했다.
  • 함수에 책임을 명확하게 부여하지 않아서 기능을 추가할 때마다 함수에 로직을 추가했다.

 

 

 

 

 

어떻게 고쳤는가

1. 코드 구조 리팩토링 (아토믹 패턴에서 feature based 구조로 변경)

변경 이유

아토믹 패턴은 프로젝트 규모가 큰 경우일 수록 유리한데, 우리 프로젝트는 규모가 크지도 않고 기능이 단순하여 아토믹 패턴으로 인해 오히려 코드베이스가 복잡해지는 부작용이 발생했다.

 

 

feature based 구조로 선택한 이유

코드의 관심사를 기능 기준으로 묶음으로써, 아토믹 패턴으로 인해 흩어진 코드들을 모아서 기능별로 관리하고자 했다.

특히 우리 프로젝트는 기능 개발 위주로 진행되기 때문에 기능을 기반으로 코드를 묶었을 때 직관적인 설계와 개발이 가능하다.

 

또한 feature based 구조가 지금 코드 베이스가 가진 낮은 응집도와 높은 결합도 문제를 해결하기에 직접적인 방법이라고 판단했다.

 

 

2. 파일명 규칙 및 코드 컨벤션 확립 (워크 플로우 확립)

공통 네이밍 규칙과 파일명, 변수명, 상수명, 함수명, 주석 규칙을 정했다.

# 공통 네이밍 규칙

- 역할과 목적을 알 수 있도록 간결하고 명료하게 작성

# 파일명

#### jsx 파일

- 카멜케이스 (CamelCase)

#### 소스코드 파일 (js, ts, py)

- 스네이크 케이스 (snake_case)

#### 기타 파일 (md 등)

- 스네이크 케이스 (snake_case)

# 변수명

- 사용하는 언어의 규칙을 준수

#### JavaScript

- 카멜 케이스 (camelCase)

#### Python

- 스네이크 케이스 (snake_case)

# 상수명

- 언어 상관 없이 대문자 스네이크 케이스 (SNAKE_CASE)

# 함수명

- 동사 + 명사 + 전치사 구조
  - searchPillByImage
    - search + pill + by + image
- 사용하는 언어의 규칙을 준수

# 주석

- 코드가 어떻게 동작하는지, 무엇을 하는지에 대해서는 작성 금지
- 코드를 간결하게 짤 수 없거나 코드가 복잡하여 코드 자체만으로 판단할 수 없는 경우에만 작성
- 코드의 고칠점이나 유의사항이 있다면 [코드 태그](https://2mukee.tistory.com/599)를 활용

#### 함수 주석
- 함수의 역할만 간단하게 설명

 

 

3. expo migration

기존에 bare react-native 로 개발한 코드를 expo 기반으로 마이그레이션을 진행했다.

 

 

expo로 마이그레이션 한 이유

팀원들이 개발을 하면서 가장 애먹었던 부분들을 개선하여 개발 속도와 유지 보수성을 향상하기 위해 expo로의 마이그레이션을 추진했다.

더군다나 그간 expo를 사용하지 않았떤 이유인 성능과 호환성이 대폭 향상되었다는 소식이 있었기에 사용해보지 않을 수 없었다.

 

 

expo 마이그레이션을 통해 얻을 수 있는 것

- 개발 환경 세팅 단순화

네이티브 레이어를 expo가 관리해주기 때문에 XCode 등 네이티브 환경을 개발자가 관리하지 않아도 된다. 

거기다가 환경변수를 클라우드에 올리고 받을 수 있기 때문에 개발자 간 동일한 개발 환경을 유지할 수 있다.

 

- 네이티브 관리 용이

네이티브 레이어를 expo가 관리해주기 때문에 프로젝트 내에 안드로이드 및 iOS 프로젝트가 불필요하게 된다.

SDK 업데이트 때 마다 네이티브 프로젝트까지 같이 수정하는 곤욕을 치루지 않아도 된다.

 

- 빌드 용이성

React-Natvie CLI로 개발을 하면 안드로이드 빌드와 ios 빌드를 각각 맞춰진 환경에서 해야한다.

expo는 클라우드를 통한 안드로이드 및 iOS 빌드를 제공하기 때문에 iOS 빌드를 위한 맥북이 필요없어지게 된다.

 

- 다양한 네이티브 API 제공

expo는 Camera, Image Picker, Location, Hapics, FileSystem 등 네이티브 기능을 기본적으로 제공한다.

react-native CLI로 했을 때는 안드로이드와 iOS 둘 다 호환되는 네이티브 라이브러리를 찾았어야 했다.

 

 

 

4. 유지보수에 방해가 되던 라이브러리 대체

최신 react-native에서 지원을 하지 않거나, 최신 안드로이드 및 iOS 버전을 지원하지 않는 라이브러리들을 네이티브 API로 대체했다.

 

realm > expo-sqlite

네이티브 의존성이 강하여 안드로이드나 iOS 빌드 시 문제가 되던  realm을 expo에서 기본으로 제공하는 expo-sqlite로 대체했다.

커뮤니티도 축소되고, React-Native에 대한 지원도 중단되는 문제가 있었기 때문에 가장 우선적으로 교체를 진행했다.

 

DB 라이브러리를 대체하면서 앱 내 DB 초기화 구조까지 개선했다.

realm이 파일기반 DB이다 보니, 앱 초기 실행 시 초기 데이터를 클라우드 저장소로 부터 파일로 받아와서 DB를 초기화했었는데,

이렇게 하다보니 스키마가 바뀌면 앱 업데이트를 강제해야하는 문제가 있었다.

 

그리고 클라우드 저장소로 부터 파일을 받아와야하기 때문에 이를 위한 SDK를 따로 사용했어야 했다.

때문에 유지보수에 매우 큰 악영향을 미치고 있었고, SDK에 대한 종속성까지 가지고 있었다.

 

이 두 문제를 해결하기 위해 테이블 기반인 expo-sqlite로 DB 라이브러리를 교체하고,  

DB 초기화 시 스키마와 데이터를 rest API를 통해 받아오는 방식으로 변경했다.

스키마 정보는 SQL 인젝션을 방지하기 위해 임의로 정의한 JSON 구조로 API를 통해 받아오며,

애플리케이션에서 밸리데이션까지 수행한다.

 

초기화는 DROP TABLE 후 CREATE TABLE을 하는 식으로 진행하여 복잡한 로직 없이 스키마 변경과 데이터 업데이트를 수행할 수 있게 개선하였다.

 

그리고 데이터의 용량이 큰 것을 고려하여 데이터를 내려주는 API의 response를 gzip으로 압축하여 비용을 절감하였고, 

페이징을 통해 정해진 횟수 만큼만 API를 호출하게 하였다.

 

 

그 외 라이브러리

- react-native-image-picker > expo-image-picker

- react-native-splash-screen > expo-splash-screen

- react-native-permissions > expo API 별 내부 권한 처리

 

 

 

5. 그 외 개선사항

워크로드 분리 (서버리스 API를 특성에 맞게 분리)

서버리스 API들을 데이터 특성, 비용, 기능 특징을 기준으로 클라우드 플레어와 GCP에 최적 배치시켰다.

 

- AS-IS

read만 수행하며 데이터 변경이 없는 rest API가 클라우드 플레어와 GCP에 나눠져 있다.

 

- TO-BE

read만 수행하고 데이터 변경이 없는 rest API를 GCP 서버리스에만 배치

CRUD가 발생하는 API의 경우 클라우드 플레어 D1과 workers에만 배치

 

 

로깅 기능 도입

사용자의 휴대폰에서 발생하는 오류와 애플리케이션 개발 간 발생하는 오류를 로깅하기 위해 서버리스 서비스를 활용하여 로깅 API를 구현하여 도입했다.

 

 

클라이언트 테스트 도입

기존에 서버리스 API의 클라이언트 테스트를 위해 별도의 레포지터리를 두는 대신,

애플리케이션 레포지터리 내에 테스트 스크립트를 작성하고 jest를 통해 테스트를 수행 할 수 있도록 개선

이를 통해 애플리케이션을 개발하면서 API 요청 동작까지 같이 확인이 가능해져 개발 속도를 개선할 수 있다.

 

 

스크린 플로우 개선

스크린 플로우가 정립되어있지 않음으로 인해 화면 전환 타이밍이 꼬이는 등의 문제가 있었다.

이에대해 스크린 플로우 다이어그램을 작성하여 리팩토링 간 원활한 화면 전환이 이루어질 수 있도록 요청했다.  

 

 

추상화 수준 통일

팀원 간 원활한 의사소통을 위해 기능명과 같은 용어를 document 레포지터리에 정리하여 통일했다.

 

 

 

 

 

느낀점

두서없이 개발하다보니 기술부채가 많이 쌓였고, 이를 땜빵식으로 고치다보니 부채가 부채를 낳은 격이 되었다.

이를통해 프로젝트 방향과 목적에 맞는 구조의 중요성을 많이 느꼈고,

기술부채와 복잡성이 쌓이기전에 항상 고민하고 개발을 해야겠다는 생각이 들었다.

 

리팩토링을 통해 "프로젝트 목적에 맞는", "프로젝트 조건에 맞는" 방법론에 대해서 고민하는 시간을 가질 수 있었다.

직접 디렉터리 구조도 짜보고, 시스템 아키텍처도 새로 그려봤다.

그리고 원활한 소통을 위해 다이어그램을 작성하고 규격화되고 통일된 문서를 작성하면서 소통의 방법을 배울 수 있었다.

 

300x250
728x90