Tech

섬세한 ISFP의 코드 가독성 개선 경험

ENTER TECH 2022. 12. 8. 12:00

 

 

개발 초기에 발생하는 비용 대비 유지보수에 드는 비용은 기하급수적으로 늘어납니다.

혼자서 큰 규모의 프로젝트를 완성할 수 없기 때문에 협업은 필수입니다.

 

유지보수에 드는 비용을 줄이는 것, 그리고 원활한 협업!

이 두 가지에서 중요한 것은 무엇일까요?

 

바로, 코드 가독성입니다.

구글에서는 '가독성 승인'이라는 별도의 리뷰 절차를 둘 정도로 중요히 여기고 있습니다.

 

오늘 저는 '저의 MBTI와 경험을 기반으로 코드 가독성을 개선했던 이야기'를 여러분들과 나누고자 합니다.

 

 

 

특정 MBTI의 보편적으로 알려진 특징 중 제 자신과 비슷하다고 느낀 특징을 기반으로 작성했습니다.
ISFP인 모두가 그렇다는 것이 아니니 읽으실 때 참고해주세요!

 

 

 


 

 

 

저의 MBTI는 ISFP인데요. 

ISFP는 타인의 감정에 민감하다 보니 유난히 가독성에 집착하는 면이 있습니다.

코드를 작성할 때, 저의 코드를 처음 보는 누군가가 초를 다투는 상황에서 급하게 수정해야 하는 최악의 상황까지도 걱정합니다.

 

가독성에 대해 집착하게 되는 ISFP

 

그렇기 때문에 모범 답안을 쉽게 찾을 수 없는 경우, 가독성에 대한 고민은 깊어지게 되는데요, 고민을 깊이 한다고 해서 겉으로 쉽게 드러나는 부분이 아니다 보니 때론 가독성을 고민하는데 투자했던 시간에 회의감이 들기도 했습니다.

하지만 돌이켜 생각해보면 이런 고민들은 도움이 되었고 가치가 있었습니다. 

 

또, ISFP의 성향 중 '숲보다 나무를 본다'라는 이야기를 많이 하는데요.

이러한 저의 성향때문에 디테일한 부분을 확인하고, 시각과 같은 감각에 의존하는 특징이 있습니다. 그래서 가독성의 방향도 정확한 단어는 어떤 것일지, 잘 보이는 형태로 코드를 작성하려면 어떻게 하면 좋을지 위주로 고민을 하게 됩니다.

 

 

 

 

저의 가독성 개선 사례들을 보시고 '나만 고민해봤던 부분이 아니구나' 하며
가독성 고민에 대한 회의감을 덜고 안도감을 얻으시거나
혹은 '이런 고민을 하기도 하는구나' 하며 다양한 관점을 확인하는데 도움이 되셨으면 좋겠습니다.









가독성 개선 사례를 크게 두 가지 측면에 집중하여 살펴보겠습니다.

 

 

🎯 정확한 단어 고르기

 

1. 다른 뜻을 가진 단어와 구분하기

const data = await loadData();

load라고 사용된 단어에 주목해보겠습니다.

 

load와 fetch의 차이점

사전적인 의미를 살펴보면 fetch는 '가져오다'라는 의미를 가진 단어이고 load는 가져오는 것뿐만 아니라 싣는 것까지 포함하는 단어입니다.

 

따라서 'loadData' 함수는 이미 '싣는' 행위인 저장을 완료했을 것으로 기대할 수 있습니다. 그런데 다시 저장에 필요한 값을 리턴 값으로 반환하고 있기 때문에 중복처럼 느껴지고 실제로 loadData 함수 내에서 함수에 쓰인 단어의 의미 그대로 저장까지 완료했을지 의문이 듭니다.

 

'load'를 'fetch'로 치환하면 가져오는 동작만 기대할 수 있어 리턴 값을 받아오는 부분이 자연스럽습니다.

 

혹은 'loadData'에서 특정 공간에 값을 저장하도록 구현해두고 성공 여부만 리턴해도 단어에서 기대한 바와 함수의 구현이 일치하여 자연스럽습니다.

 

 

이러한 단어 의미 차이를 구분하는 것은 가독성을 높이는데 도움이 될 뿐만 아니라 라이브러리의 용법을 익히거나 코드를 읽을 때에도 도움이 되는 부가적인 효과가 있었습니다.

const { isLoading } = useQuery(['todos'], fetchTodos);
const { isFetching } = useQuery(['todos'], fetchTodos);

이제 앞선 두 라인의 차이점은 문서를 살펴보지 않아도 단어의 의미 만으로 유추가 가능합니다.

 

'load'는 데이터를 싣는 행위까지 포함하는 단어이기 때문에, 데이터를 다시 불러올 때마다 초기화되는 'isFetching'과는 다르게 'isLoading'이라는 변수는 최초에 데이터가 없을 때는 true로 바뀌고, 데이터를 불러온 이후에는 변하지 않고 계속 true의 상태로 남아있게 됩니다. 

 

다음은 웹 디자인 측면에 관한 예시입니다. 예시에서는 컴포넌트 명칭을 'Box'라고 표현하고 있습니다.

 

하지만 주요 UI 컴포넌트 라이브러리들의 정의를 읽어보면 앞선 예시의 'Box'는 내용물을 감싸고 있는 외곽 요소를 표현할 때 많이 쓰이는 용어이고, 실제 구현은 사과의 이름이나 이미지, 텍스트를 모두 포함하고 있으니 하나의 주제로 묶인 콘텐츠와 액션 등 모든 것을 의미하는 'Card'라는 웹 디자인 요소가 더 적합합니다.

 

이렇게 언뜻 비슷해 보이지만 미묘하게 다른 의미를 가진 단어를 구분해서 가독성을 높인 사례를 살펴보았습니다.

 

 

 

2. 보다 구체적인 단어로 바꾸기

if (expirationTime < PROMOTION_END_TIME) {
  return remainTime / totalTime
}

앞선 코드는 이해하는데 무리는 없습니다.

if (expirationDate < PROMOTION_END_DATE) {
  return remainDuration / totalDuration;
}

여기에서 시간 ('Time')이라는 단어를 시각을 의미하는 'Date'나 기간을 의미하는 단어인 'Duration'으로 바꿔보았습니다.

if문 내에서는 비교할 수 있는 값이라는 것이 한눈에 드러나고, return을 할 때에는 나눌 수 있는 정량적인 값이라는 것이 한눈에 드러납니다.

 

 

'짜증 난다'라는 단어를 서운하다, 억울하다 등의 좀 더 구체적인 상황을 설명할 수 있는 단어로 표현할 수 있듯이, 주로 사용하는 단어들 중에 광범위한 의미를 포괄한 단어들을 다음의 대체 단어들로 치환해보니 조금 더 정확하게 표현할 수 있었습니다.

 

get (가져오다) extract (추출하다), parse (분해하다), aggregate (합치다)
number (숫자) limit (제한이 되는 수), count (총계)
change (변경하다) convert (변환하다), filter (거르다), override (덮어쓰다)
changed (바뀐) dirty (더러운 = 수정이 이루어진)

 

3. 정확하지 않아도 좋은 경우

다음은 초를 날짜 단위로 환산해주는 'convertSecondToText'라는 함수에 대한 테스트 코드입니다.

test('should convert seconds to days', () => {
  const MIN = 60;
  const HOUR = MIN * 60;
  const DAY = HOUR * 24;

  convertSecondToText(3 * DAY + 12 * HOUR + 30 * MIN).toEqual('3.5 days');
});

엄밀히 따지면 'MIN'과 같은 단어는 애매모호한 표현입니다. 60이라는 숫자는 '분'을 의미하는 것이 아니라, '초를 분으로 환산하기 위한 승수' 이기 때문입니다.

하지만 이런 부정확함을 허용했을 때 오히려 전체적인 맥락이 한눈에 들어오면서 잘 읽히는 경우도 있었습니다.

따라서 항상 정확한 표현을 찾기보다는 문맥에 맞게 가독성을 고민하는 것이 효과적입니다.

 

하단에 위치한 코드처럼 일부 생략되어 부정확한 표현이더라도 더 잘 읽히는 경우

 

 

 

🔎 잘 보이는 형태로 작성해보기

 

1. 처럼 작성해보기

const type = exception
  ? undefined
  : condA
  ? 'A'
  : condB
  ? condC
    ? 'BC'
    : 'BD'
  : 'A';

 앞선 코드를 찬찬히 따라가 보면, exception이나 conditionA, conditionB, conditionC 변수의 참 거짓 조건에 따라

 type에 어떤 결과가 올지를 파악할 수 있습니다. 하지만 순차적으로 살펴보아야 결과를 알 수 있는데요, 이는 '플로우 차트'가 연상됩니다.

 

플로우차트와 같이 순차적으로 확인해야 결과를 알 수 있는 코드

한눈에 관계를 보여주고 싶으면 를 사용할 것입니다.

순차적으로 보지 않아도 원하는 행과 열에만 시선을 두면 인과관계를 파악할 수 있다는 장점이 있습니다.

코드를 다음과 같이 변경해보면 표와 가까운 형태가 되어 인과관계가 비교적 잘 드러납니다.

 

 

 

표 형태로 코드의 모양을 개선한 또 다른 사례입니다.

let str = '';

switch (type) {
  case 'apple':
    str = '사과';
    break;
  case 'banana':
    str = '바나나';
    break;
  default:
    str = '포도';
}

앞선 코드는 표와 같은 형태로 맵을 작성할 수 있습니다. 결과적으로 key와 value가 일직선 상에 위치하며 시각적으로 대응관계가 쉽게 눈에 들어옵니다.

 

 

 

2. 목차처럼 작성해보기

<목차>
서론  —————————————— p9
정확한 단어 고르기———————— p20
잘 보이는 형태로 작성하기 ————  p31
결론  —————————————— p55

 

목차는 높은 곳에서 숲을 조망하듯 전체적인 구조를 요약해서 볼 수 있습니다.

코드에 사용된 z-index를 관리할 때 목차를 작성하듯 코드를 작성하였더니 가독성이 좋아졌던 예시를 살펴보겠습니다

 

배치의 높이를 결정하는 z-index(https://developer.mozilla.org/ko/docs/Web/CSS/z-index)라는 css 속성을 앱 전역에서 여러 군데에 적용하다 보면 어떤 요소에 z-index가 무슨 값으로 적용되었는지를 파악하기 힘듭니다. 때로는 특정 요소에 적용된 z-index 값을 파악하지 못하고 새로 추가되는 요소에 z-index를 설정했다가 요소가 의도치 않게 가려진다던지 하는 문제들이 발생합니다.

 

곳곳에 위치한 z-index의 사용을 다음과 같이 `ZINDEX_USAGES` 한 곳에 모아두면 책에서 목차를 보고 한눈에 페이지의 위치를 파악하듯이 앱에 사용된 모든 z-index를 한 곳에서 확인할 수 있어 가독성이 좋아집니다.

 

 

 

3. 용어 정리하듯 작성해보기

FE개발팀: front-end 개발팀
Coze: 카카오 엔터테인먼트에서 사용하는 영문 호칭

“저는 카카오 엔터테인먼트의 FE개발팀에 속해있는 Coze입니다.”

 

용어를 정리해두면 의미를 모를 때 확인하며 읽어 내려갈 수 있으므로 이해에 도움이 됩니다. 코드에도 이런 용어 정리가 있으면 가독성에 도움이 될 것입니다.

 

if (accessType === 'kakao') {
  return Array.from(data)
    .filter(item => !(item.sugar > 5000))
    .sort((a, b) => a.energy - b.energy);
}

앞선 코드는 어떻게 동작할지는 쉽게 예측할 수 있습니다. 하지만 다음과 같이 부분마다 이해를 돕는 표식을 삽입해보는 것으로 개선할 수 있습니다.

const shouldDisplay = accessType === 'kakao';
if (shouldDisplay) {
  const foods = Array.from(data);
  const healthyFoods = foods.filter(menu => {
    const isUnhealthy = food.sugar > 5000;
    return !isUnhealthy;
  })
  const calorieOrderedFoods = healthyFoods.sort((a, b) => a.energy - b.energy);
  return calorieOrderedFoods;
}

if문 안에 비교한 조건문이 '보여줘야 할지를 판단'하는 shouldDisplay라던가,

data는 'food'를 의미하는 것이었고,

5000이라는 숫자와 비교하는 것은 '건강하지 못한 정도에 대한 척도'였다는 의도를 밝히도록 코드 중간에 표식을 삽입해보았습니다.

이로써 코드는 더 길어졌지만, 의도를 명확히 알 수 있어 가독성을 높여주었습니다.

 

 

 

4. 각주처럼 작성해보기

나는 아침 식사 1)를 먹었습니다.
나는 학교 2)에 갑니다.

1) 칠레산 양상추와 경상북도 포항에 위치한 양계장의 닭이 낳은 계란을 버무린 샐러드
2) 서울특별시 강동구 아리수로 93가 길에 위치

 

원하는 핵심만 우선 전달하고, 부연설명이 필요한 경우 위 첨자로 표시만 해두면 아래에 위치한 각주에서 자세한 내용을 찾아볼 수 있습니다.

코드 가독성을 높이기 위해 이런 형태를 적용해본 사례를 살펴보겠습니다.

 

다음은 소설 하위에 챕터들이 있을 때, 각 항목이 클릭되면 로그를 전송하는 코드입니다.

onClick 이벤트 핸들러 함수를 등록해서 로그 전송을 처리하고 있습니다.

하지만 이 코드의 핵심은 소설과 챕터를 어떻게 그려주는지입니다.

로그 전송은 이에 비하면 부차적인 정보입니다.

 

const handleNovelClick = () => {
  if (novel) {
    sendLog(Events.NovelClick)({ novel });
  }
};
const handleChapterClick = () => {
  if (novel && chapter) {
    sendLog(Events.ChapterClick)({ novel, chapter });
  }
};
…

<article onClick={handleNovelClick}>
  소설 {novel.name}
  <section onClick={handleChapterClick}>
    챕터 {chapter.name}
  </section>
</article>

 

부차적인 정보를 흐릿하게 표시해보면 다음과 같이 중요한 부분만 하이라이트 됩니다. 하지만 로그 전송을 위해서는 중간에 부피를 차지하는 흐릿하게 표시된 코드를 작성할 수밖에 없습니다. 이 부분을 각주에서 위 첨자만 남기듯 따로 떼어낼 수는 없을까요?

 

 

다음과 같이 클릭 이벤트를 모아서 처리하는 High Order Component를 작성해보았습니다.

그리고 애플리케이션의 최상단에 위치시켜두고 이벤트 전파를 활용할 수 있도록 하였습니다.

DOM에 data-click-log라는 어트리뷰트가 있는 경우에 로그를 전송하는 처리를 일괄 담당하도록 해두었습니다.

 

 

HOC를 작성한 덕분에 앞선 코드는 다음과 같이 간단한 형태로 변경해서 작성할 수 있었습니다.

<article data-click-log={Events.NovelClick}>
  소설 {novel.name}
  <section data-click-log={Events.ChapterClick}>
    챕터 {chapter.name}
  </section>
</article>

'data-click-log'가 각주의 위 첨자 표기 역할을 하고, 이를 HOC에서 처리하여 실제 기능을 수행하고 있습니다.

 

 

📝 정리

ISFP의 비교적 섬세한 측면에서 크게 두 가지로 가독성을 개선한 사례들을 살펴보았습니다.

 

  • 좀 더 정확한 단어를 고려해본 사례
     
    1. 서로 비슷해 보이지만 미묘하게 다른 의미를 갖는 단어가 있어 함수명을 비롯한 UI 컴포넌트 이름에 의미를 구별하여 사용해보았습니다.
    2. 광범위한 의미를 가진 단어를 구체적인 단어로 치환해보았습니다.
    3. 때론 부정확함을 허용했을 때 문맥에서 이해가 잘 되는 특이한 경우도 있었습니다.

  • 더 잘 보이는 형태를 고려해본 사례 

    1. 와 유사하게 작성해보았습니다.
    2. 목차의 형태를 빌려 작성해보았습니다.
    3. 용어를 정리하듯이 표식을 삽입하며 작성해보았습니다.
    4. 각주를 활용하듯 작성해보았습니다.