리팩토링 - 코드에서 나는 악취를 찾아서
저는 일하기 귀찮을때 가끔씩 제가 개발했던 코드를 한번씩 훑어보곤 합니다. 그때마다 악취가 올라오는 코드를 발견하면 지체없이 리팩터링 2판을 끼고 이 부분을 어떻게 개선하면 좋을 지 즐거운(?) 고민을 하곤 합니다.
이번 글에는 리팩터링2판 내용중 냄새나는 코드의 특징 및 연관된 내용을 담았습니다.
냄새나면 갈아라 - 켄트 백 할머니의 육아 원칙
기이한 이름 (Mysterious Name)
- 코드는 항상 단순하고 명료해야합니다.
- 함수, 모듈, 변수, 클래스등 이름만 보고도 용도를 파악할 수 있어야 합니다.
- 물론, 이름 짓기는 프로그래밍에서 가장 어려운 일 중 하나입니다.
정말.. 어렵습니다😩
Example - meme
const zoneBer = (time) => {
//time만큼 실행을 대기하는 함수
};
‘존버’ 라는 밈을 모르면 알 수 없습니다. 재미는 있겠네요.
Example - 너무 추상적
const processPermission = (user) => {
//user의 권한을 확인
//user의 권한이 없으면 권한을 요청함.
//user의 권한이 있으면 true 반환.
};
process라는 말로는 어떤 행위를 하는지 명확하지 않습니다.
Example - 모호함
const zoneIn = (user) => {
//user를 특정 구역에 진입처리 로직
};
zoomIn
으로 착각할 수 있습니다.
어떻게 고치나요?
- 함수 선언 바꾸기
- 필드 선언 바꾸기
- 변수 선언 바꾸기
중복 코드 (Duplicated Code)
- 똑같은 코드 구조가 반복되면 통합 할 수 있습니다.
- 중복이 많은 코드는 수정하기가 어렵습니다. 각각 비교 해야하니까요.
Example - 비슷한 패턴이 반복
var findManager = function () {
employees.forEach(function (person) {
if (person.type == "Manager") {
console.log(person.name);
}
});
};
var findCleaner = function () {
employees.forEach(function (person) {
if (person.type == "Cleaner") {
console.log(person.name);
}
});
};
var findDeveloper = function () {
employees.forEach(function (person) {
if (person.type == "Developer") {
console.log(person.name);
}
});
};
Example - 동일한 코드가 반복
const array_a = [];
const array_b = [];
let sum_a = 0;
for (let i = 0; i < 4; i++) sum_a += array_a[i];
let average_a = sum_a / 4;
let sum_b = 0;
for (let j = 0; j < 4; i++) sum_b += array_b[j];
let average_b = sum_b / 4;
어떻게 고치나요?
- 함수 추출하기
- 문장 슬라이드하기
- 메서드 올리기
긴 함수 (Long Function)
- 긴 함수는 한번에 이해하기 어렵습니다.
-
Example - 정신이 혼미해질 정도로 긴 함수(JAVA)
어떻게 고치나요?
- 함수 추출하기
- 임시변수를 질의 함수로 바꾸기
- 매개변수 객체 만들기
- 객체 통째로 넘기기
- 함수를 명령으로 바꾸기
- 조건문 분해하기
- 조건문 다형성으로 바꾸기
- 반복문 쪼개기
긴 매개변수 목록 (Long Parameter List)
- 매개변수가 길어지면 그 자체로 이해하기 어렵습니다.
Example - 긴 매개 변수
const createUser = (
name,
address,
hobby,
income,
car,
length,
mailAddress,
weight,
friendLength,
job,
assets
) => {
//User 객체를 생성하는 함수
};
어떻게 고치나요?
- 매개변수를 질의 함수로 바꾸기
- 객체 통째로 넘기기
- 매개변수 객체로 만들기
- 플래그 인수 제거하기
- 여러 함수를 클래스로 묶기
전역 데이터 (Global Data)
- 쓰지마세요
- 쓰면 벌받습니다. 특히 javascript에서는요!
- 전역 변수가 많아지면 감당할 수 없습니다.
Example - const, let, var 없는 전역 변수 사용 😡
globalFlag = true; //const, let 심지어 var도 없는 끔찍한 전역 변수
if (globalFlag) {
//do bad thing
} else {
//do hell thing
}
어떻게 고치나요?
- 변수 캡슐화하기
가변 데이터 (Mutable Data)
- 데이터가 변경되면 예상치 못한 결과나 버그로 이어질 수 있습니다.
Example - 변수가 계속 갱신되어 의미가 바뀌는 경우
let temp = 2 * (height + width);
console.log(temp);
temp = height * width;
console.log(temp);
어떻게 고치나요?
- 변수 캡슐화하기
- 변수 쪼개기
- 문장 슬라이드하기
- 함수 추출하기
- 질의 함수와 변경 함수 분리하기
- setter 제거하기
- 패상 변수를 질의 함수로 바꾸기
- 여러 함수를 클래스로 묶기
- 여러 함수를 변환 함수로 묶기
- 참조를 값으로 바꾸기
뒤엉킨 변경 (Divergent Change)
- 여러 기능을 추가 할때 하나의 모듈이 계속 변경되는경우를 말합니다.
- 단일 책임 원칙이 지켜지지 않고, 각각 독립된 코드로 분리되어야하나 그렇지 못한경우 발생합니다.
어떻게 고치나요?
- 단계 쪼개기
- 함수 옮기기
- 함수 추출하기
- 클래스 추출하기
산탄총 수술 (Shotgun Surgery)
- 뒤엉킨 변경과 비슷하지만 정 반대의 개념입니다.
- 하나의 기능을 변경하기 위해 소스코드의 여러 부분을 수정해야하는 경우를 말합니다.
어떻게 고치나요?
- 함수 옮기기
- 필드 옮기기
- 여러 함수를 클래스로 묶기
- 여러 함수를 변환 함수로 묶기
- 단계 쪼개기
- 함수 인라인하기
- 클래스 인라인하기
기능 편애 (Feature Envy)
- 메소드가 자신이 속한 클래스보다 다른 클래스에 더 깊게 관여되어 있는 경우입니다.
- 이로 인해 두 클래스의 의존성이 발생합니다.
어떻게 고치나요?
- 함수 옮기기
- 함수 추출하기
데이터 뭉치 (Data Clumps)
- 하나의 클래스에 너무 많은 필드가 존재하는 경우입니다.
-
Example - 너무 많은 필드
class Animal { name: string; addresss: string; color: string; food: string; sleeepingTime: string; age: number; roomNumber: number; manager: string; zoo: string; //그외 동물을 나타내는 간접적인 클래스 필드들.. // // // // // // // // //.. constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } }
어떻게 고치나요?
- 클래스 추출하기
- 매개변수 객체 만들기
- 객체 통째로 넘기기
기본형 집착 (Primitive Obsession)
- 기본형에만 너무 집착하는 경우를 말합니다.
- 화폐단위, 물리단위, 시간 단위를 오직 string, number로만 처리하려는게 그 예입니다.
- 객체지향을 활용하여 세련된 코드로 작성 할 수 있습니다.
어떻게 고치나요?
- 기본형을 객체로 바꾸기
- 타입 코드를 서브 클래스로 바꾸기
- 조건부 로직을 다형성으로 바꾸기
- 클래스 추출하기
- 매개변수 객체 만들기
반복되는 switch문 (Repeated Switches)
- 모든 switch - case 문이 나쁜건 아닙니다.
- 계속 반복되는 switch - case 문은 변경을 어렵게 합니다.
-
객체지향의 다형성 or Key-Value 방식으로 개선 할 수 있습니다.
-
Example - 긴 switch - case 문
const getFoodCategory = (food) => { let category = null; switch (food) { case "coke": category = "drink"; break; case "pizza": category = "food"; break; case "orange": case "strawberry": case "apple": category = "fruit"; break; case "icecreame": category = "indulgence"; break; case "vegetable": category = "bean"; category = "tomato"; break; case "bread": category = "bread"; break; default: category = "Unknown food!"; } }; console.log(getFoodCategory("pizza"));
어떻게 고치나요?
- 조건부 로직을 다형성으로 바꾸기
반복문 (Loops)
- 단순 for, while loop는 파이프라인으로 처리 할 수 있습니다.
- reduce, map, filter 등등..
어떻게 고치나요?
- 반복문을 파이프라인으로 바꾸기
성의 없는 요소 (Lazy Element)
- 불필요한 클래스, 인터페이스, 메서드 사용을 한 경우입니다.
어떻게 고치나요?
- 함수 인라인하기
- 클래스 인라인하기
- 계층 합치기
추측성 일반화 (Speculative Generality)
- 언젠가 사용될 것이라고 추측하여 작성하였지만, 단 한번도 쓰이지 않는 경우를 말합니다.
어떻게 고치나요?
- 계층 합치기
- 함수 인라인하기
- 클래스 인라인하기
- 함수 선언 바꾸기
- 죽은 코드 제거하기
임시 필드 (Temporary Field)
- 코드 내에 특정한 상황에서만 의미가 부여되는 임시 필드가 존재하는 경우입니다.
어떻게 고치나요?
- 클래스 추출하기
- 함수 옮기기
- 특이 케이스 추가하기
메시지 체인 (Message Chains)
- 객체가 연쇄적으로 나열되는 경우를 말합니다.
-
메시지 체인의 중간 단계가 수정되면 이후 모든 단계에 영향을 미칩니다.
-
Example
//memberName을 얻기 위해 총 3개의 객체에 접근하는 경우 const memberName = aPerson.department.manager.name;
어떻게 고치나요?
- 위임 숨기기
- 함수 추출하기
- 함수 옮기기
중개자 (Middle Man)
- 현재 클래스가 가지고 있는 메서드 절반 이상이 다른 클래스의 메서드를 호출하는 경우를 말합니다.
- 즉, 지나치게 위임을 하는 경우를 말합니다.
어떻게 고치나요?
- 중개자 제거하기
내부자 거래 (Insider Trading)
- 자식클래스가 부모클래스에 대해 필요 이상으로 접근하려는 경우를 말합니다.
어떻게 고치나요?
- 함수 옮기기
- 필드 옮기기
- 위임 숨기기
- 서브클래스를 위임으로 바꾸기
- 슈퍼클래스를 위임으로 바꾸기
거대한 클래스 (Large Class)
- 클래스 하나가 너무 거대한 경우를 말합니다.
어떻게 고치나요?
- 클래스 추출하기
- 슈퍼클래스 추출하기
- 타입 코드를 서브클래스로 바꾸기
주석 (Comments)
- 주석은 나쁜게 아닙니다.
- 다만, 주석이 필요 없을 수준으로 코드를 작성하는게 더 좋습니다.
어떻게 고치나요?
- 함수 추출하기
- 함수 선언 바꾸기
- 어서션 추가하기
언급은 되었지만 좀더 생각해 봐야하는 냄새들
- 서로 다른 인터페이스의 대안 클래스들 (Alternative Classes with Different Interfaces)
- 데이터 클래스(Data Class)
- 상속 포기(Refused Badquest)
참고했던 글
좋은 이름, 나쁜 이름, 이상한 이름, NDC2018 - 전현규
https://www.slideshare.net/devcatpublications/ndc2018
Replacing switch statements with Object literals - by Todd Motto
https://ultimatecourses.com/blog/deprecating-the-switch-statement-for-object-literals
Leave a comment