
실무에서의 클린 코드란
- 특정 개발자만이 다룰 수 있는 코드는 클린 코드라 할 수 없음
- 이를 '지뢰 코드'라 함
- 흐름 파악이 어려움 / 도메인 맥락 표현이 안 됨 / 동료에게 물어봐야 함
- 즉, 유지보수 시간이 늘어나게 됨
- 실무에서의 클린 코드 = 유지보수 시간의 단축
- 처음에는 클린했음
- 하지만, 기존 코드에 기능을 추가하며 더러워짐
- 회사 일의 90%가 기존 코드에 기능을 추가하는 것이라 더더욱 중요
안일한 코드 추가의 함정
- 마구잡이로 생각 없이 추가하다 보면 금방 들쑥날쑥한 코드가 되어버림
더러운 코드
- 하나의 목적인 코드가 흩뿌려져 잇어 기능 파악 시 스크롤을 위아래로 이동해줘야 함
- 하나의 함수가 여러가지 일을 함
- 함수의 세부 구현 단계가 제각각
- 비슷한 이름의 함수인데, 서로 다른 기능을 하고 있다거나...
- 가령
getA
와getB
라는 이름의 함수가 있다 하면 getA
는 서버에 데이터를 Request한 뒤 이를 파싱하고, 그 결과를 반환해주는 함수getB
는 단순히b
라는 이름의 Class member를 반환하는 함수- 이러한 함수는 서로 구현 단계가 제각각이라 볼 수 있음
리팩토링 방안
- 함수의 세부 구현 단계 통일
- 비슷한 이름의 함수는 서로 비슷한 동작을 하게끔 함
- 가령,
getA
와getB
는 둘 다 각각a
와b
라는 이름의 Class member를 반환하도록 함
- 하나의 목적인 코드는 뭉쳐 두기
- 만약 팝업을 여는 버튼 컴포넌트 코드와 팝업 컴포넌트 코드가 동떨어져 있는 경우
// jsx return ( <article> <button onClick={openPopup}>팝업 열기</button> {popupOpened && (<PopupComponent />)} </article> );
// jsx return ( <article> <PopupTriggerButton popup={(<PopupComponent />)}> 팝업 열기 </PopupTriggerButton> </article> );
- 함수가 한 가지 일만 하도록 쪼개기
- 함수 하나에서 하나의 일만 하도록
// before async function fetchX() { const data = await (await fetch('url_1')).text(); const parsedData = parse(data); const xValue = parsedData.x; await fetch('url_2', { method: 'post', body: { value: xValue } }); return xValue; } // after async function fetchUrl_1() { return await (await fetch('url_1')).text(); } async function postValueToUrl_2(value) { return await fetch('url_2', { method: 'post', body: { value } }); } async function fetchXFromUrl_1() { const data = await fetchUrl_1(); const parsedData = parse(data); return parsedData.x; }
클린 코드란
- 클린 코드 ≠ 짧은 코드
- 클린 코드 = 원하는 로직을 빠르게 찾을 수 있는 코드
- 원하는 로직을 빠르게 찾으려면?
- 응집도를 높임: 하나의 목적을 가진 코드는 뭉쳐두기
- 단일책임 원칙: 함수는 하나의 일만 하도록
- 추상화: 추상화 단계를 조정해 함수의 세부 구현 단계를 네이밍과 일관되게 맞춰줌
로직을 빠르게 찾을 수 있는 코드
응집도: 같은 목적의 코드는 뭉쳐 두자
- React의 Custom Hooks나 Vue의 Composables를 이용해 같은 목적의 코드를 뭉침
- 다만 어떤 내용을 담고 있고, 어떤 동작을 하는지 명확하지 않다는 단점이 있음
- 뭉치는 코드는 ⇒ 당장 몰라도 되는 디테일을 뭉침
- 짧은 코드만 보고도 빠르게 파악이 가능하도록 함
- 코드 파악에 필수적인 핵심 정보까지 뭉쳐두면 되려 파악이 어려워짐
- 클린 코드 ≠ 짧은 코드
- 코드 응집 팁: 핵심 데이터와 세부 구현 나누기
- 남겨야 할 핵심 데이터와 숨겨도 될 세부 구현을 나눔
- 팝업을 예로 들자면...
- 핵심 데이터: 팝업 버튼 클릭 시 액션, 팝업 제목, 팝업 내용
- 세부 구현: 팝업을 여닫을 때 사용되는 State, 컴포넌트의 세부 마크업, 팝업 버튼 클릭 시 팝업을 열어야 한다는 데이터 바인딩
- 핵심 데이터인 팝업 제목, 내용, 팝업 버튼 Action은 외부에서 넘기도록 구성
- 이를 선언적 프로그래밍이라 함
- 핵심 데이터를 전달하면 미리 구성된 세부 구현을 이용해 결과물을 보여줌
- 무엇을 해야할지만 알려주는 것 ⇒ "무엇"을 하는 함수인지 빠르게 파악이 가능
- 명령형 프로그래밍의 경우 어떻게 해야 할지 하나 하나 구성이 필요
- 다만 당연히 패러다임은 뭐가 좋다 이런게 아니고 특정 상황마다 유동적으로 사용
- 이렇게 구성하는 것이 좋은 이유 한 가지 더
- 각각의 코드(함수)가 Testable 해짐
단일책임: 하나의 일을 하는 뚜렷한 이름의 함수를 만들자
- 함수가 하는 일을 명확하게 나타내는 함수 이름을 짓도록 함
handle질문제출
= 질문 제출만을 위한 함수handle약관동의팝업&질문제출
= 약관동의 팝업과 질문 제출을 위한 함수 ⇒ 다만, 이 경우 이 둘을 나눠주도록 함 (handle약관동의팝업
)
- 단일책임 원칙을 따르지 않을 경우
- 개발이 진행되며 함수명으로 의미를 파악하기 어려워지게 됨
- 의미 파악이 어려워지면 함수의 세부 구현을 봐야 하고 ⇒ 개발 및 유지보수 비용 증가
- 기능성 컴포넌트
- 선언적 프로그래밍을 통해, 한 가지 일만 하는 컴포넌트를 정의해 사용
slot
을 통해 하위 컴포넌트를 감싸는 방식으로도 개발이 가능
<LogClick log-message='제출 버튼 클릭'> <button onClick={openConfirm} /> </LogClick>
- 이름 짓기가 어렵다면 한글 변수도 유용
- 결국 "빠르게 로직을 판별할 수 있도록 하는 것"이 목적
추상화: 핵심 개념을 뽑아내자
- 핵심 데이터만 남기고 나머지는 추상화
- 얼마나 추상화? 추상화 레벨
- 최대한 재사용 가능한 레벨까지 상황에 따라 필요한 만큼 추상화
- 사용하는 입장에서 알아야 하는 레벨까지, 그리고 유연성을 생각해볼 수 있겠지
- 추상화 레벨은 서로 맞춰줌
- 전체적인 코드가 어느 수준으로 추상화 되어있는지 파악할 수 있어야 함
- 추상화 단계를 비슷하게 정리해주면 코드를 빠르게 이해할 수 있음
바로 사용 가능한 액션 아이템
- 담대하게 기존 코드 수정하기
- pr에 file changes가 많아져도 괜찮음
- 이를 피하고자 한다면 새로운 branches를 따서 refactoring pr하도록 해도 됨
- 큰 그림 보는 연습하기
- 기능 추가 자체는 클린해도, 전체적으로 보기에 어지러울 수 있음
- 팀과 함께 공감대 형성하기
- 코드에 정답은 없음 ⇒ 명시적으로 이야기를 나누는 시간이 필요
- 클린 코드 관련된 코멘트가 필요하다고 판단되면 달자
- 당장에 "이게 굳이 Clean 해야 하나?" 생각되어도 점차 쌓이면 더러워질 것이 분명하기 때문
- 문서로 적어보기: 글로 적어야 명확해진다
- 향후 어떤 점에서 이 로직이 위험할 수 있는가?
- 이 로직을 어떻게 개선할 수 있는가?