44장. 리팩터링: 되는 코드를 읽히는 코드로 바꾸기
![[8부] 리팩터링: 되는 코드를 읽히는 코드로 바꾸기](https://images.tilnote.io/pages/8224ea1f-3798-47b1-8484-604923b08a06.webp)
![[8부] 리팩터링: 되는 코드를 읽히는 코드로 바꾸기](https://images.tilnote.io/pages/2ca9fabd-b83f-40d1-bcec-8214af5e4318.webp)
![[8부] 리팩터링: 되는 코드를 읽히는 코드로 바꾸기](https://images.tilnote.io/pages/7024f695-a012-4777-877a-08ef31368246.webp)
일단 되게 만든 다음, 이제 읽히게 만들어야 한다
처음 서비스를 만들 때는 속도가 중요하다. 화면이 뜨고, 로그인되고, 글이 저장되면 정말 뿌듯하다. 마치 빈 방에 급하게 책상, 의자, 조명을 들여놓고 "살 수는 있다!"라고 외치는 느낌이다.
문제는 그다음부터다. 코드는 한 번 쓰고 끝나지 않는다. 버튼 문구를 바꾸고, 검색 조건을 바꾸고, 권한을 다듬고, 버그를 고치고, 기능을 붙인다. 이때 코드를 다시 열었는데 내가 쓴 코드가 낯설게 느껴지면 꽤 당황스럽다.
"이 변수는 왜 이렇게 이름을 지었지?" "이 함수는 왜 이렇게 길지?" "같은 로직이 여기저기 복사되어 있네?"
바로 이 순간 필요한 것이 리팩터링이다.
리팩터링은 기능은 그대로 두고, 코드를 더 이해하기 쉽고 고치기 쉽게 정리하는 작업이다. 겉으로 보기엔 같은 서비스인데, 내부를 더 질서 있게 다듬는 일이다. 집으로 비유하면 벽을 새로 짓는 공사라기보다, 서랍을 정리하고, 라벨을 붙이고, 자주 쓰는 물건을 손이 닿는 곳으로 옮기는 작업에 가깝다.
이 장의 핵심은 단순하다. 돌아가는 코드에서 멈추지 말고, 다시 읽을 수 있는 코드까지 가야 진짜 완성이라는 점이다.
리팩터링은 새 기능 추가가 아니다
초보자가 가장 많이 헷갈리는 부분이 있다. 리팩터링을 하다가 기능까지 같이 바꾸는 것이다.
예를 들어 글 작성 폼 코드를 정리하려고 들어갔는데, 갑자기 버튼 디자인을 바꾸고, 입력 검증 규칙을 바꾸고, 저장 방식까지 건드리기 시작하면 리팩터링이 아니라 작은 개편이 되어 버린다. 그러면 무엇 때문에 문제가 생겼는지 찾기 어려워진다.
리팩터링의 원칙은 이렇다.
기능은 그대로 둔다. 사용자가 느끼는 동작은 바뀌지 않는다. 대신 코드를 더 잘 읽히게 만들고, 중복을 줄이고, 구조를 분명하게 만든다.
즉, 이런 변화는 리팩터링에 가깝다.
data,item,temp같은 애매한 이름을 더 분명한 이름으로 바꾸기길어진 함수를 여러 개의 작은 함수로 나누기
중복된 UI를 공통 컴포넌트로 분리하기
화면 코드 안에 섞인 계산 로직을 별도 함수로 꺼내기
한 파일에 몰린 책임을 나누기
반대로 이런 변화는 리팩터링만으로 보기 어렵다.
로그인 방식을 바꾸기
검색 기능 자체를 새로 추가하기
저장 규칙이나 권한 규칙을 바꾸기
사용자 화면 흐름을 변경하기
물론 실제 작업에서는 둘이 섞일 때도 있다. 하지만 초보자일수록 리팩터링과 기능 변경을 가능하면 분리하는 습관이 중요하다. 그래야 코드가 덜 무섭다.
왜 읽히는 코드가 중요한가
서비스는 처음 만드는 순간보다, 두 번째 열어볼 때부터 진짜 시작된다. 내일의 나, 일주일 뒤의 나, 함께 작업하는 동료, 그리고 AI 도구까지 모두 코드를 다시 읽어야 한다.
읽히는 코드는 여러 면에서 이득이 크다.
버그를 더 빨리 찾을 수 있다.
새 기능을 붙일 때 덜 망가뜨린다.
AI에게 수정 요청을 할 때 맥락 전달이 쉬워진다.
내가 직접 관리할 수 있다는 자신감이 생긴다.
특히 이 책처럼 AI와 함께 개발하는 흐름에서는 더 중요하다. 코드가 지저분하면 Claude Code나 Codex에게 요청할 때도 설명이 길어지고, 도구가 잘못 건드릴 가능성도 커진다. 반대로 구조가 분명하면 "이 컴포넌트는 UI만 담당하고, 이 함수는 검증만 한다"처럼 역할을 쉽게 설명할 수 있다.
좋은 코드는 똑똑해 보이는 코드가 아니다. 다시 봐도 이해되는 코드다.
리팩터링이 필요한 코드에는 냄새가 난다
개발자들은 종종 "코드 스멜"이라는 표현을 쓴다. 정말 냄새가 나는 것은 아니고, 뭔가 더 다듬어야 한다는 신호라는 뜻이다. 초보자에게 필요한 냄새 감각은 몇 가지면 충분하다.
이름이 너무 모호하다
data, result, temp, value, handle, thing 같은 이름은 처음엔 편하다. 하지만 며칠만 지나도 의미가 흐려진다. 이름이 모호하면 코드를 읽는 사람이 머릿속에서 추리 게임을 해야 한다.
예를 들어 이런 코드는 읽기 어렵다.
const data = await getData()
const result = data.filter((item) => item.userId === user.id)이렇게 바꾸면 훨씬 낫다.
const posts = await getPosts()
const myPosts = posts.filter((post) => post.userId === user.id)무슨 데이터를 가져왔는지, 무엇을 걸러냈는지 바로 보인다.
함수가 너무 길다
함수 하나에 입력 검증, API 호출, 성공 메시지, 에러 처리, 화면 이동까지 다 들어가면 처음엔 편해 보여도 나중엔 무거워진다. 한 함수가 너무 많은 일을 하면 수정 범위도 커지고, 어디서 문제가 났는지 찾기 어려워진다.
같은 코드가 여러 군데 있다
복사해서 붙여 넣는 순간은 빠르다. 하지만 같은 로직이 세 군데에 흩어지면 나중에 한 군데만 고치고 두 군데는 놓치기 쉽다. 특히 버튼 스타일, 입력 검증, 날짜 포맷, 에러 메시지 조립 같은 부분에서 이런 일이 자주 생긴다.
화면과 로직이 한데 뭉쳐 있다
JSX 안에 조건문이 너무 많고, 데이터 가공까지 섞이고, 클릭 이벤트마다 긴 로직이 붙어 있으면 화면 파일이 점점 숨이 차오른다. 화면은 보여 주는 일에 집중하고, 계산이나 정리는 따로 빼는 편이 읽기 쉽다.
조건문이 너무 복잡하다
if가 여러 번 중첩되면 머리가 금방 꼬인다. 로그인 상태, 작성자 여부, 관리자 여부, 공개 여부까지 한 덩어리로 섞이면 읽는 사람이 길을 잃는다.
코드 냄새를 본다고 해서 당장 모든 걸 갈아엎을 필요는 없다. 다만 "아, 여기는 다음에 정리할 지점이구나" 하고 표시를 해둘 수 있으면 이미 큰 발전이다.
리팩터링의 첫걸음은 이름 바꾸기다
가장 쉬우면서 효과가 큰 리팩터링은 이름 정리다. 변수, 함수, 컴포넌트, 파일 이름만 다듬어도 코드 가독성이 크게 올라간다.
좋은 이름은 길이가 아니라 역할을 말해 준다. 짧은데도 의미가 분명하면 좋고, 조금 길어도 역할이 보이면 괜찮다.
예를 들어 아래 이름은 아쉽다.
datainfodoSubmitpage2utils
이런 식이면 더 읽기 쉽다.
postListuserProfilesubmitPostFormPostDetailPageformatDate
특히 함수 이름은 "무엇을 하는가"가 보여야 한다. handleThing보다 handleDeletePost, fetchUserPosts, validateLoginForm처럼 쓰는 편이 훨씬 낫다.
이름을 잘 짓는 요령은 어렵지 않다. 이 코드를 처음 보는 사람이 "이건 무슨 역할이지?"라고 묻기 전에 답이 보이면 된다.
긴 함수를 쪼개면 코드가 숨을 쉰다
리팩터링에서 두 번째로 체감이 큰 작업은 긴 함수를 쪼개는 것이다. 특히 폼 제출 함수에서 자주 효과를 본다.
예를 들어 처음에는 이런 코드가 생기기 쉽다.
async function handleSubmit() {
if (!title.trim()) {
setError('제목을 입력하세요.')
return
}
if (!content.trim()) {
setError('내용을 입력하세요.')
return
}
setLoading(true)
setError('')
const { error } = await createPost({ title, content })
if (error) {
setError('글 저장에 실패했습니다.')
setLoading(false)
return
}
setLoading(false)
router.push('/posts')
}이 정도는 아직 괜찮아 보일 수 있다. 하지만 여기에 태그 검증, 이미지 업로드, 성공 토스트, 로깅까지 붙기 시작하면 금방 복잡해진다.
이럴 때는 역할별로 분리하면 좋다.
function validatePostForm(title: string, content: string) {
if (!title.trim()) return '제목을 입력하세요.'
if (!content.trim()) return '내용을 입력하세요.'
return null
}
async function savePost(title: string, content: string) {
return createPost({ title, content })
}
async function handleSubmit() {
const validationMessage = validatePostForm(title, content)
if (validationMessage) {
setError(validationMessage)
return
}
setLoading(true)
setError('')
const { error } = await savePost(title, content)
if (error) {
setError('글 저장에 실패했습니다.')
setLoading(false)
return
}
setLoading(false)
router.push('/posts')
}이렇게 나누면 세 가지가 좋아진다. 검증 로직을 따로 테스트하기 쉽고, 저장 로직을 다른 곳에서 재사용할 수 있고, handleSubmit은 흐름만 읽으면 된다.
좋은 함수는 한 번에 모든 걸 보여 주는 함수가 아니라, 읽는 사람이 단계별로 이해할 수 있게 해 주는 함수다.
중복 제거는 유지보수 비용을 줄이는 가장 현실적인 방법이다
리팩터링의 핵심 목표 중 하나는 중복 줄이기다. 중복은 단순히 보기 싫어서 문제가 아니다. 나중에 바꿀 때 비용이 커져서 문제다.
예를 들어 버튼 스타일이 여기저기 흩어져 있다고 해 보자.
<button className="px-4 py-2 rounded bg-blue-600 text-white">저장</button>
<button className="px-4 py-2 rounded bg-blue-600 text-white">수정</button>
<button className="px-4 py-2 rounded bg-blue-600 text-white">등록</button>당장은 괜찮다. 하지만 나중에 색상, 여백, 비활성화 스타일을 바꾸려면 모든 파일을 돌아다녀야 한다.
이럴 때 공통 버튼 컴포넌트를 만들 수 있다.
type PrimaryButtonProps = {
children: React.ReactNode
disabled?: boolean
} & React.ButtonHTMLAttributes<HTMLButtonElement>
export function PrimaryButton({ children, disabled, ...props }: PrimaryButtonProps) {
return (
<button
className="px-4 py-2 rounded bg-blue-600 text-white disabled:opacity-50"
disabled={disabled}
{...props}
>
{children}
</button>
)
}이제 사용하는 쪽은 훨씬 간결해진다.
<PrimaryButton>저장</PrimaryButton>
<PrimaryButton>수정</PrimaryButton>
<PrimaryButton disabled={loading}>등록</PrimaryButton>중복 제거는 화려하지 않다. 하지만 서비스가 커질수록 가장 큰 차이를 만든다. 한 군데를 바꿨는데 전체가 같이 좋아지는 경험은 생각보다 짜릿하다.
화면 파일은 보여 주는 일에 집중시키자
초보자가 만든 프로젝트를 보면 page.tsx나 큰 컴포넌트 파일 안에 모든 것이 몰려 있는 경우가 많다. 데이터 가져오기, 정렬, 필터링, 버튼 이벤트, 검증, 렌더링까지 한 파일에서 다 해낸다. 처음에는 빠르지만, 나중에는 파일을 열기만 해도 숨이 막힌다.
이럴 때는 관심사를 나누는 감각이 필요하다.
예를 들어 게시글 목록 화면이라면 이렇게 나눠 볼 수 있다.
화면 컴포넌트: 무엇을 보여 줄지 결정한다.
데이터 로딩 함수: 어디서 데이터를 가져올지 담당한다.
정렬/필터 함수: 데이터를 어떻게 가공할지 담당한다.
공통 UI 컴포넌트: 카드, 버튼, 빈 상태 화면 같은 반복 UI를 담당한다.
예를 들어 처음에는 이런 식일 수 있다.
export default function PostListPage() {
const [search, setSearch] = useState('')
const posts = usePosts()
const filteredPosts = posts
.filter((post) => post.title.includes(search) || post.content.includes(search))
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)} />
{filteredPosts.length === 0 ? (
<p>검색 결과가 없습니다.</p>
) : (
filteredPosts.map((post) => <PostCard key={post.id} post={post} />)
)}
</div>
)
}이 정도는 괜찮지만, 검색 규칙이 늘어나고 정렬 조건이 추가되면 화면 파일이 무거워진다. 이럴 때는 가공 로직을 빼낼 수 있다.
export function getVisiblePosts(posts: Post[], searchKeyword: string) {
return posts
.filter((post) => {
const keyword = searchKeyword.toLowerCase()
return (
post.title.toLowerCase().includes(keyword) ||
post.content.toLowerCase().includes(keyword)
)
})
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
}화면 쪽은 더 또렷해진다.
export default function PostListPage() {
const [searchKeyword, setSearchKeyword] = useState('')
const posts = usePosts()
const visiblePosts = getVisiblePosts(posts, searchKeyword)
return (
<div>
<input value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)} />
{visiblePosts.length === 0 ? (
<p>검색 결과가 없습니다.</p>
) : (
visiblePosts.map((post) => <PostCard key={post.id} post={post} />)
)}
</div>
)
}기능은 같지만 읽는 속도가 빨라진다. 리팩터링의 좋은 신호는 대부분 여기서 보인다. 코드를 이해하는 데 걸리는 시간이 줄어든다.
컴포넌트는 작게 쪼개되, 억지로 쪼개지는 말자
리팩터링을 배우면 모든 것을 컴포넌트로 쪼개고 싶어진다. 하지만 너무 잘게 쪼개면 오히려 파일이 늘어나고 흐름이 끊긴다.
좋은 기준은 "이 조각이 반복되는가?"와 "이름만 봐도 역할이 분명한가?"다.
예를 들어 아래 같은 것은 분리할 가치가 있다.
여러 화면에서 반복되는 버튼, 입력창, 모달
게시글 카드, 사용자 프로필 카드 같은 재사용 UI
빈 상태 안내, 로딩 상태, 에러 메시지 블록
반면 한 화면에서 한 번만 쓰이고, 분리해도 더 이해하기 쉬워지지 않는다면 굳이 빼지 않아도 된다.
컴포넌트 분리는 파일 수를 늘리는 기술이 아니다. 의미 단위를 또렷하게 만드는 기술이다.
리팩터링은 작은 단위로 해야 안전하다
초보자가 가장 위험하게 하는 리팩터링은 "오늘 싹 정리해 버리자" 모드다. 파일 여러 개를 한꺼번에 옮기고, 함수 이름을 다 바꾸고, 폴더 구조를 갈아엎으면 중간에 길을 잃기 쉽다.
안전한 리팩터링은 작게 나눈다.
1단계: 불편한 지점을 한 개만 정한다
예를 들어 "글 작성 폼의 검증 로직이 너무 지저분하다"처럼 한 가지 문제만 잡는다.
2단계: 바꾸기 전 동작을 확인한다
직접 눌러 보거나, 이미 만들어 둔 테스트가 있다면 먼저 돌려 본다. 리팩터링은 청소이지만, 청소하다가 냉장고를 망가뜨리면 곤란하다.
3단계: 한 번에 한 가지 수정만 한다
이름 바꾸기, 함수 분리, 중복 제거를 동시에 몰아서 하기보다 하나씩 처리하는 편이 좋다.
4단계: 바로 다시 확인한다
수정 후에는 즉시 실행해 본다. "나중에 한꺼번에 확인하지 뭐"가 사고를 만든다.
5단계: 커밋을 작게 남긴다
게시글 폼 검증 함수 분리, 공통 버튼 컴포넌트 추출처럼 변경 이유가 보이는 커밋이 좋다. 나중에 되돌리기도 쉽다.
리팩터링에서 중요한 것은 멋진 결과보다 안전한 과정이다.
테스트는 리팩터링의 안전벨트다
앞 장에서 살펴본 테스트는 여기서 진가를 발휘한다. 리팩터링은 기능을 안 바꾸는 작업이지만, 실제로는 실수로 동작을 건드릴 수 있다. 그래서 테스트가 있으면 훨씬 덜 불안하다.
예를 들어 입력 검증 함수를 따로 뺐다면, 그 함수에 붙어 있는 테스트가 여전히 통과하는지 보면 된다. 게시글 작성 핵심 흐름에 대한 스모크 테스트가 있다면, 리팩터링 후에도 한 번 돌려 보면 된다.
물론 테스트가 없더라도 리팩터링은 할 수 있다. 다만 그럴수록 더 작게, 더 자주 확인해야 한다. 수동 체크리스트라도 충분히 도움이 된다.
리팩터링의 감정은 원래 이렇다. "이거 바꿔도 괜찮을까?" 테스트는 그 질문에 "한번 확인해 보자"라고 답하게 해 준다.
우리 프로젝트에서 먼저 리팩터링할 만한 곳
이 책의 실습 프로젝트를 기준으로 보면, 초보자가 가장 먼저 정리하기 좋은 지점은 비교적 분명하다.
폼 입력 검증
회원가입, 로그인, 글 작성, 글 수정 폼에는 비슷한 검증 로직이 반복되기 쉽다. 비어 있는지 확인하고, 길이를 확인하고, 에러 메시지를 보여 주는 구조가 비슷하다. 이런 부분은 함수로 빼기 좋다.
API 호출 처리
여러 화면에서 loading, error, success 흐름이 비슷하게 반복된다. 공통 함수나 커스텀 훅으로 정리할 여지가 있다.
목록 카드 UI
게시글 카드, 버튼, 배지, 빈 상태 메시지처럼 반복되는 UI는 공통 컴포넌트화하기 좋다.
날짜와 문자열 포맷
createdAt을 보기 좋게 바꾸는 코드, 긴 본문을 잘라 미리보기로 보여 주는 코드 등은 유틸 함수로 분리하면 재사용성이 높아진다.
권한 조건문
내 글이면 수정 버튼 보이기, 비로그인 상태면 리다이렉트 같은 조건이 여기저기 퍼져 있다면 이름 붙은 함수로 정리하는 편이 읽기 쉽다.
예를 들어 이런 코드는,
const canEdit = user && post && user.id === post.userId처음엔 괜찮지만 프로젝트가 커질수록 의도를 이름으로 드러내는 편이 낫다.
function isPostOwner(userId: string | undefined, postUserId: string | undefined) {
return !!userId && !!postUserId && userId === postUserId
}
const canEdit = isPostOwner(user?.id, post?.userId)이렇게 하면 "무엇을 비교하는가"보다 "왜 비교하는가"가 더 잘 보인다.
AI에게 리팩터링을 맡길 때는 목표와 금지선을 같이 줘야 한다
Claude Code나 Codex는 리팩터링에도 꽤 유용하다. 다만 "코드 예쁘게 정리해 줘"라고 하면 너무 넓다. 도구가 과하게 바꾸거나, 내가 의도하지 않은 구조 변경까지 해버릴 수 있다.
좋은 요청은 범위를 좁히고, 금지선을 분명히 둔다.
예를 들면 이런 식이다.
이 파일에서 기능은 바꾸지 말고 리팩터링만 해 줘. 목표는 세 가지다. 첫째, 함수 이름을 더 명확하게 바꾸기. 둘째, 중복된 입력 검증 로직을 별도 함수로 추출하기. 셋째, 50줄이 넘는 핸들러 함수를 작은 함수로 나누기. UI 문구, API 경로, 데이터 구조는 바꾸지 마.
또는 이렇게 요청할 수 있다.
PostForm.tsx파일만 대상으로 작업해 줘. 기능 변경 없이 가독성을 높이고 싶다. 리팩터링 후에는 변경 이유를 항목별로 설명하고, 내가 직접 확인해야 할 동작 체크리스트도 같이 적어 줘.
이런 요청이 좋은 이유는 도구에게 자유를 주되, 서비스의 경계를 지키게 하기 때문이다.
특히 AI가 리팩터링한 뒤에는 꼭 아래를 확인해야 한다.
실제로 기능이 그대로 동작하는가
이름은 좋아졌지만 오히려 파일 구조가 과하게 복잡해지지 않았는가
한 파일에서만 해결할 수 있는 문제를 괜히 여러 파일로 흩뜨리지 않았는가
설명은 그럴듯한데, 코드가 실제 프로젝트 맥락과 맞는가
AI는 정리를 빨리 도와줄 수 있지만, 최종 판단은 여전히 사람의 몫이다.
읽히는 코드에는 공통된 특징이 있다
좋은 코드를 한 줄로 정의하기는 어렵지만, 읽히는 코드에는 비슷한 특징이 있다.
첫째, 이름만 봐도 역할이 보인다. 둘째, 한 함수가 너무 많은 일을 하지 않는다. 셋째, 비슷한 로직이 흩어져 있지 않다. 넷째, 화면과 계산 로직이 적절히 나뉘어 있다. 다섯째, 수정할 때 어디를 봐야 할지 감이 온다.
반대로 읽기 어려운 코드는 대체로 이런 느낌을 준다. "일단 작동은 하는데, 어디서부터 손대야 할지 모르겠다."
리팩터링의 목적은 완벽한 아름다움이 아니다. 수정 지점을 예측 가능하게 만드는 것이다.
과한 리팩터링은 오히려 독이 된다
리팩터링이 좋다고 해서 무조건 많이 할수록 좋은 것은 아니다. 초보자에게 특히 위험한 패턴은 두 가지다.
첫째, 아직 바뀔 가능성이 큰 코드를 너무 일찍 일반화하는 것이다. 예를 들어 버튼 종류가 두 개밖에 없는데 거대한 공통 시스템을 만들면 오히려 복잡해질 수 있다.
둘째, 지금 불편한 문제보다 미래의 상상 속 문제를 해결하려 드는 것이다. "나중에 엄청 커질 수도 있으니까"라는 이유로 구조를 너무 크게 잡으면 현재의 이해도가 떨어진다.
좋은 리팩터링은 현재의 불편을 줄인다. 아직 오지 않은 거대한 미래를 대비하는 것도 의미는 있지만, 입문 단계에서는 지금 반복되는 문제를 먼저 해결하는 것이 더 중요하다.
리팩터링 전후를 비교하면 감이 빨리 잡힌다
리팩터링은 설명만 들으면 추상적으로 느껴질 수 있다. 그래서 가장 좋은 훈련은 "전과 후를 비교해 보는 것"이다.
예를 들어 전에는 이런 상태였다고 해 보자.
게시글 폼에서 검증, 저장, 에러 처리, 이동이 한 함수에 몰려 있다.
버튼 스타일이 세 파일에 복사되어 있다.
변수 이름이
data,item,res처럼 모호하다.검색 로직이 화면 파일 안에 길게 들어 있다.
리팩터링 후에는 이렇게 달라질 수 있다.
검증 함수와 저장 함수가 분리되어 있다.
공통 버튼 컴포넌트를 사용한다.
변수 이름이
posts,filteredPosts,validationMessage처럼 명확하다.화면은 렌더링에 집중하고, 검색 로직은 별도 함수로 빠져 있다.
기능은 그대로다. 하지만 코드를 읽는 속도와 수정의 안정감이 달라진다. 이것이 리팩터링의 보상이다.
오늘 바로 해볼 수 있는 현실적인 리팩터링 순서
처음부터 대공사를 하지 말고, 아래 순서로 시도해 보자.
가장 자주 열어보는 파일 하나를 고른다.
애매한 이름 3개만 먼저 바꾼다.
너무 긴 함수 하나를 두세 개의 작은 함수로 나눈다.
복사된 코드 한 덩어리를 공통 함수나 공통 컴포넌트로 뺀다.
실행해서 동작이 그대로인지 확인한다.
변경 내용을 작은 커밋으로 남긴다.
이 정도만 해도 충분히 훌륭한 리팩터링이다. 중요한 것은 거창함이 아니라 반복 가능성이다.
이 장의 핵심 정리
리팩터링은 코드를 다시 쓰는 일이 아니라, 기능은 유지한 채 구조를 더 이해하기 쉽게 다듬는 일이다. 이름을 명확하게 바꾸고, 긴 함수를 나누고, 중복을 줄이고, 화면과 로직을 정리하는 것만으로도 유지보수 난이도는 크게 내려간다.
특히 초보자에게 리팩터링은 "개발자답게 보이는 기술"이 아니라 "내 코드를 내가 다시 읽을 수 있게 만드는 습관"이어야 한다. 이 습관이 생기면 다음 기능을 추가할 때 덜 겁나고, 버그를 고칠 때도 덜 헤맨다.
그리고 AI와 협업할수록 리팩터링의 가치는 더 커진다. 구조가 분명한 코드는 Claude Code나 Codex와 대화하기도 쉽고, 검토하기도 쉽다. 결국 좋은 리팩터링은 화려한 정리가 아니라, 내일의 나와 AI가 함께 이해할 수 있는 코드로 바꾸는 일이다.
돌아가는 코드는 출발선이다. 다시 읽을 수 있는 코드가 되어야, 그 서비스는 비로소 다음 단계로 나아갈 수 있다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.