소프트웨어 엔지니어링/프론트 엔드

클린코드 IN 프론트엔드

dhsimpson 2022. 11. 5. 01:37

팀 내 FE 소스코드는 대체로 잘 짜여진 코드, 모던한 VueJS, JS 의 장점을 잘 살린 코드였고 많은 구조 개선을 거친 것으로 보였다.

 

그럼에도 불구하고 처음 업무를 하는 나로썬 수정할 부분이 어디인 지 파악하기가 어려웠다. (물론 지금도...)

 

 

사무실에서 쉬면서 유튜브를 뒤적이고 있는데, TOSS  SLASH 21 영상중 하나가 눈길을 끌었다.

 

https://www.youtube.com/watch?v=edWbHp_k_9Y 

 

제목 부터 '실무에서 바로 쓰는 FE 클린코드'.....

 

핵심 내용은

  • '추상화, 단일책임원칙'
  • '코드가 짧다고 읽기 쉬운 코드가 아니다.'
  • '코드가 길어져도 짧을 때 보다 읽기 쉬운 코드가 될 수 있다.'

로 보인다.

 

영상을 접하고 난 후엔 '이렇게 하면 다소 코드가 길어지고 파일이 많아져도 훨씬 더 보기 쉬운 코드를 만들 수 있구나!' 라는 생각이 들게 됐다.

물론 영상을 본 바로 다음 날에 진행 중이던 프로젝트의 코드에 영상의 '클린 코드화' 를 적용했다.!!

 

 

회사 코드를 갖고 올 순 없어 예시 코드를 만들어 봤다.

 

깃허브 : https://github.com/dhsimpson/sample_abstract_vue_component

 

 

'상품 리스트' 를 필터링 하는 기능을 가진 코드이며,

 

세부적으론

  1. 필터 버튼을 클릭해 필터를 추가하는 기능,
  2. '리스트 새로 쿼리하기' 버튼을 클릭해 새로운 리스트를 쿼리하는 기능을 가진다.

(예제니깐 쿼리는 alert 으로 대체 하자!)

이제 추상화를 통한 클린코드 적용 예시 코드를 보자.

 

 

디렉터리 구조 - store 는 '추상화 이후 코드' 에서 필요한 상태관리를 위해 추가했다. (vuex)

 - src 
   - store
   - components
     - beforeRefactor
     - afterRefactor
 - main.js
 - App.vue

 

추상화 전 코드

<!--FilterArea.vue-->
<template>
    <div class="filter_wrapper">
        <span>상품 필터를 선택해 주세요</span>
        <button v-for="filter in filterButtonList" :key="filter.name" class="filter-button" @click="updateFilter(filter.name)">{{filter.value}}</button>
        <div>
            <input type="number" placeholder="가격입력" v-model="price"/>원 미만 <button @click="updatePriceFilter()">적용하기</button>
        </div>
        <br/>
    </div>
    <p>현재 적용된 필터들 : {{filterList}}</p>
    <button class="query-button" @click="queryProductList()">리스트 새로 쿼리하기</button>
</template>

<button> 이라는 Html 태그명만 보고선 각 버튼이 무슨 버튼인지 직관적으로 이해되지 않는다.

 

필터 적용하는 버튼은 <filter-button> (버튼형 필터), <price-filter> (input형 가격필터) 컴포넌트로,

쿼리하는 버튼은 <query-button> 컴포넌트로 추상화 시켜줬다.

 

추상화 이후 코드

<!--FilterArea.vue-->
<template>
    <div class="filter_wrapper">
        <span>상품 필터를 선택해 주세요</span>
        <filter-button v-for="filter in filterButtonList" :key="filter.name" :filter="filter"/>
        <price-filter/>
        <br/>
    </div>
    <p>현재 적용된 필터들 : {{filterList}}</p>
    <query-button/>
</template>

html 태그명(vue컴포넌트 이지만) 만 보고도 어떤 역할을 하는 코드인 지 알 수 있게 됐다.

 

 

만약 신입 개발자가 '버튼형 필터 버튼을 수정해 주세요' 라는 요청 사항을 받았을 때, 수정해야 할 코드가 어디 있는 지

단번에찾을 수 있을 것이다.

 

(c.f. 실무를 겪다 보니 몇 백, 몇 천 줄의 코드 내에서 비슷한 역할을 하는 코드가 많아 수정할 코드를 찾는데 시간을 많이 낭비한 경험이 꽤 있었다.)

 

methods, data 와 같은 기능 영역도 단일책임의 원칙을 통해 가독성을 높여주었다.

 

추상화 전 코드

<!--FilterArea.vue-->
export default {
    data() {
        return {
            price: null,
            filterList: new Map(),
            filterButtonList: [
                {
                    name: 'md-products',
                    value: 'MD추천상품'
                },
                {
                    name: 'good-review-products',
                    value: '리뷰 평점 4.0 이상 상품'
                }
            ]
        }
    },
    methods: {
        updateFilter(filter, value=null) {
            this.filterList.set(filter, value);
        },
        updatePriceFilter() {
            this.updateFilter('products-price', this.price);
        },
        queryProductList() {
            let alertMsg = '';
            for(let [filter, value] of this.filterList) {
                let tempMsg = `필터명 :${filter}`;
                if(value) {
                    tempMsg += ` | 필터값 : ${value}`;
                }
                alertMsg += tempMsg+'\n'
            }
            alert(alertMsg)
        }
    },
    watch: {
        price(newPrice) {
            newPrice
            // console.log(newPrice)
        }
    }
}

필터를 적용하는 메서드, 가격을 필터에 적용하는 메서드, 쿼리하는 메서드 등이 하나의 컴포넌트에 있어 가독성이 많이 떨어진다.

단일책임을 갖도록 해 각 메서드를 해당되는 컴포넌트로 옮겨 보자.

 

추상화 이후 코드

<!--FilterArea.vue-->
export default {
    components: { FilterButton, PriceFilter, QueryButton },
    created() {
        this.$store.dispatch('FETCH_FILTER_LIST');
    },
    computed: {
        ...mapGetters({
            filterList: 'GET_FILTER_LIST',
            filterButtonList: 'GET_FILTER_BUTTON_LIST',
        })
    }
}

FilterArea.vue 컴포넌트는

  1. 필터 리스트를 쿼리하는 기능
  2. 적용 된 필터를 보여주는 기능
  3. 필터 리스트 컴포넌트를 생성하는 기능

만을 하므로 FETH_FILTER_LIST, filterButtonList, filterList 만 가지고 있다.

 

 

각 컴포넌트가 해야 할 역할에 따라 기능들을 분배(?) 시켜 줬다.

물론 css 코드 또한 컴포넌트에 따라 분배 시켰다.

<!--FilterButton.vue-->
<template>
    <button class="filter-button" @click="updateFilter(filter.name)">{{filter.value}}</button>
</template>

<script>
export default {
    props: ['filter'],
    methods: {
        updateFilter(filter) {
            this.$store.commit('ADD_FILTER', {filter});
        },
    }
}
</script>
<style scoped>
.filter-button {
    background-color: yellow;
}
</style>
<!--PriceFilter.vue-->
<template>
    <div>
        <input type="number" placeholder="가격입력" v-model="price"/>원 미만 <button @click="updatePriceFilter()">적용하기</button>
    </div>
</template>

<script>
export default {
    data() {
        return {
            price: null,
        }
    },
    methods: {
        updatePriceFilter() {
            this.$store.commit('ADD_FILTER', {filter: 'products-price', value: this.price});
        },
    },
    watch: {
        price(newPrice) {
            newPrice
            // console.log(newPrice)
        }
    }
    
}
</script>

<style>

</style>
<!--QueryButton.vue-->
<template>
    <button class="query-button" @click="queryProductList()">리스트 새로 쿼리하기</button>
</template>

<script>
import { mapGetters } from 'vuex';

export default {
    computed: {
        ...mapGetters({
            filterList: 'GET_FILTER_LIST',
        })
    },
    methods: {
        queryProductList() {
            let alertMsg = '';
            for(let [filter, value] of this.filterList) {
                alertMsg += this.newMsg(filter, value);
            }
            alert(alertMsg)
        },
        newMsg(filter, value) {
            let tempMsg = `필터명 :${filter}`;
            if(value) {
                tempMsg += ` | 필터값 : ${value}`;
            }
            return tempMsg + '\n';
        }
    },

}
</script>

<style scoped>
.query-button {
    background-color: pink;
}
</style>

 

 

SLASH21 의 영상에서 전달 한 내용을 재현하기에 적절한 예제 코드가 됐길 바란다.