logo
새로운 블로그로 이전하였습니다.

(리팩터링 2판) ch08.8 반복문을 파이프라인으로 바꾸기

  • 리팩터링
  • 컬렉션_파이프라인
  • Collection_Pipeline
  • filter
  • map

✅ 배경

컬렉션 파이프라인 Collection Pipeline

  • 어떤 연산을 일련의 연산으로 구성하는 프로그래밍 패턴으로, 한 연산의 출력으로 컬렉션을 가져와 다음 연산에 공급하는 방식으로 구성된다. (일반적인 연산은 filter, map, reduce이다.)
  • 이 패턴은 함수형 프로그래밍과 람다를 사용하는 객체 지향 언어에서도 흔히 볼 수 있다.
  • 이를 이용해 객체 컬렉션 순회 과정을 일련의 연산으로 표현할 수 있다.

사용될 수 있는 연산

  1. map

입력 컬렉션의 각 원소를 변환한다.

  1. filter

입력 컬렉션을 필터링해 부분집합을 생성한다.

효과

객체가 파이프라인을 따라 어떻게 처리되는지를 읽을 수 있어 코드 가독성이 좋아진다.


✅ 절차

1. 반복문에서 사용하는 컬렉션을 가리키는 변수를 하나 만든다.
-> 기존 변수를 단순히 복사한 것일 수도 있다.
 
2. 반복문의 첫 줄부터 시작해, 각각의 단위 행위를 적절한 컬렉션 파이프라인 연산으로 대체한다.
이 때 컬렉션 파이프라인 연산은 1.에서 만든 반복문 컬렉션 변수에서 시작하며, 
이전 연산의 결과를 기초로 연쇄적으로 수행한다.
하나를 대체할 때마다 테스트한다.
 
3. 반복문의 모든 동작을 대체했다면 반복문 자체를 지운다.
-> 반복문이 결과를 누적 변수 accumulator에 대입했다면 파이프라인의 결과를 그 누적 변수에 대입한다.

🧑🏻‍🏭 리팩토링 실습

예시 코드

1. CSV 형태의 데이터

office, country, telephone
Chicago, USA, +1 234 234 1234
Beijing, China, +86 1234 123 123
Bangalore, India, +91 80 4098 1234
Porto Alegre, Brazil, +55 123 123 123
... 

회사의 지점 사무실 정보를 CSV 형태로 정리했다.

2. 함수

function acquireData(input) {
	const lines = input.split("\n"); // 컬렉션
	let firstLIne = true;
	const result = [];
	
	for (const line of lines) { // 반복문
		if (firstLine) {
			firstLine = false;
			continue;
		}
		
		if (line.trim() === "") continue;
		
		const record = line.split(",");
		
		if (record[1].trim() === "India") {
			result.push({city: record[0].trim(), phone: record[2].trim()});
		}
	}
	
	return result;
}
  • 인도에 위치한 사무실의 도시명과 전화번호를 반환하는 함수이다.
  • 이 코드의 반복문을 컬렉션 파이프라인으로 바꾸어보겠다.

리팩토링

1. 루프 변수 loop variable 생성

function acquireData(input) {
	const lines = input.split("\n"); 
	let firstLIne = true;
	const result = [];		
	const loopItems = lines; // 루프 변수
	
	for (const line of lines) { 
		if (firstLine) {
			firstLine = false;
			continue;
		}
		
		if (line.trim() === "") continue;
		
		const record = line.split(",");
		
		if (record[1].trim() === "India") {
			result.push({city: record[0].trim(), phone: record[2].trim()});
		}
	}
	
	return result;
}

2. 반복문을 대체할 수 있는 연산 사용

silce() 사용

function acquireData(input) {
	const lines = input.split("\n"); 
	// let firstLIne = true; 
	const result = [];		
	const loopItems = lines.slice(1); 
	
	for (const line of lines) { 
	//	if (firstLine) {
	//		firstLine = false;
	//		continue;
	// 	}
		
		if (line.trim() === "") continue;
		
		const record = line.split(",");
		
		if (record[1].trim() === "India") {
			result.push({city: record[0].trim(), phone: record[2].trim()});
		}
	}
	
	return result;
}
  • 반복문의 첫 if문은 CSV 데이터의 첫 줄을 건너뛰는 역할로, slice() 로 대체할 수 있다.

trim()filter()로 대체

function acquireData(input) {
	const lines = input.split("\n");  
	const result = [];		
	const loopItems = lines
										.slice(1)
										.filter(line => line.trim() !== "")
										; // 문장 종료 세미콜론을 별도 줄에 적기
	
	for (const line of lines) { 
	//	if (line.trim() === "") continue;
		
		const record = line.split(",");
		
		if (record[1].trim() === "India") {
			result.push({city: record[0].trim(), phone: record[2].trim()});
		}
	}
	
	return result;
}
  • 빈 줄을 지울 목적으로 사용한 trim()filter()로 대체할 수 있다.
  • 문장 종료 세미콜론(; )을 별도 줄에 적어두면 가독성이 향상된다.

map()을 사용해 여러 줄짜리 CSV 데이터를 문자열 배열로 변환

function acquireData(input) {
	const lines = input.split("\n");  
	const result = [];		
	const loopItems = lines
			.slice(1)
			.filter(line => line.trim() !== "")
			.map(line => line.split(","))
			; 
	
	for (const line of lines) { 
		const record = line;
		
		if (record[1].trim() === "India") {
			result.push({city: record[0].trim(), phone: record[2].trim()});
		}
	}
	
	return result;
}

if문을 filter()로 대체

function acquireData(input) {
	const lines = input.split("\n");  
	const result = [];		
	const loopItems = lines
			.slice(1)
			.filter(line => line.trim() !== "")
			.map(line => line.split(",")
			.filter(record => record[1].trim() === "India"))
			; 
	
	for (const line of lines) { 
		const record = line;
		
//		if (record[1].trim() === "India") {
			result.push({city: record[0].trim(), phone: record[2].trim()});
//		}
	}
	
	return result;
}

map()으로 결과 레코드 생성

function acquireData(input) {
	const lines = input.split("\n");  
	const result = [];		
	const loopItems = lines
			.slice(1)
			.filter(line => line.trim() !== "")
			.map(line => line.split(",")
			.filter(record => record[1].trim() === "India")
			.map(record => {cite: record[0].trim(), phone: record[2].trim()}))
			; 
	
	for (const line of lines) { 
		const record = line;			
		result.push(line);
	}
	
	return result;
}

3. 파이프라인의 결과를 누적 변수에 대입

function acquireData(input) {
	const lines = input.split("\n");  
	const result = lines
			.slice(1)
			.filter(line => line.trim() !== "")
			.map(line => line.split(",")
			.filter(record => record[1].trim() === "India")
			.map(record => {cite: record[0].trim(), phone: record[2].trim()}))
			; 
	
//	for (const line of lines) { 
//		const record = line;			
//		result.push(line);
//	}
	
	return result;
}

참고