쏙쏙 들어오는 함수형 코딩 - Chapter 10, 11

Haz·2023년 9월 11일
0
post-thumbnail

코드의 냄새


비슷하게 생긴 함수들이 마케팅 팀 요청으로 인해 늘어났다는 이야기로 시작한다. 분명 실무에서 그때그때 필요한 코드를 짜다보면 발생 가능한 문제다. 비슷한 기능을 하는 함수들을 하나의 함수로 통합해 모듈로 사용하면 불필요한 용량을 줄일 수 있기 때문이다.

이를 위해 분명하게 보이지 않으나 비슷한 기능을 하는 함수들의 차이점은 필드를 결정하는 문자열이 함수 이름에 있다는 것으로, 그 일부가 인자처럼 동작한다는 해결방법을 제시하며, 이 코드의 냄새를 “함수 이름에 있는 암묵적 인자” 라고 부른다.

  • 코드 냄새 의미: 더 큰 문제를 가져올 수 있는 코드
    • 함수 이름에 있는 암묵적 인자
      • 함수 구현이 거의 같음
      • 함수 이름이 구현의 차이를 만듦
      • 해결 방법(리팩토링): 필드명을 일급으로 만든다 → 암묵적 인자를 드러내기(express implicit argument)
        1. 함수 이름에 있는 암묵적 인자 확인
        2. 명시적인 인자 추가
        3. 함수 본문에 하드 코딩된 값을 새로운 인자로 변경
        4. 함수를 호출하는 곳 수정

해결 예시

// 이전
function setPriceByName(cart, name, price) {
	var item = cart[name];
	var newItem = objectSet(item, 'price', price);
	...
}
cart = setPriceByName(cart, "shoe", 13);
// 이후
function setFieldByName(cart, name, value, field) {
	var item = cart[name];
	var newItem = objectSet(item, field, value);
	...
}
cart = setFieldByName(cart, "shoe",'price', 13);

"암묵적 인자를 드러내기" 리팩토링을 통해 필드명을 일급 값으로 만든다. 리팩토링 전에는 필드명이 함수 이름에 암묵적으로 있었으며 API로도 제공되지 않았으나, 이제 암묵적인 이름은 인자로 넘기는 값이 되었다.

  • 일급 의미: 값은 변수나 배열에 담을 수 있음
    • 숫자, 문자열, 불리언, 배열, 객체 등 변수에 할당할 수 있는 값이라면 일급
    • 함수명, 수식 연산자, if, for 키워드 등은 일급이 아님
    • 자바스크립트에서 함수는 일급 값

필드명을 문자열로 하면 버그 발생?

해당 문제를 해결하기 위한 두 가지 방법은 컴파일 타임에 검사하는 것런타임에 검사하는 것이다.

컴파일 타임에 검사하기

주로 정적 타입 시스템에서 사용하며 자바스크립트의 슈퍼셋인 타입스크립트로 문자열이 사용할 수 있는 필드인지 확인 가능하다.

런타임에 검사하기

함수를 실행할 때마다 동작하여 전달한 문자열이 올바른 문자열인지 확인하는 방식이다.

var validItemFields = ['price', 'quantity', 'shipping', 'tax'];

function setFieldByName(cart, name, value, field) {
	if(!validItemFields.includes(field))
		throw "Not a valid item field: " + "'" + field + "'.";

	var item = cart[name];
	var newItem = objectSet(item, field, value);
	...
}

일급 필드를 사용하면 API를 바꾸기 더 어렵다?

필드명을 일급으로 만들어 사용하면 세부 구현을 밖으로 노출하는 것인지에 대한 우려 발생한다.

내부에서 정의한 필드명이 바뀐다고 해도 사용하는 사람들이 원래 필드명을 그대로 사용하고 싶다면 내부에서 필드명을 바꿔주는 코드 추가하면 된다.

var translations = {'quantity': 'number'};

// 함수 내에 추가
if(translations.hasOwnProperty(field)) field = translations[field];

연습 문제

// 1번
const multyply = (x, y) => {return x * y};

// 2번
const incrementFieldByName = (cart, name, field) => {
	var item = cart[name];
	var value = item[field];
	var newValue = value + 1;
	var newItem = objectSet(item, field, newField);
	var newCart = objectSet(cart, name, newItem);

	return newCart;
}

객체와 배열 과잉 사용

해시 맵이 속성과 값을 잘 표현하는데 자바스크립트에서는 객체로 동일한 효과를 볼 수 있다.

❗ 데이터를 사용할 때 인터페이스로 감싸주면 데이터를 정해진 방법으로만 쓸 수 있다. 그러나 보편적인 엔티티는 객체와 배열처럼 일반적인 데이터 구조 사용해야 함.

  • 데이터 지향 의미: 이벤트와 엔티티에 대한 사실을 표현하기 위해 일반 데이터 구조를 사용하는 프로그래밍 형식

고차함수

  • 고차 함수 의미: 인자로 함수를 받거나 리턴값으로 함수를 리턴할 수 있는 함수
    • 반복문을 만들지 않도록 하는 해결 방법(리팩토링): 함수 본문을 콜백으로 바꾸기
      1. 바꿀 부분의 앞과 뒤를 확인
      2. 리팩토링 할 코드를 함수로 추출
      3. 추출한 함수의 인자로 넘길 부분을 또 다른 함수로 추출
      • 예시 코드

        // f는 함수로, forEach 함수는 인자를 함수로 받을 수 있는 고차함수
        function forEach(array, f) {
        	for(var i = 0; i < array.length; i++) {
        		var item = array[i];
        		f(item);
        	}
        }
        
        function cookAndEat(food) {
        	cook(food);
        	eat(food);
        }
        
        function clean(dish) {
        	wash(dish);
        	dry(dish);
        	putAway(dish);
        }
        
        // 수정
        forEach(foods, function(food) {
        	cook(food);
        	eat(food);
        });
        
        forEach(dishes, function(dish) {
        	wash(dish);
        	dry(dish);
        	putAway(dish);
        });


일급 함수를 활용해 카피-온-라이트 리팩토링 하기


카피-온-라이트의 규칙은 복사본을 만들고 수정한 다음 복사본을 리턴하는 것이므로, 달라지는 부분은 수정하는 방법이고, 복사본을 만들고 리턴하는 동작은 항상 같다.

따라서 달라지는 부분을 본문으로, 달라지지 않는 앞뒤 사이에 오면 적용이 가능하다.

  1. 본문의 앞, 뒤를 확인하기
// 앞, 뒤 확인하기
function arraySet(arr, idx, val) {
	let copy = arr.slice(); // 앞. 복사본을 만드는 과정으로 어떤 경우에도 변하지 않음
	copy[idx] = val; // 본문
	return copy; // 뒤. 복사본을 반환하는 과정으로 어떤 경우에도 변하지 않음
}
  1. 함수 빼내기
// 원본에서 함수 추출
function arraySet(arr, idx, val) {
	return withArrayCopy(arr);
}

// 추출한 함수
function withArrayCopy(arr) {
	let copy = arr.slice();
	copy[idx] = val; // val 값이 없어 작동 x
	return copy;
}
  1. 콜백 빼내기
// 본문을 인자로 받음
function arraySet(arr, idx, val) {
	return withArrayCopy(arr, function(copy) {
		copy[idx] = val;
	});
}

// 추출한 함수 → copy-on-write 원칙을 따르고 재사용할 수 있는 함수
function withArrayCopy(arr, modify) {
	let copy = arr.slice();
	modify(copy);
	return copy;
}

arraySet() 함수 실행 단계

  1. arraySet() 함수 실행
  2. 반환값을 만들기 위해 withArrayCopy() 실행
  3. withArrayCopy() 내에서 만든 변수 copy 가 콜백 함수 인자가 되어 modify 를 실행
  4. modify 가 익명 함수로 실행되어 copy[idx] = val 를 수행함
  5. 수정된 copy 를 반환하며 withArrayCopy() 실행 종료
  6. arraySet() 실행 종료

연습 문제

// 배열 메소드를 활용하는 함수에 withArrayCopy 적용하기
const push = (arr, el) => {
	return withArrayCopy(arr, (copy) => {
		copy.push(el);
	});
}

const drop_last = (arr) => {
	return withArrayCopy(arr, (copy) => {
		copy.pop();
	});
}

const drop_first = (arr) => {
	return withArrayCopy(arr, (copy) => {
		copy.shift();
	});
}
// 객체에도 카피-온-라이트 적용하기
function objectSet(object, key, value) {
	let copy = Object.assign({}, object);
	copy[key] = value;
	return copy;
}

function objectDelete(object, key) {
	let copy = Object.assign({}, object);
	delete copy[key]
	return copy;
}

// withObjectCopy 함수 생성
function withObjectCopy(object, modify) {
	let copy = Object.assign({}, object);
	modify(copy);
	return copy;
}

// 상기 함수 리팩토링
function objectSet(object, key, value) {
	return withObjectCopy(object, function(copy) {
		copy[key] = value;
	});
}

function objectDelete(object, key) {
	return withObjectCopy(object, function(copy) {
		delete copy[key];
	});
}



결론


함수형 프로그래밍에서 함수를 통합하고 단순화하면서 인자와 함수의 역할이 뭔지를 명확하게 하는 작업이 얼마나 중요한 부분인지 이 두 장을 통해서 확실히 알았다.

이에 더해서 단순히 인자로 변수에 저장된 값만 전달하는 것이 아니라 함수를 넘겨주어 고차 함수로 만든다면 굳이 불필요한 중복 함수를 만들 필요가 없다는 점도 앞으로 프로그래밍을 할 때 적절하게 사용하는 연습을 해야겠다고 다짐했다.

이제 파트2로 넘어와서 조금 더 구체적이고 실제로 코딩에 쉽게 반영하기 어려운 내용들이 나오고 있는데 잊지 않고 머릿속에 잘 저장해뒀다가 반영하게 된다면 기쁠 것 같다!😄

profile
나도 재밌고, 남들도 재밌는 서비스 만들어보고 싶다😎

0개의 댓글