성급한 추상화란? 말 그대로 추상화를 미리 과도하게 적용한 것을 의미합니다. 복잡성을 줄이고 재사용성을 높여서 버그나 오타를 한 곳에서 해결하기 위해 추상화를 했지만, 오히려 안 하는 것이 더 나은 상황이 있습니다. 이런 성급한 추상화는 코드 이해와 관리를 더 어렵게 만듭니다.
성급한 추상화가 어떤 경우에 발생하며, 그로 인한 문제점에 대해 알아보겠습니다.
const phil = {
name: { honorific: "Dr.", first: "Philp", last: "Radriquez" },
username: "philpr",
};
// navigation.js
// ...
const navDisplayName = `${phil.name.first} ${phil.name.lest}`;
console.log(navDisplayName); // Philp undefined
// profile.js
// ...
const profileDisplayName = `${phil.name.first} ${phil.name.lest}`;
console.log(profileDisplayName); // Philp undefined
// card.js
// ...
const cardDisplayName = `${phil.name.first} ${phil.name.lest}`;
console.log(cardDisplayName); // Philp undefined
위의 예에서 name.lest 오타가 발견되었습니다. 중복이 발생했고 앞으로 버그가 발생하면 한 군데에서 코드를 수정할 수 있으니깐 추상화를 고려해볼 수 있습니다.
오타를 수정한 후(lest → last) 이름을 표시하는 로직을 추상화했습니다.
const phil = {
name: { honorific: "Dr.", first: "Philp", last: "Radriquez" },
username: "philpr",
};
function getDisplayName(user) {
return `${user.name.first} ${user.name.last}`; // lest -> last
}
// navigation.js
// ...
const navDisplayName = getDisplayName(phil);
console.log(navDisplayName); // Philp Radriquez
// profile.js
// ...
const profileDisplayName = getDisplayName(phil);
console.log(profileDisplayName); // Philp Radriquez
// card.js
// ...
const cardDisplayName = getDisplayName(phil);
console.log(cardDisplayName); // Philp Radriquez
이 경우에는 추상화가 적절하게 적용되어 중복을 제거하고 코드의 유지보수를 용이합니다.
getDisplayName
에 이 유스케이스를 적용하는 방법과 추상함수를 제거하는 방법이 있습니다. 보통 재사용성과 편리함으로 인해 추상함수 로직을 조금 개선하는 쪽으로 택합니다.function getDisplayName(user, { includeHonorific = false } = {}) {
let displayName = `${user.name.first} ${user.name.last}`;
if (includeHonorific) {
displayName = `${user.name.honorific} ${displayName}`;
}
return displayName;
}
// navigation.js
// ...
const navDisplayName = getDisplayName(phil);
console.log(navDisplayName); // Philp Radriquez
// profile.js
// ...
const profileDisplayName = getDisplayName(phil, { includeHonorific: true });
console.log(profileDisplayName); // Dr. Philp Radriquez
// card.js
// ...
const cardDisplayName = getDisplayName(phil);
console.log(cardDisplayName); // Philp Radriquez
이정도 조건문 추가는 괜찮습니다. 아직까지 봐줄만합니다. 하지만 이제부터 문제가 발생합니다.
이러한 요구사항을 적용하기 위해 추상함수를 수정합니다.
const phil = {
name: { honorific: "Dr.", first: "Philp", last: "Radriquez" },
username: "philpr",
};
function getDisplayName(
user,
{
includeHonorific = false,
includeUserName = false,
firstInitial = false,
} = {}
) {
let first = user.name.first;
if (firstInitial) {
first = `${first.slice(0, 1)}.`;
}
let displayName = `${first} ${user.name.last}`;
if (includeHonorific) {
displayName = `${user.name.honorific} ${displayName}`;
}
if (includeUserName) {
displayName = `${displayName} (${user.username})`;
}
return displayName;
}
// navigation.js
// ...
const navDisplayName = getDisplayName(phil, { firstInitial: true });
console.log(navDisplayName); // Philp Radriquez
// profile.js
// ...
const profileDisplayName = getDisplayName(phil, { includeHonorific: true });
console.log(profileDisplayName); // Dr. Philp Radriquez
// card.js
// ...
const cardDisplayName = getDisplayName(phil, { includeUserName: true });
console.log(cardDisplayName); // Philp Radriquez
유지 보수의 편리함과 가독성을 위해 추상함수를 만들었는데, 어느 순간 조건문이 많아져 복잡해졌습니다. 이건 간단한 예제라서 복잡해 보이지 않을 수 있지만, 실제 비즈니스 로직을 담고 있는 코드에서는 더욱 복잡할 것입니다. 다른사람이나 미래의 자신이 이런 추상화 함수를 수정해야 하는 상황이 발생할 때 어려움을 겪습니다.
// navigation.js
// ...
const navDisplayName = getDisplayName(phil, { firstInitial: true });
console.log(navDisplayName); // P. Radriquez
// profile.js
// ...
const profileDisplayName = getDisplayName(phil);
console.log(profileDisplayName); // Philp Radriquez
// card.js
// ...
const cardDisplayName = getDisplayName(phil, { onlyUsername: true });
console.log(cardDisplayName); // Philp Radriquez (philpr)
profileDisplayName
에서 넣어준 includeHonorific 옵션을 지웁니다. 하지만 추상함수의 includeHonorific 로직을 지우기에는 조금 꺼려집니다. 그 이유는 다음과 같습니다.
지금까지 과정에서 성급한 추상화의 문제점을 2가지로 볼 수 있습니다.
profileDisplayName
에서 세 가지 옵션이 true인 경우는 실제로 사용하고 있지 않는데 테스트 코드는 그 쓸데 없는 경우를 생각해서 작성합니다.그러면 해결책은 무엇일까요?
추상화를 안하면 됩니다. 중복이 발생해도 추상화 하지말고 그대로 놔둡니다. 여기저기 추상화할 부분이 발견되더라도 복사 붙여넣기하고 중복되도록 내버려 둡니다. 그러면 처음 추상화하고 싶었을 때 생각했던 것만큼 비슷하지 않기 때문에 별개의 것으로 내버려 두길 잘했다는 생각이 들 수 있습니다. ‘중복은 잘못된 추상화보다 비용이 낮다.’ 이 말을 기억하시길 바랍니다.
만약, 비슷한 로직의 코드가 두 군데서 사용되고 코드의 양이 많다면, 함수를 두 개 따로 만듭니다. 이렇게 하면 각 함수의 기능이 명확해지고 해당 함수를 사용하는 컴포넌트에서 코드의 양이 줄어 가독성이 증가합니다.