Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[14주차] 11. API 리팩터링 (~11.6) #13

Open
minkukjo opened this issue Dec 28, 2021 · 3 comments
Open

[14주차] 11. API 리팩터링 (~11.6) #13

minkukjo opened this issue Dec 28, 2021 · 3 comments
Labels

Comments

@minkukjo
Copy link
Contributor

minkukjo commented Dec 28, 2021

진도

  1. API 리팩터링 (11.1 ~ 11.6)

방식

  • 코멘트로 자유롭게 느낀점 (좋았단 점) 을 적어주시면 됩니다.
  • 질문, 다른 좋은 참고자료, 실무 적용 사례 등을 공유해주셔도 좋습니다.
@minkukjo minkukjo added the share label Dec 28, 2021
@donghoon-song
Copy link

donghoon-song commented Jan 4, 2022

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

겉보기 부수효과가 전혀 없이 값을 반환해주는 함수를 추구해야 한다.

명령과 질의를 분리한다.

11.2 함수 매개변수화하기

함수의 로직이 아주 비슷하고 리터럴 값만 다르다면, 매개변수로 받아 처리하는 함수 하나로 처리

11.3 플래그 인수 제거하기

플래스 인수가 있으면 함수들의 기능 차이가 잘 드러나지 않는다.

사용할 함수를 선택한 후에도 플래그 인수로 어떤 값을 넘겨야 하는지를 또 알아내야 한다.

플래그 인수를 싫어하는 이유는 불리언 값을 데이터가 아닌 리터럴로 설정하기 때문

11.4 객체 통째로 넘기기

레코드를 통째로 넘기면 변화에 대응하기 쉽다.

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

매개변수를 건넬 필요가 없이 필요할 때 직접 호출가능하다면 바꾼다.

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

참조 투명성 - 같은 인풋에 같은 아웃풋을 배출

의존성을 모듈 바깥으로밀어낸다 - 책임을 호출자에게 지운다.

@minkukjo
Copy link
Contributor Author

minkukjo commented Jan 4, 2022

11.1

코드 보기
fun setOffAlarms() {

}

fun findMiscreant(people: List<People>): String {
    for (p: People in people) {
        if (p.name == "조커") {
            return "조커"
        }

        if (p.name == "사루만") {
            return "사루만"
        }
    }
    return ""
}

fun alertForMiscreant(people: List<People>) {
    for (p: People in people) {
        if (p.name == "조커") {
            setOffAlarms()
            return
        }

        if (p.name == "사루만") {
            setOffAlarms()
            return
        }
    }
    return
}

fun main() {
    val people = listOf(People("조커"), People("테스트"), People("사루만"))
    alertForMiscreant(people)
}

11.2

코드 보기
fun usd(number: Int): String {
    return NumberFormat.getCurrencyInstance(Locale.US).format(number / 100)
}

fun usd(number: Double): String {
    return NumberFormat.getCurrencyInstance(Locale.US).format(number / 100)
}

fun withBand(usage: Long, bottom: Long, top: Long): Long {
    return if (usage > bottom) {
        usage.coerceAtMost(top) - bottom
    } else {
        0
    }
}

fun withBand(usage: Long, bottom: Long, top: Double): Long {
    return if (usage > bottom) {
        usage.coerceAtMost(top.toLong()) - bottom
    } else {
        0
    }
}

fun baseCharge(usage: Long): String {
    if (usage < 0) return usd(0)
    val amount =
            withBand(usage, 0, 100) * 0.03
    +withBand(usage, 100, 200) * 0.05
    +withBand(usage, 200, Infinity) * 0.07
    return usd(amount)
}

11.3

코드 보기
fun bookConcert(aCustomer: Any, isPremium: Boolean) {
    if (isPremium) {
        // 프리미엄 예약용 로직
    } else {
        // 일반 예약용직로직
    }
}

// 아래와 같이 로직 수정

fun premiumBookConcert(aCustomer: Any) {
    
}

11.4

코드 보기
fun main() {
    val daysTempRange = TempRange(10, 50)
    val aRoom = Room(daysTempRange)
    val low = aRoom.daysTempRange.low
    val high = aRoom.daysTempRange.high
    val aHeatingPlan = HeatingPlan(daysTempRange)
    if (!aHeatingPlan.withRange(aRoom.daysTempRange)) {
        println("방 온도가 지정 범위를 벗어났습니다")
    }
}

11.5

코드 보기
// 매개변수를 질의 함수로 바꾸기

class Order(
        val quantity: Long,
        val itemPrice: Long,
) {
    fun finalPrice(): Double {
        val basePrice = this.quantity * this.itemPrice
        return this.discountedPrice(basePrice)
    }

    private fun discountedPrice(basePrice: Long): Double {
        return when (discountLevel()) {
            1L -> basePrice * 0.95
            2L -> basePrice * 0.9
            else -> 0.0
        }
    }

    private fun discountLevel(): Long {
        return if (this.quantity > 100) {
            2L
        } else {
            1L
        }
    }
}

11.6

코드 보기
// 11.6 질의 함수를 매개변수로 바꾸기
// 11.5의 리팩터링과 반대의 리팩터링이다.
// 그러나 저자는 이 리팩터링 보다는 호출자가 단순해지는 11.5의 리팩토링을 더 선호한다고 밝혔다.
// 여기서 이 리팩토링을 하는 이유는 함수 내에서 전역 변수를 사용하면서 참조 투명성을 잃게 되는 문제를 해결하기 위함이다.

// Thermostat : 온도 조절기
class Thermostat(
        val selectedTemperature: Long,
        val currentTemperature: Long,
)

val thermostat: Thermostat = Thermostat(20, 30)

class HeatingPlan(
        val max: Long,
        val min: Long,
) {
    fun targetTemperature(selectedTemperature: Long): Long {
        return if (selectedTemperature > this.max) this.max
        else if (selectedTemperature < this.min) this.min
        else selectedTemperature
    }
}


fun main() {
    val thePlan = HeatingPlan(50, 10)
    if (thePlan.targetTemperature(thermostat.selectedTemperature) > thermostat.currentTemperature) {
        setToHeat()
    } else if (thePlan.targetTemperature(thermostat.selectedTemperature) < thermostat.currentTemperature) {
        setToCool()
    } else {
        setOff()
    }
}

fun setOff() {
    TODO("Not yet implemented")
}

fun setToCool() {
    TODO("Not yet implemented")
}

fun setToHeat() {
    TODO("Not yet implemented")
}

@DaehunGwak
Copy link
Contributor

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

// before
public long getTotalOutstandingAndSendBill() {
  long result = customer.getInvoices()
      .stream()
      .mapToLong(Invoice::getAmount)
      .sum();
  emailGateway.send(formatBill(customer));
  return result
}

// after
public long getTotalOutstanding() {
  return customer.getInvoices()
      .stream()
      .mapToLong(Invoice::getAmount)
      .sum();
}

public void sendBill() {
  emailGateway.send(formatBill(customer));
}
  • 겉보기 부수효과(observable side effect)가 전혀 없이 값을 반환해주는 함수를 추구해야 함
    • 겉보기 부수효과: 데이터 값이 변경되는 것, 외부로 입출력이 있는 것
    • 언제 어디서든 호출되도 문제가 없음
    • 명령-질의 분리(command-query separation) 라고도 함

11.2 매개 변수화하기

// before
public void raiseSalaryTenPercent() {
  this.salary = this.salary.multiply(1.1);
}
public void raiseSalaryFivePercent() {
  this.salary = this.salary.multiply(1.05);
}

// after
public void raiseSalary(double factor) {
  this.salary = this.salary.multiply(factor);
}
  • 리터럴 값만 다른 함수가 있다면 매개변수화 해서 함수의 재사용성을 늘리는 전략

11.3 플래그 인수 제거하기

// before
public void setDimension(String name, double value) {
  if (name.equals("height")) {
    this.height = value;
    return;
  }
  if (name.equals("width")) {
    this.width =  value;
    return;
  }
}

// after
public void setHeight(double value) {
  this.height = value;
}
public void setWidth(double value) {
  this.width = value;
}
  • 인자에 boolean, enum, string이 들어가면 의심해 볼 수 있음
  • 플래그 인수는 호출 할 수 있는 함수가 무엇이고 어떻게 해야하는지 파악이 어려워짐
  • 플래그 인수가 둘 이상이면 함수 하나가 너무 많은 일을 하고 있다는 신호

11.4 객체 통째로 넘기기

// before
double low = room.getDaysTempRange().getLow();
double high = room.getDaysTempRange().getHigh();
if (plain.withinRange(low, high))

// after
if (plain.withinRange(room.getDaysTempRange()))
  • 객체를 통째로 넘기면 좋은점
    • 변화에 대응하기 쉬움 (함수에 인자가 더 필요하면 객체에 담을 수 있음)
    • 가독성 증가
    • 로직 중복 제거
  • 함수가 레코드(객체)에 의존하기를 원치 않을땐 X
  • 객체의 일부를 꺼내 그것으로 무엇을 하는 로직이 있다면, 이는 해당 객체에 메소드화 할 수 있다는 신호

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

// before
availableVacation(employee, employee.getGrade());
public boolean availableVacation(Employee employee, Grade grade) {...}

// after
availableVacation(employee);
public boolean availableVacation(Employee employee) {
  Grade grade = employee.getGrade(); // 매개변수를 질의함수로 바꾼 부분
  ...
}
  • 매개변수 목록은
    • 중복을 피하는게 좋음
    • 짧을수록 좋음
  • 저자는 호출하는 쪽을 간소화하고, 피호출 함수에 책임을 지게 만드는 구조를 주로 다룸
    • 물론 해당 클래스가 그정도의 책임을 가져도 될 경우에만!
    • 그래서 해당 리팩터링 시, 피호출자(리팩터링 대상)의 클래스가 과도한 의존성을 지니게 된다면 주의해야 함
  • 리팩터링 대상 함수는 참조 투명해야 함
    • 참조 투명: 똑같은 값에 항상 같은 결과 -> 해당 함수가 가변 전역 변수를 이용하는 일이 없어야 함

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

  • 11.5 와 반대되는 리팩터링
  • 클래스 설계 시 책임의 소재에 따라 달라질수 있는 문제
  • 매개변수 객체가 참조 투명하지 않다면 해당 함수도 참조 투명하지 않음
    • 이때 해당 리팩터링 적용 가능
  • 단점은 호출자가 매개변수로 어떤 값을 보내야 할 지 알아야 함
    • 결국 설계실력을 닦아야 하는 문제로 귀결
  • 자바스크립트, 파이썬 등의 일부 언어에서는 객체 안에 데이터를 직접 얻어 낼 수 있는 방법이 항상 존재 하지만,
    • 해당 클래스는 불변용으로 설계한 것을 알리는 것만으로도 좋은 효과를 누릴 수 있음

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants