선생님, 개발을 잘하고 싶어요.

[잡학] for문을 파이프라인 함수로 대체하기, (filter, map) 본문

개발/소프트웨어 개발

[잡학] for문을 파이프라인 함수로 대체하기, (filter, map)

알고싶은 승민 2020. 5. 17. 21:58

도입

프로그래밍에서 가장 중요한 요소는 뭘까요? 변수, 함수, 클래스, 연산자?

저는 감히 반복문이라고 할 수 있을 것 같습니다.

 

프로그램은 사람이 다룰 수 없는 단위의 데이터를 가지고 놉니다. 그렇다고 데이터 하나하나의 처리를 사람이 지정해 주는 것은 불가능하죠.

 

그래서 프로그래밍에선 반복문을 제공해서 데이터의 리스트, 컬렉션을 순차적으로 다룰 수 있는 문법인 for을 제공하고 있습니다.

 

그런데 프로그램이 복잡해지면서, 기존의 for 문 안의 내용이 쉽게 비대해지고, 복잡해지기 일쑤입니다.

 

그래서 컬렉션을 가지고 하는 작업을, 조합하는 형태로 단일 문으로 작성할 수 있는 문법을 구상했는데요. 그게 바로 컬렉션 파이프라인입니다.

파이프 라인 함수

컬렉션 파이프라인 함수 예제를 살펴보기 전에, 어떤 기본적인 함수가 있는지 알아봅시다.

필터

컬렉션을 가지고 작업할 때, 모든 데이터에 흥미있는 경우는 별로 없습니다. 무수히 많은 데이터 중 조건에 맞는 데이터만 가지고 작업하는 경우가 많죠.

 

이럴 때 for 문을 사용해서 구현하면 다음과 같이 표현할 수 있습니다.

for (item in collection) {
	if (! /* 조건문 */) continue
	// Do something
}

for 문을 이용한다면, 필터는 원하는 조건에 만족하지 않을 때 continue 하는 식으로 구현되었을 것입니다.

이 코드도 괜찮습니다. 다만, 이 if 문이, filtering의 역할을 한다는 것이 불명확하죠.

 

컬렉션 파이프라인 함수 중 filter가 바로 이런 상황을 대변합니다.

filter는, 컬렉션에서 특정 조건에 만족하는 아이템만 가지고 작업하고 싶을 때 사용합니다.

collection.filter(/* 조건문 */)

위의 코드는, filter를 사용하면 for문이 사라지고 의도가 더 명확한 코드가 됩니다.

people 중에서, 남성인 사람만 가지고 작업하겠다.라는 의도가 for문 코드보다 명확하게 드러나죠?

다음으로 컬렉션 작업 중에 흔한 상황은, 컬렉션의 요소를 다른 요소로 변경하는 일입니다.

예를 들어 person의 모든 정보가 아니라, 이름 정보만 보고 싶은 경우가 있을 것입니다.

 

이럴 때 for 문을 사용해서 구현하면 다음과 같이 표현할 수 있습니다.

person 객체를, 문자열로 변경하여 출력했습니다.

이 코드 역시 (상당히 괜찮은 코드지만) person 객체를 문자열로 mapping 하겠다는 의도를 명확하게 전달하지 않습니다.

 

컬렉션 파이프라인 함수 중 map이 바로 이런 상황을 대변합니다.

map, 컬렉션에서 아이템을 다른 아이템으로 변경하고 싶을 때 사용합니다.

collection<A>.map(/* A type 을 B type 으로 변환하는 함수 */)

위의 코드는, map을 사용하면 for문이 사라지고 의도가 더 명확한 코드가 됩니다.

파이프라인?

이제 filter, map과 같은 파이프라인 함수를 알아보았습니다.

 

이 두 함수만 사용하여도 프로그램에서 for문을 쓰지 않고도 컬렉션을 유연하고 명확하게 다루는데 도움이 될 것입니다.

그런데, 위의 예시를 보면, 명확한 것은 알겠는데 굳이 이렇게 쓰는 이유가 뭔지 알기 어렵습니다. 또 왜 유연하다는 건지 감이 안 오실 겁니다.

 

우선, 왜 이 함수들을 파이프라인 함수라고 하는 걸까요? 파이프처럼 서로 연결하여 하나의 긴 파이프를 만들 수 있기 때문입니다.

 

감이 잘 안 오시나요? 이 함수들의 반환은 모두 Collection입니다!

// 이 전체는, 조건문에 해당하는 요소만 남은 collection이 된다.
collection.filter(/* 조건문 */)

// 이 전체는, B type으로 구성된 collection이 된다.
collection<A>.map(/* A type 을 B type 으로 변환하는 함수 */)

이 말은, 파이프라인 함수의 결과에 파이프라인 함수를 호출할 수 있다는 말입니다!

 

이제 남자인 사람의 이름을 리스트로 만들 때, for 문을 사용하는 것이 아니라 이런 식으로 파이프라인 함수를 조합해서 사용하면 됩니다!

만약, 나이 조건을 추가하고 싶으면 filter 하나만 더 걸어 주면 됩니다.

어때요. 유연하고 명확하죠?

살아있는 예제

살아있는 예제를 확인해봅시다. (이 예제는, 리팩터링 2판의 8.8절의 예제를 따왔습니다.)

 

우리는 20대 남성에게 전화로 설문 조사를 하려고 합니다.

회사 데이터베이스에는 회원 정보가 CSV 형태로 되어있습니다.

첫 번째 행은 column의 이름이 적혀있고 그다음 행부터 고객의 정보가 저장되어 있습니다.

우리가 원하는 것은 나이와, 성별을 가지고 필터링해서, 전화번호만 출력하고 싶습니다.

 

for 문을 사용한 구현

우선, input 파일을 개행 기준으로 자르고, 필요한 번호를 넣어줄 리스트를 준비합니다.

 

첫 번째 row는 column의 메타 정보가 들어 있었죠? 무시해 줍시다. for 문에서 어떤 요소를 무시할 때는 continue를 사용하면 됩니다.

 

row 가 비어 있을 때는 의미 없는 정보이니 무시해 줍시다.

 

이제 본격적으로 필요한 정보를 추출해 봅시다. row 하나는 ", "를 기준으로 각 요소가 지정되어 있죠?

 

그리고 20대인 남자만 result에 추가해줍니다.

 

전체 코드는 다음과 같이 됩니다.

 

파이프라인 함수 사용한 구현

이것도 순차적으로 가 볼까요? collection의 첫 n개의 요소를 무시하는 파이프라인 함수 drop을 사용하면 firstLine 변수를 제거할 수 있을 것 같습니다.

 

row가 비어 있으면 무시하고 싶습니다. 이런 경우, row가 비어 있지 않은 요소만 filtering 해주면 됩니다.

 

이제, row를 쉼표로 구분된 문자열 리스트로 만들어 봅시다.

record를 해석할 때, 문자열 양 옆의 의미 없는 공백 문자들도 trim을 사용해서 정리해 줍니다.

splite이라는 함수로 record collection이 반환되고, 해당 collection에 각 요소에 trim을 해준 요소를 mapping 하였습니다.

 

이제 records의 정보 중에, 필요한 요소만 꺼내와 봅시다. map을 사용해서 records를 필요한 데이터로 mapping 해 주면 됩니다.

 

이제, 성별과 나이를 알게 되었으니 성별 조건, 나이 조건에 따라서 필터를 추가해 줍시다.

 

최종적으로 번호만 mapping 해서 꺼내옵니다.

 

결론

 

두 코드를 읽어 나갈 때, 어떤 것이 더 편한가요?

파이프라인은 데이터가 변경되는 흐름을 명확하게 논리적 흐름대로 볼 수 있다는 장점이 있습니다.

 

또한 for문을 사용했을 때 있던, 변수인 result, firstLine 없이 코드를 작성할 수 있었습니다. pipeResult를 만드는 데 사용한 변수는 rows 하나뿐이라는 사실이 보이시나요? (파이프라인 함수를 사용하면 변수를 줄일 수 있습니다.)

 

이번 포스팅에서 filter, map, drop 총 3가지의 파이프라인 함수를 다루어 보았는데요. collection이 collection을 반환하는 모든 함수는 파이프라이닝 할 수 있습니다. 스스로 다양한 파이프라인 함수를 만들어 보시는 것도 재밌을 것 같네요 :)

 

다들 for문 없는 개발 생활되시길 🙏

Comments