질의 함수와 변경 함수 분리하기
// before
function getSomeDataAndSetAnother() {
const result = func();
setAnother();
return result;
}
// after
function getSomeData() {
return func();
}
function setAnother() {
thie.field = 0;
}
- 질의 함수 (읽기 함수) 는 어떠한 부수효과도 없어야 한다.
- 명렁-질의 분리 (Command-query seperation)
- 만약 읽기 함수 내에 무언가 다른 일을 하는 게 있다면, 바로 추출해주자.
함수 매개변수화하기
// before
function addTwoTimes(number) {
return number + number;
}
function addThreeTimes(number) {
return number + number + number;
}
// after
function addForCounts(number, count) {
return number * count;
}
- 두 함수의 로직이 아주 비슷하고 리터럴 값만 다를 경우, 그 리터럴 값을 매개변수로 받아 처리하는 함수 하나로 합쳐 중복을 없앰
플래그 인수 제거하기
// before
function setDimention(name, value) {
if (name === "height") {
this.height = value;
return;
}
if (name === "width") {
this.width = value;
return;
}
}
// after
function setHeight(value) {}
function setWidth(value) {}
- 플래그 인수 : 호출되는 함수가 실행할 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수
- 플래그 인수는 함수 시그니처만 보고도 내부 동작을 알기 쉽지 않다.
- 만약 boolean 값일 경우, true, false로는 어떤 동작이 되는지 매우 어려울 것이다.
- 이러한 플래그 인수는 제거해주고, 각기 다른 함수로 분리해주는 것이 함수가 하나의 기능만 하게 만들 수 있다.
- 만약 플래그 인수가 너무 많은 분기문으로 사용된다면, 이미 함수 하나가 너무 많은 역할을 하는 것이므로 더 간단한 로직을 구성해야 한다 → 각기 다른 함수로 빼기에는 너무 많을 수 있기 때문
객체 통째로 넘기기
// before
const low = room.daysTempRange.low;
const high = room.daysTempRange.high;
if (plan.withinRange(low, high))
// after
if (plan.withinRange(room.daysTempRange))
- 어떤 객체에서 값을 뽑아 매개변수로 주는 형태라면, 그냥 그 객체 자체를 주어 매개변수의 수를 줄일 수 있다.
- 하지만 이 리팩터링의 경우 해당 함수가 해당 레코드에 종속되는 결과를 가져올 수도 있다. → 레코드나 객체의 필드가 바뀌면 동작이 안되는 경우
- 이는 해당 함수가 애초에 해당 객체의 메소드로 들어갔어야 한다는 의미일 수도 있다.
매개변수를 질의 함수로 바꾸기
// before
someFunction(object, object.field);
function someFunction(object, field) {}
// after
someFunction(object);
function someFunction(object) {
const field = object.field;
}
- 매개변수 목록은 중복을 피하는 게 좋으며 짧을수록 좋다.
- 호출되는 함수 안에서 쉽게 결정할 수 있는 값을 매개변수로 받는 것 또한 중복이라고 볼 수 있다.
- 이러한 부분이 존재한다면 해당 매개변수를 제거하고 피호출 함수 내에서 뽑아 쓰자.
질의 함수를 매개변수로 바꾸기
// before
someFunction(object);
function someFunction(object) {
const field = object.field;
}
// after
someFunction(object, object.field);
function someFunction(object, field) {}
- 위와 반대 경우로, 더 이상 해당 객체에 의존적이지 않게 함수가 수정되는 경우 해당 참조를 매개변수로 바꿔 풀어낼 수 있다.
세터 제거하기
// before
class Person {
get name() {}
set name(string) {}
}
// after
class Person {
get name() {}
}
- 불변으로 만들고 싶은 객체의 경우 세터가 있어서는 안되고, 상수 필드를 가지며 생성자로 값을 초기화한 후로는 변경되지 않아야 한다.
- 따라서 불변 객체라는 의미를 확실히 주기 위해서는 세터가 제거되어야 한다.
- 세터로 받는 필드를 하나씩 생성자에 추가해주고, 세터를 하나씩 제거하면서 리팩토링할 수 있다.
생성자를 팩터리 함수로 바꾸기
// before
leadEngineer = new Employee(document.leadEngineer, 'E');
// after
leadEngineer = createEngineer(document.leadEngineer);
- 자바 생성자의 경우 반드시 그 생성자를 정의한 클래스의 인스턴스를 반환해야하며, 서브클래스나 프록시를 반환할 수 없다.
- 팩터리 함수의 경우 이러한 제약이 없다. 또한 이름을 부여할 수 있어 뭔가 다른 객체를 반환할 때 그 의도 또한 명확히 해줄 수 있다.
함수를 명령으로 바꾸기
// before
function score(candidate, medicalExam, scoringGuide) {
let result = 0;
let healthLevel = 0;
// 긴 코드 생략
}
// after
class Scorer {
constructor(candidate, medicalExam, scoringGuide) {
this.candidate = candidate;
this.medicalExam = medicalExam;
this.scoringGuide = scoringGuide;
}
execute() {
this.result = 0;
this.healthLevel = 0;
// 긴 코드 생략
}
}
- 명령(Command), 명령 객체 : 어떠한 함수만을 위한 객체를 만들어 메소드 하나로 요청해 실행하는 것
- 이는 함수를 객체로 만들어 표현한다는 점에서 주목할 만하다.
- undo와 같은 보조 연산 제공이 가능하고, 상속, 훅을 사용해 사용자 맞춤형으로 만들 수도 있다 (뭔소리지..)
- 디자인 패턴의 명령 패턴과 같다
명령을 함수로 바꾸기
// before
class ChargeCalculator {
constructor(customer, usage) {
this.customer = customer;
this.usage = usage;
}
execute() {
return this.customer.rate * this.usage;
}
}
// after
function charge(customer, usage) {
return customer.rate * usage;
}
- 명령은 복잡한 연산을 여러 메서드로 쪼개 필드를 이용해 쪼개진 메서드끼리 정보 공유를 하는 역할이 크다.
- 달리 말하면, 너무 단순한 작업의 경우는 굳이 명령으로 만들 필요가 없다는 얘기이다.
- 객체를 생성하는 것도 그렇고 메모리를 무시할 수 없으니 이러한 경우에는 명령을 함수로 바꿔주자.
수정된 값 반환하기
// before
let total = 0;
addAll();
function addAll() {
for (const p of this.point) {
total += p;
}
}
// after
const total = addAll();
function addAll() {
let result = 0;
for (const p of this.point) {
result += p;
}
return result;
}
- 함수가 외부 변수를 통해 결과를 바꾸고 아무것도 리턴하지 않는 형태는 나중에 이해하기 어렵다.
- 함수는 웬만하면 외부 변수의 상태를 변경하는 형태가 아닌 내부에서 값을 계산해서 리턴하고, 외부 변수가 이 값을 받아 저장하는 형태가 가독성 및 유지보수성이 좋다.
오류 코드를 예외로 바꾸기
// before
if (data) {
return new Object(data);
}
else {
return -1;
}
// after
if (data) {
return new Object(data);
}
else {
throw new Exception(-1);
}
- 오류 코드를 사용할 경우 이 함수를 사용한 부모 함수에서 해당 값을 검증하는 로직이 반드시 들어가야 한다.
- 그러나 예외를 사용하면 이러한 부분을 신경쓰지 않아도 되고, 예외 클래스나 메시지를 통해 더 명확하게 디버깅이 가능하다.
- 하지만 남발하면 안되고, 정확히 예상 밖의 동작일 때만 사용해야 한다.
- 예외 대신에 프로그램 종료 코드로 바꾸고, 이게 정상 동작할지를 생각한다. → 만약 정상 동작하지 않을 것 같다면 예외가 아닌 오류를 검출하여 정상 흐름으로 되돌리게 해야함(무슨 말인지 정확하게는 모르겠다)
예외를 사전확인으로 바꾸기
// before
function getNumber(index) {
try {
return values[index];
}
catch (ArrayIndexOutOfBoundsException) {
return 0;
}
}
// after
function getNumber(index) {
if (index < 0) {
return 0;
}
return values[index];
}
- 앞서 말했듯이, 예외는 과용되어서는 안된다.
- 문제가 될 수 있는 조건을 함수 호출 전에 검사할 수 있다면, 예외를 던지는 대신 호출하는 곳에서 조건을 검사하도록 하는 것도 좋다.
https://product.kyobobook.co.kr/detail/S000001810241