✅ 배경
컬렉션 파이프라인 Collection Pipeline
- 어떤 연산을 일련의 연산으로 구성하는 프로그래밍 패턴으로, 한 연산의 출력으로 컬렉션을 가져와 다음 연산에 공급하는 방식으로 구성된다. (일반적인 연산은 filter, map, reduce이다.)
- 이 패턴은 함수형 프로그래밍과 람다를 사용하는 객체 지향 언어에서도 흔히 볼 수 있다.
- 이를 이용해 객체 컬렉션 순회 과정을 일련의 연산으로 표현할 수 있다.
사용될 수 있는 연산
- map
입력 컬렉션의 각 원소를 변환한다.
- 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;
}