항해 플러스에서 클린 코드 과제를 진행하기에 앞서,
클린코드가 과연 무엇인지 정리하게 위해 포스팅을 작성한다.
이어 prettier, eslint 등 클린 코드와 관련 있는 것들에 대해서도 자세히 알아보려 한다.
1. 클린코드 (Clean Code)
클린 코드는 말 그대로 읽기 쉽고 이해하기 쉬운 코드를 작성하는 것을 말한다.
코드 자체가 가독성 있고, 유지보수가 쉽도록 작성되어야 한다는 것이다.
기본적으로 클린 코드는 다음과 같은 특징을 가져야 한다.
1) 클린 코드 특징
- 가독성 : 읽기 쉽고 이해하기 쉬워야 한다.
- 유지보수성 : 수정사항에 대응하기 쉬우며, 독립적이어야 한다.
- 확장성 : 새로운 기능을 추가할 때, 기존 코드를 크게 수정하지 않아야 한다.
- 견고성 : 에러를 발견하기 쉽고, 에러가 발생했을 때에도 동작하거나 대응해야 한다.
- 테스트 가능성 : 테스트 작성이 쉬우며, 단위별 테스트가 가능해야 한다.
- 자기문서화 : 요구사항을 코드 자체로 이해할 수 있게 해야 한다.
- 일관성 : 같은 규칙과 철학으로 작성되어, 예측이 가능해야 한다.
2) 클린 코드 원칙
- DRY(Don't Repeat Yourself) : 같은 코드를 반복적으로 작성하지 않는다.
- KISS(Keep It Simple, Stupid) : 코드를 최대한 간단하게 작성한다.
- YAGNI(You Ain't Gonna Need It) : 필요하지 않은 코드는 작성하지 않는다.
위와 같은 클린 코드 특징을 지키기 위해서는 클린 코드 원칙을 따르는 것이 좋다.
① 의미있는 이름 사용
변수, 함수, 클래스 이름을 작성할 때, 그 목적과 역할을 명확히 나타내는 이름을 부여해야 한다.
- 약어나 모호한 단어 사용 x
// Bad
if (employee.age > 65) {
// retire
}
// Good
const RETIREMENT_AGE = 65;
if (employee.age > RETIREMENT_AGE) {
// retire
}
Bad 코드에서 65라는 숫자는 정확히 어떤 의미인지 알 수 없다.
따라서 RETIREMENT_AGE = 65라고 상수로 표시한 후, 해당 숫자가 퇴직 연령임을 명확히 나타내는 것이 좋다.
*매직 넘버(Magic Number) : 코드 내에 의미 없이 직접적으로 사용되는 숫자.
- 네이밍 컨벤션 일관성 있게 유지
함수, 변수, 클래스명 등을 특정한 형식을 정해두고 사용하는 것이 좋다.
대표적으로 CamelCase나 Snake_case가 있다.
- 같은 역할은 비슷한 이름으로, 비슷한 위치에 두기
유사하게 생긴 요소들은 같은 종류로 보이기 때문에, 같은 역할을 하는 함수나 변수는 비슷한 이름으로 작성하는 것이 좋다.
예를 들어, use로 시작하면 hook일거라 생각하고, handle로 시작하면 이벤트 핸들러일 거라고 예상하게 된다.
따라서 역할에 맡게 보편적으로 사용하는 이름을 붙여주는 것이 좋다.
② 함수를 작고 단일 책임을 가지도록 만들기
- 함수는 한 가지 작업만 수행하도록 만들기
// Bad
function calculateTotalPrice(items) {
let total = 0;
for (let item of items) {
total += item.price * item.quantity;
}
return total * 1.1; // 10% tax
}
// Good
function calculateSubtotal(items) {
return items.reduce((total, item) => total + item.price * item.quantity, 0);
}
function applyTax(amount, taxRate = 0.1) {
return amount * (1 + taxRate);
}
function calculateTotalPrice(items) {
const subtotal = calculateSubtotal(items);
return applyTax(subtotal);
}
Bad 코드의 경우 한 함수에서 두 가지 기능이 존재하고 있다.
하나의 함수는 하나의 기능만 사용하는 것이 좋으므로, 해당 함수를 나눠서 처리하는 것이 좋다.
- 매개변수 수를 3개 이하로 제한
// Bad
function createUser(name, age, city, country, email) {
// create user
}
// Good
function createUser(userInfo) {
const { name, age, city, country, email } = userInfo;
// create user
}
매개변수가 너무 많으면, 가독성이 나빠지고 재사용성이 떨어지기 때문에 3개 이하로 제한하는 것이 좋다.
만일 3개 이상 사용해야 한다면, Good 예시처럼 object로 매개변수를 받아오는 것이 좋다.
- 부수효과를 최소화하고, 있다면 명확히 표현
// Bad
let total = 0;
function addToTotal(value) {
total += value;
}
// Good
function addToTotal(currentTotal, value) {
return currentTotal + value;
}
total이 전역으로 선언되어 있으면, addToTotal 함수가 실행됨에 따라 그 값이 변할 수 있다.
따라서 2번째 함수와 같이 외부 상태의 값을 수정하지 않도록 바꾸는 것이 좋다.
*부수효과(Side Effects) : 함수가 입력을 받고 처리한 결과 외에, 그 함수의 실행에 따라 외부 상태가 변경되는 경우
- 함수의 길이는 20-30 줄 이내로 유지
③ 중복 코드 제거
- 반복되는 코드 패턴을 식별하고 공통 함수나 모듈로 추출
//Bad
function calculateDiscountedPriceA(originalPrice) {
return originalPrice - (originalPrice * 0.2);
}
function calculateDiscountedPriceB(originalPrice) {
return originalPrice - (originalPrice * 0.15);
}
//Good
function calculateDiscount(originalPrice, discountRate) {
return originalPrice - (originalPrice * discountRate);
}
const priceA = calculateDiscount(100, 0.2); // 80
const priceB = calculateDiscount(100, 0.15); // 85
- 상속, 컴포지션, 고차 함수 등을 활용해 코드 재사용성을 높이기
- 유틸리티 함수나 헬퍼 클래스를 만들어 공통 기능을 모듈화
④ 주석 대신 자체 설명적인 코드 작성
- 코드 자체로 의도와 동작을 명확히 표현
- 복잡한 알고리즘이나 비즈니스 로직의 경우에만 주석 달기
- 주석 대신 함수 이름이나 변수 이름으로 의도 표현
⑤ 일관된 포맷팅 유지
- 팀 내에 합의된 코딩 스타일 가이드 따르기
팀 프로젝트를 진행하는 경우, 이런 식으로 팀 코드 컨벤션을 정한 후 개발하는 것이 좋다.
- 자동 포맷터 사용으로 일관성 강제하기
Prettier, ESLint 등을 사용해 일관성 있는 코드를 작성할 수 있도록 강제화 하는 것이 좋다.
- 파일, 클래스, 함수의 구조를 일관되게 유지
- 연관된 것끼리 함수로 그룹 짓기
- 의존성을 바탕으로 순서대로 두기
- 들여 쓰기, 중괄호 위치, 공백 사용 등을 일관되게 유지
- 빈 줄을 통해서 영역을 구분하고 모으기
⑥ 복잡한 조건문 단순화
- 깊은 중첩을 피하고, early return을 활용
- 복잡한 불리언 표현식을 명명된 함수로 추출
- 스위치 문 대신 객체 리터럴이나 Map을 사용
// Bad
if (isActive === true) {
// do something
}
// Good
if (isActive) {
// do something
}
⑦ 적절한 추상화 수준 유지
- 비즈니스 로직과 저수준 구현 세부사항을 분리
- 인터페이스와 구현을 명확히 구분
- 과도한 추상화를 피하고, 필요한 만큼만 추상화
- 동일한 수준의 추상화를 한곳에서 다루기
- SOLID 원칙을 적용하여 모듈 간 결합도를 낮추고 응집도를 높이기
3) 클린 코드 장점
클린 코드를 작성하게 되면, 아래와 같은 장점이 있다.
- 코드의 가독성이 높아져 유지보수가 용이해진다.
- 버그 발생 가능성이 낮아져 코드의 안정성이 높아진다.
- 개발 속도가 향상되고, 개발 비용이 절감된다.
- 팀원 간 협업이 용이해진다.
클린 코드의 중요한 점은 "다른 사람의 관점"을 항상 고려해서 작성하는 것이다.
좋은 코드는 주관적일 수 있지만, 다른 사람이 보았을 때 이해하기 쉽고 유지보수 용이한 코드가 진정한 좋은 코드이기 때문이다.
2. Prettier와 ESLint
위처럼 클린코드를 작성할 때 도움을 주는 도구들이 있다.
바로 Prettier와 ESLint이다.
해당 도구들이 어떤 역할을 하고, 어떻게 사용해야 하는지 알아보자.
1) Prettier
Prettier는 코드 포맷터(formatter)로 코드 스타일을 일관성 있게 자동으로 맞춰주는 도구를 말한다.
Javascript 뿐만 아니라 Typescript, HTML 등 다양한 곳에 사용이 가능하다.
프로젝트 초기 세팅부터 Prettier를 설정해 놓으면, 코드 작성하면서 자동으로 스타일이 적용된다.
특히 팀 프로젝트에서 일관적인 코드 스타일을 유지할 때 사용하면 좋다.
줄 바꿈, 공백, 들여 쓰기, 세미콜론 여부 등의 스타일을 자동으로 설정해 준다.
npm install --save-dev prettier
이렇게 npm을 통해서 설치 후 사용해 주면 된다.
기본적으로 포맷팅 설정이 되어 있어서 따로 설정 없이도 사용할 수 있다.
//.prettierrc
{
"semi": false
}
만일 기본 설정을 바꾸고 싶다면, 이런 식으로 prettierrc 파일을 만든 후 원하는 설정을 입력해주면 된다.
이런식으로 vscode에서 estension을 설치해 주면, 에디터에서 실시간으로 코드 포맷팅을 적용할 수 있다.
2) ESLint
Javascript 코드에서 발생할 수 있는 오류나 문제를 미리 찾아주는 도구이다.
코딩 스타일뿐만 아니라 코드의 품질을 높이기 위해 다양한 검사를 수행한다.
ESLint는 "선언했지만 사용하지 않는 변수가 있는 경우 경고 표시", "== 대신 === 쓰도록 강제" 등
수백 가지의 규칙들을 제공하고 있다.
npm install --save-dev eslint
npm을 이용해 설치한 후 사용하면 된다.
npx eslint --init
init 명령어를 통해 초기 설정을 실행할 수 있다.
해당 명령어를 입력하면 몇 가지 질문이 나오는데, 원하는 것에 맞춰서 선택해 주면 된다.
초기 설정이 끝나면. eslintrc 파일이 생성되며, 해당 파일에서 설정을 변경할 수 있다.
Prettier와 마찬가지로, vscode에서 확장팩으로 설치가 가능하다.
{
extends: ["plugin:prettier/recommended"],
plugins: ["prettier"],
rules: {
"prettier/prettier": "error",
},
},
만일 ESLint와 Prettier를 같이 쓰고 싶다면, eslintrc.js 설정 파일에서 위 설정을 추가해 주면 된다.
이렇게 설정하면, ESLint가 Prettier 규칙을 적용하고, 스타일과 관련된 문제를 자동으로 수정하게 된다.
이렇게 클린 코드에 대해서 알아봤는데, 역시 설명만 읽어서는 제대로 감이 오지 않는 것 같다.
정리해 둔 것을 바탕으로 이번 과제를 리팩토링 하면서 적용하는 시간을 거쳐야겠다!
그러면 좀 이해가 되지 않을까,,
여기서 작성하진 않았지만 토스에서 제공하는 코딩 컨벤션이 있어서 공유한다.
ESLint로 검출할 수 없는 부분들을 가이드하기 때문에 보면서 코드 작성하면 좋을 것 같다.
보면서 많은 도움이 되었다. 진짜 생각하지 못했던 부분들도 세세하게 작성되어 있어서 참고하기 좋았다!