본문으로 건너뛰기

스프린트 개선 실패와 성공담

· 약 22분

회사에서 개발 스프린트를 진행하던 중 문제와 불편한 점이 생겼습니다. 이를 개선하려고 무리하게 속도를 높였지만, 오히려 스프린트가 지연되었습니다. 이 경험을 바탕으로 스프린트를 개선하기 위한 과정을 정리했습니다.

도입, 계기

회사의 기존 개발 스프린트 주기는 주말을 포함해 총 8일이었습니다. 이번주 월요일에 기획한 내용을 다음주 월요일에 반영하는 구조였고, 일반적으로 금요일날을 목표로 코드 작업을 마무리하고 월요일날 남은 작업 후 배포와 QA를 진행하곤 했습니다. 하지만 이 구조 속에서, 몇 가지 불편함이 계속 반복되었습니다.

  • 금요일까지 개발을 끝내지 못하면 주말에 시간을 써야 했습니다.
    • 이로 인해 개발 속도에 집중하게 되었고, 코드 퀄리티가 저하되었습니다.
    • 낮아진 코드 퀄리티는 앞으로의 스프린트 생산성을 떨어뜨렸습니다.
  • 금요일에 개발을 마치더라도, 주말 동안 작업에서 멀어지면서 일부 프로세스나 맥락, 동기 등을 잊는 일이 생겼습니다.
    • 그래서 QA를 진행할 때, 버그 수정이나 기획 변경을 반영하는 데 리마인드 시간이 더 들었습니다.
  • 팀원 모두 "월요일까지면 된다"는 안도감과, "그래도 금요일까지는 끝내야 한다"는 압박감이 함께 작용했습니다.
    • 이 모순된 마인드셋이 작업의 집중도와 책임감을 떨어뜨렸습니다.

그래서 “지금 방식보다 더 나은 스프린트 구조가 있을까?” 하는 고민이 생겼습니다. 사실 입사 초기에 이 문제의식을 가지고 개선을 시도한 적도 있었습니다.

당시에는 금요일까지 개발을 마무리하고, 개발 서버에 배포한 뒤 바로 QA를 요청드렸습니다. 하지만 일정에 맞추기 위해 서두르다 보니 개발 과정에서 놓친 문제가 예상보다 컸고, QA 범위도 넓어졌습니다. 결국 버그와 기획 변경이 몰려들면서, 다음 주 월요일이 되어도 스프린트를 완전히 마무리하지 못했습니다.

이 경험을 통해 많은 것을 배웠습니다. 무엇보다 책임감이 부족했고, 문제의 원인을 지나치게 단순하게 해석했다는 점을 깨달았습니다. 이를 계기로, 스프린트를 정해진 기간 안에 효율적으로 마무리하기 위해, 그리고 더 줄여나가기 위한 계획을 세우고 하나 씩 실행하게 되었습니다.

원인 파악

스프린트가 이렇게 촉박해진 원인을 찾아보았습니다. 기획팀과 프로덕트팀, 팀원들과 소통하면서 얻어낸 원인은 다음과 같았습니다.

  1. 기획 측에서 서비스 런칭 직전까지 페이지의 에셋이나 타이포 수정 요청이 잦았습니다. 이에 적절히 대응하지 못했습니다.
  2. 그동안 축적된 레거시 코드와 기존 개발 방식도 일정 지연의 원인이었습니다.

이 원인을 조금씩 줄이기 위한 노력들을 이야기 해보겠습니다.

파이프라인 최적화

서비스 런칭을 앞두고 기획 측에서 페이지의 에셋이나 타이포 수정 요청이 자주 발생했습니다. 그에 제대로 대응하지 못한 것이 스프린트 일정이 꼬이는 핵심 원인 중 하나라고 판단했습니다.

실제로 코드를 수정하는 데는 10초도 걸리지 않습니다. 문제는 프로젝트 특성상 CRA 개발 서버를 띄우고, 수정을 적용한 뒤 CI/CD 과정을 거치기까지 많은 시간이 소요된다는 점이었습니다.

이 때문에 수정 요청이 들어와도 한 번에 처리하려고 미루게 되었고, 그 사이 요청을 잊거나 다른 작업과 순서가 엉키는 문제가 자주 발생했습니다. 이 과정을 겪으며 빌드 최적화의 필요성을 절실히 느꼈고, 이에 따라 아래와 같은 최적화 작업을 진행했습니다.

빌드 도구 변경

더 빠른 개발 및 빌드 환경을 만들기 위해 기존 CRA(Webpack) 대신 Vite로 빌드 도구를 변경했습니다. Vite는 ESM(ECMAScript Module) 기반으로 빠른 빌드 속도를 제공하지만, 브라우저 호환성 측면에서는 Webpack보다 제한이 있을 수 있습니다.(Vite 브라우저 호환성 참고)

그럼에도 불구하고, 빌드 속도 향상과 작업 효율을 위해 변경이 필요하다고 판단했습니다. CTO님과 팀원들에게 아래와 같은 근거로 변경을 제안했고, 긍정적인 동의를 얻어 도입할 수 있었습니다.

  • 기존 프로젝트는 Webpack만큼 세밀한 설정을 요구하지 않았습니다.
    • 앞으로도 Webpack이 필요한 정도의 복잡한 설정이 생길 가능성은 낮다고 보았습니다.
  • 브라우저 호환성 문제를 대비한 플랜을 마련했습니다.
  • 호환성 문제가 발생할 수 있는 사용자 층을 별도로 모니터링할 수 있도록 준비했습니다.
    • 봇을 제외하면, 실제 사용자 약 13,000명 중 2명 정도만이 호환성 문제를 겪을 수 있다는 데이터를 제시했습니다.
이미지

덕분에 CD 과정에서 약 30초 시간 단축을 가져올 수 있었습니다.

이미지
패키지 매니저 변경

기존에는 Yarn을 사용했지만, CI 시간 단축을 위해 Yarn 버전을 올려 Yarn Berry 도입을 제안했습니다. 업그레이드 후, 초기 패키지 설치 속도와 CI 시간은 눈에 띄게 단축되었습니다.

하지만 프로젝트가 다양한 라이브러리에 의존하고 있어, Yarn Berry의 PnP(Plug'n'Play) 호환성을 맞추는 데 상당한 시간이 소요되었습니다. 예를 들어, VSCode Stylelint Extension과의 호환 문제처럼 예상치 못한 이슈가 발생했습니다.

앞으로 라이브러리를 더 추가하고, 팀 차원에서 패키지를 관리하는 상황을 고려했을 때, CI 시간 단축 이상의 개발 효율 저하가 발생할 수 있다고 판단했습니다. 그래서 최종적으로 pnpm을 선택하게 되었습니다.

pnpm 또한 전역 저장소에 패키지를 저장하는 특징 때문에 설치 속도를 크게 단축할 수 있었습니다. 그리고 Github Actions에 아래와 같은 의존성 설치 캐시 설정을 추가해 CI 시간도 단축할 수 있었습니다.

CI workflow 일부
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

캐시의 유무 차이는 약 20초 정도의 시간 단축을 가져왔습니다.

이미지
페이지 리소스 최적화

근본적으로 페이지 리소스를 줄이기 위한 작업도 함께 진행했습니다.

프로젝트의 의존성을 하나하나 확인하며, 레거시로 남은 불필요한 라이브러리를 제거했습니다. 또한 일부 기능만 사용하는 라이브러리는 외부 의존성을 줄이기 위해 자체 구현으로 점진적으로 대체해 나갔습니다.

이 과정을 통해 페이지 리소스를 절반 가까이 줄일 수 있었습니다.

이미지

이 과정에서 개발 환경과 CI/CD 속도 모두 개선되었고, 특히 CI/CD 시간은 약 1분 정도 단축할 수 있었습니다. 덕분에 수정 요청이 들어왔을 때 즉시 반영할 수 있는 여유가 생겼습니다.

요청 사항이 하루에 20번 이상도 있었기 때문에, 1분이라는 절감이 반복되면서 결과적으로 큰 시간의 차이로 돌아왔습니다. 그 결과 기획 변경에도 빠르게 대응할 수 있었고, 반복되던 일정 지연 문제 역시 눈에 띄게 줄일 수 있었습니다.

구조 개선과 공용 로직

그동안 축적된 레거시 코드와 비일관적인 개발 방식도 일정 지연의 주요 원인이었습니다.

다행히 입사 후 가장 먼저 한 일이 개발 컨벤션을 정리하고 쌓아가는 작업이었습니다. 기초적인 코드 리뷰 문화를 바탕으로 컨벤션을 구체화했고, 현재는 이를 Lint 규칙으로 정착시키는 단계에 도달했습니다. 덕분에 개발 방식은 점차 일관성과 효율을 갖춰 가고 있다고 판단했습니다.

이에 따라 기존의 레거시 코드를 정비하고, 공용 로직을 확장해 기능 개발에 드는 시간을 줄이고자 했습니다.

이 과정에서 페이지 구조를 컨벤션에 맞게 정비했고, 시간이 날 때마다 수정 빈도가 높은 페이지부터 우선적으로 리팩토링했습니다.

프로젝트 전반에 걸친 변화는 크게 두 가지로 정리할 수 있습니다.

API 처리

레거시 코드에서 가장 문제가 된 부분은 API 처리 방식이라고 판단했습니다. 그래서 API 처리 전반을 우선적으로 고쳐 나갔습니다.

  • 먼저, API와 가장 밀접하게 연결된 tanstack-query 관련 로직을 더 효율적으로 개선했습니다.

  • 또한, API에 사용되는 타입과 별도로 선언된 타입들이 뒤얽힌 구조를 분리했습니다.

    • types 폴더 아래 도메인별 폴더를 생성하고, API의 요청과 응답에 쓰이는 타입을 모아 remote.ts 파일에 두었습니다.
    • API 값에서 파생되는 client 측 타입은 client.ts 파일에 정리했습니다.
디자인 시스템 구축

UI/UX 시안을 받을 때마다, 디자인이 미묘하게 다른 컴포넌트를 전달받거나 글자 색상처럼 사소한 요소가 바뀌는 경우가 자주 있었습니다. 여러 디자이너가 프로젝트를 거쳐 가면서 디자인의 통일성이 점점 무너지고 있다는 문제의식이 들었습니다.

그래서 회사에서 새로운 비즈니스 모델을 제시할 시점에, 브랜딩에 맞춘 일관된 사용자 경험을 제공하고 기능 단위 개발 시간을 줄이기 위해 디자인 시스템 구축을 제안했습니다.

이전에 구조 개선과 지표 개선을 이끌었던 경험이 인정받았고, 디자이너도 제안에 공감하며 적극적으로 참여해 주었습니다. 덕분에 디자인 시스템 구축을 본격적으로 시도할 수 있었습니다.

한 번에 전체를 만들기보다는, 색상, 글자 서식, 아이콘 등 기본 요소부터 시작해, 현재 프로젝트에서 자주 사용하는 컴포넌트를 중심으로 우선순위를 정해 단계적으로 작업을 진행했습니다.

1. variables 적용

figma에서 만든 variables를 css variables로 활용하기 위해 figma의 token studio plugin을 활용했습니다. token을 받으면 아래와 같은 형태로 나오게 됩니다.

token 예시
{
"dots-blue": {
"0": {
"value": "#f5f5ff",
"type": "color",
"parent": "palette/Mode 1",
"description": ""
},
"5": {
"value": "#ebebff",
"type": "color",
"parent": "palette/Mode 1",
"description": ""
},
...
},
...
}

해당 토큰을 style-dictionary를 활용해 프로젝트 src root에 variables.css를 생성하도록 했습니다.

코드 예시
const styleDictionary = require('style-dictionary');

const StyleDictionary = styleDictionary
.extend({
source: ['./tokens/**.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'src/',
files: [
{
destination: 'variables.css',
format: 'css/variables',
},
],
},
},
})
.registerFormat({
name: 'css/variables',
formatter: function ({ dictionary }) {
const variablesObject = dictionary.allProperties.reduce((acc, { value, path }) => {
if (path[0] === 'gap' || path[0] === 'radius' || path[0] === 'padding') {
acc[path.join('-')] = `${value}px`;
} else if (path[0] === 'opacity') {
acc[path.join('-')] = `${value / 100}`;
} else {
acc[path.join('-')] = value;
}

return acc;
}, {});

const variablesString = Object.entries(variablesObject)
.map(([key, value]) => `--${key}: ${value};`)
.join('\n');

return `:root {\n${variablesString}\n}`;
},
});

StyleDictionary.cleanAllPlatforms();
StyleDictionary.buildAllPlatforms();

build 스크립트에 이 과정을 진행할 수 있도록 작성해주면 됩니다.

package.json
// 중략...

"build": "tsc && pnpm build:style-dictionary && vite build"

그럼 다음과 같이 css variables 정보를 얻을 수 있습니다.

이미지

2. 공용 컴포넌트 구현

css variables를 활용해 빠르게 공용 컴포넌트를 만들어 나갔습니다. 기본적으로 컴포넌트가 variant 속성을 통해 다양한 스타일을 가질 수 있도록 구현했고, 꼭 필요한 기능이 있을 때에만 로직을 추가하는 방식으로 구성했습니다.

컴포넌트 예시
interface ButtonProps extends ComponentPropsWithRef<'button'> {
size?: 'small' | 'medium' | 'large';
variant?: 'default' | 'filled' | 'outlined-primary' | 'outlined-secondary' | 'outlined-assistive';
type?: 'button' | 'submit' | 'reset';
isSelected?: boolean;
leftIcon?: ReactNode;
rightIcon?: ReactNode;
fullWidth?: boolean;
}

function Button(
{
size = 'medium',
variant = 'default',
type = 'button',
children,
isSelected = false,
leftIcon,
rightIcon,
fullWidth = false,
...props
}: ButtonProps,
ref: ForwardedRef<HTMLButtonElement>,
) {
return (
<S.Button
type={type}
ref={ref}
$size={size}
$variant={variant}
$isSelected={isSelected}
$fullWidth={fullWidth}
{...props}
>
{leftIcon}
<span>{children}</span>
{rightIcon}
</S.Button>
);
}

export default forwardRef(Button);

대체로 스타일 중심의 코드가 많아 작업은 어렵지 않았지만, 일부 컴포넌트는 구현에 시간이 오래 걸렸습니다. 대표적으로 Select 컴포넌트가 가장 기억에 남습니다.

이 두 가지 변화 덕분에 개발 속도는 확연히 빨라졌습니다. 바뀐 API 처리 구조에 익숙해지고, 디자인 시스템 컴포넌트를 활용하게 되면서, Button, Input, Select 같은 반복 요소를 만들 때마다 매번 스타일과 로직을 새로 구현하던 비효율적인 작업이 사라졌습니다. 그 대신, 정해진 규칙과 재사용 가능한 컴포넌트를 기반으로 필요한 부분만 조합하는 방식으로 바뀌면서 기능 개발에 훨씬 더 집중할 수 있게 되었습니다.

5일 스프린트

팀원들이 컨벤션에 익숙해지고, 공용 컴포넌트를 적절히 활용할 수 있게 되면서 이제는 ‘기능’ 자체에 더 집중할 수 있는 환경이 만들어졌습니다.

덕분에 개발 속도는 눈에 띄게 빨라졌고, 코드 퀄리티도 안정적으로 향상되었습니다. 연쇄적으로 PR 이후 코드 리뷰의 양도 줄었고, 리뷰 과정에서도 구현된 기능 자체에 집중하며 더 본질적인 피드백을 주고받을 수 있게 되었습니다.

QA 단계에서도 기술적 이슈가 크게 줄어들었고, 이러한 변화들이 누적되면서 5일 스프린트 체계로도 안정적으로 완료할 수 있는 수준에 도달했습니다.

결론

돌이켜보면, 스프린트를 줄이기 위해 정말 많은 작업을 해왔습니다. 개선 작업은 작은 설정 하나부터 팀의 개발 문화까지 걸쳐 있었고, 무려 3개월 가까운 시간 동안 지속적으로 공을 들일 만큼 쉽지 않은 과정이었습니다.

처음에는 속도를 높이는 것 자체가 목표였고, 그래서 때로는 성급한 결정과 무리한 일정을 밀어붙이기도 했습니다. 하지만 정말 필요한 것은 속도를 내는 것이 아니라, 그 속도를 감당할 수 있는 구조를 만드는 일이었다는 것을 시간이 지나면서 점점 알게 되었습니다.

그리고 이제, 그 구조를 만들어가는 첫 단계를 안정적으로 밟고 있다는 확신을 얻을 수 있었습니다.