팀 내 FE 소스코드는 대체로 잘 짜여진 코드, 모던한 VueJS, JS 의 장점을 잘 살린 코드였고 많은 구조 개선을 거친 것으로 보였다.
그럼에도 불구하고 처음 업무를 하는 나로썬 수정할 부분이 어디인 지 파악하기가 어려웠다. (물론 지금도...)
사무실에서 쉬면서 유튜브를 뒤적이고 있는데, TOSS SLASH 21 영상중 하나가 눈길을 끌었다.
https://www.youtube.com/watch?v=edWbHp_k_9Y
제목 부터 '실무에서 바로 쓰는 FE 클린코드'.....
핵심 내용은
- '추상화, 단일책임원칙'
- '코드가 짧다고 읽기 쉬운 코드가 아니다.'
- '코드가 길어져도 짧을 때 보다 읽기 쉬운 코드가 될 수 있다.'
로 보인다.
영상을 접하고 난 후엔 '이렇게 하면 다소 코드가 길어지고 파일이 많아져도 훨씬 더 보기 쉬운 코드를 만들 수 있구나!' 라는 생각이 들게 됐다.
물론 영상을 본 바로 다음 날에 진행 중이던 프로젝트의 코드에 영상의 '클린 코드화' 를 적용했다.!!
회사 코드를 갖고 올 순 없어 예시 코드를 만들어 봤다.
깃허브 : https://github.com/dhsimpson/sample_abstract_vue_component
'상품 리스트' 를 필터링 하는 기능을 가진 코드이며,
세부적으론
- 필터 버튼을 클릭해 필터를 추가하는 기능,
- '리스트 새로 쿼리하기' 버튼을 클릭해 새로운 리스트를 쿼리하는 기능을 가진다.
(예제니깐 쿼리는 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 컴포넌트는
- 필터 리스트를 쿼리하는 기능
- 적용 된 필터를 보여주는 기능
- 필터 리스트 컴포넌트를 생성하는 기능
만을 하므로 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 의 영상에서 전달 한 내용을 재현하기에 적절한 예제 코드가 됐길 바란다.
'소프트웨어 엔지니어링 > 프론트 엔드' 카테고리의 다른 글
ReactJS, NextJS 공부 링크들 (0) | 2022.11.27 |
---|---|
TypeScript 공부 (0) | 2022.11.19 |
vue 라이브러리 컴포넌트 배포 경험기 (vue, vite, npm, typescript, css) (0) | 2022.10.31 |
모듈 번들링 (0) | 2022.10.30 |
vuejs 디렉티브 (0) | 2022.08.08 |