본문 바로가기
Programming Knowledge/CS Knowledge

[CS Knowledge] Clean code 클린코드 정리

by SheenaKaze 2024. 8. 30.

1. 개요

수많은 회사 , 수많은 사람들 개발 과정에서 좋은 코드 리뷰를 driven하는 방법과 클린 코드는 어떤 것일까?.

개발자로써의 협업의 목표는 여러가지가 있을 것이다.

1. 안전한 개발

2. 개발 생산성 증대 

좋은 협업 프로세스가 완성되면 -> 안정성 + 생산성이 증대된다.

질서와 규칙이 있기 때문에, 협업 구성원 모두가 conventions와 rules를 지키면서 코드를 작성해야, 남이 보아도 이해가 쉽다.

 

클린 코드는 그냥 깔끔한 코드, 보기 쉬운 코드라고도 볼 수 있다. 쉽게 요약하자면 그렇다.. 

여기서 좀 더 복잡하게 들어가면 , 내가 적은 코드와 변수에 의도를 담고, 그 의도를 남들이 봐도 "아.. 이 개발자는 이렇게 작성했구나 이해는 된다 이렇게 로직 작성했구나" 하는게 보이도록 설계, 납득 시키는 것이라 생각한다. 

너무 추상적으로 보일 수도 있으나, 클린 코드를 작성하는 것에 대해서는 정답은 없다.

 

2. 의미있는 변수명 짓기

2-1 조직 내에서 코드 컨벤션을 통해 camelCase, kebab-case , snake_case, PascalCase 등 을 잘 활용하자

 

-> 팀원 모두 같은 코드를 작성한 것처럼 보일 수 있다. -> 한 사람이 짠 코드처럼 느껴진다. -> 인지 안되는 부분의 코드도 빠르게 파악할 수 있다. -> 코드만 보고도 어떤 코드인지 파악하기가 쉽다. 

3. 기본적인 변수, 함수 선언법(자바스크립트에서..)

//good
let phoneNumber = "010-1234-5678"

//bad

var getData = 1; //getData 같은 것 함수명으로써가 더 적합

var a = 1; 

var b = 86400;

//good

var itemId = 1; 

var ONE_DAY_SECONDS = 86400; //상수

// 축약어는 최소화 const userNm (x) => cosnt userName (o) 명확하게 표현할 것 

 

* boolean 관련

//bad 

const good = true;

const girlFriend = true;

 

//good

const isGood = true;

const hasGirlFriend = false;

 

* 함수 관련

//bad 

const home = () => {}

//good

const goToHome = () => {}

cpmst handleEvent= () => {}

 

 

* 복수 표현 제대로 사용

//bad

var todo = [1,2,3,4];

//good 

var todos = [1,2,3,4]

 

 

* 스프레드 연산자 

// 배열

const array = [1,2,3];

const newArray = [...array];

// 객체

const obj = { key: 1 }

const newObj = {...obj}

 

*구조 분해 할당 배열 

const [state,setState] = useState();

* 구조 분해 할당 객체 

// 하나의 동일한 변수명만 존재
const ItemComponent = ({title}) => {
return <span>{title}</span>
}

// 같은 변수명을 여러번 써야할때 ex) isLoading, isError
const ItemComponent = ({title}) => {
const {title: someHooksTitle} = someHook();
return <span>{title}, {someHooksTitle}</span>
}

 

 

 

* 스프레드 연산자 + 구조 분해 할당 

* 컴포넌트에서 

// child로 값을 내려줄 때 key, value만 같으면 저렇게도 쓸 수 있음

// rest = {content, subTitle}
const Parent = ({title, ...rest}) => {
return <>
<span>{title}</span>
<Child {...rest}/>
</>
}

const Child = ({content, subTitle}) => {
return <>
<span>{content}</span>
<span>{subTitle}</span>
</>
}

*함수에서

const validBoolean = (firstBoolean, secondBoolean, thirdBoolean) => {
return firstBoolean && secondBoolean && thirdBoolean
}

//arg = [firstBoolean, secondBoolean, thirdBoolean]
const validBoolean = (...args) => {
return args.every(isTrue => isTrue);
}

 

 

4.조건문과 탈출

4-1 명확한 조건 사용하기 (긍정조사용하기)

//bad
const isNotBoy = false;

//good 
const isBoy = true;

// ! 표기가 잘보이지 않고 부정의 부정이라 조건문을 잘못 해석할 여지가 다분함
if(!isNotBoy){} 
if(isBoy){}

 

4-2 조건문에 이름 붙이기

//bad
if(name === 'js' 
&& obj.someProperty.title === "sparta"
&& height < 180) {...}

//good 
const isJsTutor = name === 'js' && obj.someProperty.title === "sparta"
|| height < 180;

if(isJsTutor) {...}

 

4-3 함수를 통해서 조건을 사용하자

//bad
if(name === 'js' 
&& obj.someProperty.title === "sparta"
&& height < 180
|| lastName === 'lee') {...} // 근데 다음에 몸무게도 추가될지도 몰라요~

//good 
const checkThisDataValid = ({name, title, hegiht, lastName})=>{
let result = true;
... // 비지니스 로직

return result;
}

if(checkThisDataValid()) {...}

 

 

4-4 조건문의 성능을 크게는 생각하지 말자.

  • 10개의 조건을 2개로 줄인다면 성능상으로 얼마나 이득일까요? 티도 나지 않는 적은 양입니다.
  • 조건문을 압축하지 마세요.
  • 시간이 지나면 너무나 압축된 조건문을 다시 해석하느라 시간을 많은 시간을 써야할 가능성이 높아요.
  • 조건문을 최적화하려고 하기보다는 이름을 명확하게 지어줌으로써 보기 좋고 읽기 편하게 변경하는것이 더 좋습니다.

4-5 If문 작성 시, 가장 주의해야하는 적은 Deepth 이다.  Early return 

// bad
async function guardClause(userId) {
    const user = await getUser(userId);
    if (user) {
        const orders = await getOrders(user);        
        if (orders && orders.length > 0) {
            const order = orders.find(order => order.value > 100);
            if (order) {
                // 실제 처리할 로직
                console.log(`Found an order from user ${user.id} with value greater than 100: ${order.id}`);
            }
        }
    }
}

// good
async function guardClause(userId) {
    const user = await getUser(userId);
    if (!user) return;
    
    const orders = await getOrders(user);
    if (!orders || orders.length === 0) return;

    const order = orders.find(order => order.value > 100);
    if (!order) return;
    
    // 실제 처리할 로직
    console.log(`Found an order from user ${user.id} with value greater than 100: ${order.id}`);
}

4-6 IF문을 효과적으로 줄여주기 위해 Object Mapping 방식도 고려해볼 수 있다.

function loginWithKakao() {}
function loginWithGoogle() {}
function loginWithApple() {}

// switch
const socialLogin = (social) => {
  switch (social) {
    case "kakao":
      loginWithKakao();
    case "google":
      loginWithGoogle();
    case "apple":
      loginWithApple();
  }
};

//obj mapping
const socialMapper = {
  kakao: loginWithKakao,
  google: loginWithGoogle,
  apple: loginWithApple,
};

const socialLogin = (social) => {
  socialMapper[social]();
};

 

//import 

import img1 from '...'
import img2 from '...'
import img3 from '...'
import img4 from '...'
import img5 from '...'
import img6 from '...'

let imgSrc;
if(imgId === 1){imgSrc = img1}
if(imgId === 2){imgSrc = img2}
if(imgId === 3){imgSrc = img3}
if(imgId === 4){imgSrc = img4}
if(imgId === 5){imgSrc = img5}
if(imgId === 6){imgSrc = img6}

//imgMapper.js
export const imageMapper = {
  1:img1,
  2:img2,
  3:img3,
  4:img4,
  5:img5,
  6:img6,
}

// 사용처
let imgSrc = imgMapper[imgId];

 

4-5. Early return 주의할 점 

*함수의 일관성이 떨어질 수 있다. (먼저 리턴시키기 때문에 , 함수의 흐름을 분산시킴)
*명확하게 종료되지 않는다는 의미를 내포 -> return을 작성할 때는 신중하게

단순히 return; 만 작성할 경우 함수는 원하는 값이 아닌 undefined 을 반환하게 됩니다.

반환값이 없는 void 함수일때는 문제가 없겠지만 필요한 반환값이 존재할 경우 원치 않는 undefined 을 반환하게 되고 만약 undefined check 를 외부에서 하지 않으면 오류가 발생하겠죠?

 


function calculateTotalPrice(items) {
    // 초기값 설정
    let totalPrice = 0;

    if (!items || items.length === 0) {
        return totalPrice;
    }

    items.forEach(item => {
        totalPrice += item.price * item.quantity;
    });

    return totalPrice;
}

이렇게 명확한 초기값을 할당해주게 되면, 꼭 필요한 함수의 경우 만약 내부 조건문이 잘못되어 이상한 값을 받는다고 한들

undefinded가 초래되는 문제는 방지된다. 

결론은 무분별하게 early return 을 사용하지 않도록 return을 사용할 때는 신중하게 작성 

 

5. 추상화 구체화

추상화  : 코드 furnction 의 의도가 숨겨져 있고, 자세히 상세히 기재되어 있지않다 -> 코드의 동작, 의도가 예상되지 않는다. 
구체화 : 코드, function의 의도가 숨겨져 있지 않고, 자세히 상세되어 있다 -> 코드의 동작, 의도가 예상이 된다.

// 추상 -> 구체

import { 컵들기 } from 'cup.js'

컵들기();

// cup.js
// 높은 추상화 수준
function 컵들기(){
팔뻗기();
컵쥐기();
컵들어올리기();
}

// cupDetail.js
function 팔뻗기(){
컵방향으로손방향맞추기();
손움직이기();
}

function 컵쥐기(){
손피기();
손펴서컵에갖다대기();
손가락웅크리기();
손에힘주기();
}

function 컵들어올리기(){
손고정하기();
팔꿈치각도줄이기()
}

function 컵방향으로손방향맞추기(){
손선택하기();
컵방향얼만큼인지확인하기();
선택한손방향변경하기();
}
// 낮은 추상화 수준

{...}

 

 

6. 코드 퀄리티 & 가독성 높이기( 함수의 분리 & html template코드에 method chain은 최대한 지양)

6-1 함수의 분리

const someArray = [...]

//bad
someArray.filter((item)=>{ ... })
 .map((item)=>{ ... })
 .forEach((item)=>{ ... })

//good
someArray.filter(checkValid)
 .map(itemToAnotherItem)
 .forEach(doSomething)

const checkValid = (item) => { return boolean; }
const itemToAnotherItem = (item) => { ... return anotherItem; }
const doSomething = (item) => { ... }

//bad
{
return<>
{newArray.filter(...).map(return <>...</>)}
</>
}

//good
const newArray = someArray.filter(checkValid);

{
return <>
{newArray.map(return <>...</>)}
</>
}

 

자바스크립트는 넘겨주는 함수의 인수와 받는 함수의 인수가 같은 경우

const checkValid = (item) => { return boolean; }

someArray.filter(checkValid)

함수를 축약해서 쓸 수 있다.

 

6-2 한가지 일만 하도록 하기(단일 책임 원칙 SRP)

// bad
const calculate = (n1, n2, op) => {
  if (op == "*") {
    return n1 * n2;
  } else if (op == "+") {
    return n1 + n2;
  } else if (op == "-") {
    return n1 - n2;
  } else {
    return n1 / n2;
  }
};

//good
function add(n1, n2) {
    return n1 + n2;
}

function minus(n1, n2) {
    return n1 - n2;
}

function multiply(n1, n2) {
    return n1 * n2;
}

function divide(n1, n2) {
    if(n2 != 0) {
        return n1 / n2;
    } else {
        console.log("Error: Division by zero is not allowed.");
        return null;
    }
}

 

 

6-3 함수의 arguments는 최소화 , 이상적인 개수는 0개, 1개, 2개..

최대한 arguments가 2개를 넘지 않도록 조절해주는 것이 좋다.

 

//bad
function makeFullName(firstName, lastName, isEnglish) {
    if (isEnglish) {
        return lastName + firstName;
    } else {
        return firstName + lastName;
    }
}

//good
let name = {
    first: 'John',
    last: 'Doe'
};

function makeKoreanFullName(name) {
    return name.first + name.last;
}

function makeEnglishFullName(name) {
    return name.last + name.first;
}

 

6-4 커스텀 훅 Custom hook 사용하기

여러분이 서버에서 데이터를 가져오는 작업을 해야 한다고 가정해 봅시다. 이 작업은 여러 컴포넌트에서 필요로 할 확률이 높겠죠? 그럴때, "서버에서 데이터를 가져오는" 이라는 공통된 로직을 컴포넌트 밖의 커스텀 훅으로 분리해 낼 수 있습니다.

import { useState, useEffect } from 'react';
import axios from 'axios';
// 커스텀 훅
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const res = await axios.get(url);
        setData(res.data);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [url]);

  return { data, loading, error };
}

function Component() {
  const { data, loading, error } = useFetch('https://api.myserver.com/data');

  if (loading) return 'Loading...';
  if (error) return 'Error!';

  return <div>{data && data.map(item => <div key={item.id}>{item.name}</div>)}</div>;
}

 

6-5 파일을 분리하고, 파일 이름 명확하게 짓기

6-5-0 중첩된 폴더 구조 만들기

/src
  /components
    /Button
      index.js
      Button.test.js
    /List
      index.js
      List.test.js
  /hooks
    useFetch.js
    useLocalStorage.js
  /services
    apiService.js
    authService.js

프로젝트가 커지고 복잡해질 경우, 폴더를 여러 단계로 중첩하여 구조를 만드는 것이 유용합니다. 이렇게 하면 관련된 코드들을 명확하게 그룹화할 수 있습니다.

 

6-5-1 일관된 파일 네이밍 컨벤션 사용하기 

파일의 이름은 해당 파일이 무슨 역할을 하는지 바로 알 수 있도록 직관적이어야 합니다.

'useFetch.js'라는 커스텀 훅 파일은 네트워크 요청을 처리하는 훅을 정의하고 있다는 걸 암묵적으로 알려주죠.

6-5-2 파일 이름 명확히 하고 , 파일 분리할 것

파일은 그 안에 있는 기능이나 목적에 따라 분리하는 게 좋습니다. 컴포넌트나 서비스, 훅 등이 각각의 파일에 위치하면 좋겠죠. 그리고 관련된 파일들은 같은 폴더에 묶어 놓는 것도 깔끔하게 정리하는 데 도움이 됩니다.

 

파일 일므은 해당 파일이 무슨 역할을 하는지 바로 알 수 있도록, 직관적으로 지어야한다. 

 

6-6 파일이든, 코드든 비슷한 것은 곁에 두기(응집도 높이기)

비슷한 코드가 모여있어서 파악하기 용이한 것을 개발자들은 응집도가 높다. 라고 표현합니다.

위에서 썼던것처럼 파일을 분리해서 잘 찾도록 묶어둔다던지, custom hook 으로 필요한 부분의 로직들만 모아둔다던지, 부정조건을 사용할때 조건문 바로 위에서 긍정조건으로 변경해둔다던지 하는 것들이 응집도를 높여주는 아주 간단한 스킬입니다.

느낌이 오셨겠지만 관련된 코드가 서로 가까이 있도록 만드는 것입니다.

프로젝트의 코드를 보면서 '이 함수나 컴포넌트는 어떤 다른 코드와 밀접하게 관련이 있을까?'라고 고민해본적이 있으신가요?

같은 파일, 또는 같은 폴더 안에 관련된 코드를 묶는 것은 코드를 읽고 이해하는 데 큰 도움이 됩니다.

예를 들어, 특정 컴포넌트에만 사용하는 훅이라면 그 컴포넌트와 같은 위치에 components.hooks.ts 과 같은 파일을 통해 정의하는 것이 좋습니다.

반대로 여러 컴포넌트에서 사용하는 훅은 별도의 'hooks' 폴더로 옮겨놓을 수도 있습니다.

이런 방식으로 비슷한 것은 곁에 두고, 응집도를 높이면 코드의 가독성은 물론 연관 코드를 빠르게 찾을 수 있기 때문에 유지보수성이 크게 향상됩니다.

한 것은 곁에 두기 (응집도 높이기)