[The RED : 프론트엔드 Back to the Basics] 8. 리팩토링

posinity·2023년 6월 22일

리팩토링의 의미

소프트웨어의 겉보기 동작은 그대로 유지한 채,
코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법
[마틴 파울러, 리팩터링 2판, 한빛미디어]

리팩토링은 언제?

3의 법칙 - 돈 로버츠

  1. 처음에는 그냥 한다.
  2. 비슷한 일을 두 번째로 한다면(중복이 생겨 당황스럽겠지만) 그래도 일단은 그냥 진행한다.
  3. 비슷한 일을 세 번째 하면 리팩터링한다. > 자동화가 필요한 때

Code Smell

나쁜 코드에서는 악취가 난다고 하고 이를 Code Smell이라고 부름. 켄트 백과 마틴 파울러가 90년대 사용해서 유명해진 용어.
1. 너무 큰 함수나 클래스
2. 이름이 명확하지 않은 함수나 변수 이름. 지나치게 짧은 것보다는 조금 긴 게 나음. 개인 프로젝트거나 구성원 간 협의만 있으면 차라리 한국어로 작성해도 좋다고 생각. 어차피 번들러와 미니파이어가 다른 이름으로 바꿀 거거든.
3. 중복 코드: 같은 일을 하는 코드가 여기저기 산재해있으면 수정
4. 전역 변수: 가능하면 함수 내 혹은 모듈 내에 변수를 두자
5. 과도한 콜백. 조건문 중첩
6. 과도하게 긴 식별자

테스트 케이스

리팩터링 하기 전에 테스트 케이스부터 작성하자.
리팩토링하면서는 코드 작성에 집중하느라 테스트를 해피 패스(좋은 사례)만 해보고 말기도 함.
테스트없는 리팩터링은 끔찍하다 -> 반드시 테스트 해보기

리팩토링의 방법

  1. 함수 추출 혹은 옯기기: 로직을 별도의 함수나 모듈로 분리
function processPosts() {
    const posts = getPosts();
    ...
    posts.forEach(post => {
        console.log(post);
    });
    ...
}

to

function logPosts(posts) {
    posts.forEach(post => {
        console.log(post);
    });
}

function processPosts() {
    const posts = getPosts();
    ...
    logPosts(posts);
    ...
}
  1. 중간 변수 도입: 어떤 값인지 설명하는 중간 변수 도입.
const user = getUser();
if (user.authKey) {
    ...
}

to

const user = getUser();
const isLoggedIn = Boolean(user.authKey);

if (isLoggedIn) {
    ...
}

조금 복잡한 계산이 필요하다면 함수로 만드는 방법도 있음

const user = getUser();
const purchases = getPurchaseHistory(user);

// 지금까지 구매 내역이 없는 한국어 사용자에게 이벤트 배너 표시
if (user.authKey && user.locale === 'kr' && purchases.length === 0) {
    showEventBanner();
}

to

function isEligibleForEvent(user) {
    const purchases = getPurchaseHistory(user);
    const isLoggedIn = Boolean(user.authKey);
    const isKoreanUser = user.locale === 'kr';
    const hasPurchaseHistory = purchases.length > 0;

    return ( isLoggedIn && isKoreanUser && ! hasPurchaseHistory );
}


const user = getUser();
if (isEligibleForEvent(user)) {
    showEventBanner();
}

이 사용자가 이벤트 배너를 보여주기에 적합한가 명확하게 보여주는 코드로 변경

  1. var가 보이거든 let, const로 변환. let 보다는 const 위주로 사용. let이 보인다면 꼭 필요한 것인지(반드시 변환이 되어야 하는 변수인지) 다시 한 번 고려.

  2. 함수의 사이드 이펙트 최소화. 가능하면 순수 함수로 작성하자.
    순수함수 : 똑같은 입력값이 주어졌을 때 똑같은 출력을 하는 함수

  3. 조건식 통합: 책에서는 '조건식 통합'이랬지만 차라리 분리하고 함수로 나누는 방법을 오히려 추천. 코드가 더 명확함. 그렇지 않다면 변수 이름으로 받아서 작성하는 방법도 있음. 들여쓰기와 괄호 중괄호 조합때문에 오히려 지저분하게 느껴지기도 함

  4. 빠른 실패: fast fail. 들여쓰기 단계를 줄여줌

  5. 반복문 보다는 파이프: 이해하기 쉬워서.
    *파이프라인 코드 : filter, map 등

const names = [];
for (const i of input) {
    if (i.job === 'programmer') {
        names.push(i.name);
    }
}

const names = input.filter(i => i.jpb === 'programmer').map(i => i.name);

// 비슷한 사례
const items = [1, 2, 3];
for (let i = 0; i < items.length; i++) {
    let item = items[i];
    console.log(item);
}

[1, 2, 3].forEach( item => {
    console.log(item);
} );

굳이 따지자면 성능면에서는 손해인데 막 수만건씩 다루는 게 아닌 이상 없다고 봐야 할 정도이므로 걱정말고 쓰기.

  1. 조건, 반복문에 중괄호는 꼭 쓰는 편. 한 줄에 표현할 정도로 짧고 명확하면 생략하기도 함.

  2. switch 대신 object literal

  • 기존 switch문
  • 변경
  1. 배열이나 객체는 불변 객체처럼 Immutable하게 다루자. 변화가 추적되지 않아서 문제가 많이 생기는 부분이기 때문.
//변하지 않은 것으로 간주되는 코드
const blackPink = [ '지수', '제니', '로제', '리사' ];
blackPink.forEach((value, index) => {
    blackPink[index] += '❤️';
});
console.log(blackPink);

to 

//새로운 변수를 만들어 새롭게 배열을 작성. 기존의 blackPink라는 배열도 그대로 유지되고 blackPinkWithLove라는 새로운 값으로 저장됌
const blackPink = [ '지수', '제니', '로제', '리사' ];
const blackPinkWithLove = blackPink.map((value, index) => {
    blackPink[index] += '❤️';
});
console.log(blackPinkWithLove);

함수에 전달된 파라미터도 마찬가지. 앞서 순수 함수처럼 작성하자는 것과도 같은 맥락.

  1. 문자열 합치기 보다는 ES6 템플릿 문자열
    중간에 + 기호를 사용하지 말고 아래 방식처럼 사용하기
profile
문제를 해결하고 가치를 제공합니다

0개의 댓글