킹의 개발일지

쏙쏙 들어오는 함수형 코딩 (21 ~ 25일차) 본문

카테고리 없음

쏙쏙 들어오는 함수형 코딩 (21 ~ 25일차)

k1ng 2023. 9. 7. 21:55

21일차

<일급 추상> 코드의 냄새와 중복을 없애 추상화를 잘 할 수 있는 방법

암묵적 인자란 함수 본문에서 사용하는 어떤 값이 함수 이름에 나타나는것인데, 

 

* 냄새나는 코드

function setPriceByName (cart, name, price) { // 여기서 함수이름에 있는 Price가 암묵적 인자다.
  var item = cart[name];
  var newItem = objectSet(item, 'price', price); // copy and write
  var newCart = objectSet(cart, name, newItem);  // copy and write
  return newCart;
 }

위와 같은 함수가 암묵적 인자를 가지고 있는 함수다. 함수 이름에 있는 암묵적 인자 냄새 특징으로

  1. 함수 구현이 거의 똑같다.

        setPriceByName, setQuantityByName, ... 처럼 내부 구현은 객체의 필드 이름만 다를 뿐 구현은 비슷하다.

 

  2. 함수 이름이 구현의 차이를 만든다.
        => 필드명을 일급으로 만든다. **일급이란 쓸수 있는 값(변수에 담을 수 있는..)
        예를 들어 자바스크립트에서는 숫자, 문자열, 함수, 배열, 등등 이런 것들이 일급값이다.

 

이 냄새를 해결하기 위해서는 아래와 같은 방법을 사용할 수 있다.

 

[암묵적 인자를 드러내기.]
  1. 함수 이름에 있는 암묵적 인자를 확인한다. => (price)
  2. 명시적인 인자를 추가한다.  => (field)
  3. 함수 본문에 하드 코딩된 값을 새로운 인자로 바꾼다. => (field)
  4. 함수를 호출하는 곳을 고친다.


* 개선한 코드

function setFieldByName(cart, name, field, value) {
  var item = cart[name];
  var newItem = objectSet(item, field, value);
  var newCart = objectSet(cart, name, newItem);
  return newCart;
}

 

 

저자가 설명한 냄새나는 코드는 지금껏 내가 많이 싸질러 왔던 코드들이었다... 동작은 비슷하면서도 필드가 다름에 따라 비슷한 함수를 계속해서 찍어내고 있었다.

 

이런 단점은 느껴봐서 잘 안다. 기능이 추가됨에 따라 변경하기도 까다롭다. 또한 만들어둔 함수가 많다보니 만든것 마저 까먹어 다시 만드는 불상사를 만들기도 했다..

어서 다음장을 독파해서 코드에 페브리즈좀 뿌려보고 싶다. 탈취좀하자 주홍아!!


22일차

정적타입 vs 동적 타입

자바스크립트는 대표적인 동적타입의 언어이다.

코드를 실행하기전에 컴파일을 거치는 정적타입의 언어들과는 달리, 실행 후 코드 한 줄 한 줄을 해석한다. 이로인해 컴파일 시간을 줄일 수 있지만, 정적타입의 언어가 컴파일 과정에서 에러를 찾아내 런타임 에러를 예방 하는 장점을 잃는 것이다.

 

그래서 특히나 '관대한' 자바스크립트에 정적타입의 언어의 장점을 입힌 타입스크립트를 도입하고 있는 곳이 많다.

저자는 어느 타입의 언어가 좋고 나쁨을 강조하지 않는다. 그저 편한 언어를 선택하고 이런 고민 할 시간에 잠을 더 자는것이 좋다고 말한다. ㅎㅎ

오늘 분량은 딱히 새로운 내용은 없었다. 암묵적인 인자를 들어내기 위해 문자열을 인자로 받아 들이고 이에 따라서 발생할 수 있는 예외를(타입으로 지정해두고 처리하는 것이 아니라, 어떤 문자열이 오더라도 인자로써 채택하기에 런타임에 체크해야한다는 이야기. 예를 들어 빈문자열, "" ) 어떻게 처리하나 이런 이야기들이었다. 

다음장에서는 콜백으로 바꾸기 리팩터링에 대해서 다룬다고 한다. 내일이 기대된다.


23일차

반복문이 반복적으로 쓰이는 함수 리팩터링하기. 

지금껏 해왔던 리팩터링들을 단계로 나누면 다음과 같다.

 

  1. 코드를 함수로 감싸기
  2. 더 일반적인 이름을 바꾸기
  3. 암묵적 인자를 드러내기
  4. 함수 추출하기
  5. 암묵적 인자를 드러내기

이를 함수 본문을 콜백으로 바꾸기라고하는데, 이를 더 알기 쉽게 설명하면 다음과 같다.

  1. 본문과 본문의 앞부분뒷부분을 구분한다.
  2. 전체를 함수로 빼낸다.
  3. 본문 부분을 빼낸 함수의 인자로 전달하는 함수로 바꾼다.

 

리팩터링이 필요한 코드를 먼저 살펴보자.

try {	// 앞부분
  saveUserData(user);	// 본문
} catch (err) {	// 뒷부분
  logToSnapErrors(err);
}

try {	// 앞부분
  fetchProduct(productId);	// 본문
} catch (err) {	// 뒷부분
  logToSnapErrors(err);
}

// 위 두 코드는 형태가 비슷하다. 함수 실행, 에러시 log 남기기.

 

앞부분, 본문, 뒷부분을 구분했으니 전체를 함수로 빼는 단계를 진행해보자.

function withLogging() {
  try {
    saveUserData(user);	// 불편..
  } catch (err) {
    logToSnapErrors(error);
  }
}

withLogging();

이렇게 함수로 뺐으면, 뭔가 불편한게 눈에 보일것이다. 바로 saveUserData(user) 부분이다. 일반적으로 함수를 만들기위해 리팩터링을 했으나 본문 부분이 동적이지 않으면 이와 같은 함수를 계속해서 만들어야 할 것이다.

 

이를 위해 마지막 단계 '본문 부분을 빼낸 함수의 인자로 전달하는 함수로 바꾼다.' 가 있다.

 

function withLogging(f) {
  try {
    f();	// 원하는 함수가 동작.
  } catch (err) {
    logToSnapErrors(err);
  }
}

withLogging(()=>saveUserData(user));	// 호출

뭔가 부품을 끼워 넣듯이 원하는 함수를 본문 자리에 끼워넣음으로써 좀더 제너럴한 함수가 탄생했다!!


오늘 파트에서 나의 코딩계 새로운 패러다임을 발견했다.! 언어적으로 함수를 인자로 넘기는 것은 가능했지만 실제로 사용하는 것은 이미 언어에서 지원하는 api에서나 사용해봤지 내가 만들어 볼 수 있다고는 생각하지 않았다. 

 

사용에 익숙해서 그런가 예제들을 보면서 저자가 힌트처럼 줬던 설명들을 보니 머릿속에서 절로 답을 만들고 있었다.

내가 쓰던 코드들은 기능은 전반적으로 비슷하지만 호출하는 대상에 따라 조금씩 달랐기에, 새로운 함수를 계속해서 생산하고 있었는데, 이번 파트는 다시말해 새로운 패러다임을 발견했다고 할 수 있다.


24일차

<일급 함수 2>

이번 장에서는 이전에 만들었던 카피-온-라이트 함수들을 콜백으로 바꾸는 작업을 한다. 이번장은 읽는데 시간이 좀 걸렸다. 카피온라이트 함수를 상기해야 할 뿐만 아니라 저번 파트에서 읽었던 콜백으로 바꾸기도 되새겨야 했다. 

 

* 기존 arraySet 메서드

fuction arraySet (arr, idx, value) {
  var copy = arr.slice();
  copy[idx] = value;
  return copy;
 }

* 리팩터링 후

function arraySet (array, idx, value) {
  return withArrayCopy(arr, () => copy[idx] = value);
}
function withArrayCopy(arr, modify) {
  var copy = array.slice();
  modify(copy);
  return copy;
}


잠깐 든 생각이지만 예제로 나온 코드들은 저자가 선별한것이 아닐까 하는 생각이 들었다. 현실의 문제는 예제보다 훨씬 복잡할 뿐더러, 저자가 보여준 예시는 뭔가 콜백으로 바꾸기에 적합한 코드였다.

 

더 복잡한 코드를 대면하면 이렇게 리팩터링 할 수 있을까, 라는 의구심이 들긴 했지만, 나에겐 매우 혁신적인 방법임에는 틀림없다. 

더 배워서 실제에서도 사용해 보고 싶다는 생각이 들었다.



25일차

<함수를 리턴하는 함수>

래핑이라는 기술은 이 책을 읽기 이전에도 사용해 왔었다.

예를 들어 리액트에서 컴포넌트를 구현 할 때 특정 데이터에 종속적이지 않는 표현만 하는 컴포넌트를 만들기 위해 컨테이너 컴포넌트로 한번 감싸고(래핑) 특정 방식에 종속적인 데이터를 처리하고 일반적인 데이터로 변경해서 표현 컴포넌트에 전달해 주는 방식처럼 말이다.

여기서는 이를 함수에 적용을 했다. 과정은 간략히 하면 다음과 같다.

  1. 원래 동작을 고차 함수로 전달
  2. 고차 함수 행동을 새로운 함수로 감싼다.
  3. 원래 행동에 슈퍼 파워가 추가된 함수를 리턴

(이는 함수 팩토리와 같다고 하는데, 여기서 팩토리는 디자인 패턴중에 하나이다.)

 

이전에 만들었던 withLogging 함수를 예시로 보자.

 

withLogging(()=> saveUserData(user));

이 함수는 자동으로 에러 발생시 log를 남기는 동작을 한다. 하지만 문제가 있다.

  1. 어떤 부분에 로그를 남기는 것을 깜빡 할 수 있다.
  2. 모든 코드에 수동으로 withLogging(f) 함수를 적용해야한다.

이를 개선해보자.

 

일단 본문에 들어가는 함수의 이름을 바꿔보자. 이 함수는 로그를 남기지 않기에 명확하게 바꿔주자.

  => saveUserDataNoLogging()

 

try {
  saveUserDataNoLogging(user);
} catch(err) {
  logToSnapErrors(err);
}

 

이제 위 부분을 로그를 남긴다해서 saveUserDataWithLogging 으로 만들어주자.

function saveUserDataWithLogging(user) {
  try {
    saveUserDataNoLogging(user);
  }catch(err) {
    logToSnapErrors(err);
  }
}

 

이를 함수 본문을 콜백으로 바꾸기 리팩터링을 적용해보자.

funtion wrapLogging(f) {
  return funtion(arg) {
    try {
      f(arg);
    } catch(err) {
      logToSnapErrors(err);
    }
  }
}

이제 wrapLogging 메서드는 인자로 들어온 일급 함수에 try, catch를 적용하는 함수를 반환하는 래핑 메서드가 된것이다.

 

var saveUserDataWithLogging = wrapLogging(saveUserDataNoLogging);

이렇게 사용할 수 가 있게된다.

 

이번 파트에서는 일일히 수작업을 해야할 일을 고차함수를 통해서 반복적인 일을 줄일 수 있는 방법을 배웠다. 저자는 고차함수는 중복을 줄여주지만 가독성을 해칠 수 있다고 말한다. 중복제거와 가독성 이 둘사이에서 적절한 타협점을 찾아야 할 것이다.