굿즈 스토어 프로젝트 14 - 코드 최적화, 중복 제거와 가독성의 향상.

이유승·2023년 12월 2일
0

이전에 진행했던 굿즈 스토어 프로젝트에서 관리자가 제품을 등록할 때의 로직을 구현했던 적이 있었다. 그런데 그 당시에 Redux Store와 DB의 구조를 어떻게 만들어야하는지에 대한 이해가 부족하여 기능을 구현하면서 새로운 구조를 추가하거나 기존에 존재하던 구조를 지속적으로 수정해야만 했다.

시간에 쫒기는 데다가 새로운 기능을 추가한 다음에 발생하는 여러 버그, 에러 등을 잡는데 정신이 없었다지만 이렇게 주먹구구식으로 작업을 진행하다보니 최종적으로 제품 등록시 DB에 정보를 알맞게 입력하는 기능의 코드가 아래와 같이 참담한 모습을 갖게 되었다.

const productOptionInfoProcess = (productInfo, productOptionInfo) => {

    const resultData = {
        option1: '',
        option1SurchargeType: '',
        option1SurchargePrice: '',
        option1PurchaseQuantityLimit: '',
        option1Inventory: '',
        option1SalesRate: '',
        option2: '',
        option2SurchargeType: '',
        option2SurchargePrice: '',
        option2PurchaseQuantityLimit: '',
        option2Inventory: '',
        option2SalesRate: '',
        option3: '',
        option3SurchargeType: '',
        option3SurchargePrice: '',
        option3PurchaseQuantityLimit: '',
        option3Inventory: '',
        option3SalesRate: '',
        option4: '',
        option4SurchargeType: '',
        option4SurchargePrice: '',
        option4PurchaseQuantityLimit: '',
        option4Inventory: '',
        option4SalesRate: '',
        option5: '',
        option5SurchargeType: '',
        option5SurchargePrice: '',
        option5PurchaseQuantityLimit: '',
        option5Inventory: '',
        option5SalesRate: '',
    };

    // 옵션 데이터 1차 가공.
    // 옵션을 입력하지 않았을 경우.
    if (productOptionInfo.option1 === '') {
        resultData.option1 = '옵션없음';
        resultData.option1SurchargeType = '';
        resultData.option1SurchargePrice = '';
        resultData.option1PurchaseQuantityLimit = '';
        resultData.option1Inventory = '';
        resultData.option1SalesRate = 0;
    };
    if (productOptionInfo.option2 === '') {
        resultData.option2 = '옵션없음';
        resultData.option2SurchargeType = '';
        resultData.option2SurchargePrice = '';
        resultData.option2PurchaseQuantityLimit = '';
        resultData.option2Inventory = '';
        resultData.option2SalesRate = 0;
    };
    if (productOptionInfo.option3 === '') {
        resultData.option3 = '옵션없음';
        resultData.option3SurchargeType = '';
        resultData.option3SurchargePrice = '';
        resultData.option3PurchaseQuantityLimit = '';
        resultData.option3Inventory = '';
        resultData.option3SalesRate = 0;
    };
    if (productOptionInfo.option4 === '') {
        resultData.option4 = '옵션없음';
        resultData.option4SurchargeType = '';
        resultData.option4SurchargePrice = '';
        resultData.option4PurchaseQuantityLimit = '';
        resultData.option4Inventory = '';
        resultData.option4SalesRate = 0;
    };
    if (productOptionInfo.option5 === '') {
        resultData.option5 = '옵션없음';
        resultData.option5SurchargeType = '';
        resultData.option5SurchargePrice = '';
        resultData.option5PurchaseQuantityLimit = '';
        resultData.option5Inventory = '';
        resultData.option5SalesRate = 0;
    };

    // 옵션 데이터 2차 가공.
    // 옵션이 있으나 가격변동이 없을 경우.
    if (productOptionInfo.option1 !== '' && productOptionInfo.option1SurchargeType === '변동없음') {
        resultData.option1 = productOptionInfo.option1;
        resultData.option1SurchargeType = productOptionInfo.option1SurchargeType;
        resultData.option1SurchargePrice = parseInt(productInfo.price);
        resultData.option1PurchaseQuantityLimit = productOptionInfo.option1PurchaseQuantityLimit;
        resultData.option1Inventory = productOptionInfo.option1Inventory;
        resultData.option1SalesRate = 0;
    };
    if (productOptionInfo.option2 !== '' && productOptionInfo.option2SurchargeType === '변동없음') {
        resultData.option2 = productOptionInfo.option2;
        resultData.option2SurchargeType = productOptionInfo.option2SurchargeType;
        resultData.option2SurchargePrice = parseInt(productInfo.price);
        resultData.option2PurchaseQuantityLimit = productOptionInfo.option2PurchaseQuantityLimit;
        resultData.option2Inventory = productOptionInfo.option2Inventory;
        resultData.option2SalesRate = 0;
    };
    if (productOptionInfo.option3 !== '' && productOptionInfo.option3SurchargeType === '변동없음') {
        resultData.option3 = productOptionInfo.option3;
        resultData.option3SurchargeType = productOptionInfo.option3SurchargeType;
        resultData.option3SurchargePrice = parseInt(productInfo.price);
        resultData.option3PurchaseQuantityLimit = productOptionInfo.option3PurchaseQuantityLimit;
        resultData.option3Inventory = productOptionInfo.option3Inventory;
        resultData.option3SalesRate = 0;
    };
    if (productOptionInfo.option4 !== '' && productOptionInfo.option4SurchargeType === '변동없음') {
        resultData.option4 = productOptionInfo.option4;
        resultData.option4SurchargeType = productOptionInfo.option4SurchargeType;
        resultData.option4SurchargePrice = parseInt(productInfo.price);
        resultData.option4PurchaseQuantityLimit = productOptionInfo.option4PurchaseQuantityLimit;
        resultData.option4Inventory = productOptionInfo.option4Inventory;
        resultData.option4SalesRate = 0;
    };
    if (productOptionInfo.option5 !== '' && productOptionInfo.option5SurchargeType === '변동없음') {
        resultData.option5 = productOptionInfo.option5;
        resultData.option5SurchargeType = productOptionInfo.option5SurchargeType;
        resultData.option5SurchargePrice = parseInt(productInfo.price);
        resultData.option5PurchaseQuantityLimit = productOptionInfo.option5PurchaseQuantityLimit;
        resultData.option5Inventory = productOptionInfo.option5Inventory;
        resultData.option5SalesRate = 0;
    };

    // 옵션 데이터 3차 가공.
    // 옵션이 있으나 가격변동이 있을 경우.
    if (productOptionInfo.option1 !== '' && (productOptionInfo.option1SurchargeType === '추가가격' || '가격감소')) {
        resultData.option1 = productOptionInfo.option1;
        resultData.option1SurchargeType = productOptionInfo.option1SurchargeType;

        if (productOptionInfo.option1SurchargeType === '추가가격') {
            resultData.option1SurchargePrice = parseInt(productInfo.price) + parseInt(productOptionInfo.option1SurchargePrice);
        };

        if (productOptionInfo.option1SurchargeType === '가격감소') {
            resultData.option1SurchargePrice = parseInt(productInfo.price) - parseInt(productOptionInfo.option1SurchargePrice);
        };

        resultData.option1PurchaseQuantityLimit = productOptionInfo.option1PurchaseQuantityLimit;
        resultData.option1Inventory = productOptionInfo.option1Inventory;
        resultData.option1SalesRate = 0;
    };
    if (productOptionInfo.option2 !== '' && (productOptionInfo.option2SurchargeType === '추가가격' || '가격감소')) {
        resultData.option2 = productOptionInfo.option2;
        resultData.option2SurchargeType = productOptionInfo.option2SurchargeType;

        if (productOptionInfo.option2SurchargeType === '추가가격') {
            resultData.option2SurchargePrice = parseInt(productInfo.price) + parseInt(productOptionInfo.option2SurchargePrice);
        };

        if (productOptionInfo.option2SurchargeType === '가격감소') {
            resultData.option2SurchargePrice = parseInt(productInfo.price) - parseInt(productOptionInfo.option2SurchargePrice);
        };

        resultData.option2PurchaseQuantityLimit = productOptionInfo.option2PurchaseQuantityLimit;
        resultData.option2Inventory = productOptionInfo.option2Inventory;
        resultData.option2SalesRate = 0;
    };
    if (productOptionInfo.option3 !== '' && (productOptionInfo.option3SurchargeType === '추가가격' || '가격감소')) {
        resultData.option3 = productOptionInfo.option3;
        resultData.option3SurchargeType = productOptionInfo.option3SurchargeType;

        if (productOptionInfo.option3SurchargeType === '추가가격') {
            resultData.option3SurchargePrice = parseInt(productInfo.price) + parseInt(productOptionInfo.option3SurchargePrice);
        };

        if (productOptionInfo.option3SurchargeType === '가격감소') {
            resultData.option3SurchargePrice = parseInt(productInfo.price) - parseInt(productOptionInfo.option3SurchargePrice);
        };

        resultData.option3PurchaseQuantityLimit = productOptionInfo.option3PurchaseQuantityLimit;
        resultData.option3Inventory = productOptionInfo.option3Inventory;
        resultData.option3SalesRate = 0;
    };
    if (productOptionInfo.option4 !== '' && (productOptionInfo.option4SurchargeType === '추가가격' || '가격감소')) {
        resultData.option4 = productOptionInfo.option4;
        resultData.option4SurchargeType = productOptionInfo.option4SurchargeType;

        if (productOptionInfo.option4SurchargeType === '추가가격') {
            resultData.option4SurchargePrice = parseInt(productInfo.price) + parseInt(productOptionInfo.option4SurchargePrice);
        };

        if (productOptionInfo.option4SurchargeType === '가격감소') {
            resultData.option4SurchargePrice = parseInt(productInfo.price) - parseInt(productOptionInfo.option4SurchargePrice);
        };

        resultData.option4PurchaseQuantityLimit = productOptionInfo.option4PurchaseQuantityLimit;
        resultData.option4Inventory = productOptionInfo.option4Inventory;
        resultData.option4SalesRate = 0;
    };
    if (productOptionInfo.option5 !== '' && (productOptionInfo.option5SurchargeType === '추가가격' || '가격감소')) {
        resultData.option5 = productOptionInfo.option5;
        resultData.option5SurchargeType = productOptionInfo.option5SurchargeType;

        if (productOptionInfo.option5SurchargeType === '추가가격') {
            resultData.option5SurchargePrice = parseInt(productInfo.price) + parseInt(productOptionInfo.option5SurchargePrice);
        };

        if (productOptionInfo.option5SurchargeType === '가격감소') {
            resultData.option5SurchargePrice = parseInt(productInfo.price) - parseInt(productOptionInfo.option5SurchargePrice);
        };

        resultData.option5PurchaseQuantityLimit = productOptionInfo.option5PurchaseQuantityLimit;
        resultData.option5Inventory = productOptionInfo.option5Inventory;
        resultData.option5SalesRate = 0;
    };

    return resultData;
};

const dateFormat = (date) => {
    const time = new Date(date);

    let year = time.getFullYear().toString(); //년도 뒤에 두자리
    let month = ("0" + (time.getMonth() + 1)).slice(-2); //월 2자리 (01, 02 ... 12)
    let day = ("0" + time.getDate()).slice(-2); //일 2자리 (01, 02 ... 31)
    let hour = ("0" + time.getHours()).slice(-2); //시 2자리 (00, 01 ... 23)
    let minute = ("0" + time.getMinutes()).slice(-2); //분 2자리 (00, 01 ... 59)
    let second = ("0" + time.getSeconds()).slice(-2); //초 2자리 (00, 01 ... 59)

    return year + '년 ' + month + '월 ' + day + '일, ' + hour + '시 ' + minute + '분 ' + second + '초.';
};

export { productOptionInfoProcess, dateFormat };



1. 문제점

제품 옵션에 대한 처리가 아예 동일하거나, 유사한 패턴으로 반복되고 있다.

옵션의 유무와 가격 변동 유형에 따라 조건문들이 사용되었는데, 읽고 이해하기 어려울 정도로 복잡하게 구현되었다.

이로 인해 전반적인 효율성이 극히 떨어지고, 가독성이 매우 저하되어 유지보수의 난이도가 크게 증가하였다.



2. 어떻게 수정해야 할까..

중복된 로직은 별도의 함수로 분리하여 동일한 코드가 반복되는 것을 방지한다.

if 대신 switch를 사용하여 조건문의 구조를 간소화 한다.

각 옵션의 정보를 처리하고 필요한 부분을 객체로 반환하는 processOption 함수를 새로 구현하였다. 코드가 전반적으로 길어지게 된 원인 중 하나는 option1, option2, option3... 등 똑같은 개념인데 key의 값만 다른 존재들이 있었기 때문이다. processOption 함수의 사용으로 함수를 호출했을 때, 옵션의 key값을 받아오도록 해서 필요한 대상을 능동적으로 지정할 수 있게 되었다.

또한 if - else if - else의 구조를 사용하고, 3차례에 걸쳐 데이터를 가공하던 기존의 로직을 대신하여 switch문을 사용하였다. 기존의 코드와 달리 case의 구분이 명확해져 코드의 가독성, 유지보수성이 크게 향상되었다.

이로써 동일한 기능을 수행하지만 여러 곳에서 중복되어 사용되는 불필요한 부분이 모두 제거되었다.



3. 코드가 이렇게 바뀌었습니다

const processOption = (productInfo, productOptionInfo, optionNumber) => {
    const optionKey = `option${optionNumber}`;
    const option = productOptionInfo[optionKey];
    const surchargeType = `${optionKey}SurchargeType`;
    const surchargePrice = `${optionKey}SurchargePrice`;
    const purchaseQuantityLimit = `${optionKey}PurchaseQuantityLimit`;
    const inventory = `${optionKey}Inventory`;
    const salesRate = `${optionKey}SalesRate`;

    let result = {
        [optionKey]: option || '옵션없음',
        [surchargeType]: '',
        [surchargePrice]: '',
        [purchaseQuantityLimit]: '',
        [inventory]: '',
        [salesRate]: 0,
    };

    if (option) {
        result[surchargeType] = productOptionInfo[surchargeType];
        switch (productOptionInfo[surchargeType]) {
            case '변동없음':
                result[surchargePrice] = parseInt(productInfo.price);
                break;
            case '추가가격':
                result[surchargePrice] = parseInt(productInfo.price) + parseInt(productOptionInfo[surchargePrice]);
                break;
            case '가격감소':
                result[surchargePrice] = parseInt(productInfo.price) - parseInt(productOptionInfo[surchargePrice]);
                break;
        }
        result[purchaseQuantityLimit] = productOptionInfo[purchaseQuantityLimit];
        result[inventory] = productOptionInfo[inventory];
    }
    return result;
};

const productOptionInfoProcess = (productInfo, productOptionInfo) => {
    let resultData = {};
    for (let i = 1; i <= 5; i++) {
        Object.assign(resultData, processOption(productInfo, productOptionInfo, i));
    }
    return resultData;
};

const dateFormat = (date) => {
    const time = new Date(date);
    let year = time.getFullYear().toString();
    let month = ("0" + (time.getMonth() + 1)).slice(-2);
    let day = ("0" + time.getDate()).slice(-2);
    let hour = ("0" + time.getHours()).slice(-2);
    let minute = ("0" + time.getMinutes()).slice(-2);
    let second = ("0" + time.getSeconds()).slice(-2);
    return year + '년 ' + month + '월 ' + day + '일, ' + hour + '시 ' + minute + '분 ' + second + '초.';
};

export { productOptionInfoProcess, dateFormat };

이번 최적화 작업을 통해 기능은 동일하되, 코드의 성능 / 가독성 / 유지보수성 등이 크게 향상되었다. 코드의 중복이 사라지고 복잡함이 해소되어 더 이해하게 쉬운 코드가 되었다. 이 코드를 최초로 개발했던 때에는 바쁘고 정신도 없어서 신경쓰지 못했지만, 시간이 지나고 그 당시의 작업물을 천천히 살펴보니 이렇게 기본도 지키지 못한 코드를 구현했던 사실에 부끄러움이 느껴졌다. 실력있는 개발자가 되기 위해서는 한번 구현했다고 그냥 끝나는게 아닌, 주기적으로 검토하고 개선할 수 있는 자세를 가져야 할 것이다.

profile
프론트엔드 개발자를 준비하고 있습니다.

0개의 댓글