2nd Edition
๊ฐ. ๋ฆฌํฉํฐ๋ง ์ฃผ์ ์์น
1) ๋ฆฌํฉํฐ๋ง์ด๋
โข
๋ฆฌํฉํฐ๋ง์ด๋ ์ฝ๋์ ์ธ๋ถ ๋์์ ๋์ผํ์ง๋ง ๋ด๋ถ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ์ํํธ์จ์ด๋ฅผ ์์ ํ๋ ๊ฒ์
๋๋ค. ๋ฆฌํฉํฐ๋ง์ ํ๋ฉด ์ฝ๋ ์์ฑ ํ์ ์ฒด๊ณ์ ์ผ๋ก ์ค๊ณ๋ ์ฝ๋๋ก ๋ฐ๊ฟ ์ ์์ต๋๋ค. ๋ฆฌํฉํฐ๋ง์ ๋ฐ๋ผ ์ค๊ณ๊ฐ ๊ฐ์ ๋๋ฉด ๊ฒฐ๊ณผ์ ์ผ๋ก ๊ฐ๋ฐ๋น์ฉ ๊ฐ์์ ๋ฐ๋ฅธ ๊ฒฝ์ ์ ํจ๊ณผ๊ฐ ๋ฐ์ํฉ๋๋ค.
โข
๋ฆฌํฉํฐ๋ง vs ์ฑ๋ฅ๊ฐ์
: ๋ ์ ๊ทผ ๋ชจ๋ ์ํํธ์จ์ด์ ๋ด๋ถ ๊ตฌ์กฐ์ ๋ณํ๋ฅผ ์ฃผ์ง๋ง ์ธ๋ถ ๋์ ๋ฐฉ์์๋ ๋ณํ๋ฅผ ์ฃผ์ง ์์ต๋๋ค.
: ๋ฆฌํฉํฐ๋ง์ ๋ชฉ์ ์ ์ํํธ์จ์ด๋ฅผ ์ฝ๊ฒ ์ฝ๊ณ ์์ ํ ์ ์๋๋ก ๋ณ๊ฒฝํ๋ ๊ฒ์
๋๋ค. ๋ฐ๋ฉด ์ฑ๋ฅ๊ฐ์ ์ ๋ชฉ์ ์ ์ํํธ์จ์ด์ ์ฑ๋ฅ์ ๋์ด๋ ๊ฒ์
๋๋ค. ๋ฆฌํฉํฐ๋ง์ ๋ฐ๋ผ ์ฝ๋๊ฐ ์ ๋ฆฌ๋ ํ์ ์ฑ๋ฅ๊ฐ์ ์์
์ ํ๋ ๊ฒ์ ๊ถํฉ๋๋ค. ์ฑ๋ฅ๊ฐ์ ๋ง์ ๋ชฉ์ ์ผ๋ก ์์
ํ๋ค๋ณด๋ฉด ์ข
์ข
์ฝ๋๊ฐ ๋์ฑ ๋ณต์กํด์ง ์ ์์ต๋๋ค.
2) ๋ฆฌํฉํฐ๋ง์ ํจ๊ณผ
โข
๋ค์ํ ๋ณํ์ ๋์ํ ์๋ ๊ตฌ์กฐ๋ฅผ ๋ค์ง ์ ์์ต๋๋ค.
: ์ ์ค๊ณ๋ ์ํํธ์จ์ด๋ ์๊ตฌ์ฌํญ ๋ณ๊ฒฝ์ ๋ฐ๋ผ ์ฝ๋๊ฐ ์ง์ ์์ ๋๋ฉด, ์์คํ
์ ๋ฌด๊ฒฐ์ฑ์ด ๋ง๊ฐ์ง๊ณ ๊ธฐ์กด ์ค๊ณ๊ฐ ๋ฌด๋์ง๊ธฐ ๋ง๋ จ์
๋๋ค.
: ์ค๊ณ ์์
์ด ์๋ฃ๋์ด๋ ๊ตฌํ์ ๋ค์ด๊ฐ๋ฉด ํ์ฐ์ ์ผ๋ก ๊ธฐ์กด ์ค๊ณ์ ๋ค๋ฅธ ๋ถ๋ถ์ด ์๊น๋๋ค.
โข
๊ตฌํ ์ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํด๋ ๋ฐ๋ก ์ก๋ ๊ฒ์ด ์๋์ ์ผ๋ก ์ฝ์ต๋๋ค.
: ๋ฆฌํฉํฐ๋ง์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฝ๋๋ฅผ ์์ ์กฐ๊ฐ์ผ๋ก ๋ง๋ค๊ธฐ ๋๋ฌธ์ ์๋ฌ ๋ฐ์๋ถ๋ถ์ ๋ํด ์๋์ ์ผ๋ก ์ฝ๊ฒ ํฌ์ฐฉํ ์ ์์ต๋๋ค.
โข
์ํํธ์จ์ด ์ ์ง๋ณด์ ๋น์ฉ์ ๋ฎ์ถฅ๋๋ค.
: ํ์
์๊ฐ ์ํํธ์จ์ด๋ฅผ ์ฝ๊ฒ ์ดํดํ ์ ์๊ณ ์ฝ๊ฒ ์์ ํ ์ ์๊ธฐ ๋๋ฌธ์ ํ ๋จ์์ ๋ฅ๋ฅ ์ ๋์
๋๋ค.
: ์๊ฐ์ด ์ง๋จ์ ๋ฐ๋ผ ๊ตฌ์กฐ๊ฐ ๋ง๊ฐ์ง๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์๊ธฐ ๋๋ฌธ์ ์ํํธ์จ์ด
3) ๋ฆฌํฉํฐ๋ง์ด ํนํ ํ์ํ ์์
๋ฆฌํฉํฐ๋ง์ ํญ์ ํ๋ ๊ฒ์
๋๋ค. ๋ฆฌํฉํฐ๋ง๋ง์ ์ํด ๋ฐ๋ก ์๊ฐ์ ๋ด์ ํ๋ ๊ฒ์ด ์๋๋๋ค.
โข
์๋ก์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ ์์ ์ ๋์ฑ ์ ๊ฒฝ ์จ์ ๋ฆฌํฉํฐ๋ง์ ํด์ผ ํฉ๋๋ค.
: ์๋ก์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ฉด ์๋ฌ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค. ๋ฆฌํฉํฐ๋งํ์ฌ ์ฝ๋๋ฅผ ๋ช
ํํ ๋ง๋ค๋ฉด ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ์ ๋ฐ๋ฅธ ์๋ฌ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ ๋ฎ์ถ ์ ์์ต๋๋ค.
โข
ํด๊ฒฐ์ด ์๋๋ ๋ฒ๊ทธ๊ฐ ์์ ๋ ๋ฆฌํฉํฐ๋งํ๋ฉด ๋ฒ๊ทธ์ ์์น๋ฅผ ์ฝ๊ฒ ์ฐพ์ ์ ์์ต๋๋ค.
: ๋์ฒด๋ก ๋ฒ๊ทธ์ ์์น๋ฅผ ์ฝ๊ฒ ์ฐพ์ ์ ์๋ ์ด์ ๋ ์ฝ๋๋ฅผ ์ฝ๊ฒ ์ฝ์ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
โข
ํ์์๊ฒ ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ๊ธฐ ์ ์ ๋ฆฌํฉํฐ๋งํ๋ฉด ๋์ฑ ๊ฐ์น ์๋ ์ฝ๋ฉํธ๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
: ๋จ์ ์ฝ๋๋ฅผ ์ฝ๋ ๊ฒ์ ์ผ๋ฐ์ ์ผ๋ก ํ๋ค๊ณ ๋๋ก๋ ๊ท์ฐฎ์ ์ผ์
๋๋ค. ๋ง์ฝ ๋ฆฌ๋ทฐ ๋์์ด ๋๋ ์ฝ๋๊ฐ ๊น๋ํ๊ฒ ์ ๋ฆฌ๋์ด ์๋ค๋ฉด, ๋์ฑ ์ํํ๊ณ ์์ฐ์ ์ธ ์ฝ๋ฉํธ๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค.
๋. Bad Smells in Code
๋ฆฌํฉํฐ๋ง์ด ์ ์คํ ์ฝ๋์๋ ์ผ์ ํ ํจํด์ด ์์ต๋๋ค.
1) ๊ธฐ์ดํ ์ด๋ฆ(Mysterious Name)
โข
์ฝ๋์ ์ด๋ฆ์ด ๋จ์ ๋ช
๋ฃํ์ง ์์ต๋๋ค.
2) ์ค๋ณต ์ฝ๋(Duplicated Code)
โข
๋์ผํ ์ฝ๋ ์กฐ๊ฐ์ด ์ฌ๋ฌ ๊ณณ์ ์กด์ฌํฉ๋๋ค.
fun calculateSalesTax(amount: Double): Double {
return amount * 0.08
}
fun calculateServiceTax(amount: Double): Double {
return amount * 0.08
}
Kotlin
๋ณต์ฌ
3) ๊ธด ํจ์(Long Function)
โข
ํ๋์ ํจ์์ ๋๋ฌด ๊ธด ๋ก์ง์ด ์กด์ฌํฉ๋๋ค.
fun calculateTotal(cart: List<Product>): Double{
var total = 0.0
// caculate sub tottal
for (product in cart) {
total += product.price
}
// apply discount
if (total > 100) {
total -= total * 0.1
}
// add sales tax
val tax = total * 0.08
total += tax
return total
}
Kotlin
๋ณต์ฌ
4) ๊ธด ๋งค๊ฐ๋ณ์ ๋ชฉ๋ก (Long Parameter List)
โข
ํจ์ ์๊ทธ๋์ฒ์ ๋๋ฌด ๋ง์ ๋งค๊ฐ๋ณ์๊ฐ ์กด์ฌํฉ๋๋ค.
5) ์ ์ญ ๋ฐ์ดํฐ(Global Data)
โข
์ฝ๋๋ฒ ์ด์ค์ ๋ชจ๋ ๊ณณ์ ์ ๊ทผํ ์ ์๋ ์ ์ญ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํฉ๋๋ค.
6) ๊ฐ๋ณ ๋ฐ์ดํฐ(Mutable Data)
โข
๋ณ๊ฒฝ์ ๋ฐ๋ผ ๋ถ์์์ฉ์ด ๋ฐ์ํ ์ ์๋ ๊ฐ๋ณ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํฉ๋๋ค.
7) ๋ค์ํจ ๋ณ๊ฒฝ (Divergent Change)
โข
์ฌ๋ฌ ๊ด์ฌ์ฌ์ ๋ฐ๋ผ ํ๋์ ๊ฐ์ฒด๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋ฉ๋๋ค.
data class Address(var street: String, var city: String, var state: String, var zip: String)
data class Order(var id: Int, var product: String, var quantity: Int)
class UserProfile {
var username: String = ""
var email: String = ""
var address: Address = Address("", "", "", "")
var orders: MutableList<Order> = mutableListOf()
// Method related to user profile management
fun changeUsername(newUsername: String) {
username = newUsername
}
// Method related to address management
fun changeAddress(newAddress: Address) {
address = newAddress
}
// Method related to order management
fun placeOrder(newOrder: Order) {
orders.add(newOrder)
}
}
Kotlin
๋ณต์ฌ
8) ์ฐํ์ด ์์ (Shotgun Surgery)
โข
๋์ผํ ์ฝ๋ ์กฐ๊ฐ์ด ์ฌ๋ฌ ๊ด์ฌ์ฌ์ ๋ถ์ฐ๋์ด ์์ต๋๋ค.
class OrderConfirmation {
fun sendOrderConfirmationEmail(order: Order) {
// Send order confirmation email
}
}
class OrderHistory {
fun sendOrderConfirmationEmail(order: Order) {
// Send order confirmation email
}
}
class OrderStatus {
fun sendOrderConfirmationEmail(order: Order) {
// Send order confirmation email
}
}
Kotlin
๋ณต์ฌ
9) ๊ธฐ๋ฅ ํธ์ (Feature Envy)
โข
๊ฐ์ฒด ๋ด ์ผ๋ถ ์์ฑ์ด ๋ค๋ฅธ ๊ฐ์ฒด์์ ์์ฃผ ์ฌ์ฉ๋ฉ๋๋ค.
class Customer(
val name: String,
val age: Int,
val address: Address
)
class Order(
val customer: Customer,
val items: List<Item>
) {
fun calculateTotal(): Double {} { /*...*/ }
fun printCustomerDetails() {
println("Customer Name: ${customer.name}")
println("Customer Age: ${customer.age}")
println("Customer Address: ${customer.address}")
}
}
Kotlin
๋ณต์ฌ
10) ๋ฐ์ดํฐ ๋ญ์น (Data Clumps)
โข
๋๋ถ๋ถ์ ๊ฒฝ์ฐ ํจ๊ป ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ์ ์กฐํฉ์ด ์กด์ฌํฉ๋๋ค.
class Order(
private val orderNumber: String,
private val customerName: String,
private val customerEmail: String,
private val customerAddress: Address,
private val items: List<Item>
) {
// ... other methods and logic ...
}
class Address(
val street: String,
val city: String,
val state: String,
val zipCode: String
)
class Item(
val name: String,
val price: Double
)
Kotlin
๋ณต์ฌ
11) ๊ธฐ๋ณธํ ์ง์ฐฉ (Primitive Obsession)
โข
์๊ตฌ์ฌํญ์ ๋ง์กฑํ๋ ๊ฐ์ฒด๋ฅผ ์ ์ํ์ง ์๊ณ ๊ธฐ๋ณธํ ํ์
์ ์ฌ์ฉํฉ๋๋ค.
data class User(
val name: String,
val email: String,
val phoneNumber: String
)
Kotlin
๋ณต์ฌ
๊ฐ์
12) ๋ฐ๋ณต๋๋ switch๋ฌธ (Repeated Switches)
โข
์ผ์ด์ค๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค๋ฉด switch๋ฌธ์ด ์กด์ฌํ๋ ๋ชจ๋ ๊ณณ์์ ์ฐ๊ด ์ผ์ด์ค๋ฅผ ํจ๊ป ์ถ๊ฐํด์ผ ํฉ๋๋ค.
enum class AnimalType {
DOG, CAT, BIRD
}
class Animal(val type: AnimalType) {
fun makeSound() {
when (type) {
AnimalType.DOG -> println("Woof!")
AnimalType.CAT -> println("Meow!")
AnimalType.BIRD -> println("Tweet!")
}
}
fun eat() {
when (type) {
AnimalType.DOG -> println("Dog is eating...")
AnimalType.CAT -> println("Cat is eating...")
AnimalType.BIRD -> println("Bird is eating...")
}
}
}
Kotlin
๋ณต์ฌ
๊ฐ์
13) ๋ฐ๋ณต๋ฌธ(Loops)
โข
์ฝ๋์ ์๋๋ฅผ ์ฝ๊ฒ ํ์
ํ ์ ์๋ ๋ฐ๋ณต๋ฌธ์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.
fun sumOfPositiveNumbers(numbers: List<Int>): Int {
var total = 0
for (number in numbers) {
if (number > 0) {
total += number
}
}
return total
}
Kotlin
๋ณต์ฌ
๊ฐ์
14) ์ฑ์ ์๋ ์์(Lazy Element)
โข
๋น์ฝํ ๊ธฐ๋ฅ์ผ๋ก ์ ์๋ ๊ฐ์ฒด๊ฐ ์กด์ฌํฉ๋๋ค.
class UserName(val name: String)
fun printUserName(userName: UserName) {
println(userName.name)
}
val user = UserName("John Doe")
printUserName(user) // Outputs: John Doe
Kotlin
๋ณต์ฌ
15) ์ถ์ธก์ฑ ์ผ๋ฐํ (Speculative Generality)
โข
์์ผ๋ก ํ์ํ ๊ฒ์ด๋ผ๊ณ ์ถ์ธกํ์ฌ ๋ฏธ๋ฆฌ ๊ตฌํํ ๊ฒฝ์ฐ๊ฐ ์กด์ฌํฉ๋๋ค.
interface PaymentMethod {
fun processPayment(amount: Double)
}
class CreditCardPayment : PaymentMethod {
override fun processPayment(amount: Double) {
// ... code to process credit card payment ...
}
}
class BitcoinPayment : PaymentMethod {
override fun processPayment(amount: Double) {
// ... code to process cash payment ...
}
}
Kotlin
๋ณต์ฌ
16) ์์ ํ๋ (Temporary Field)
โข
ํน์ ์ํฉ์์๋ง ์ฌ์ฉ๋๋ ๋ณ์๊ฐ ์กด์ฌํฉ๋๋ค.
class Shopping {
var eventDiscount: Double? = null
fun getTotalPrice(items: List<Item>): Double {
var total = items.sumOf { it.price }
if (eventDiscount != null) {
total -= total * eventDiscount!!
}
return total
}
}
Kotlin
๋ณต์ฌ
๊ฐ์
17) ๋ฉ์์ง ์ฒด์ธ (Message Chain)
โข
ํด๋ผ์ด์ธํธ๊ฐ ํ๋์ ๊ฐ์ฒด์ ๋ค๋ฅธ ๊ฐ์ฒด๋ฅผ ์์ฒญํ ๋, ๊ทธ ๊ฐ์ฒด๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด๋ฅผ ๋ ๋ค์ ์์ฒญํ๋ ๊ตฌ์กฐ๊ฐ ์กด์ฌํฉ๋๋ค.
class Order(val customer: Customer)
class Customer(val address: Address)
class Address(val city: City)
class City(val name: String)
// Client code
val order = Order(Customer(Address(City("New York"))))
val cityName = order.customer.address.city.name
Kotlin
๋ณต์ฌ
๊ฐ์
18) ์ค๊ฐ์ (Middle Man)
โข
๊ฐ์ฒด๊ฐ ์ค์ค๋ก์ ์ฑ
์์ ๋คํ์ง ๋ชปํ๊ณ , ๋ค๋ฅธ ๊ฐ์ฒด๋ก ์ฑ
์์ ์์ํฉ๋๋ค.
class Order(val totalCost: Double)
class PaymentProcessor {
fun processPayment(order: Order, paymentMethod: PaymentMethod) {
paymentMethod.pay(order.totalCost)
}
}
interface PaymentMethod {
fun pay(amount: Double)
}
class CreditCard : PaymentMethod {
override fun pay(amount: Double) {
// Implement payment logic here
}
}
// Client code
val order = Order(100.0)
val paymentMethod = CreditCard()
PaymentProcessor.processPayment(order, paymentMethod)
Kotlin
๋ณต์ฌ
๊ฐ์
19) ๋ด๋ถ์ ๊ฑฐ๋ (Insider Trading)
โข
ํ๋์ ๊ฐ์ฒด๊ฐ ๋ค๋ฅธ ๊ฐ์ฒด์ ๋ด๋ถ์ ์ง๋์น๊ฒ ๊ฒฐํฉ๋์ด ์์ต๋๋ค.
class Trader(private val stockMarket: StockMarket) {
fun buy(stock: Stock, quantity: Int): Double {
// needs to know about the inner workings of StockMarket to make a trade.
if (!stockMarket.stocks.contains(stock)) {
throw RuntimeException("Stock not available in the market.")
}
val cost = stock.price * quantity
stock.price += quantity // directly manipulates the price of Stock
return cost
}
}
class Stock(var price: Double)
class StockMarket {
var stocks: MutableList<Stock> = mutableListOf()
fun addStock(stock: Stock) {
stocks.add(stock)
}
}
Kotlin
๋ณต์ฌ
๊ฐ์
20) ๊ฑฐ๋ํ ํด๋์ค (Large Classes)
โข
ํ ํด๋์ค๊ฐ ๋๋ฌด ๋ง์ ์ฑ
์์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
class Trader(var name: String, var address: String, var phoneNumber: String, var accountBalance: Double) {
//...
fun buy(stock: Stock, quantity: Int) {
// buy logic
}
fun sell(stock: Stock, quantity: Int) {
// sell logic
}
fun updateAddress(newAddress: String) {
this.address = newAddress
}
fun updatePhoneNumber(newPhoneNumber: String) {
this.phoneNumber = newPhoneNumber
}
fun deposit(amount: Double) {
this.accountBalance += amount
}
fun withdraw(amount: Double) {
if (this.accountBalance < amount) {
throw IllegalArgumentException("Insufficient balance!")
}
this.accountBalance -= amount
}
//...
}
Kotlin
๋ณต์ฌ
๊ฐ์
21) ์๋ก ๋ค๋ฅธ ์ธํฐํ์ด์ค์ ๋์ ํด๋์ค๋ค (Alternative Classes with Different Interfaces)
โข
๋ ๊ฐ์ฒด๊ฐ ํฐ ํ์์ ์ ์ฌํ ๊ธฐ๋ฅ์ ํ์ง๋ง ์ธํฐํ์ด์ค๊ฐ ๋ค๋ฆ
๋๋ค.
class StockTrader {
fun purchaseStock(stock: Stock, quantity: Int) {
// Buy logic...
}
fun sellStock(stock: Stock, quantity: Int) {
// Sell logic...
}
}
class CryptoTrader {
fun acquireCrypto(crypto: Crypto, quantity: Int) {
// Buy logic...
}
fun disposeCrypto(crypto: Crypto, quantity: Int) {
// Sell logic...
}
}
Kotlin
๋ณต์ฌ
๊ฐ์
22) ๋ฐ์ดํฐ ํด๋์ค (Data Class)
โข
๋ฐ์ดํฐ ํด๋์ค์์ ์์ฑ์ ์กฐ์ํฉ๋๋ค.
// Data Class
data class Image(var width: Int, var height: Int, val format: String) {
fun resize() {
// ... code to resized width and height ...
}
}
Kotlin
๋ณต์ฌ
23) ์์ ํฌ๊ธฐ (Refused Bequest)
โข
์์ ๊ฐ์ฒด์์ ๋ถ๋ชจ ๊ฐ์ฒด์ ๋ฉ์๋ ์ค ์ผ๋ถ๋ฅผ ์ฌ์ฉํ์ง ์์ต๋๋ค.
open class Vehicle {
open fun startEngine() {
// Start engine
}
open fun move() {
// Move
}
open fun stop() {
// Stop
}
}
class Car: Vehicle() {
override fun startEngine() {
// Start car engine
}
override fun move() {
// Car moves
}
override fun stop() {
// Car stops
}
}
class Bicycle: Vehicle() {
override fun startEngine() {
throw UnsupportedOperationException("Bicycles don't have engines")
}
override fun move() {
// Bicycle moves
}
override fun stop() {
// Bicycle stops
}
}
Kotlin
๋ณต์ฌ
๊ฐ์
24) ์ฃผ์ (Comments)
โข
์ฝ๋ ์์ฒด๋ก ๋น์ง๋์ค ๋ก์ง์ ์ถฉ๋ถํ ์ค๋ช
ํ ์ ์๊ธฐ ๋๋ฌธ์ ์ฃผ์์ ์ถ๊ฐํฉ๋๋ค.
class OrderProcessing {
fun processOrder(order: Order) {
// Validate order
if (order.items.isEmpty()) {
throw IllegalArgumentException("Order cannot be empty")
}
if (order.customer == null) {
throw IllegalArgumentException("Customer information is required")
}
// Calculate total
var total = 0.0
for (item in order.items) {
total += item.price
}
// Apply discount
if (order.customer.isPremium) {
total *= 0.9 // 10% discount for premium customers
}
// Create invoice
val invoice = Invoice(order.customer, total)
// Send invoice
sendInvoice(invoice)
}
private fun sendInvoice(invoice: Invoice) {
// logic to send the invoice
}
}
Kotlin
๋ณต์ฌ
๊ฐ์
๋ค. Basic Refactoring
1) ๊ฐ์
graph TD stage1[๋จ๊ณ 1: ํจ์ ๊ตฌ์ฑ ๋ฐ ์ด๋ฆ ์ง๊ธฐ] Function(ํจ์ ์ถ์ถ ๋๋ ์ธ๋ผ์ธ) Variable(๋ณ์ ์ถ์ถ ๋๋ ์ธ๋ผ์ธ) ChangeFunctionDeclaration(ํจ์ ์ ์ธ ๋ณ๊ฒฝ) ChangeVariableName(๋ณ์ ์ด๋ฆ ๋ณ๊ฒฝ) VariableEncapsulation(๋ณ์ ์บก์ํ) CreateParameterObject(๋งค๊ฐ๋ณ์ ๊ฐ์ฒด ์์ฑ) stage2[๋จ๊ณ 2: ํจ์ ๋ฌถ๊ธฐ] GroupFunctionsInClass(ํด๋์ค๋ก ํจ์ ๋ฌถ๊ธฐ) GroupFunctionsInTransformFunction(๋ณํ ํจ์๋ก ํจ์ ๋ฌถ๊ธฐ) stage3[๋จ๊ณ 3: ๋จ๊ณ ์ชผ๊ฐ๊ธฐ] stage1 --> Function --> stage2 stage1 --> Variable --> stage2 stage1 --> ChangeFunctionDeclaration --> stage2 stage1 --> ChangeVariableName --> stage2 stage1 --> VariableEncapsulation --> stage2 stage1 --> CreateParameterObject --> stage2 stage2 --> GroupFunctionsInClass --> stage3 stage2 --> GroupFunctionsInTransformFunction --> stage3
Mermaid
๋ณต์ฌ
2) ํจ์ ์ถ์ถ ๋๋ ์ธ๋ผ์ธ
โ๋ชฉ์ โ๊ณผ โ๊ตฌํโ์ ๋ถ๋ฆฌ๋ฅผ ๊ธฐ์ค์ผ๋ก ํจ์๋ฅผ ์ถ์ถํ ๊ฒ
โข
์ฝ๋๊ฐ ๊ตฌํ์ ๋ด๊ณ ์์ผ๋ฉด ํจ์๋ก ์ถ์ถํฉ๋๋ค. ๋ฐ๋๋ก ์ถ์ถ๋ ํจ์๊ฐ ๋ชฉ์ ์ ๋ด๊ณ ์์ผ๋ฉด ์ธ๋ผ์ธํฉ๋๋ค.
โข
ํจ์ ์ด๋ฆ์ด ํจ์ ๋ณธ๋ฌธ ๋ณด๋ค ๊ธธ๋ค๊ฑฐ๋ ํจ์ ๋ณธ๋ฌธ์ด ๋๋ฌด ์งง๋ค๋ ๊ฒ์ ์ค์ํ์ง ์์ต๋๋ค.
โข
ํจ์ ์ถ์ถ ์์
class OrderProcessing() {
fun placeOrder(order: Order)
val totalPrice = order.items.sumOf { item -> item.price }
var finalPrice = if (order.items.size >= 10) totalPrice * 0.9 else totalPrice
order.status = Status.COMPLETED
order.totalPrice = finalPrice
}
}
Kotlin
๋ณต์ฌ
to
class OrderService() {
fun placeOrder(order: Order) {
val totalPrice = order.items.sumOf { item -> item.price }
val finalPrice = applyDiscounts(totalPrice)
finalizeOrder(finalPrice)
}
private fun applyDiscounts(totalPrice: Double): Double {
return if (order.items.size >= 10) totalPrice * 0.9 else totalPrice
}
private fun finalizeOrder(finalPrice: Double) {
order.status = Status.COMPLETED
order.totalPrice = finalPrice
}
}
Kotlin
๋ณต์ฌ
โข
ํจ์ ์ธ๋ผ์ธ ์์
class OrderService() {
fun placeOrder(order: Order) {
val totalPrice = calculateTotal()
val finalPrice = applyDiscounts(totalPrice)
finalizeOrder(finalPrice)
}
// ...์๋ต
}
Kotlin
๋ณต์ฌ
to
class OrderService() {
fun placeOrder(order: Order) {
val totalPrice = order.items.sumOf { item -> item.price }
val finalPrice = applyDiscounts(totalPrice)
finalizeOrder(finalPrice)
}
// ...์๋ต
}
Kotlin
๋ณต์ฌ
3) ๋ณ์ ์ถ์ถ ๋๋ ์ธ๋ผ์ธ
ํจ์์ ๋ง์ฐฌ๊ฐ์ง๋ก โ๋ชฉ์ โ๊ณผ โ๊ตฌํโ์ ๊ธฐ์ค์ผ๋ก ๋ณ์๋ฅผ ์ถ์ถํ ๊ฒ
โข
๋ง์ฝ ์ถ์ถ ๋์ ์ฝ๋๊ฐ ํจ์ ์ธ๋ถ์๋ ์๋ฏธ๋ฅผ ๊ฐ๋๋ค๋ฉด ๋ณ์๊ฐ ์๋๋ผ ํจ์๋ก ์ถ์ถํด์ผ ํฉ๋๋ค.
โข
๋ณ์ ์ถ์ถ ์์
private fun applyDiscounts(totalPrice: Double): Double {
return if (order.items.size >= 10) totalPrice * 0.9 else totalPrice
}
Kotlin
๋ณต์ฌ
to
private fun applyDiscounts(totalPrice: Double): Double {
val isDiscountApplicable = order.items.size >= 10
val discountRate = 0.9
return if (isDiscountApplicable) totalPrice * discountRate else totalPrice
}
Kotlin
๋ณต์ฌ
โข
๋ณ์ ์ธ๋ผ์ธ ์์
private fun applyDiscounts(totalPrice: Double): Double {
val itemSize = order.items.size
val discountRate = 0.9
return if (itemSize >= 10) totalPrice * discountRate else totalPrice
}
Kotlin
๋ณต์ฌ
to
private fun applyDiscounts(totalPrice: Double): Double {
val discountRate = 0.9
return if (order.items.size >= 10) totalPrice * discountRate else totalPrice
}
Kotlin
๋ณต์ฌ
4) ํจ์ ์ ์ธ ๋ณ๊ฒฝ
ํจ์์ ์ด๋ฆ๊ณผ ๋งค๊ฐ๋ณ์๋ฅผ โ๋ชฉ์ โ์ ๋ง๊ฒ ์ ์ธํ ๊ฒ
โข
ํจ์ ์ด๋ฆ์ ๊ฒฝ์ฐ, ํจ์ ๋ด๋ถ์ ๊ตฌํ์ด ์๋๋ผ ํจ์๊ฐ ํธ์ถ๋๋ ๊ณณ์์ ์ฌ์ฉ๋๋ ๋ชฉ์ ์ ๋ง๊ฒ ์ ์ํด์ผ ํฉ๋๋ค.
โข
๋งค๊ฐ๋ณ์์ ๊ฒฝ์ฐ, ํ์ ๊ฐ๋ง์ ์ ๋ฌํ์ฌ ํ์ฉ๋๋ฅผ ๋์ผ ์ง ๋๋ ์ฐ๊ด ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ์ฌ ์บก์ํ ์์ค์ ๋์ผ ์ง๋ฅผ ๊ฒฐ์ ํด์ผ ํฉ๋๋ค.
โข
ํจ์ ์ด๋ฆ ๋ณ๊ฒฝ ์์
class Bank() {
fun calculateInterestByAccountType(account: Account): Double {
val rate = if (account.type == AccountType.CHECKING) 0.01 else 0.02
return account.balance * rate
}
}
Kotlin
๋ณต์ฌ
to
class Bank() {
fun calculateInterest(account: Account): Double {
val rate = if (account.type == AccountType.CHECKING) 0.01 else 0.02
return account.balance * rate
}
}
Kotlin
๋ณต์ฌ
โข
๋งค๊ฐ๋ณ์ ๋ณ๊ฒฝ ์์
fun applyDiscount(totalPrice: Double, numberOfItems: Int): Double {
val discountRate = 0.9
return if (numberOfItems >= 10) totalPrice * discountRate else totalPrice
}
Kotlin
๋ณต์ฌ
to
fun applyDiscount(order: Order): Double {
val discountRate = 0.9
return if (order.items.size >= 10) order.totalPrice * discountRate else order.totalPrice
}
Kotlin
๋ณต์ฌ
5) ๋ณ์ ์บก์ํ
๋ฐ์ดํฐ์ ์ ํจ๋ฒ์๊ฐ ๋์ ๊ฐ๋ณ ๋ฐ์ดํฐ๋ ์ ๊ทผ๊ณผ ๊ฐฑ์ ์ ๋ํด ์บก์ํํ ๊ฒ
โข
์ ์ฐํ ํ๋ก๊ทธ๋๋ฐ ํ๊ฒฝ์์๋ ์ฝ๋ ์ ๋ฐ์ ๊ฑธ์ณ ์ ํจ๋ฒ์๊ฐ ๋์ ๊ฐ๋ณ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.
โข
OOP ํ๊ฒฝ์์๋ ํด๋์ค๋ฅผ ๋ฒ์ด๋ ๊ฐ๋ณ ๋ฐ์ดํฐ ์์ฒด๊ฐ ์ฉ์ธ๋์ง ์์ต๋๋ค.
6) ๋ณ์ ์ด๋ฆ ๋ณ๊ฒฝ
๋งฅ๋ฝ ์์์ ๋ณ์์ โ๋ชฉ์ โ์ ๋ช
ํํ๊ฒ ์ดํดํ ์ ์๊ฒ ๋ช
๋ช
ํ ๊ฒ
โข
๋งฅ๋ฝ ์์์ ์ดํดํ ์ ์๋ค๋ฉด ๋ณ์๋ช
์ ํ ๊ธ์๋ก ์ง๋ ๊ฒ๋ ๊ด์ฐฎ์ต๋๋ค.
โข
DB์ ์ ์ฅ๋๋ ์์์ฑ ๋ฐ์ดํฐ์ ๊ฒฝ์ฐ, ๋ณ์๋ช
์ ๋ ์ ๊ฒฝ์จ์ผ ํฉ๋๋ค.
7) ๋งค๊ฐ๋ณ์ ๊ฐ์ฒด ์์ฑ
๊ด๊ณ ์๋ ๋ฐ์ดํฐ ๋ญ์น๊ฐ ๋ณด์ด๋ฉด ๊ฐ์ฒด๋ก ๋ฌถ์ ๊ฒ
โข
๋ฐ์ดํฐ ๋ญ์น๋ฅผ ๊ฐ์ฒด ๊ธฐ๋ฐ์ผ๋ก ๊ตฌ์กฐํํ๋ ๊ณผ์ ์ด ๋ฐ๋ณต๋๋ฉด, ๋ฌธ์ ๋ฅผ ๋ณด๋ค ์ฝ๊ฒ ํด๊ฒฐํ ์ ์๋ ๊ธธ์ด ๋ณด์
๋๋ค.
8) ์ฌ๋ฌ ํจ์๋ฅผ ํด๋์ค๋ก ๋ฌถ๊ธฐ
๊ณตํต ๋ฐ์ดํฐ ์ค์ฌ์ ํจ์ ๋ญ์น๊ฐ ๋ณด์ด๋ฉด ํด๋์ค๋ก ๋ฌถ์ ๊ฒ
9) ์ฌ๋ฌ ํจ์๋ฅผ ๋ณํ ํจ์๋ก ๋ฌถ๊ธฐ
ํ์ ์คํ์ผ์ด โ๋ณํ ํจ์๋ก ๋ฌถ๊ธฐโ์ผ ๊ฒฝ์ฐ๋ง ์ฑํ, ์๋๋ผ๋ฉด ํด๋์ค๋ก ๋ฌถ์ ๊ฒ
1st Edition
๊ฐ. Basic Refactoring
1. ๋ฉ์๋๋ฅผ ๋๋๋ ์ด์
โข
๊ธด ๋ฉ์๋์๋ ๋ณต์กํ ๋ก์ง๊ณผ ๋ง์ ์ ๋ณด๊ฐ ์ํค๊ธฐ ์ฝ๊ธฐ ๋๋ฌธ์ ๋ฌธ์
โข
๋ค๋ฅธ ๋ฉ์๋์์ ์ฌ์ฉํ ๊ฐ๋ฅ์ฑ์ด ๋์์ง
โข
์ค๋ฒ๋ผ์ด๋ฉ์ด ์ฌ์์ง
2. Precondition of Extracting Method
โข
ํน์ ์ฝ๋๋ฅผ ์๋ก์ด ๋ฉ์๋๋ก ๋๋๊ธฐ ์ , ์ถ์ถํ๋ ค๋ ์ฝ๋์ ์งํฉ์ ๋ํ ์ ์๋ฏธํ ์ด๋ฆ์ด ๋ ์ค๋ฅด์ง ์๋๋ค๋ฉด ์ถ์ถํ์ง ๋ง ๊ฒ
3. No Local Varialbes
โข
์ถ์ถํ๋ ค๋ ์ฝ๋ ๋ด์ ์ฌ์ฉ๋๋ ์ง์ญ๋ณ์๊ฐ ์๋ค๋ฉด ๊ทธ๋ฅ ๋ณต์ฌ ๋ถ์ฌ๋ฃ๊ธฐํ์ฌ ์๋ก์ด ๋ฉ์๋๋ฅผ ์์ฑ
4. Using Local Variables
โข
์ถ์ถํ๋ ค๋ ์ฝ๋ ๋ด์ ์ฌ์ฉ๋๋ ์ง์ญ๋ณ์๊ฐ ์๋ค๋ฉด ์๋ก์ด ๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌํจ
5. Reassigning a Local Variable
โข
์ถ์ถํ๋ ค๋ ์ฝ๋ ๋ด์ ์ฌ์ฉ๋๋ ๋ณ์๊ฐ ์๋ก์ด ๋ฉ์๋๋ก ๋ถ๋ฅํ ์ฝ๋ ์ธ์์๋ ์ฌ์ฉ๋๋ค๋ฉด ๋ฐํ๊ฐ์ผ๋ก ์ง์ ํจ.
โข
์๋ก์ด ๋ฉ์๋๋ก ๋ถ๋ฅํ ์ฝ๋ ๋ด์์๋ง ์ฌ์ฉ๋๋ค๋ฉด ์๋ก ์์ฑํ ๋ฉ์๋์ ์ง์ญ๋ณ์๋ก ์ฌ์ฉํจ
// Before
void printOwing() {
Enumeration e = _orders.elements();
double outstanding = 0.0;
printBanner();
// calculate outstanding
while (e.hasMoreElements()) {
Order each = (Order) e.nextElement();
outstanding += each.getAmount();
}
printDetails(outstanding);
}
// After
void printOwing(double previousAmount) {
printBanner();
double outstanding = getOutstanding(previousAmount * 1.2);
printDetails(outstanding);
}
double getOutstanding(double initialValue) {
double result = initialValue;
Enumeration e = _orders.elements();
while (e.hasMoreElements()) {
Order each = (Order) e.nextElement();
result += each.getAmount();
}
return result;
}
Java
๋ณต์ฌ
6. Inline Method & Temp
โข
๊ฐ๋จํ๊ฒ ํํํ ์ ์๋ ๋ฉ์๋๋ฅผ ๊ตณ์ด ๋๋๋ ๊ฒ์ ์ฝ๋๋ฅผ ๋ณต์กํ๊ฒ ํจ
โ ์ฝ๋ํด์์ ์ด๋ ค์์ด ์๋ค๋ฉด inline์ผ๋ก ์ฝ๋๋ฅผ ๊ฐ๋จ
int getRating() {
return (moreThanFiveLateDeliveries()) ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return _numberOfLateDeliveries > 5;
}
// After
int getRating() {
return (_numberOfLateDeliveries > 5) ? 2 : 1;
}
Java
๋ณต์ฌ
โข
์๋ฏธ๊ฐ ๊ฐ๋จํ ๊ฒฝ์ฐ๋ฉด์ ๋์์ ํ๋ฒ๋ง ์ฌ์ฉ๋๋ ๋ณ์์ ๊ฒฝ์ฐ, ๋ณ์๋ฅผ ์ ๊ฑฐํ๊ณ ๋ฉ์๋๋ก ๋์ฒดํจ
double basePrice = anOrder.basePrice();
return (basePrice > 1000)
// After
return (anOrder.basePrice() > 1000)
Java
๋ณต์ฌ
7. Replace Temp with Query
โข
๋ฉ์๋ ์ด๋ฆ๋ง์ผ๋ก ๋งฅ๋ฝ์ ์ ์ ์์ ๋งํผ ๊ฐ๋จํ ์์ ๋ณ์๋ก ํ ๋นํ๋ค๋ฉด, ์ด๋ฅผ ๋ฉ์๋๋ก ๋ณํํ์ฌ ์ฌ์ฉ
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
// After
if (basePrice() > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
double basePrice() {
return _quantity * _itemPrice;
}
Java
๋ณต์ฌ
8. Introduce Explaining Variable
โข
ํน์ ์์ ๋ํ ์๋ฏธ๊ฐ ํ ๋์ ๋ค์ด์ค์ง ์๋๋ค๋ฉด ๋ณ์๋ฅผ ์ ์ธํ๋ ๋ฐฉ์์ผ๋ก ์๋ฏธ๋ฅผ ๋ช
ํํ ํ ๊ฒ
if ( (platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}
// After
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") >-1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") >-1;
final boolean wasResized = resize > 0;
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
// do something
}
Java
๋ณต์ฌ
9. Split Temporary Variable
โข
์์์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋ณ์์ผ์ง๋ผ๋ ๊ฐ์ ์ด๋ฆ์ ๋ณ์๋ฅผ ์ฌ์ฉํ์ง ๋ง๊ฒ
โ ์ฝ๋๋ฅผ ์ฝ๋ ์ฌ๋์๊ฒ ํผ๋์ ์ค ์ ์์
โข
์ํ๋ฌธ์ ์ธ๋ฑ์ค์ Collecting temporary๋ ์์ธ
double temp = 2 * (_height + _width);
System.out.println (temp);
temp = _height * _width;
System.out.println (temp);
// After
final double perimeter = 2 * (_height + _width);
System.out.println (perimeter);
final double area = _height * _width;
System.out.println (area);
Java
๋ณต์ฌ
10. Remove Assignments to Parameters
โข
๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌ๋ ๊ฐ์ ์กฐ์ํ์ง ๋ง ๊ฒ
โ ์๋ฐ์์ ๋ pass by value์ ์์น์ด ์ ์ฉ๋๊ธฐ ๋๋ฌธ์ discount ๋ฉ์๋ ์ธ๋ถ์์ inputVal์ ๊ฐ์ด ๋ณ๊ฒฝ๋์ง ์์ง๋ง, ๋ค๋ฅธ ์ธ์ด(pass by reference)๋ฅผ ์ฌ์ฉํ๋ reader์๊ฒ ํผ๋์ ์ค ์ ์์
โ ์ผ๊ด์ ์ธ ์ฌ์ฉ์ฑ์ ๊ฐ์ํ๋ฉด ๋งค๊ฐ๋ณ์์ ๋ณํ๋ฅผ ์ฃผ์ง ์๋ ๊ฒ์ด ์ฝ๋๋ฅผ ๋์ฑ ๋ช
ํํ๊ฒ ๋ง๋ค์ด ์ค
int discount (int inputVal, int quantity, int yearToDate) {
if (inputVal > 50) inputVal -= 2;
// After
int discount (int inputVal, int quantity, int yearToDate) {
int result = inputVal;
if (inputVal > 50) result -= 2;
Java
๋ณต์ฌ
11. Replace Method with Method Object
โข
์ง์ญ๋ณ์๊ฐ ๋๋ฌด ๋ง์์ ๋ฉ์๋ ๋ถํ ์ด ์ด๋ ค์ธ ๊ฒฝ์ฐ, ๋ฉ์๋ ๊ฐ์ฒด๋ก ๋์ฒดํ๋ค
class Order...
double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation;
...
}
// After
class Order...
double price(){
return new PriceCalculator(this).compute()
}
}
class PriceCalculator...
compute(){
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// long computation;
return ...
}
Java
๋ณต์ฌ
Class Account
int gamma (int inputVal, int quantity, int yearToDate) {
int importantValue1 = (inputVal * quantity) + delta();
int importantValue2 = (inputVal * yearToDate) + 100;
if ((yearToDate - importantValue1) > 100)
importantValue2 -= 20;
int importantValue3 = importantValue2 * 7;
// and so on.
return importantValue3 - 2 * importantValue1;
}
}
// After
class Gamma...
private final Account _account;
private int inputVal;
private int quantity;
private int yearToDate;
private int importantValue1;
private int importantValue2;
private int importantValue3;
Gamma (Account source, int inputValArg, int quantityArg, int yearToDateArg) {
_account = source;
inputVal = inputValArg;
quantity = quantityArg;
yearToDate = yearToDateArg;
}
int compute () {
importantValue1 = (inputVal * quantity) + _account.delta();
importantValue2 = (inputVal * yearToDate) + 100;
if ((yearToDate - importantValue1) > 100)
importantValue2 -= 20;
int importantValue3 = importantValue2 * 7;
// and so on.
return importantValue3 - 2 * importantValue1;
}
int gamma (int inputVal, int quantity, int yearToDate) {
return new Gamma(this, inputVal, quantity,yearToDate).compute();
}
Java
๋ณต์ฌ
๋. Moving features between elements
1. Move method / field
โข
๋ฉ์๋๋ ํ๋๋ ๋ง์ด ์ฌ์ฉ๋๋ class์ ์ ์ํ ๊ฒ
2. Extract Class
โข
ํ๋์ ํด๋์ค์ ๋๋ฌด ๋ง์ ๊ธฐ๋ฅ์ด ์๋ค๋ฉด ์ฐ๊ด์ฑ์ ๊ธฐ์ค์ผ๋ก ๋ ๊ฐ ์ด์์ ํด๋์ค๋ก ๋๋ ๊ฒ
โ ์์คํ
์ ์๊ฐ์ด ๊ฐ ์๋ก ๋์ฑ ๋ณต์กํด์ง. ํด๋์ค๋ ๋์ฑ ๋ณต์กํด์ง๊ธฐ ๋๋ฌธ์ ๋๋ ์ ์๋ ํด๋์ค๋ ๋ฏธ๋ฆฌ ๋๋๋ ๊ฒ์ด ํจ์จ์
class Person {
name,
officeAreaCode,
officeNumber,
getTelephoneNumber()
}
// After
class Person {
name,
getTelephoneNumber()
}
class TelephoneNumber {
areaCode,
number,
getTelephoneNumber()
}
Java
๋ณต์ฌ
3. Inline Class
โข
๋ฆฌํฉํฐ๋ง์ ์ ๋๋ก ํ๋ฉด ๊ฐ๊ฐ์ ํด๋์ค๋ค์ด ๋๋ฌด ๋น๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ๋ฏ๋ก, ๋ง์ด ์ฌ์ฉ๋์ง ์๊ฑฐ๋ ๊ณผํ๊ฒ ์ถ์ถ๋ ํด๋์ค๋ ๊ด๋ จ ํด๋์ค์ ํฌํจ์ํด
4. Hide Delegate
5. Remove Middle Man
โข
4, 5๋ฒ์ ์ถํ ๋ค์
6. Introduce Foreign Method
โข
Server class์ ๋ฉ์๋๋ก ์ ์ธ๋์ง ์์์ง๋ง client class์ ํ์ํ ๋ฉ์๋๊ฐ ์๋ค๋ฉด ์๋์ ๊ฐ์ ๋ฐฉ์์ผ๋ก Foreign Method ์ ์ธํ์ฌ ์ฌ์ฉ ๊ฐ๋ฅ
Date newStart = new Date(previousEnd.getYear(),previousEnd.getMonth(),previousEnd.getDate()+1);
// After
Date newStart = nextDay(previousEnd);
private static Date nextDay(Date date){
return new Date(date.getYear(),date.getMonth(),date.getDate()+1);
}
Java
๋ณต์ฌ
7. Introduce Local Extension
โข
์ถํ ๋ค์ ํ์ต
๋ค. SIMPLIFYING CONDITIONAL EXPRESSIONS
1. Decompose Conditional
if (date.before (SUMMER_START) || date.after(SUMMER_END))
charge = quantity * _winterRate + _winterServiceCharge;
else charge = quantity * _summerRate;
// After
if (notSummer(date))
charge = winterCharge(quantity);
else charge = summerCharge (quantity);
Java
๋ณต์ฌ
2. Consolidate Conditional Expression
double disabilityAmount() {
if (_seniority < 2) return 0;
if (_monthsDisabled > 12) return 0;
if (_isPartTime) return 0;
// after
double disabilityAmount() {
if (isNotEligableForDisability()) return 0;
Java
๋ณต์ฌ
3. Consolidate Duplicate Conditional Fragments
if (isSpecialDeal()) {
total = price * 0.95;
send();
}
else {
total = price * 0.98;
send();
}
// after
if (isSpecialDeal()) {
total = price * 0.95;
}
else {
total = price * 0.98;
}
send();
Java
๋ณต์ฌ
4. Remove Control Flag
โข
control flag ๋์ break๋ continue ์ฌ์ฉ์ ๊ถ์ฅ
โ ์กฐ๊ฑด๋ฌธ์ ๋ชฉ์ ์ฑ์ ๋ณด๋ค ๋ช
ํํ๊ฒ ํ๋ฏ๋ก
void checkSecurity(String[] people) {
boolean found = false;
for (int i = 0; i < people.length; i++) {
if (! found) {
if (people[i].equals ("Don")){
sendAlert();
found = true;
}
if (people[i].equals ("John")){
sendAlert();
found = true;
}
}
}
}
// after
void checkSecurity(String[] people) {
for (int i = 0; i < people.length; i++) {
if (people[i].equals ("Don")){
sendAlert();
break; // or return
}
if (people[i].equals ("John")){
sendAlert();
break; // or return
}
}
}
Java
๋ณต์ฌ
5. Replace Nested Conditional with Guard Clauses
double getPayAmount() {
double result;
if (_isDead) result = deadAmount();
else {
if (_isSeparated) result = separatedAmount();
else {
if (_isRetired) result = retiredAmount();
else result = normalPayAmount();
};
}
return result;
};
// after
double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
};
Java
๋ณต์ฌ
๋ผ. ์์ฝ
๋ฆฌํฉํฐ๋ง์ด๋ ์ฝ๋์ ๊ฒฐ๊ณผ๋ ๋์ผํ์ง๋ง ๋ด๋ถ์ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ์ํํธ์จ์ด๋ฅผ ์์ ํ๋ ๊ฒ์ด๋ค.
๋ฆฌํฉํฐ๋ง์ ๋ชฉ์ ์ ์ฝ๋ ์์ฑ ํ์ ์ฒด๊ณ์ ์ผ๋ก ์ค๊ณ๋ ์ฝ๋๋ก ๋ฐ๊พธ๋ ๊ฒ์ด๋ค.
1) ์์ ์ดํด
โข
Movie class: ๋จ์ํ ๋ฐ์ดํฐ ํด๋์ค
โข
Rental class: ๊ณ ๊ฐ์ด ์ํ๋ฅผ ๋น๋ฆฌ๋ ํ์์ ํํ
โข
Customer class: ์์ ์ ๋ฐฉ๋ฌธํ ๊ณ ๊ฐ์ ํํํ๋ฉฐ ๊ณ ๊ฐ์ ๋ฐ์ดํฐ ์์ธ์(๋ค๋ฅธ ํด๋์ค ์ ๊ทผํจ์)๋ฅผ ํฌํจํจ
2) ๋ฆฌํฉํฐ๋ง ์ ํ ์คํธ ์ฝ๋ ์์ฑ
โข
Refactoring ์์ ์ , ๋ฐ๋์ ์ผ๋ จ์ ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํจ
โข
ํ
์คํธ ์ฝ๋๋ self-checking ๊ธฐ๋ฅ์ ํฌํจํ์ฌ ์ถํ ํ
์คํธ ์์
์ ๋ฐ๋ฅธ ์๊ฐ์ ์ต์ํ ํด์ผ ํจ
: self-chcking ๊ธฐ๋ฅ์ด๋ ๋ค์์ ๊ธฐ๋ฅ์ ํฌํจํจ. ๋ชจ๋ ํ
์คํธ ํต๊ณผ ์, okay ๋ฑ์ ํ์, ์๋ํ ๊ฒฐ๊ณผ์ ํ
์คํธ ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฅผ ์, ์ฐจ์ด์ ์ถ๋ ฅ ๋ฑ
3) ๋ฉ์๋ ์ถ์ถ
โข
๋ ์์ ์กฐ๊ฐ์ ์ฝ๋๋ก ์์ฑํ๋ ๊ฒ์ ์ฝ๋์ ์๋ฏธ๋ฅผ ๋์ฑ ๋ช
ํํ ํ์ฌ ํ์
์ ์์ฐ์ฑ์ด ์ฆ๊ฐํจ
โข
Decomposing์ด๋ ์์
๋์์ ์ฐพ์์ ๊ธฐ์กด ์ฝ๋์์ ์ถ์ถํ๋ ๊ฒ์ด๋ค. ๋
ผ๋ฆฌ์ ์ผ๋ก ๋ญ์ณ์ง ์ฝ๋ ๊ทธ๋ฃน์ด 1์ฐจ ํ๊ฒ์ด๋ค. ์ฃผ๋ก ๋ฐ๋ณต๋ฌธ์ด๋ switch๋ฌธ ๋ฑ์ ํฐ ๋
ผ๋ฆฌ์ ์งํฉ(logical clump)
โข
Decomposing ํ ๋, ์ฃผ์ํ ์ ์ผ๋ก ์๋์ ์ธ ๊ฐ์ง๊ฐ ์๋ค.
โข
๊ธฐ์กด ์ฝ๋์์ Decomposingํ์ฌ ์๋ก์ด ๋ฉ์๋๋ก ๋ถ๋ฆฌ ์, ๋ถ๋ฆฌ๋ ๋ฉ์๋ ๋ด์์ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ ๋ณ์์ ๋ณ๊ฒฝ๋์ง ์๋ ๋ณ์์ ๋๋์ด ๊ด๋ฆฌํด์ผ ํ๋ค. ํนํ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ ๋ณ์๋ ์ฃผ์๊ฐ ํ์ํจ. ๊ฐ์ด ๋ณ๊ฒฝ๋๋ ๋ณ์๋ ๋ถ๋ฆฌ๋ ๋ฉ์๋์์ ์๋ก์ด ๋ณ์๋ก ์์ฑํ์ฌ ๋ฉ์๋์ ๋ฐํ๊ฐ์ผ๋ก ์ฌ์ฉ
๋ค. ๊ฐ์ด ๋ณ๊ฒฝ๋์ง ์์ ๊ฒฝ์ฐ, ๋งค๊ฐ๋ณ์๋ก ๋ฐ์์ ์ฌ์ฉ
โข
๋ฐ๋์ ํ
์คํธํ์ฌ ์ด์์ ๋ฌด ํ์ธํ ๊ฒ(์ธ๊ฐ์ ์ค์ ๊ฑธ๋ฌ๋ด๊ธฐ)
โข
์๋กญ๊ฒ ์์ฑํ ๋ฉ์๋์ ๋ง๊ฒ ๋ณ์๋ช
์ ๋ณ๊ฒฝํ ๊ฒ
4) ๋ฉ์๋ ์ด์
โข
์ผ๋ฐ์ ์ผ๋ก ๋ฉ์๋๋ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๋ ๊ฐ์ฒด ๋ด ์กด์ฌํ๋ ๊ฒ์ด ๊น๋ํจ
: ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๋ ํด๋์ค ๋ด ๋ฉ์๋๊ฐ ์์ฑ๋๋ฉด ๊ฐ์ ๋ฐ์ดํฐ์ ๋ํด ๋งค๊ฐ๋ณ์๋ก ์ ๋ฌ ๋ฐ์ง ์์๋ ๋จ. ๋ณ์ ์ฌ์ฉ์ ์ต์ํํ ์ ์์.
โข
new method๋ฅผ ์์ฑํ๊ณ old method๋ฅผ ๋ฐ๋ก ์ ๊ฑฐํ๊ธฐ ๋ณด๋ค old method๊ฐ ์ฌ์ฉ๋ software interface ๋ด์์ new method๋ฅผ ํ
์คํธํด์ผ ํ๋ค.
โข
ํ
์คํธํ ํ ์ด์ ์์ ์, ๊ธฐ์กด ๋ฉ์๋(amountFor)๊ฐ ์ฌ์ฉ๋ ์ฝ๋์์ ์ด๋ฅผ ์ ๊ฑฐํ๊ณ new method๋ก ๋ฐ๊ฟ
โข
์ผ์์ ์ผ๋ก ์ฌ์ฉํ๋ ๋ณ์๋ฅผ ์ต์ํํ์ฌ ์ฌ์ฉํด์ผ ํจ
: ์ผ์์ ์ผ๋ก ์ฌ์ฉํ๋ ๋ณ์๊ฐ ์ด๊ณณ์ ๊ณณ์์ ๋ง์ด ์ฌ์ฉ๋๋ฉด ๊ฐ๊ฐ์ ๋ณ์์ ์ฌ์ฉ์ฒ๋ฅผ ๋์น ์ ์์ผ๋ฏ๋ก ๋์ค์ ํฐ ๋ฌธ์ ๋ก ์ด์ด์ง ์ ์์
: ์ด๋ฌํ ์์๋ณ์๋ ๊ธธ๊ฒ ์์ฑ๋ ๋ฉ์๋์ ๋ง์ด ์์
: ๋ณ์๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋ฉ์๋ ํธ์ถ์ ๋ ๋ฒ ์ด์ ํ๋ ๊ฒ์ ๊ณ์ฐ์ ์ค๋ณต ์คํํ๋ ๊ฒ์ด๋ฏ๋ก ์ฑ๋ฅ์ ์ํฅ์ ์ค ์ ์์. ์ด ๋ฌธ์ ์ ๋ํด 'Refactoring and Performance' ๋ถ๋ถ์์ ๋ค๋ฃธ
// Old method
class Customer...
private double amountFor(Rental aRental) {
double result = 0;
switch (aRental.getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (aRental.getDaysRented() > 2)
result += (aRental.getDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
result += aRental.getDaysRented() * 3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (aRental.getDaysRented() > 3)
result += (aRental.getDaysRented() - 3) * 1.5;
break;
}
return result;
}
Java
๋ณต์ฌ
// New method
class Customer...
private double amountFor(Rental aRental) {
return aRental.getCharge();
}
class Rental...
double getCharge() {
double result = 0;
switch (getMovie().getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (getDaysRented() > 2)
result += (getDaysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
result += getDaysRented() * 3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (getDaysRented() > 3)
result += (getDaysRented() - 3) * 1.5;
break;
}
return result;
}
Java
๋ณต์ฌ
5) ๋ฉ์๋ ์ถ์ถ
โข
frequentRenterPoints๋ฅผ ๊ณ์ฐํ๋ ๋ก์ง์ ๋ถ๋ฆฌํ ์ ์์ต๋๋ค.
โข
frequentRenterPoints ๊ณ์ฐ ์ ์ฌ์ฉํ๋ ๋ชจ๋ ๋ฉ์๋๊ฐ Rental class์ ์์ผ๋ฏ๋ก ๊ฐ์ฒด๋ฅผ ์ด๋ํ์ฌ ๋ฉ์๋ ์์ฑํฉ๋๋ค.
โข
frequentRenterPoints์ ์ ์ธ๋ถ๊ฐ while ๋ฐ๋ณต๋ฌธ ์์ชฝ์ ์กด์ฌํจ์ผ๋ก, ์๋ก์ด ๋ฉ์๋ ๋ด๋ถ์๋ frequentRenterPoints ๋ณ์์ ์ ๊ทผํ๋ ๋ก์ง์ ์ฌ์ฉํ ์ ์์
// Old method
class Customer...
int frequentRenterPoints = 0;
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
// add frequent renter points
frequentRenterPoints++;
// add bonus for a two day new release rental
if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1)
frequentRenterPoints++;
// show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
totalAmount += each.getCharge();
}
Java
๋ณต์ฌ
// New method
class Rental...
int getFrequentRenterPoints() {
if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1)
return 2;
else
return 1;
}
class Customer...
int frequentRenterPoints = 0;
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
// add frequent renter points
frequentRenterPoints += each.getFrequentRenterPoints();
// show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
totalAmount += each.getCharge();
}
Java
๋ณต์ฌ
6) ์์๋ณ์ ์ ๊ฑฐ
โข
frequentRenterPoints์ totalAmount ๋ณ์๋ฅผ ์ ๊ฑฐํ๊ณ ์๋ก์ด ๋ฉ์๋๋ฅผ ์์ฑํฉ๋๋ค.
โข
์ผ์์ ์ธ ๋ณ์๋ฅผ ์ ๊ฑฐํจ์ผ๋ก์จ ๊ธฐ์กด ๋ฉ์๋์ ์ฌํ์ฉ์ฑ์ด ํฅ์๋ฉ๋๋ค.
private double getTotalCharge() {
double result = 0;
Enumeration<Rental> rentals = _rentals.elements();
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
result += each.getCharge();
}
return result;
}
private int getTotalFrequentRenterPoints() {
int result = 0;
Enumeration<Rental> rentals = _rentals.elements();
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
result += each.getFrequentRenterPoints();
}
return result;
}
public String statement() {
Enumeration<Rental> rentals = _rentals.elements();
String result = "Rental Record for " + getName() + "\n";
while (rentals.hasMoreElements()) {
Rental each = (Rental) rentals.nextElement();
// show figures for this rental
result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
}
// add footer lines
result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points";
return result;
}
Java
๋ณต์ฌ
7) ๋ถ๊ธฐ๋ฌธ์ ๋คํ์ฑ ๊ธฐ๋ฐ์ ๊ฐ์ฒด๋ก ์ด์
โข
์กฐ๊ฑด๋ฌธ ๋ด์์ ์ฌ์ฉ๋๋ ๋ฐ์ดํฐ์ ๊ฒฝ์ฐ, ์ผ๋ฐ์ ์ผ๋ก ๊ฐ์ ๊ฐ์ฒด ๋ด ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
: ๊ฐ์ ๊ฐ์ฒด ๋ด ๋ฐ์ดํฐ์ ๋ฉ์๋๊ฐ ๊ณต์กดํ๋ฉด, ๋ฐ์ดํฐ์ ์์ ์ฌํญ ๋ฐ์ ์ ํด๋น ๋ฉ์๋์ ์ฝ๊ฒ ๋ฐ์ํ ์ ์์ต๋๋ค.
: getCharge ๋ฉ์๋์ ๊ฒฝ์ฐ Movie ํด๋์ค ๋ด ๋ฐ์ดํฐ๋ฅผ ์ฃผ๋ก ๋ค๋ฃจ๋ฏ๋ก, Movie ํด๋์ค๋ก ์ด๋ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
: ๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ฅผ ์ ํํ ๋, ๋ณ๋ ๊ฐ๋ฅ์ฑ์ด ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ ํํ์ฌ ์ถํ ์์ ์ ํธ๋ฆฌํ ์ ์๋๋ก ํฉ๋๋ค. Type information์ ๊ฒฝ์ฐ ๋ณ๋์ฑ์ด ํฌ๋ฏ๋ก, ๋ฉ์๋ ๋ด ๋ฐ์ดํฐ๋ก ๋จ๊ธฐ๋ ๊ฒ์ด ํฅํ ๊ด๋ฆฌ๋ฉด์์ ํจ์จ์ ์
๋๋ค.
// Old Methods
Class Rental...
double getCharge() {
return _movie.getCharge(_daysRented);
}
int getFrequentRenterPoints() {
return _movie.getFrequentRenterPoints(_daysRented);
}
class movie...
Java
๋ณต์ฌ
// New Methods
class Movie...
double getCharge(int daysRented) {
double result = 0;
switch (getPriceCode()) {
case Movie.REGULAR:
result += 2;
if (daysRented > 2)
result += (daysRented - 2) * 1.5;
break;
case Movie.NEW_RELEASE:
result += daysRented * 3;
break;
case Movie.CHILDRENS:
result += 1.5;
if (daysRented > 3)
result += (daysRented - 3) * 1.5;
break;
}
return result;
}
int getFrequentRenterPoints(int daysRented) {
if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1)
return 2;
else
return 1;
}
Java
๋ณต์ฌ
โข
๋ฉ์๋ ์ด๋ ์, ๊ธฐ์กด ๋ฉ์๋๋ฅผ ์์ ํ ์ญ์ ํ์ง ์์์ผ๋ก์จ ํ์
๋ฐ ์ธํฐํ์ด์ค ํ๊ฒฝ์ ์ ์งํฉ๋๋ค.
: ๊ฐ์ ๊ฐ์ฒด ๋ด ๋ฐ์ดํฐ์ ๋ฉ์๋๊ฐ ๊ณต์กดํ๋ฉด, ๋ฐ์ดํฐ์ ์์ ์ฌํญ ๋ฐ์ ์ ํด๋น ๋ฉ์๋์ ์ฝ๊ฒ ๋ฐ์ ๊ฐ๋ฅ
: getCharge method์ ๊ฒฝ์ฐ Movie class ๋ด ๋ฐ์ดํฐ๋ฅผ ์ฃผ๋ก ๋ค๋ฃจ๋ฏ๋ก Movie class๋ก ์ด๋ํ๋ ๊ฒ์ด ์ข์
: ๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ฅผ ์ ํํ ๋, ๋ณ๋ ๊ฐ๋ฅ์ฑ์ด ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ ํํ์ฌ ์ถํ ์์ ์ ํธ๋ฆฌํ ์ ์๋๋ก ํจ. Type information์ ๊ฒฝ์ฐ ๋ณ๋์ฑ์ด ํฌ๋ฏ๋ก, ๋ฉ์๋ ๋ด ๋ฐ์ดํฐ๋ก ๋จ๊ธฐ๋ ๊ฒ์ด ํฅํ ๊ด๋ฆฌ๋ฉด์์ ํจ์จ์
8) ์์ ๊ตฌ์กฐ ๊ธฐ๋ฐ์ ํด๋์ค ์ธ๋ถํ
โข
switch๋ฌธ์ ๋ถํ ํ๋ ๋ฐฉ๋ฒ์ ๊ฐ Movie type์ ๋ํด Movie๋ฅผ ์์ ๋ฐ๋ ํ์ ํด๋์ค๋ก ์ธ๋ถํํ๋ ๊ฒ์
๋๋ค.
โข
ํ์ง๋ง ๋ฌธ์ ์ ์ Movie ํด๋์ค์ price ๊ด๋ จ ๋ฉ์๋๋ค์ ๋ชจ๋ ํ์ ํด๋์ค์ ๋ถ๋ชจ ํด๋์ค(Movie)๊ฐ ๊ฐ์ ๊ด๋ฆฌํ๋ค๋ณด๋ฉด ์ถํ ์ ์ง๋ณด์์ ํฐ ์ด๋ ค์์ ๊ฒช์ ์ ์๋ค๋ ์ ์
๋๋ค.
: price์ ๊ด๋ จ๋ class๋ฅผ ์ถ๊ฐ ์์ฑํ์ฌ Movie ๊ด๋ จ ํด๋์ค๋ค์ด price class์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋๋ก ํจ
โข
switch๋ฌธ์ ๋ํด Movie์ ์์ ํด๋์ค๋ก ๋ณํํ๋ ๊ณผ์ ์ ์๋ ์ธ ๋จ๊ณ๋ฅผ ๊ฑฐ์นฉ๋๋ค.
1๋จ๊ณ: Replace Type Code with State/Strategy
โ Price class๋ฅผ abstract class๋ก ์์ฑ ๋ฐ ์ดํ priceCode๋ณ ์์ class ์์ฑ
โ ๋ชจ๋ ์์ ํด๋์ค์ getPriceCode ๋ฉ์๋ ์์ฑ
โ Movie์ Price class ๊ฐ ์ ์ ๋ฉ์๋ ์์ฑ(setPriceCode)
2๋จ๊ณ: Move Method
โ getCharge ๋ฉ์๋๋ฅผ Movie ํด๋์ค์์ Price ํด๋์ค๋ก ์ด๋
3๋จ๊ณ: Replace Conditional with Polymorphism
โ getCharge, getFrequentRenterPoints ๋ ๋ฉ์๋์ ๋ํด ๋คํ์ฑ์ ํ์ฉํ์ฌ Price class ๋ด ์์ ํด๋์ค์ ํ์ ๋ฉ์๋๋ก ๋ถ๋ฆฌ
9) ๋ฆฌํฉํฐ๋ง ๊ฒฐ๊ณผ ๋น๊ต
โข
์ฒซ๋ฒ์งธ ๋ค์ด์ด๊ทธ๋จ๊ณผ ๋น๊ตํด๋ณด๋ฉด ์๋น๋ถ๋ถ์์ ์ฐจ์ด ํ์ธ
โข
โข
Before
โข
After