Skip to main content

11. API 리팩터링

  • 좋은 API는 데이터를 갱신하는 함수와 그저 조회만 하는 함수를 명확히 해야합니다. -> 질의 함수와 변경 함수 분리하기
  • 값 하나 때문에 여러개로 나뉜 함수들을 하나로 모아야합니다. -> 함수 매개변수화하기
  • 매개변수는 그저 함수의 동작 모드를 전환하는 용도로 써야합니다. -> 플래그 인수 제거하기
  • 데이터 구조가 필요 이상으로 분해하는 경우에 깔끔해져야합니다. -> 객체 통째로 넘기기
  • 매개변수를 피호출 함수가 결정할 지, 호출 함수가 적절할지 정리해야합니다. -> 매개변수를 질의 함수로 바꾸기, 질의 함수를 매개변수로 바꾸기
  • 객체가 불변이 되게 하려면 -> 세터 제거하기
  • 호출자에 새로운 객체를 만들어 반환하려 할 때, 일반적인 생상자 능력으로 부족한 경우 -> 생성자를 팩터리 함수로 바꾸기
  • 수많은 데이터를 받는 복잡한 함수를 쪼개는 문제를 다뤄야 하는 경우 -> 함수를 명령으로 바꾸기, 명령을 함수로 바꾸기

11.1 질의 함수와 변경 함수 분리하기#

// before
function getTotalOutstandingAndSendBill() {
const result = customer.invoices.reduce(
(total, each) => each.amount + total,
0
)
sendBill()
return result
}
// after
function totalOutstanding() {
return customer.invoices.reduce((total, each) => each.amount + total, 0)
}
function sendBill() {
emailGateway.send(formatBill(customer))
}

배경#

  • 겉보기 부수효과가 있는 함수와 없는 함수는 명확히 구분하는 것이 좋습니다.
  • 질의 함수는 모두 부수효과가 없어야 한다는 규칙을 준수하는 것이 좋습니다.
  • 상태를 변경하는 부분과 질의하는 부분이 같으면 이를 분리하는 것이 좋습니다.

절차#

  1. 대상 함수를 복제하고 질의 목적에 충실한 이름을 짓습니다.
  2. 새 질의 함수에서 부수효과를 모두 제거합니다.
  3. 정적 검사를 수행합니다.
  4. 원래 함수를 호출하는 곳을 모두 찾아냅니다. 호출하는 곳에서 반환 값을 사용한다면 질의 함수를 호출하도록 바꾸고, 원래 함수를 호출하는 코드를 바로 아래 줄에 새로 추가합니다. 하나를 수정할 때마다 테스트합니다.
  5. 원래 함수에서 질의 관련 코드를 제거합니다.
  6. 테스트합니다.

예시#

악당을 찾는 함수#

function alertForMiscreant(people) {
for (const p of people) {
if (p === '조커') {
setOffAlarms()
return '조커'
}
if (p === '사루만') {
setOffAlarms()
return '사루만'
}
}
return ''
}
const found = alertForMiscreant(people)
  • 리팩터링 이후 코드 변경 (질의 함수 빼기, 알고리즘 교체)
function alertForMiscreant(people) {
if (findMiscreant(people) !== '') setOffAlarms()
}
const found = findMiscreant(people)
alertForMiscreant(people)

11.2 함수 매개변수화하기#

// before
function tenPercentRaise(aPerson) {
aPerson.salary = aPerson.salary.multiply(1.1)
}
function fivePercentRaise(aPerson) {
aPerson.salary = aPerson.salary.multiply(1.05)
}
// after
function raise(aPerson, factor) {
aPerson.salary = aPerson.salary.multiply(1 + factory)
}

배경#

  • 함수 들의 로직이 아주 비슷하고 단지 리터럴 값만 다르다면, 다른 값만 매개변수로 받아 처리하는 함수 하나로 합쳐서 중복을 없앨 수 있습니다.
  • 이렇게 하면 여러 곳에서 쓸 수 있으니 함수의 유용성이 커집니다.

절차#

  1. 비슷한 함수 중 하나를 선택합니다.
  2. 함수 선언 바꾸기로 리터럴들을 매개변수로 추가합니다.
  3. 이 함수를 호출하는 곳 모두에 적절한 리터럴 값을 추가합니다.
  4. 테스트합니다.
  5. 매개변수로 받은 값을 사용하도록 함수 본문을 수정합니다. 하나 수정할 때마다 테스트합니다.
  6. 비슷한 다른 함수를 호출하는 코드를 찾아 매개변수화된 함수를 호출하도록 하나씩 수정합니다. 하나 수정할 때마다 테스트합니다.

예시#

function baseCharge(usage) {
if (usage < 0) return usd(0)
const amount =
bottomBand(usage) * 0.03 + middleBand(usage) * 0.05 + topBand(usage) * 0.07
return usd(amount)
}
function bottomBand(usage) {
return Math.min(usage, 100)
}
function middleBand(usage) {
return usage > 100 ? Math.min(usage, 200) - 100 : 0
}
function topBand(usage) {
return usage > 200 ? usage - 200 : 0
}
  • 이를 요챡하면 다음과 같습니다.
function baseCharge(usage) {
if (usage < 0) return usd(0)
const amount =
withinBand(usage, 0, 100) * 0.03 +
withinBand(usage, 100, 200) * 0.05 +
withinBand(usage, 200, Infinity) * 0.07
return usd(amount)
}
function withinBand(usage, bottom, top) {
return usage > bottom ? Math.min(usage, top) - bottom : 0
}

11.3 플래그 인수 제거하기#

// before
function setDimension(name, value) {
if (name === 'height') {
this._height = value
return
}
if (name === 'width') {
this._width = value
return
}
}
// after
function setHeight(value) {
this._height = value
}
function setWidth(value) {
this._width = value
}

배경#

  • 플래그 인수란 호출되는 함수가 실행할 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수입니다.
  • 플래그 인수를 제거하면 코드가 깔끔해짐은 물론 프로그래밍 도구에도 도움을 줍니다.
  • 다만 플래그 인수를 두 개 이상 사용해야하는 경우에는 플래그 인수를 써야할 수도 있습니다.

절차#

  1. 매개변수로 주어질 수 있는 값 각각에 대응하는 명시적 함수들을 생성합니다.
  2. 원래 함수를 호출하는 코드들을 모두 찾아서 각 리터럴 값에 대응되는 명시적 함수를 호출하다록 수정합니다.

예시#

aShipment.deliveryDate = deliveryDate(anOrder, true)
function deliveryDate(anOrder, isRush) {
if (isRush) {
let deliveryTime
if (['MA', 'CT'].includes(anOrder.deliveryState)) deliveryTime = 1
else if (['NY', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 2
else deliveryTime = 3
return anOrder.placedOn.plusDays(1 + deliveryTime)
} else {
let deliveryTime
if (['MA', 'CT', 'NY'].includes(anOrder.deliveryState)) deliveryTime = 2
else if (['ME', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 3
else deliveryTime = 4
return anOrder.placedOn.plusDays(2 + deliveryTime)
}
}
  • 이를 분해하면 다음과 같습니다.
function deliveryDate(anOrder, isRush) {
if (isRush) return rushDeliveryDate(anOrder)
else return regularDeliveryDate(anOrder)
}
function rushDeliveryDate(anOrder) {
let deliveryTime
if (['MA', 'CT'].includes(anOrder.deliveryState)) deliveryTime = 1
else if (['NY', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 2
else deliveryTime = 3
return anOrder.placedOn.plusDays(1 + deliveryTime)
}
function regularDeliveryDate(anOrder) {
let deliveryTime
if (['MA', 'CT', 'NY'].includes(anOrder.deliveryState)) deliveryTime = 2
else if (['ME', 'NH'].includes(anOrder.deliveryState)) deliveryTime = 3
else deliveryTime = 4
return anOrder.placedOn.plusDays(2 + deliveryTime)
}

11.4 객체 통째로 넘기기#

// before
const low = aRoom.daysTempRange.low
const high = aRoom.daysTempRange.high
if(aPlan.withinRange(low, high))
// after
if(aPlan.withinRange(aRoom.daysTempRange))

배경#

  • 하나의 레코드에서 값 두어 개를 가져와 인수로 넘기는 코드를 수정하면 레코드를 통째로 넘기고 함수 본문에서 필요한 값을 꺼내 쓰도록 수정합니다.
  • 레코드를 통째로 넘기면 변화에 대응하기 쉽습니다.
  • 함수가 레코드 자체에 의존하기를 원치 않을 때는 이 리팩터링을 수행하지 않습니다.

절차#

  1. 매개변수들을 원하는 형태로 받는 빈 함수를 만듭니다.
  2. 새 함수의 본문에서는 원래 함수를 호출하도록 하며, 새 매개변수와 원래 함수의 매개변수를 매핑합니다.
  3. 정적 검사를 수행합니다.
  4. 모든 호출자가 새 함수를 사용하게 수정합니다. 하나씩 수정하며 테스트합니다.
  5. 호출자를 모두 수정했다면 원래 함수를 인라인합니다.
  6. 새 함수의 이름을 적절히 수정하고 모든 호출자에 반영합니다.

예시#

const low = aRoom.daysTempRange.low
const high = aRoom.daysTempRange.high
if (aPlan.withinRange(low, high))
alerts.push('방 온도가 지정 범위를 벗어났습니다.')
class HeatingPlan {
withinRange(bottom, top) {
return (
bottom >= this._temperatureRange.low && top <= this._temperatureRange.high
)
}
}
  • 리팩터링 후 코드
class HeatingPlan {
withinRange(aNumberRange) {
return (
aNumberRange.low >= this._temperatureRange.low &&
aNumberRange.high <= this._temperatureRange.high
)
}
}
if (!aPlan.withinRange(aRoom.daysTempRange))
alerts.push('방 온도가 지정 범위를 벗어났습니다.')

11.5 매개변수를 질의 함수로 바꾸기#

// before
availableVacation(anEmployee, anEmployee.grade)
function availableVacation(anEmployee, grade) { ... }
// after
availableVacation(anEmployee)
function availableVacation(anEmployee) {
const grade = anEmployee.grade
...
}

배경#

  • 매개변수 목록은 함수의 변동 요인을 모아놓은 곳입니다.
  • 다른 코드와 마찬가지로 이 목록에서도 중복은 피하는 게 좋으며 짧을수록 이해하기 쉽습니다.
  • 제거하려는 매개변수의 값을 다른 매개변수에 질의해서 얻을 수 있다면 안심하고 질의 함수로 바꿀 수 있습니다.
  • 다만 매개변수를 없애고 가변 전역 변수를 이용하면 안됩니다.

절차#

  1. 필요하다면 대상 매개변수의 값을 계산하는 코드를 별도 함수로 추출해놓습니다.
  2. 함수 본문에서 대상 매개변수로의 참조를 모두 찾아서 그 매개변수의 값을 만들어주는 표현식을 참조하도록 바꿉니다. 하나 수정하 때마다 테스트합니다.
  3. 함수 선언 바꾸기로 대상 매개변수를 없앱니다.

예시#

class Order {
get finalPrice() {
const basePrice = this.quantity * this.itemPrice
let discountLevel
if (this.quantity > 100) discountLevel = 2
else discountLevel = 1
return this.discountedPrice(basePrice, discountLevel)
}
discountedPrice(basePrice, discountLevel) {
switch (discountLevel) {
case 1:
return basePrice * 0.95
case 2:
return basePrice * 0.9
}
}
}
  • 리팩터링 이후 코드
class Order {
get finalPrice() {
const basePrice = this.quantity * this.itemPrice
return this.discountedPrice(basePrice)
}
discountedPrice(basePrice) {
switch (this.discountLevel) {
case 1:
return basePrice * 0.95
case 2:
return basePrice * 0.9
}
}
}

11.6 질의 함수를 매개변수로 바꾸기#

// before
targetTemperature(aPlan)
function targetTemperature(aPlan) {
currentTemperature = thermostat.currentTemperature
...
}
// after
targetTemperature(aPlan, thermostat.currentTemperature)
function targetTemperature(aPlan, currentTemperature) {
...
}

배경#

  • 전역 함수를 참조하거나 제거하길 원하는 원소를 참조하는 경우, 해당 참조를 매개변수로 바꿔 해결해야합니다.
  • 일반적으로 코드의 의존 관계를 바꾸려할 때 벌어집니다.
  • 이러한 경우에는 호출자가 복잡해지는 문제가 있습니다.

절차#

  1. 변수 추출하기로 질의 코드를 함수 본문의 나머지 코드와 분리합니다.
  2. 함수 본문 중 해당 질의를 호출하지 않는 코드들을 별도 함수로 추출합니다.
  3. 방금 만든 변수를 인라인하여 제거합니다.
  4. 원래 함수도 인라인합니다.
  5. 새 함수의 이름을 원래 함수의 이름으로 고쳐줍니다.

예시#

class HeatingPlan {
get targetTemperature() {
if (thermostat.selectedTemperature > this._max) return this._max
else if (thermostat.selectedTemperature < this._min) return this._min
else return thermostat.selectedTemperature
}
}
// 호출자
if (thePlan.targetTemperature > thermostat.currentTemperature) setToHeat()
else if (thePlan.targetTemperature < thermostat.currentTemperature) setToCool()
else setOff()
  • 리팩터링 후 코드
class HeatingPlan {
targetTemperature(selectedTemperature) {
if (selectedTemperature > this._max) return this._max
else if (selectedTemperature < this._min) return this._min
else return selectedTemperature
}
}
// 호출자
if (
thePlan.targetTemperature(thermostat.currentTemperature) >
thermostat.currentTemperature
)
setToHeat()
else if (
thePlan.targetTemperature(thermostat.currentTemperature) <
thermostat.currentTemperature
)
setToCool()
else setOff()

자바 스크립트와 불변 클래스

  • 자바 스크립트의 클래스 모델에서는 객체 안의 데이터를 직접 얻어낼 방법이 항상 존재하므로 불변 클래스임을 보장하는 수단이 없다는 문제가 있습니다.
  • 클래스에 불변 성격을 부여하는 건 좋은 전략이며, 질의 함수를 매개변수로 바꾸기 리팩터링은 이 전략을 실행하는 것은 도움이 됩니다.

11.7 세터 제거하기#

// before
class Person {
get name() {...}
set name(aString) {...}
}
// after
class person {
get name() {...}
}

배경#

  • 세터 메서드가 있음은 필드가 수정될 수 있다는 뜻입니다.
  • 세터 제거하기 리팩터링이 필요한 상황은 주로 두가지입니다.
    • 사람들이 무조건 접근자 메서드를 통해서만 필드를 다루려합니다.
    • 클라이언트에서 생성 스크립트를 사용해 객체를 생성해야합니다.

절차#

  1. 설정해야 할 값을 생성자에서 받지 않는다면 그 값을 받을 매개변수를 생성자에 추가합니다.
  2. 생성자 밖에서 세터를 호출하는 곳을 찾아 제거하고, 대신 새로운 생성자를 사용하도록 합니다. 하나를 수정할 때마다 테스트합니다.
  3. 세터 메서드를 인라인합니다. 가능하다면 해당 필드를 불변으로 만듭니다.
  4. 테스트합니다.

예시#

class Person {
get name() {
return _name
}
set name(arg) {
this._name = arg
}
get id() {
return _id
}
set id(arg) {
this._id = arg
}
}
const martin = new Person()
martin.name = '마틴'
martin.id = '1234'
  • 세터 제거한 이후는 다음입니다.
class Person {
constructor(id) {
this._id = id
}
get name() {
return _name
}
set name(arg) {
this._name = arg
}
get id() {
return _id
}
}
const martin = new Person('1234')
martin.name = '마틴'

11.8 생성자를 팩터리 함수로 바꾸기#

// before
leadEngineer = new Employee(document.leadEngineer, 'E')
// after
leadEngineer = createEngineer(document.leadEngineer)0

배경#

  • 많은 객체 지향 언어에서 제공하는 생성자는 객체를 초기화하는 특별한 용도의 함수입니다. 다만 이는 이상한 제약이 붙어 있는 경우가 많습니다.
  • 팩터리 함수에는 이러한 제약이 없습니다.

절차#

  1. 팩터리 함수를 만듭니다. 팩터리 함수의 본문에서는 원래의 생성자를 호출합니다.
  2. 생성자를 호출하던 코드를 팩터리 함수 호출로 바꿉니다.
  3. 하나씩 수정할 때마다 테스트합니다.
  4. 생성자의 가시 범위가 최소가 되도록 제한합니다.

예시#

class Employee {
constructor(name, typeCode) {
this._name = name
this._typeCode = typeCode
}
get name() {
return this._name
}
get type() {
return Employee.legalTypeCodes[this._typeCode]
}
static get legalTypeCodes() {
return { E: 'Engineer', M: 'Manager', S: 'Salesperson' }
}
}
// 호출자
const leadEngineer = new Employee(document.leadEngineer, 'E')
  • 리팩터링 이후 코드
const leadEngineer = createEngineer(document.leadEngineer)
function createEngineer(name) {
return new Employee(name, 'E')
}

11.9 함수를 명령으로 바꾸기#

// 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
// 긴 코드 생략
}
}

배경#

  • 함수를 그 함수만을 위한 객체 안으로 캡슐화하면 더 유용해지는데 이러한 객체를 가리켜 명령 객체 혹은 단순히 명령이라고 합니다.
  • 명령은 평범한 함수 메커니즘보다 훨씬 유연하게 함수를 제어하고 표현할 수 있습니다.
  • 다만 유연성을 높이는 것은 복잡성을 키울 수 있다는 것을 알고 있어야합니다.

절차#

  1. 대상 함수의 기능을 옮길 빈 클래스를 만듭니다. 클래스 이름은 함수 이름에 기초해 짓습니다.
  2. 방금 생성한 빈 클래스로 함수를 옮깁니다.
  3. 함수의 인수들 각각은 명령의 필드로 만들어 생성자를 통해 설정할지 고민해봅니다.

예시#

건강보험 애플리케이션에서 점수를 계산하는 함수입니다.

function score(candidate, medicalExam, scoringGuide) {
let result = 0
let healthLevel = 0
let highMedicalRiskFlag = false
if (medicalExam.isSmoker) {
healthLevel += 10
highMedicalRiskFlag = true
}
let certificationGrade = 'regular'
if (scoringGuide.stateWithLowCertification(candidate.originState)) {
certificationGrade = 'low'
result -= 5
}
// 비슷한 코드...
result -= Math.max(healthLevel - 5, 0)
return result
}
  • 리팩터링 이후의 코드
class Scorer {
constructor(candidate, medicalExam, scoringGuide) {
this._candidate = candidate
this._medicalExam = medicalExam
this._scoringGuide = scoringGuide
}
execute() {
this._result = 0
this._healthLevel = 0
this._highMedicalRiskFlag = false
this.scoreSmoking()
this._certificationGrade = 'regular'
if (
this._scoringGuide.stateWithLowCertification(this._candidate.originState)
) {
this._certificationGrade = 'low'
this._result -= 5
}
// 비슷한 코드...
result -= Math.max(healthLevel - 5, 0)
return result
//
}
scoreSmoking() {
if (this._medicalExam.isSmoker) {
this._healthLevel += 10
this._highMedicalRiskFlag = true
}
}
}

11.10 명령을 함수로 바꾸기#

// 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
}

배경#

  • 명령 객체는 복잡한 연산을 다룰 수 있는 강력한 메커니즘을 제공합니다.
  • 명령은 그저 함수를 하나 호출해 정해진 일을 수행하는 용도로 쓰이는데 이러한 상황이고 로직이 복잡하지 않다면 명령 객체는 장점보다 단점이 크므로 평범함 함수로 바꾸는 것이 좋습니다.

절차#

  1. 명령을 생성하는 코드와 명령의 실행 메서드를 호출하는 코드를 함께 함수로 추출합니다.
  2. 명령의 실행 함수가 호출하는 보조 메서드들 각각을 인라인합니다.
  3. 함수 선언 바꾸기를 적용하여 생성자의 매개변수 모두를 명령의 실행 메서드로 옮깁니다.
  4. 명령의 실행 메서드에서 참조하는 필드들 대신 대응하는 매개변수를 사용하게끔 바꿉니다. 하나씩 수정할 때마다 테스트합니다.
  5. 생성자 호출과 명령의 실행 메서드 호출을 호출자 안으로 인라인합니다.
  6. 테스트합니다.
  7. 죽은 코드 제거하기로 명령 클래스로 없앱니다.

예시#

class ChargeCalculator {
constructor(customer, usage, provider) {
this._customer = customer
this._usage = usage
this._provider = provider
}
get baseCharge() {
return this._customer.baseRate * this._usage
}
get charge() {
return this.baseCharge + this._provider.connectionCharge
}
}
// 호출자
monthCharge = new ChargeCalculator(customer, usage, provider).charge
  • 함수로 바꾸면 다음과 같습니다.
function charge(customer, usage, provider) {
const baseCharge = customer.baseRate * usage
return baseCharge + provider.connectionCharge
}

11.11 수정된 값 반환하기#

// before
let totalAscent = 0
calculateAscent()
function calculateAscent() {
for (let i = 1; i < points.length; i++) {
const verticalCharge = points[i].elevation - points[i - 1].elevation
totalAscent += verticalChange > 0 ? verticalCharge : 0
}
}
// after
const totalAscent = calculateAscent()
function calculateAscent() {
let result = 0
for (let i = 1; i < points.length; i++) {
const verticalCharge = points[i].elevation - points[i - 1].elevation
result += verticalChange > 0 ? verticalCharge : 0
}
return result
}

배경#

  • 변수를 갱신하는 함수라면 수정된 값을 반환하여 호출자가 그 값을 변수에 담아두도록 합니다.
  • 값 하나를 계산한다는 분명한 목적이 있는 함수들에 가장 효과적이고 반대로 값 여러 개를 갱신하는 함수에는 효과적이지 않습니다.

절차#

  1. 함수가 수정된 값을 반환하게 하여 호출자가 그 값을 자신의 변수에 저장합니다.
  2. 테스트합니다.
  3. 피호출 함수 안에 반환할 값을 가리키는 새로운 변수를 선언합니다.
  4. 테스트합니다.
  5. 계산이 선언과 동시에 이뤄지도록 통합합니다.
  6. 테스트합니다.
  7. 피호출 함수의 변수 이름을 새 역할에 어울리도록 바꿔줍니다.
  8. 테스트합니다.

예시#

GPS 위치 목록으로 다양한 계산을 수행하는 코드가 있습니다.

let totalAscent = 0
let totalTime = 0
let totalDistance = 0
calculateAscent()
calculateTime()
calculateDistance()
const pace = totalTime / 60 / totalDistance
function calculateAscent() {
for (let i = 1; i < points.length; i++) {
const verticalChange = points[i].elevation - points[i - 1].elevation
totalAscent += verticalChange > 0 ? verticalChange : 0
}
}
  • 리팩터링 이후 코드
let totalAscent = calculateAscent()
let totalTime = calculateTime()
let totalDistance = calculateDistance()
const pace = totalTime / 60 / totalDistance
function calculateAscent() {
let result = 0
for (let i = 1; i < points.length; i++) {
const verticalChange = points[i].elevation - points[i - 1].elevation
result += verticalChange > 0 ? verticalChange : 0
}
return result
}

11.12 오류 코드를 예외로 바꾸기#

// before
if (data) return new ShippingRules(data)
else return -23
// after
if (data) return new ShippingRules(data)
else return new OrderProcessingError(-23)

배경#

  • 예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘
  • 예외는 정교한 메커니즘이지만, 다른 정교한 메커니즘과 같이 정확하게 사용할 때 최고의 효과를 냅니다.

절차#

  1. 콜스택 상위에 해당 예외를 처리할 예외 핸들러를 작성합니다.
  2. 테스트합니다.
  3. 해당 오류 코드를 대체할 예외와 그 밖의 예외를 구분할 식별 방법을 찾습니다.
  4. 정적 검사를 수행합니다.
  5. catch절을 수정하여 직접 처리할 수있는 예외는 적절히 대처하고 그렇지 않은 예외는 다시 던집니다.
  6. 테스트합니다.
  7. 오류 코드를 반환하는 곳 모두에서 예외를 던지도록 수정합니다. 하니씩 수정할 때마다 테스트합니다.
  8. 모두 수정했다면 그 오류 코드를 콜 스택 위로 전달하는 코드를 모두 제거합니다. 하나씩 수정할 때마다 테스트합니다.

예시#

전역 테이블에서 배송지의 배송 규칙을 알아내는 코드

function localShippingRules(country) {
const data = countryData.shippingRules[country]
if (data) return new ShippingRules(data)
else return -23
}
  • 리팩터링 이후의 코드
try {
calculateShippingCosts(orderData)
} catch (e) {
if (e instanceof OrderProcessingError)
errorList.push({ order: orderData, errorCode: e.code })
else throw e
}
function calculateShippingCosts(anOrder) {
const shippingRules = localShippingRule(anOrder.country)
}
function localShippingRules(country) {
const data = countryData.shippingRules[country]
if (data) return new ShippingRules(data)
else throw new OrderProcessingError(-23)
}

11.13 예외를 사전확인으로 바꾸기#

// before
double getValueForPeriod(int periodNumber) {
try {
return values[periodNumber]
} catch (ArrayIndexOutOfBoundsException e) {
return 0
}
}
// after
double getValueForPeriod(int periodNumber) {
return periodNumber >= values.length ? 0 : values[periodNumber]
}

배경#

  • 함수 수행 시 문제가 될 수 있는 조건을 함수 호출 전에 검사할 수 있다면 예외를 던지는 대신 호출하는 곳에서 조건을 검사해야합니다.

절차#

  1. 예외를 유발하는 상황을 검사할 수 있는 조건문을 추가합니다. catch 블록의 코드를 조건문의 조건절 중 하나로 옮기고남은 try 블록의 코드를 다른 조건절로 옮깁니다.
  2. catch 블록에 어서션을 추가하고 테스트합니다.
  3. try문과 catch 블록을 제거합니다.
  4. 테스트합니다.

예시#

데이터베이스 연결 같은 자원들을 관리하는 자원 풀 클래스

public class ResourcePool {
public Resource get() {
Resource result;
try {
result = available.pop();
allocated.add(result);
} catch (NoSuchElementException e) {
result = Resource.create();
allocated.add(result);
}
return result;
}
private Deque<Resource> available;
private List<Resource> allocated;
}
  • 리팩터링 이후의 코드
public class ResourcePool {
public Resource get() {
Resource result = available.isEmpty() ? Resource.create() : available.pop()
allocated.add(result)
return result;
}
}
Last updated on