도서/기술

[책] 데이터 지향 프로그래밍 - 14장 고급 데이터 유효성 확인

egg528 2025. 2. 9. 15:49

 지금까지 다룬 로직은 대부분 데이터 조작과 관련된 내용이다. 하지만 시스템 로직은 데이터 조작으로만 이루어지지 않고 비즈니스 로직이 포함되어 있기 마련이다. 결국 로직은 비즈니스와 데이터 조작으로 복잡해지기 마련인데 이번 장에서는 복잡한 로직에서 가독성을 챙기는 여러 방법들을 제안한다. 

 

범용 함수 활용하기


// AS-IS
function removeAuthorDuplicates(book) {
    var authors = _.get(book, "authors");
    var uniqAuthors = _.uniq(authors);
    return _.set(book, "authors", uniqAuthors);
}

// 범용 함수
function update(map, path, fun) {
    var currentValue = _.get(map, path);
    var nextValue = fun(currentValue);
    return _.set(map, path, nextValue);
}

// TO-BE
function removeAuthorDuplicates(book) {
    return update(book, "authors", _.uniq);
}

 위 코드는 uniq()를 활용해 중복 저자를 제거하는 메서드이다. 이런 로직의 경우 핵심은 맵의 값을 특정 동작을 통해 수정하는 update 메서드로 볼 수 있다. 비슷한 동작마다 위와 같은 코드를 작성할 수도 있지만 map 필드를 특정 방식으로 update하는 범용 함수를 둔다면 코드를 조금 더 간결하게 작성할 수 있다. 또한 범용 함수는 말 그대로 여러 곳에서 쓰일 수 있기 때문에 재사용성 측면에서도 이점을 가진다.

 

 

 

연산에 이름 부여하기


// AS-IS
function authorIdsInBooks(books) {
    return _.map(books, "authorIds");
}

// 문제의 데이터
[
    ["sean-covey", "stephen-covey"],
    ["alan-moore", "dave-gibbons"]
]

// TO-BE
function authorIdsInBooks(books) {
    return _.flatten(_.map(books, "authorIds"));
}

 authorIdsInBooks()는 이중 배열을 반환하는 문제점이 있어 평탄화를 도와주는 flatten()을 활용했다. 

 

 

function flatMap(coll, f) {
    return _.flatten(_.map(coll, f));
}

function authorIdsInBooks(books) {
    return flatMap(books, "authorIds");
}

 하지만 책에서는 flatten()과 map()메서드를 중복해서 쓰기 보다는 두 동작을 합쳐서 사용하는 데이터 조작의 의미를 이름으로 표현한 메서드를 만들고 이를 활용하는 방향을 추천한다. 

 

 

 

최적의 도구 사용하기


// with forEach
function lendingRatio(books) {
    var bookItems = flatMap(books, "bookItems");
    var lent = 0;
    var notLent = 0;
    
    _.forEach(bookItems, function(item) {
        if (_.get(item, "isLent")) {
            lent = lent + 1;
        } else {
            notLent = notLent + 1;
        }
    });
    
    return lent / (lent + notLent);
}

 forEach는 collection을 순회하며 원하는 로직을 수행할 때 사용되는 메서드이다. forEach를 통해 책 데이터를 순회하며 도서 대여 비율을 검증하는 로직을 구성할 수 있지만 로직에 따라 더 적절한 도구가 있기 마련이다.

 

 

// with reduce
function lendingRatio(books) {
    var bookItems = _.flatMap(books, "bookItems");
    var stats = _.reduce(bookItems, function(res, item) {
        if (_.get(item, "isLent")) {
            res.lent = res.lent + 1;
        } else {
            res.notLent = res.notLent + 1;
        }
        return res;
    }, { notLent: 0, lent: 0 });

    return stats.lent / (stats.lent + stats.notLent);
}

 이 도서 대여 비율을 구하는 로직에서는 reduce를 활용하면 이전보다 더 간단하게 구현이 가능하다. 개인적으로 변수 초기값 설정을 reduce 로직 내부로 가져올 수 있다는 게 마음에 든다. 이건 하나의 예시이기 때문에 항상 더 나은 선택지가 있는지를 고민하는 게 중요할 것 같다.

 

 

내 생각


 가독성을 얻기 위한 코드 작성법 3가지 방법을 제시하는데 리팩토링 시에 참고하면 좋을 내용들인 것 같다. 다만 연산에 이름 부여하기 같은 경우 어느 정도 수준부터 메서드로 분리할 것인지는 취향 차이이기 때문에 주위 친구들이나 동료들을 둘러봤을 때 그 기준이 다양할 것 같다.