[클린코드 JS] 08. 함수 다루기

Serin-B·2023년 6월 29일
0

클린코드JS

목록 보기
8/15
post-thumbnail

자바스크립트에서 함수는 일급 객체이다. 따라서 변수에 함수를 할당할 수도 있고, 인자로 함수를 전달할 수도 있고, 함수의 결과로 함수를 return 할 수도 있다. 오늘은 이러한 함수를 잘 만들고 다루는 방법에 대한 이야기를 해보려고 한다.

함수를 만드는 3가지 방법

1. 함수 선언식
: function으로 함수를 선언하여 사용하는 방식

function sum(a,b){
  return a + b
}
// 함수명으로 호출가능
sum(1,2) //3

2. 함수 표현식
: 변수에 함수를 할당하여 사용하는 방식

const sum = function(a,b){
  return a + b
}
// 나중에 변수명으로 호출가능
sum(1,2) //3

3. 화살표 함수
: ()=>{} 형식을 사용하여 만드는 방식

const sum = (a,b) => a+b
// 나중에 변수명으로 호출가능
sum(1,2) //3

⊕ 함수 선언식은 호이스팅이 되지만, 함수 표현식은 호이스팅되지 않는다.

// 함수 선언식
hello(); 
function hello() { return 'hello'; }
// 호이스팅 O -> 선언 전에 호출해도 정상 동작

//함수 표현식
hi(); // 에러 발생! hello 함수는 아직 로드안됨
const hi = function() { return 'hi'; }

⊕ 함수를 만들 때, 화살표 함수를 많이 사용하는데, 특정 상황에서는 화살표 함수가 적절하지 않을 수 있으므로 주의하여 사용해야 한다. 예를들어 화살표함수는 렉시컬 스코프를 가지기 때문에 아래와 같이 매서드를 만들경우 this를 읽을 수 없다. 그리고 화살표 함수로 만든 함수는 생성자 함수로 사용할 수 없다. 이외에도 화살표 함수에서는 arguments, call, apply, bind 를 사용할 수 없다.

const user1 = {
  name : '영희',
  getName(){
    return this.name
  }
}

user1.getName() // '영희'

// 화살표 함수로 메서드 만들 경우 
const user2 = {
  name : '철수',
  getName: ()=>{
    return this.name
  }
}

user2.getName() // 'undefined'
// 화살표 함수는 렉시컬 스코프를 따르기 때문에 this를 찾지 못한다.

함수 이름은 동사로!

함수의 이름은 서술적이고 명확하게 지어야 한다. 함수는 대체적으로 특정 동작을 수행함으로 동사로 짓는 것이 좋다. 함수의 이름만 보고도 해당 함수의 동작을 유추할 수 있도록 하는 것이 중요하다.

  • is... : 무언가를 확인하고 불린값을 반환하는 함수 ex) isUser()
  • get... : 어떤 값을 가져오는 함수. ex) getAge()
  • show... : 무언가를 보여주는 함수. ex) showModal()
  • create... : 무언가를 생성하는 함수. ex) createItem()
  • convert... : 무언가로 전환해주는 함수. ex) convertToString()
  • handle...: 무언가를 다루는 함수 ex) handleSubmit()

함수는 되도록 작게!

함수는 작으면 작을수록 좋다. 구체적으로 말하면 들여쓰기 수준이 2단을 넘지 않는 것이 좋다.

또한 하나의 함수는 한 가지 일만 해야한다. 함수는 무언가를 수행하거나, 무언가를 답하거나 등등 하나의 기능만 수행해야 한다. 만약 한 가지 함수가 여러 개의 역할을 수행할 경우 여러개의 함수로 쪼개야 한다. 함수를 만들 때 다음 문구를 법칙처럼 생각하자.

" 함수는 한 가지를 해야 한다.
그 한 가지를 잘 해야 한다.
그 한 가지만을 해야 한다."

함수의 인자는 적게!

함수의 인수는 0~2개가 적당하다. 인수가 많을수록 함수를 이해하기 어렵고 실수할 여지가 많아지기 때문이다.

그러나 어쩔수없이 인수를 많이 다루어야 할 경우에는 객체로 관리하면 좋다. 필수적인 인수와 그렇지 않은 인수를 나누어서 관리하는 것도 좋은 방법이다. 또는 필수적인 인수가 들어오지 않을 경우 Error를 던져주면 안전하게 인수를 주고받을 수 있다.

// 명시적으로 첫번째 인자가 중요하다는 것을 알 수 있음.
function createCar(name, {color, type, brand}){
  return {
    name,
    color,
    type,
    brand
  }
 createCar('차차', {color:'red', type: 'suv', brand:'Tesla'})
}

// 필수 인자가 들어오지 않은 경우 Error 표출
function createCar({name, color, type, brand}){
  if(!name){
    throw new Error('name 이 없습니다')
  }
  return {
    name,
    color,
    type,
    brand
  }
 createCar({color:'red', type: 'suv', brand:'Tesla'})
  // Error: 'name 이 없습니다'
}

Default Value
매개변수에 기본값을 설정해주면, 함수에 인자가 넘어오지 않아도 기본값으로 처리된다. Default Value(=)를 사용하면 안전하고 편리하게 초기화를 시킬 수 있다.

// 함수 내부에서 || 를 사용하여 기본값 설정
function createCarousel(options){
  options = options || {}
  const margin = options.margin || 0;
  const center = options.center || false;
  const element = options.element || 'div'

  return {
    margin, center, element
  }
}
createCarousel() // {margin : 0, center: false, element : 'div'}

// 매개변수에 기본값 설정
function createCarousel({
margin = 0, center = false, element='div'
}={}){
  return {
    margin, center, element
  }
}

createCarousel() // {margin : 0, center: false, element : 'div'}

Rest Parameters
나머지 매개변수. 가변인자(인수가 몇 개가 들어올지 모르는 상황)를 다룰 때 유용하게 사용할 수 있다. 단, 나머지 매개변수는 맨 뒤에서만 사용 가능하다.

function sumTotal(num1,num2, ...args){
  console.log(num1) // 100
  console.log(num2) //200
  console.log(args) // [1, 2, 3, 4, 5]
  return args.reduce((acc, cur)=> acc+cur)
}

sumTotal(100, 200, 1, 2, 3, 4, 5) // 15

순수함수로!

순수함수란?
동일한 인자가 들어갈 경우, 항상 같은 값을 return 하는 함수

// 순수함수 X
let num1 = 10
let num2 = 5

function sum1(){
  return num1+num2
}
sum1() // 15 

num1 = 20 // 변수가 재할당 될 가능성 O
sum1() // 25 -> 변수가 바뀌면 리턴값 변경됨

// 순수함수 O
function sum2(num1, num2){
  return num1+num2
}
sum2(10, 5) //15 -> 같은 인자를 넣으면 항상 같은 값 리턴!

특히 배열과 객체를 다루는 함수의 경우 새로운 배열 또는 객체를 생성하여 반환해주어야 한다.

const obj = {one : 1}

function changeObj(targetObj){
  targetObj.one = 100 // 객체의 상태를 직접 변경하고
  return targetObj //해당 객체를 리턴
}

console.log(changeObj(obj)) // {one: 100}
console.log(obj) // {one : 100}  -> 기존 객체도 변경되어버림

// 리펙토링 version
const obj = {one : 1};

function changeObj(targetObj){
  return {...targetObj, one: 100}
}
console.log(changeObj(obj)) // {one: 100}
console.log(obj) // {one : 1} -> 기존 객체 불변!

void 와 return 고려하자

void
아무것도 반환하지 않는 것을 의미한다. ( 리턴값이 없으면 함수는 undefined 를 반환하므로, void 함수는undefined 를 반환한다.)

함수의 역할을 고려하여, 함수가 반환할 것이 있는지 없는지 생각하여야 한다. 다음과 같은 코드에서는 return 이 불필요하다. 따라서 무조건 리턴을 해야 한다는 강박을 버리고, 함수의 역할에 집중하여 코드를 작성하여야 한다.

// Bad case
function handleClick(){
  return setState(!state)
}
//Good case
function handleClick(){
  setState(!state)
}

클로저를 활용하자

클로저란
클로저는 함수와 함수가 선언된 어휘적 환경의 조합을 말한다. 자바스크립트는 함수를 리턴할 수 있고, 모든 함수는 클로저를 형성한다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.

// 예시 1
function add(num1){
  return function sum(num2){
    return num1 + num2
  }
}

const addOne = add(1) // addOne 은 현재 함수 sum
const addTwo = add(2)
//외부 함수(add)만 실행된 상태에서 내부의 함수(sum) 환경을 기억하고 있음!

addOne(3) // 4
addOne(4) // 5 
addTwo(5) // 7 
add(1)(3) //4
// 예시 2
function makeAdder(x) {
      let y = 1;
      return function(z) {
        y = 100;
        return x + y + z;
      };
    }

    let add5 = makeAdder(5);
    let add10 = makeAdder(10);
    //클로저에 x와 y의 환경이 저장됨

    console.log(add5(2));  // 107 (x:5 + y:100 + z:2)
    console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
    //함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산

이러한 클로저를 잘 활용하면 유연하고 유용하게 코드를 짤 수 있다.

// before
const arr = [1, 2, 3, 'A', 'B', 'C']

const isNumber = (value)=> typeof value === 'number'
const isString = (value)=> typeof value === 'string'

arr.filter(isNumber) // [1, 2, 3]
arr.filter(isString) // ['A','B','C']
// 클로저를 활용하여 리펙토링
const arr = [1, 2, 3, 'A', 'B', 'C']

function isTypeOf(type){
  return function(value){
    return typeof value === type;
  }
}

const isNumber = isTypeOf('number')
const isString = isTypeOf('string')

arr.filter(isNumber) // [1, 2, 3]
arr.filter(isString) // ['A','B','C']

참조

  • 도서 Clean Code(클린 코드) / 로버트 C. 마틴 / 2013.12.24.
  • Udemy 강의 클린코드 자바스크립트 / Poco Jang / 2023. 5.
profile
프론트엔드 개발자

0개의 댓글

관련 채용 정보