알고리즘, CS

[특강] 클린코드_코드 퀄리티 & 가독성 올리기

차돌박이츄베릅 2023. 8. 4. 17:55

추상화와 구체화

알 필요 없는 정보 숨기기. 필요한 정보만 노출하기.

너무 복잡한 정보를 제공해주지 않도록 적절히 추상화를 하고, 구체적인 정보를 숨기자.

추상화 수준이 너무 깊어진다면 끝까지 확인하기 어려워지기 때문에 적절한 수준의 구체화와 추상화 수준을 유지.

// 추상 -> 구체

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

컵들기();

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

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

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

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

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

{...}

 

함수의 분리 (역할과 책임이 다른 것을 명확히 파악하기)

// ...는 내용의 축약

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 <>...</>)}
	</>
}

 

1. 한가지 일만 하게 하기 (단일 책임 원칙)

함수는 명확하게 한가지 동작만을 해야하고, 이름도 그에 맞게 지어져야 한다.

명령(action)과 조회(get)은 명확히 다른 함수 -> 한가지 일만 하도록!

// 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;
    }
}

 

2. arguments (함수 인수)

  • 함수인수의 이상적인 개수는 0개입니다. 그 다음은 1개, 그 다음은 2개 …
    최대한 두개를 넘지 않게 조절해주는 것이 좋습니다.
  • 함수 인수로 boolean 을 넘기지 않도록 해야합니다.
    boolean을 넘기면 내부에 if 문이 존재한다는 뜻이고 이는 두가지 일을 하고있는 것.
//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;
}

 

 


custom hook 사용하기 ( 비지니스 로직 분리하기 )

가장 쉽게 비지니스 로직을 분리할 수 있는 방법.

서버에서 데이터를 가져오는 작업을 해야 한다고 하면, 이 작업은 여러 컴포넌트에서 필요로 할 확률이 높음.

그럴때, "서버에서 데이터를 가져오는" 이라는 공통된 로직을 컴포넌트 밖의 커스텀 훅으로 분리해 낼 수 있습니다.

 

이 hook을 사용하는 컴포넌트에서는 데이터를 가져오는 로직 자체를 신경 쓸 필요 없이, 데이터의 상태(로딩 중, 에러 발생, 데이터 로드 완료)만을 관리하면 됩니다.

custom hook을 사용하면 비지니스 로직을 컴포넌트 밖으로 분리해서 따로 관리하기 용이해지고, 재사용이 필요할 경우 위 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>;
}

 

 


파일 분리하고 파일 이름 명확하게 짓기

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

  • 파일 이름 짓기:
    파일의 이름은 해당 파일이 무슨 역할을 하는지 바로 알 수 있도록 직관적이어야 합니다. 
    'useFetch.js'라는 커스텀 훅 파일은 네트워크 요청을 처리하는 훅을 정의하고 있다는 걸 암묵적으로 알려주죠.

  • 일관된 네이밍 컨벤션 사용하기:
    컴포넌트 파일은 대문자로 시작('MyComponent.js'), 훅 파일은 'use'로 시작('useFetch.js'), 서비스 파일은 해당 서비스의 기능을 나타내는 이름('apiService.js') 등으로 규칙을 정하면 좋겠죠.

  • 중첩된 폴더 구조 만들기:
/src
  /components
    /Button
      index.js
      Button.test.js
    /List
      index.js
      List.test.js
  /hooks
    useFetch.js
    useLocalStorage.js
  /services
    apiService.js
    authService.js

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

비슷한 코드가 모여있어서 파악하기 용이한 것 => 응집도가 높다!

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

즉, 관련된 코드가 서로 가까이 있도록 만드는 것!

 

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

반대로 여러 컴포넌트에서 사용하는 훅은 별도의 'hooks' 폴더로 옮겨놓을 수도 잇겠죠?