[리팩토링] GoodBye .forEach

cola·2024년 11월 27일

리팩토링

목록 보기
1/1

소개

forEach 없게 리팩토링 하는 방법

왜 forEach대신 범위기반 for문을?

forEach의 문제점

  • 람다 안에서 사이드 이팩트 발생
    • 사이드 이펙트가 없다는 함수형의 장점을 해침
    • 람다 안에서의 사이드 이팩트를 지양하도록 컨벤션을 규정하면, 사이드 이팩트가 발생하는 부분과 아닌 부분이 나뉘어져 코드를 읽기 편안해짐
  • 스트림은 리스트와 달리 끝이 있다는 보장이 없어 람다 안에 갇혀버릴 수 있음, for문은 break 할 수 있지만 forEach는 탈출 할 방법이 없음.
    • c#의 경우 List에는 ForEach가 있으나 IEnumerable에 ForEach가 없음
  • 코드 관점에서도 datas.forEach(something.action) 보다는 something.action(datas)가 훨씬 읽기 편하다

예시

기존 코드

    void foo() {
        List<String> names = getNames();

        // some logic

        names.forEach(name -> {
            String email = nameToEmail(name);
            sendEmail(email);
        });
    }

개선 코드

    void foo() {
        List<String> names = getNames();

        // some logic

        Stream<String> emails = names
            .stream()
            .map(name -> nameToEmail(name));

        sendMails(emails);
    }

    void sendMails(Stream<String> emails) {
        for (String email : emails) {
            sendMail(email);
        }
    }

코드관련 생각해볼만 한 거리

Q: Stream 대신 List 사용해도 되나요?
A: List 사용을 더 선호 함. Stream보다 디버깅하기 용이하기 때문. 리스트가 메모리 조금 더쓰는건 의미 없다 생각.

Q: 스트림 관련 코드가 길어서 가독성이 떨어졌어요
A: 예시로 자바를 사용하였는데 반쪽짜리 함수형 지원(확장함수의 부재)으로 가독성이 떨어졌음. 확장함수를 사용하면 간결하고 가독성 좋은 코드를 만들 수 있음.

위 코드는 예시라 타입을 다 붙였는데 타입추론까지 곁들이면 더욱 간결해짐. 아래는 코틀린에서의 예시 코드

fun foo() {
    val names = getNames()
    
    // some logic
    val emails = names.map { nameToEmail(it) }

    sendMails(emails)
}

fun sendMails(emails: List<String>) {
    for (email in emails) {
        sendMail(email)
    }
}

기타

  • 글을 쓰고 나중에 알게 된 사실인데 자바의 Stream은 범위기반 for이 지원되지 않는다... 리스트로 변환하기 싫다면 범위기반 for문은 사용하지 못한다. 왜 범위기반 for문에서 Stream을 지원하지 않는건지 이해가 가지 않는다.

0개의 댓글