hmk run dev

프론트엔드 클린코드(클린 코드 != 짧은 코드) 본문

Front-End

프론트엔드 클린코드(클린 코드 != 짧은 코드)

hmk run dev 2024. 3. 9. 14:01

이 글은 토스 SLASH21의 실무에서 바로 쓰는 Frontend Clean Code를 바탕으로 작성된 글이며
클린코드에 대한 관점은 개인마다 다를 수 있습니다.

 

클린코드를 해야 하는 이유

실무에서 클린 코드 = 유지보수 시간의 단축

시간 = 자원 = 돈



 

처음엔 클린 했다. => 요구사항에 맞는 적절한 클린 코드였다.

 

하지만, 기존 코드에 기능을 추가하는 상황이라면?

실무의 대부분은 새로운 기능을 만드는 경우도 있겠지만, 기존의 코드에 새로운 기능을 추가하는 경우가 더 많다.

 

간단한 코드를 통해 실무관점으로 클린 하게 리팩토링 해보자

 

 

요구사항은 아래와 같습니다.

- 보험에 대한 질문은 입력하는 페이지가 있다.

- 설계사가 있는 경우엔 설계사 사진이 들어간 팝업을 먼저 띄워달라는 추가 기능을 요청받음

function QuestionPage() {

	const [popupOpend, setPopupOpened] = useState(false);
    
	// 질문 등록 핸들러 함수
	async function handleQuestionSubmit() {
    	const 연결전문가 = await 연결전문가_받아오기();
        
        // 기능 추가받은 연결 전문가가 있는지 검사 분기처리
        if(연결전문가 !== null){
        	setPopupOpened(true);
        } else {
        	const 약관동의 = await 약관동의_받아오기();
            
            if(!약관동의){
				await 약관동의_팝업열기();
            }
            
            // 질문전송 성공 시 성공 alert
            await 질문전송(questionValue);
            alert("질문이 등록되었어요.");
        }
    }
    
    
    async function handleMyExpertQuestionSubmit() {
		await 연결전문가_질문전송(questionValue, 연결전문가.id);
        alert(`${연결전문가.name}에게 질문이 등록되었어요.`)
    }

	return (
    	<main>
        	<form>
        		<textarea placeholder="어떤 내용이 궁금한가요?" />
                <button onClick={handleQuestionSubmit}>질문하기</button>
            </form>
            {popupOpened && (
                <연결전문가팝업 onSubmit={handleMyExpertQuestionSubmit}/>
            )}
        </main>
    )
}

 

위 코드에서 어떤 문제가 있을까?

 

 

1. 하나의 목적인 코드가 흩뿌려져 있다.

초록색으로 강조된 부분은 "연결 중인 전문가" 팝업 관련된 코드인데,

하나의 목적을 가진 코드가 떨어져 있어 추후 유지보수 시 스크롤을 위아래로 왔다 갔다 하며, 

유지보수의 시간소요 증가 발생

 

 

3. 하나 함수가 여러 가지 일을 처리하고 있음

함수하나에 기능이 여러 개 있어

세부 구현을 모두 읽어야 함수의 역할을 알 수 있게 됨

 

- handleQuestionSubmit

- handleMyExpertQuestionSubmit

 

handleQuestionSubmit은 질문 전송 이외에도 여러 가지 일을 하고 있어 읽을 때 어지럽다.

 

3. 함수 세부 구현단계가 제각각

파란 부분과 노란 부분은 둘 다 이벤트 핸들링 관련함수

 

 

함정은 풀 리퀘스트의 변경사항만 보면

내가 추가한 기능의 코드만 잘 보이기 때문에 전체적으로 어지러운 코드인지를 파악하기 어려웠을 것이다.

 

 

 

이제 큰 그림은 보면서 리팩토링을 해보자

 

 

1. 함수 세부 구현 단계 통일

 

- handleNewExpertQuestionSubmit => 새로운 전문가에게 연결하는 로직만 넣음 

- handleMyExpertQuestionSubmit => 연결 중인 전문가에게 질문하는 로직만 넣기

 

 

 

2. 하나의 목적인 코드는 뭉쳐 두기

 

기존에는 팝업을 여는 버튼과 팝업 코드가 동떨어져 있었는데

이를 모아서 "PopupTriggerButton" 이란 컴포넌틑를 만들었다.

 

3. 함수가 한 가지 일만 하도록 나누기

 

약관 동의 함수를 쪼개서 필요한 시점에 부르도록 변경

 

 

코드가 더 길어졌다 그러나,

 

클린 코드!= 짧은 코드

=> 원하는 로직을 빠르게 찾을 수 있는 코드

 

 

 

원하는  로직을 빠르게 찾으려면?

 

응집도 - 하나의 목적을 가진 코드가 흩뿌려져 있지 않게 하기

 

단일책임 - 하나의 함수에 하나의 기능만

 

추상화 - 함수의 세부구현 단계가 제각각이지 않게 추상화 단계를 조절해 구현하기


응집도

같은 목적의 코드는 뭉쳐두자

 

노란색으로 표시한 코드는 팝업을 조작하는 코드인데, 코드가 제각각 떨어져 있다.

파악도 한 번에 안 되고 버그 발생 위험도 높다.

 

커스텀훅을 사용해 관련로직을 한 군데로 뭉쳐놓는다.

 

그런데, 이게 좋은 방법일까?

오히려 커스텀훅을 사용해서 너무 추상화되지 않았을까?

다른 동료가 이 코드를 보고 로직을 빨리 파악할 수 있을까?

 

 

그렇다면 무엇을 뭉치고 얼마나 추상화하는 게 좋을지에 대해 한번 생각해 볼 필요가 있다.

 

뭉쳐서 짧은 코드로 만든다고 코드가 깨끗해지진 않는다.

 

클린코드는 짧은 코드가 아니다.

찾고 싶은 로직을 빠르게 찾을 수 있는 코드다.

 

 

어떻게 해야 읽기 좋게 응집할 수 있을까?

 

핵심 데이터와 숨겨도 될 세부구현을 나눈다.

 

아래 코드에서 핵심 데이터는 아래 두 개 정도로 볼 수 있다.

 

- 팝업 버튼 클릭 시 액션

- 팝업의 제목, 내용

 

 

그리고 세부 구현은 아래 두 개 정도로 볼 수 있다.

 

- 팝업을 열고 닫을 때 사용하는 상태

- 컴포넌트 세부 마크업

- 팝업의 버튼 클릭 시 특정함수를 호출해야 한다는 바인딩

 

리팩토링

핵심 데이터만 남기고 세부 구현은 숨겨보자.

 

openPopup이라는 커스텀훅에 모든 코드를 다 숨기는 게 아니라,

세부 구현만 숨겨놓고 핵심 데이터인  팝업 제목, 내용, 액션은 바깥에서 넘겨주는 방식으로 변경했다.

 

위의 변경 점으로 아래의 용이성을 얻게 되었다.

 

- 세부 구현을 읽지 않고도 어떤 팝업인지 파악할 수 있게 됨

 

 

선언적 프로그래밍

 

- 팝업 너에게 선언한다.

- 제목은 보험 질문하기!

- 내용은 전문가가 설명드려요

- 확인버튼 클릭하면 질문을 제출해라! 

 

이러한 선언들을 바탕으로 팝업이 이미 구현된 세부 구현을 바탕으로 해당 내용을 뿌려주는 스타일을 일컬어

"선언적 프로그래밍"이라고 한다.

 

 

 

무엇을 하는 함수인지 빠르게 파악이 가능하다.

 

 

 

이와 반대로 선언형으로 뭉쳐두지 않고, 하나하나 세부구현을 작성한 방식을

명령형 프로그래밍이라고 한다.

 

선언적 프로그래밍도 내부를 까보면 명령형으로 되어있지만,

세부 구현이 노출되어 있어 이를 커스텀하기 쉽지만

읽는데 오래 걸리고 재사용하기 어렵다는 단점이 있다.

 

 

선언적이 명령형보다 좋은가?

아주 당연하게도 아니다.

 

리액트를 사용하면 HTML에도 선언형 프로그래밍을 이용할 수 있지만,

props로 어떻게 해야 하는지를 넘겨야 하는 경우에 명령형도 좋은 선택이다.

 


단일책임

 

이벤트 핸들러 함수를 지어보자

기능은 아래와 같다.

 

- 폼에서 질문 제출을 클릭했을 때 호출되는 함수

- 약관 동의 여부를 체크하고 질문을 제출한다.

 

단순하게 "질문제출" 정도로 함수이름을 정의할까?

그러나 함수 내용엔 '약관제출', '질문제출'이 섞여있다.

 

이렇게 중요 포인트가 가려진 함수들은 세부구현을 일일이 확인해야 하고 ,

함수명을 믿지 못하게 된다.

 

이에 더해 약관동의 체크 이외에 다른 기능이 추가가 되면 함수는 더 복잡해지고, 뚱뚱해진다.

 

 

이러한 기능추가가 반복되고 함수 이름과 다른 기능을 하는 경우가 빈번해지면,

코드에 대한 신뢰가 떨어지고 유지보수에 대한 리소스가 더 증가하게 된다 => 시간, 돈, 자원 낭비

 

 

단일 책임의 원칙을 적용해 함수가 하나의 책임만 가지게 하도록 해보자

 

 

물론 리액트의 경우엔 함수뿐만 아니라 컴포넌트로 분리해 책임을 나눌 수도 있다.

 

IntersectionObserver를 이용해 요소가 겹쳐 있을 때, 함수를 실행시키는 코드를

IntersectionArea라는 컴포넌트로 나눠 세부 구현은 숨기고, 사용하는 입장에서 Impression 시 

동작할 함수를 넘겨주게만 하는 방식으로 나눴다.

 

도메인 지식이 복잡하게 들어가거나,

직관적으로 파악하고 싶으면 한글 변수명도 이용해 보자

 


추상화

 

컴포넌트 로직에서 핵심 개념만 뽑아보자.

 

- 왼쪽은 팝업 컴포넌트 코드를 제로부터 디테일하게 구현

- 오른쪽은 선언적으로 팝업의 "제출", "성공" 액션으로 나누고 나머지는 추상화시킴

 

 

함수 로직에서 핵심 개념만 뽑아보자.

 

함수 모듈로 분리해 getPlannerLabel 함수명 안에 추상화시킴

 

 

얼마나 추상화할 것인가?

 

버튼을 눌렀을때 컨펌을 보여주는 코드

 

 

버튼을 눌렀을때 컨펌을 보여주는 기능을 ConfirmButton이라는 컴포넌트로 추상화

 

 

메시지만 넣고 컨펌창에 원하는 메시지를 보여주도록 더 추상화

 

 

모든 기능을 모두 ConfirmButton이라는 이름아래에 추상화시킬 수도 있다.

 

 

당연하게도 이러한 추상화 레벨에도 맞고 틀리다 라는 개념은 없다.

상황에 따라 필요한 만큼 추상화하면 된다.

 

 

실무에서 코드를 보고 추상화를 제안하기도 하고,

추상화 하지 않았던 이유에 대해서 답변하기도 한답니다.

 

 

 

그러나 추상화 수준을 맞춰준다면, 더 파악하기 쉬운 코드가 될 수도 있습니다.

 

 


클린 코드에 대한 의견은 

개개인마다 다를 수 있습니다.

 

코드에 정답은 없고 같이 개발하는 팀과 공감대를 형성하며,

명시적으로 정하고, 문서화해 팀의 컨벤션을 형성하는 방법이 있겠지요

 

기존에 널리 알려진 컨벤션 ex) SOLID

+

팀원과 함께 명시적으로 잘 정해진 약속, 컨벤션 기반

 


답은 없고 조직마다 처리해야 하는 업무량도 다르겠지만,

같이 일하는 개발 조직이라면 추후 유지보수 리소스를 고려해 코딩 컨벤션을 맞춰보는 게 좋을 것 같다! 라는 생각이 듭니다.


 

Reference

 

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

 

 

 

 

 

Comments