1. 보호구문 (Guard Clause)

  • 조건 충족하지 않으면 return 하는 방식
  • 남용 X (or 로 묶는 것 고려)

2. 안 쓰는 코드

  • 과감히 지우자
  • 형상관리 툴을 쓰는 이상 크게 걱정하지 않아도 됨
  • 지운 코드 복구 조건
    1) 많은 코드 (코드 양)
    2) 당장 사용하지 않음
    3) 미래에 사용하길 원함
    4) 기존과 동일한 방식
    5) 여전히 작동한다면
    바꿔도 괜찮음
  • 정리 과정에서 코드를 '조금만' 삭제할 것

3. 대칭으로 맞추기

  • 코드 작성 시 한 가지 패턴 사용 권장
  • 여러 패턴 섞여있으면 다른 로직으로 보임
  • 비슷한데 같지 않은 루틴 찾고, 그 다른 부분을 분리
  • 되도록이면 같은 프로젝트 내에서는 같은 형식 쓰는게 좋음 (기존 프로그램 일관성 지키자)

4. 새로운 인터페이스로 기존 루틴 부르기 (pass-through interface)

  • 어떤 루틴을 호출해야하는데, 기존 인터페이스때문에 복잡한 경우 사용
  • pass through interface로 설계된 경우, 변경에 용이
  • 활용 시나리오
    - 프록시 패턴
    - wrapping
    - 테스트
interface SomeService { 
	fun process(data: String): String
} 
class SomeServiceImpl: SomeService { 
	override fun process(data: String): String { 
		// 매우 복잡한 로직 ...
		return processedData
	} 
}

// wrapping
class WrappedSomeService(val someService: SomeService): SomeService {
	override fun process(data: String): String { 
		// do something before process...
		val ret = someService.process(data)
		// do something after process...
		return ret
	} 
}

// mocking
class MockSomeService: SomeService {
	override fun process(data: String): String { 
		return "MockedData"
	} 
}
// 

5. 읽는 순서

  • 읽기 좋은 순서로 작성 (정답은 없음)
    - ex) 함수가 호출되는 순서대로 내려가면서 배치
fun fun1(){
	fun2()
	fun3()
}
private fun fun2(){}
private fun fun3(){}
  • 추상화 레벨을 맞추는 것 고려

6. 응집도를 높이는 배치

  • 변경이 같이 되는 코드를 가까이 위치 (결합도가 있으면 같은 파일에)
  • 파일에 결합도가 높으면 같은 디렉토리에 위치
  • 결합도를 제거하는 것은 좋지만, 만능은 아님
  • 결합도 제거 시 고려사항
    - 결합도 제거비용, 변경비용을 따져봐야함
    - decoupling 비용 + 변경비용 < coupling 비용 + 변경비용 인 경우 결합도 제거
    - 당장 어떻게 해야될지 모르는 경우 변경 X
    - 시간적 여유 고려
    - 팀원과 같이 작업중이면 신중해야함

7. 선언과 초기화를 함께 옮기기

  • 같이 두면 보기 편함

8. 설명하는 변수

Point(  
	긴 표현식...,   
	다른 긴 표현식...  
)

vs

x = 긴 표현식...,   
y = 다른 긴 표현식...  
Point(x,y)

9. 설명하는 상수

  • 리터럴 상수로 사용된 곳 변경할 것
  • 최근에도 자주 지적받은 내용
object EntityFixture {  
    fun createProduct(  
        key: String = DefaultValues.DEFAULT_PRODUCT_KEY, // 기존: "test-key"
        name: String = DefaultValues.DEFAULT_PRODUCT_NAME, // 기존: "test-name"
        number: Long = DefaultValues.DEFAULT_PRODUCT_NUMBER, // 기존: 10
    ): Product {  
        return Product(key, name, number)  
    }
}

10. 명시적인 매개변수

  • 매개변수를 한 번에 뭉뚱그려서 주면, 어떤 데이터가 필요한지 명확하지 않은 경우 존재
  • 필요 없는 정보까지 주는 것은 비효율적
// CreatePostRequest.kt
data class CreatePostRequest(  
    val memberId: Long,
    val title: String,  
    val content: String,
    val someValue: String,
    val somdData: Data,
) {  
    fun toMemberKey(): Long { 
        return memberId  
    } 
    fun toPostContent(): PostContent { // 도메인 객체(또는 dto)
        return PostContent(  
            title = title,  
            content = content,  
        )  
    }  
}

// Controller.kt
@PostMapping  
fun createPost(@RequestBody request: PostRequest): ResponseEntity<Void> {  
    val postId = postService.createPost(request.toMemberKey(), request.toPostContent())  // 필요한 값만 전달
    ...
}
  • 매개변수를 명시적으로 드러나게 만드는 것이 좋음
fun calculateTotalPrice(item: Item){
	return item.price * item.count
}
vs
fun calculateTotalPrice(price: Int, count: Int){
	return price * count
}
  • 뒷 부분에서 명확하게 전달하는 방법도 좋음

11. 비슷한 코드끼리

  • 코드의 역할,동작이 구분될 때에는, 두 부분 사이에 빈 줄을 넣어 분리
print("\n====== vpc 생성 ======")  
vpc_id = create_vpc(ec2, '10.0.0.0/16')  
igw_id = create_and_attach_igw(ec2, vpc_id)  
allocation_id = create_eip(ec2)  
  
print("\n====== 서브넷 생성 (public, private) ======")  
public_subnet_id = create_subnet(ec2, vpc_id, '10.0.1.0/24', igw_id=igw_id)  
private_subnet_id = create_subnet(ec2, vpc_id, '10.0.2.0/24')  
  
print("\n====== EC2 생성 (2개) ======")  
ec2_instances = [  
    create_ec2_instance(ec2, public_subnet_id, allocation_id),  
    create_ec2_instance(ec2, private_subnet_id),  
]

12. 도우미(helper) 추출

  • 메서드 추출 리팩토링
  • 큰 루틴 안에서 몇 줄 변경하는 경우 -> 바꾸려는 부분을 따로 함수로 추출
fun helper(){
	...변경하고자 하는 코드...
}
fun function(){
	...그대로...
	...변경하고자 하는 코드... // <- 이 부분을 helper()로 대체
	...그대로...
}
  • 시간적 흐름을 명확히 표현
fun createAndRun(){
	create()
	run()
}
private fun create(){}
private fun run(){}
  • 하나의 메인 로직을 helper 함수들로 분리
fun processOrder(order: Order) {
    if (order.items.isEmpty()) {
        throw IllegalArgumentException("Empty order")
    }
	// ... 복잡한 로직 ...
    println("Processing order: $order")
	// ... 복잡한 로직 ...
	// ... 복잡한 로직 ...
    println("Order completed")
    // ... 복잡한 로직 ...
}

vs

fun processOrder(order: Order) {
    validateOrder(order)
    handleOrderProcessing(order)
    completeOrder()
}

fun validateOrder(order: Order) {
    if (order.items.isEmpty()) {
        throw IllegalArgumentException("Empty order")
    }
}

fun handleOrderProcessing(order: Order) {
	// ... 복잡한 로직 ...
    println("Processing order: $order")
    // ... 복잡한 로직 ...
}

fun completeOrder() {
    println("Order completed")
    // ... 복잡한 로직 ...
}

13. 하나의 더미

코드가 지나치게 많은 함수로 나뉘어져 있을 때는 코드를 하나의 더미(One Pile)처럼 느껴질 떄 까지 모아라. 그리고 다시 정리하라.

코드를 만드는 데, 가장 큰 비용이 들어가는 일은 작성이 아니라 읽고 이해하는데 드는 비용이다.

코드를 모음으로써 응집도를 높일 수 있지만, 실무적으로는 가독성을 증가시킬 수 있다.

메서드 분리의 목적은 가독성의 증가도 있다. 명확성을 찾으려면 코드를 모으는 것을 고려해보아라.

아래의 증상이 느껴진다면 코드를 모으는 것을 검토해보아라.

  • 길고 반복되는 인자 목록
  • 반복되는 코드, 그 중에서도 반복되는 조건문
  • 도우미에 대한 부적절한 이름
  • 공유되어 변경에 노출된 데이터 구조

"작은 함수 속에 코드를 이해하고자 갖은 애를 쓰다가 문득, 자신의 능력을 의심하는 데까지 이르기도 한다. 하지만, 이때 코드를 한곳으로 모으면 모든것이 이해되기 시작한다."

14. 설명하는 주석

  • 명확하지 않은 경우 주석
  • 코드의 결함에 주석

15. 불필요한 주석 지우기

  • 코드 자체로 내용을 이해할 수 있다면 주석 불필요
  • 주석이 실제 내용과 안 맞으면 삭제
profile
코딩연습

0개의 댓글

Powered by GraphCDN, the GraphQL CDN