반응형
아이템 26. 함수 내부의 추상화 레벨을 통일하라
p. 160
추상화 레벨 통일 원칙 (Single Level of Abstraction, SLA)
SLA 원칙 : 함수나 메소드가 동일한 수준의 추상화를 유지해야 한다는 원칙
SLA 원칙 위반 코드 예시
fun processData() {
val data = fetchDataFromApi() // 추상화된 단계
val results = data["results"] as List<Map<String, String>> // 추상화된 단계
val connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password") // 세부 사항
val statement = connection.prepareStatement("INSERT INTO table (column1, column2) VALUES (?, ?)")
for (item in results) {
statement.setString(1, item["field1"])
statement.setString(2, item["field2"])
statement.executeUpdate() // 세부 사항
}
connection.close() // 세부 사항
}
위 예제에서는 processData
함수가 데이터를 가져오는 추상화된 작업과 데이터베이스 연결 및 데이터 삽입과 같은 세부 사항을 모두 포함하고 있다.
올바른 예시
fun processData() {
val data = fetchDataFromApi()
val results = extractResults(data)
saveDataToDatabase(results)
}
fun fetchDataFromApi(): Map<String, Any> {
// 데이터를 API에서 가져오는 추상화된 단계
return mapOf("results" to listOf(
mapOf("field1" to "value1", "field2" to "value2"),
mapOf("field1" to "value3", "field2" to "value4")
))
}
fun extractResults(data: Map<String, Any>): List<Map<String, String>> {
// 데이터를 추출하는 추상화된 단계
return data["results"] as List<Map<String, String>>
}
fun saveDataToDatabase(data: List<Map<String, String>>) {
// 데이터를 데이터베이스에 저장하는 세부 사항
val connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password")
val statement = connection.prepareStatement("INSERT INTO table (column1, column2) VALUES (?, ?)")
for (item in data) {
statement.setString(1, item["field1"])
statement.setString(2, item["field2"])
statement.executeUpdate()
}
connection.close()
}
위 수정된 예제에서는 processData
함수가 동일한 수준의 추상화된 단계만 포함하고 있다:
- 데이터를 가져오는 추상화된 단계 (fetchDataFromApi)
- 데이터를 추출하는 추상화된 단계 (extractResults)
- 데이터를 저장하는 추상화된 단계 (saveDataToDatabase)
각 함수는 자신만의 세부 사항과 추상화 수준을 가지므로, 코드가 더 명확하고 유지보수하기 쉬워진다.
아이템 27. 변화로부터 코드를 보호하려면 추상화를 사용하라
생략
아이템 28. API 안정성을 확인하라
생략
아이템 29. 외부 API를 랩(wrap)해서 사용하라
생략
아이템 30. 요소의 가시성을 최소화하라
생략
아이템 31. 문서로 규약을 정의하라
p.198
리스코프 치환 원칙
리스코프 치환 원칙 : "S가 T의 서브타입이라면, 별도의 변경 없이 T 타입 객체를 S 타입 객체로 대체할 수 있어야 한다"
리스코프 치환 원칙 위반 코드 예시
open class Bird {
open fun fly() {
println("Bird is flying")
}
}
class Ostrich : Bird() {
override fun fly() {
// 타조는 날 수 없기 때문에 UnsupportedOperationException을 던짐
throw UnsupportedOperationException("Ostrich can't fly")
}
}
fun letBirdFly(bird: Bird) {
bird.fly()
}
fun main() {
val bird: Bird = Bird()
letBirdFly(bird) // 정상 작동: "Bird is flying"
val ostrich: Bird = Ostrich()
letBirdFly(ostrich) // 예외 발생: UnsupportedOperationException
}
Ostrich
는 Bird
의 서브타입이지만 Bird
처럼 동작하지 않는다.
올바른 예시 코드
open class Bird {
open fun move() {
println("Bird is moving")
}
}
class Sparrow : Bird() {
override fun move() {
println("Sparrow is flying")
}
}
class Ostrich : Bird() {
override fun move() {
println("Ostrich is running")
}
}
fun letBirdMove(bird: Bird) {
bird.move()
}
fun main() {
val sparrow: Bird = Sparrow()
letBirdMove(sparrow) // 정상 작동: "Sparrow is flying"
val ostrich: Bird = Ostrich()
letBirdMove(ostrich) // 정상 작동: "Ostrich is running"
}
Sparrow
는 날고 Ostrich
는 달리지만, move
메서드를 호출하는 측에서는 이를 알 필요가 없다.
이렇게 하면 Bird
타입의 객체를 Sparrow
나 Ostrich
로 대체하더라도 프로그램이 정상적으로 작동한다.
아이템 32. 추상화 규약을 지켜라
p. 203
Any
클래스는 다음 세 가지 중요한 메서드를 제공한다
public open class Any {
public open operator fun equals(other: Any?): Boolean
public open fun hashCode(): Int
public open fun toString(): String
}
4장 전체
토론 포인트
- 팀 내에서 적절한 클래스와 함수의 추상화 수준을 논의해본 적이 있는가?
- 코드 리뷰 과정에서 추상화 수준을 평가하고 개선하기 위한 체계적인 방법이 있는가?
반응형
'독서' 카테고리의 다른 글
[이펙티브 코틀린] 6장. 클래스 설계 (0) | 2024.07.28 |
---|---|
[이펙티브 코틀린] 5장. 객체 생성 (1) | 2024.07.21 |
[이펙티브 코틀린] 3장. 재사용성 (0) | 2024.07.08 |
[이펙티브 코틀린] 2장. 가독성 (1) | 2024.06.30 |
[이펙티브 코틀린] 1장. 안정성 (0) | 2024.06.23 |