[JavaScript] Clean Code

기록저장소·2023년 10월 25일

변수 다루기

전역 공간 사용 최소화

전역공간 더럽히지않기

이유 ❓ 어디서나 접근 가능하기 때문에 스코프 분리 위험

전역변수 ❌
지역변수 ⭕
window, global 조작 ❌
const, let ⭕
IIFE, Module, Closure, 스코프 나누기

임시변수 제거하기

이유 ❓
1. 명령형으로 가득한 로직
2. 어디서 어떻게 잘못된지몰라서 디버깅하기 힘듬
3. 추가적인 코드를 작성하고싶은 유혹에 빠짐

해결책 💡
1. 함수 나누기
2. 바로 return 하기
3. 고차함수 사용하기(map, filter, reduce)
4. 선언형 프로그래밍으로 바꾸기

호스팅 주의하기

함수도 호이스팅 된다 (끌어올려서 선언된다)

❕ 호이스팅 : 런타임시 바로 선언을 최상단으로 끌어올리는것

문제 ❓ 문제 코드를 작성할때 예측하지 못한 실행결과 노출

1. var X let, const 지향
2. 함수 조심
3.함수 표현식 사용

타입 다루기

타입 검사

자바스크립트 → 동적인 타입 → 타입검사 어려움 → 잘 찾아서 검색해야함
primitive / reference 타입체크를 해야함
reference에 해당하는것 : Object, Array, function, Date, new String과 같은 레퍼객체

  • typeof
    • 레퍼런스타입은 Object를 반환하게됨
  • instanceof
    • 레퍼런스타입은 최상위가 Object이기때문에 ___ instanceof Object했을때 true반환
    • 이것을 활용해 Object.prototype.toString.call(객체) 사용 가능
  • typeof null → Object 로 반환하는 것은 자바스크립트의 오류

undefined & null

  • undefined
    • 값이 없음 이지만 정의되지 않음
    • NaN
    • typeof undefined
  • null
    • 값이 없음을 명시적으로 표현
    • 숫자적으로 0에 가깝다
    • typeof Object

✓ undefined / null 의 쓰임을 조심해야한다.

eqeq 줄이기

동등연산자

  • ==

    • 그냥 동등연산자
    • 형변환이 일어남(type casting)
      ex) '1' == 1 -> true
  • === (추천)

    • strict 엄격하게 검사

형변환 주의하기

paseInt('9.99', 10) 두번째 인자로 넘기는값은 진수
이부분 놓치지않고 넣어주는게 좋다 (값을 넣지않으면 10진수가 기본값이 아니기때문에 오류발생가능)

암묵적 변환의 예)
11 + '문자' ⇒ '11 문자'
!!'문자' ⇒ true
!!'' ⇒ false
✓ 보고 예측할수있는 명시적 변환을 하자

String(11 + '문자') ⇒ '11 문자'
Boolean(!!'문자') ⇒ true
Boolean(!!'') ⇒ false

isNaN

isNaN(123) ⇒ false → 숫자가아닌게아니다 → 헷갈림.. 느슨한 검사
Number.isNaN(123) ⇒ true → 숫자다 → 엄격한 검사
✓ isNaN을 써야할때는 Number.isNaN으로 사용

경계 다루기

min - max

  1. 최소값과 최대값을 다룬다
  2. 최소값과 최대값 포함 여부를 결정해야한다 ( 이상-초과 / 이하-미만)
  3. 혹은 네이밍에 최소값과 최대값 포함 여부를 표현한다.
    ex) MAX_IN_AGE, MIN_NUMBER_LIMIT

begin - end

시작날짜와 마지막 날짜 표현 (주로 date picker같은 날짜관련에서 사용
reservationDate('YYYY-MM-DD', 'YYYY-MM-DD');
변수명만 잘지어놓으면 begin 날짜부터 end 날짜라는걸 한눈에 알수있음

first - last

포함된 양 끝을 의미
~부터 ~까지 명시적으로 표현해줄수있음

prefix - suffix

prefix : 접두사, 단어 앞에붙음
suffix : 접미사, 단어 끝에붙음
예시로 javascript getter / setter
react use- 가 있다.
이것을 활용하면 코드의 일관성을 유지할수있다.
팀에서 고민하고 붙일수있는 범위이니
어떤 prefix / suffix를 붙일지 컨벤션으로 정하면 좋다

매개변수의 순서가 경계다

호출하는 함수의 네이밍과 인자의 순서의 연관성을 고려한다.

  1. 매개변수를 2개가 넘지 않도록 만든다.
  2. 규칙적이지 않은 매개변수가 들어온다 → arguments, rest parameter
  3. 매개변수를 객체에 담아서 넘긴다.
  4. 이미 만든 함수가 있다 → 랩핑하는 함수

분기 다루기

삼항 연산자 다루기

조건 ? 참 : 거짓
3개의 피연산자를 사용할때 삼항연산자 사용

const example = condition ? (a===0 ? 'zero' : 'positive') : 'negative';

괄호를 이용해 우선순위를 나타낸다 else if 피하기

Early Return

else if 문이 많아지면 흐름을 따라 보기때문에 헷갈림
else if 가 많으면 차라리 switch 문으로 작성하는것을 고려하고
if() - else if()if() - else - if() 혹은 if() - if() 로 조건을 의식적으로 분리하기

부정 조건문 지양하기

부정 조건문 지양해야하는 이유❓

  1. 생각을 여러번 해야할 수 있다.
  2. 프로그래밍 언어 자체로 if문이 처음부터 오고 true부터 실행시킨다.

부정 조건문을 사용하는 경우

  • early return
  • form validation (유효성 검증)
  • 보안 혹은 검사하는 로직

Default Case 고려하기

사용자의 잘못을 유도할 수 있는 환경이라면 default case 고려하기

명시적인 연산자 사용 지향하기

괄호 활용하여 연산자 우선순위 넣기

if ((isLogin && token) || user)

Nullish coalescing operator

number --; -> number = number - 1;

예측 가능하고, 디버깅하기 쉽도록 명시적으로 작성하는것이 좋다
?? 를 붙이면 null 병합 연산자
null과 undefined 만 평가할수있다 (falsy 값 체크 X)
무조건 ??만 사용하는것이아닌 || (or 연산자)도 고려해야한다.
편리하지만 실수를 유도할수있기때문에 주의

드모르간의 법칙

true is not true
false is not false

if (!(A || B)if(!A && !B)

배열다루기

JavaScript의 배열은 객체다

typeof arr === 'object' 이기 때문에
배열 체크 → Array.isArray(arr) 사용하기

Array.length

arr.length 는 배열의 길이가 아닌 마지막 인덱스를 반환

const arr = [1, 2, 3];
arr[5] = 6
// arr ===> [1, 2, 3, , , 6]

이러한 특징을 역이용해서 배열 초기화 가능

function clearArray(arr) {
	arr.length = 0;
}

배열 요소에 접근하기

배열요소에 접근할때

document.getElementById('button')[0]
document.getElementById('button')[1]
document.getElementById('button')[2]

이런식으로 접근하는게 아니라

const [firstButton, secondButton, thirdButton] = document.getElementById('button')

✓ 구조분해할당으로 배열 요소 명시해주기

유사 배열 객체

배열요소 접근
→ 간단하게 util 함수 만들기 ( lodash head )

function head(arr) {
	return arr[0] ?? ''
}

?? -> undefined 일때 빈 스트링
매개변수를 미리 선언하지 않고도 유사 배열 객체(arguments) 를 받아서 처리할수있음

  • map, every ... 같은 고차함수는 사용할수없음 -> Array.from 배열로 바꿔야함.

불변성

불변성 지키는 방법

  1. 배열을 복사한다.
  2. 새로운 배열을 반환하는 메서드들을 활용한다.

기본적으로 새로운 배열을 반환하는 함수들 : map, slice, filter ...
외울필요까지는 없고, 필요할때 문서 확인하기
✓ 원본배열을 건드리지않고 항상 새로운 배열 반환하는 습관 들이기!

배열 메서드 체이닝 활용하기

고차함수를 이용해도 로직들이 늘어나며 복잡해질수있다.
선언적이고 명시적으로 사용하기
⇒ 자바스크립트의 내장 메소드 활용, 메서드 체이닝 활용하기

map vs forEach

map과 forEach의 차이
⇒ return
map은 새로운 배열을 반환
forEach undefined → 주어진 배열을 순회하면서 콜백함수를 실행시켜주는것

Continue & Break

map, reduce와 같은 함수는 continue, break 못쓴다. (반복문의 흐름을 제어하기 어렵다)
forEach는 사용가능 (for, for of, for in)
예외를 던지지않고는 forEach를 중간에 멈출수없음 → try~catch를 사용해야하는데
이 방법은 적절하지않을수있다.
every, some, find findIndex 는 참/거짓 여부에 따라 반복의 종료여부 결정
→ 이 함수들을 적절히 조합해서 사용하는것도 방법

객체 다루기

Shorthand Properties

  • shorthand properties : 객체 속성의 이름과 값이 같을 때, 값을 생략하고 이름만 작성할 수 있게 해준다.
  • concise method : function 키워드를 사용하지 않고 간결하게 정의할수있다.

Computed Property Name

computed property name
직역하자면 계산된 객체 이름
객체 리터럴 안에서 대괄호를 사용하여 속성 이름을 동적으로 계산할 수 있는 문법

Lookup Table

key: value로 이루어져있음
USER_TYPE 에 들어갈 값들이 상수로 지정되어있고
return USER_TYPE[type] || '해당없음'
type은 매개변수로 받는 값

Object Destructuring

const orders = ['First', Second', 'Third'];
`const [st3, , rd3] = orders` // 이렇게도 할수있지만 안쓰는값이 늘어나면 불편할수있으니
const { 0: st2, 2: rd2} = orders

배열도 객체라는 사실을 기억하면 값을 꺼내쓸수있다.

Object.freeze

Object.isFrozen 을 사용하면 freeze 됐는지 확인 가능
but, shallow vs deep 구분 못함
depth가 들어가는부분은 중첩된 freeze를 사용해야함

deepFreeze 하는방법

  1. 대중적인 유틸 라이브러리 (lodash)
  2. 직접 유틸 함수 생성
    a. 객체를 순회
    b. 값이 객체인지 확인
    c. 객체이면 재귀
    d. 그렇지 않으면 Object.freeze
  3. stackoverflow
  4. Typescript readonly

Prototype 조작 지양하기

  1. 이미 javascript는 많이 발전했다. 과거에는 직접 프로토타입을 건드려서 만들어써야했지만 이제는 그렇지않음.
    a. 직접 만들어서 모듈화
    b. 직접 만들어서 모듈화 → 배포 → NPM
  2. javascript 빌트인 객체를 건들지말자

프로토타입을 overwriting 하는 행위는 위험하다.

hasOwnProperty

hasOwnProperty 를 다른 object안에서 선언하여 쓸수도있기때문에
Object.prototype.hasOwnProperty.call(obj, '') 의 형태로 사용

직접 접근 지양하기

어디서나 직접 접근하는것이 아닌
getter/setter 사용해서 안전하게 접근하기
⇒ 예측 가능한 코드를 작성해서 동작이 예측 가능한 앱을 만드는것을 지향해야함

함수 다루기

argument & parameter

  • Parameter (Formal Parameter): 형식을 갖춘 매개변수
  • Argument (Actual Parameter): 실제로 사용되는 인자

복잡한 인자 관리하기

  1. 객체로 주고받기
    `function createCart({ name, type, color, brand })

  2. 첫번째 인자만 빼기 → 중요도!
    function createCart ( name, { type, color, brand })

✓ 함수의 인자를 명시적으로 표시해주기

Default Value

매개변수로 객체를 넘겨받는 함수의 경우, 매개변수를 받지못한 상황에서 (undefined) 해당 객체의 값을 읽으려하면 undefined → 에러발생
⇒ default parameter를 활용하면 더 명시적으로 코드 작성 가능

function createCarousel({
	margin: 0,
	center = false
} = {} )

Rest Parameters

  • spread operater와 다름
  • 기존에 사용되던 arguments는 배열이 아닌 Object (유사배열)

rest parameters는 배열이다.
인자중에서 가장 마지막에 들어가야함
함수의 인자가 가변적일때 활용가능

function sumTotal( initValue, ...args)

sumTotal(100, 1, 2, 3, 4)

void & return

void : 함수에 반환이없음
기본적으로 setState나 alert은 void함수이기때문에 return을 쓸 필요가 없다.
return 을 쓴다면 undefined를 return하게됨
api를 사용할때 반환값 체크하는 습관 필요

화살표 함수

화살표함수를 언제 사용해야하나 ? 에 대해 생각해볼 필요가 있다.

  1. 화살표함수는 렉시컬 스코프를 가지게된다. → 상위의 문맥을 따름 / 기존 this의 동작방식을 따르지않는다.
  2. call, apply, bind, arguments객체 등등..을 사용할 수 없다.
    → arguments대신 rest parameter 사용
  3. 화살표함수로 만든 함수는 생성자로 사용할 수 없다.

✓ 리액트가아닌 자바스크립트에서 화살표함수를 사용하면 예상치못한 결과가 나올 수 있기때문에 유의해야함

Callback Function

callback함수는 호출한 함수에 제어권을 위임할 수 있다.
→ 추상화, 계층을 나누는데에 도움

순수 함수

  1. 순수 함수는 입력에만 의존하고 부작용이 없다.
  2. 같은 입력에 대해 항상 같은 출력을 반환
  3. 순수 함수는 디버깅과 테스트가 쉽고, 병렬 처리와 최적화에 용이

객체, 배열 → 새롭게 만들어서 return 해야한다 (primitive, reference의 차이)

Closure

  1. JavaScript에서 함수와 해당 함수가 선언된 환경의 조합
  2. 함수가 정의된 시점의 환경을 기억하여 나중에 호출될 때에도 그 환경에 접근할 수 있게 해준다.
  3. 비동기 작업, 정보 은닉, 함수 팩토리 등 다양한 상황에서 활용되는 중요한 개념

추상화

Magic Number

Numeric Operator

const MIN: 1_000_000 // 1백만원
const MAX: 100_000_000 // 1억

자주쓰이는 상수 값은 snake case로 선언해서 사용하기

Object.freeze({
	MIN: 1,
	MAX: 5
})

object.freeze 1depth까지 보존가능

네이밍 컨벤션

저장소, 폴더, 파일, 함수, 변수, 상수, 깃, 브랜치, 커밋 등
프로그래밍 전반적으로 이름을 네이밍을 위한 규칙이나 관습을 만드는 것
팀이나 개인의 차원에 따라 다를 수 있으며 특히 개인의 견해와 해석에 따라 다를 수 있다.
하지만 기준을 설정할때 기본적인 논리와 이유가 있어야한다.
⚡ javascript keyword와 겹치지않게 네이밍하는게 중요

대표적인 케이스

  • camelCase - javascript
  • PascalCase - 함수의 생성자, 클래스, 컴포넌트명, enum
  • kebab-case - npm package, 저장소, 파일기반 라우팅 next.js, remix 파일명, web url
  • SNAKE_CASE - 상수

접두사, 접미사

  • prefix-, -suffix
    • attribute
      • data-id
      • data-name
      • data-value
    • container
      • AppContainer
      • BoxContainer
    • component
      • ListComponent
      • ItemComponent
    • interface
      • ICar
      • TCar
    • type
      • AType
      • BType
  • 동사-* : 함수는 동사로 시작
  • _ : private
  • : private, router에 해쉬 라우터로도 사용됨

연속적인 규칙
for문이 중첩될때 : i (index) > j > k
typescript 제네릭 : T > U > V / 경우에따라 T1, T2, T3

자료형 표현

const inputNumber = ;
const someArr = [];
const strToNum = '';

이벤트 표현

  • function on-* : onSubmit, onChange, 구독,실행하는 경우
  • function handle-* : handleChange, 사용자의 이벤트를 핸들링
  • function *-Action
  • function *-Event
  • function take-*
  • function *-Query
  • function *-All

CRUD

  • function generator-*
  • function gen-*
  • function make-*
  • function get
  • function set
  • function remove
  • function create
  • function delete

Flag

  • const isSubmit
  • const isDisabled
  • const isString
  • const isNumber

ETC

  • function selectById(id)
  • function selectAll

유효성 검사

사용자의 입력 값이 유효한지 검증
사용자와 상호작용 ⇒ 사용자의 입력을 받거나 그것을 통해 무언가 하게됨
ex) 이메일
사용자의 입력이 이메일 포맷에 맞는지 검증
이메일 포맷이 맞다면 그때 서버와 통신
어떻게 할까?

  • 정규식
  • javascript 문법 (문자열 검사..)
  • 웹 표준 API (input type..)

할수있는 모든곳에서 다 처리하는게 좋다
사용자의 입력 ⇒ 클라이언트 → 백엔드에서도 처리

에러다루기

try ~ catch

프론트엔드는 사용자의 입력을 받는다
개발자, 프로그래머가 모든 에러를 예측하여 처리하기가 어렵고 거의 불가능
→ try ~ catch 절 사용

try { 
	// 예외가 예상되는 코드 / 발생시킬 코드
} catch(error) {
	// 예외를 처리하는 코드 
} finally { 
	// 데이터 분석을 위한 로그 
}

⚠️ 주의할 점

  1. 간혹 중요하지않다 생각되는 핸들링을 try에 넣지않는데, 넣는것을 추천 (실수 방지)
  2. try ~ catch 중첩사용 하지말고 함수별로 사용하기
    a. 함수 하나하나 만들때마다 try ~ catch 사용하면 너무 피곤하기 때문에 모아서 한번에 처리
  3. catch 구문에서 해야할것 (console.error(error)로 끝나면 안됨)
    a. 개발자를 위한 예외처리
    b. 사용자를 위한 예외처리 (사용자가 볼 수 있다고 생각)
    c. 사용자에게 사용을 제안
  4. 에러 로그 수집
    a. sentry.전송() 어느부분에 문제가 생겼는지 자세하게 알 수 있음
  5. 비추천 하지만 필요에 따라 사용되는 경우
    a. 재귀 함수

사용자에게 알려주기

에러를 사용자에게 알려주기

  • 동료 개발자
  • 내가 만든 앱을 이용하는 사용자

Browser & Web API

HTML Semantic Element

Node : 문서내에 모든 객체
Element : Tag 로 둘러싸인 요소
NodeList : HTML을 형상화하고 담아내는것 (일반적인 리스트와 다름)
⇒ 노드리스트를 사용할때는 배열로 형변환을 한다 두개가 쓸 수 있는 메소드가 다름

✓ querySelectAll 로 가져올때는 노드리스트로 가져오기때문에 주의!

innerHTML

innerHTML은 보안상 위험하다 (XSS)

간단한 코드작성은 괜찮지만 이 부분을 인지하고 있는것이 중요하다

→ 대신 Element.insertAdjacentHTML() / insertAdjacentText 사용 가능

관습적으로 innerHTML을 사용하는것을 지양

  • innerText : 화면에 있는 값을 그대로 읽어옴 (리플로우 발생)
  • textContent : XSS 공격 위험이 없기때문에 더 안전함

결과적으로

  • innerHTML → insertAdjacentHTML
  • 문자열만 렌더링 innerHTML → innerText → textContent

insert* 함수들만 사용해도 더 안전하게 사용할 수 있다.

Data Attributes

비표준적인 요소를 제한/표준화하기 위해 사용

kebap case 사용

  • dataset으로 값을 가져올 수 있음 (camel case)
  • css 접근/선택자로 사용할 수 있음
  • delete로 값을 지울 수 있음

개발환경에서만 존재해야하는 attribute는 빌드단계(webpack, vite)에서 지워주기

Black Box Event Listener

Black Box

  • 내부 구현이 어떻게 동작될지 예측할 수 없는 경우
  • 추상화가 너무 과하게 되거나 명시적인 코드가 아닌 경우

버튼.이벤트등록('이벤트타입', 리스너함수실행) → 반응형으로 실행

button.addEventListener('click', handleClick) 혹은 onClick → 무슨 함수가 실행되는지 모름

명시적으로 코드 작성하여 목적에 맞게 리스너 등록해주는것이 좋다

함께하기

공백

공백도 코드 작성의 일부다

  1. 선언
  2. 로직, 문
  3. 반환

indent depth

indent depth는 복잡해질수록 깊어진다.

의식적으로 코드 작성 필요

  1. 조기 반환
  2. callback ⇒ promise ⇒ async & await
  3. 고차 함수 (map, reduce, filter)
  4. 함수를 나누고 추상화하기
  5. 메서드 체이닝 (.then().then().then())

도구를 사용해서 통일

  • eslint, pretter 등등...

스타일 가이드

네이밍 컨벤션을 포함하는 규칙을 위한 가이드라인으로 하나의 팀 혹은 집단을 위해 존재

즉, 협업에 큰 도움을 주기 위함

  • 서로를 이해하기 위한 시간 절약
  • 코드 품질
  • 일관성
  • 가독성 향상
  • 유지보수 용이성

Google, Airbnb, rush stack(마이크로소프트), javascript 등의 많은 스타일가이드들이 있음

전체를 가져다 써도 되고, 커스텀해서 필요한 부분만 사용해도됨

eslint & prettier 조합해서 쓰면 강력하다

스타일 가이드 읽고 공부 → 나만의 기준을 만들어두면 매우 도움이 많이됨

큰 틀안에서 일관성을 만들고 의식적으로 좋은 코드를 만들수있음

참조사이트

클린코드 자바스크립트(JavaScript)

profile
기록을 남기는 공간.

0개의 댓글