[Refactoring] 코드 리팩토링 with 코틀린

박상군·2022년 5월 18일
1

Software

목록 보기
1/10
post-thumbnail

기존의 것을 바꾸는 건 새로 만드는 것보다 힘들다.

소프트웨어 개발자라면 한번쯤은 들어봤을 리팩토링.. 이지만 사실 리팩토링을 안하도록 애초에 잘 만드는 게 중요하지만 막상 코딩을 해보면 소스가 지저분 해지는 건 모두 공감 하실거라 생각 합니다.

당장 몇 달전에 MVVM 연습한다고 간단하게 만든 앱도 지금 다시보니 X판이라..

가장 먼저 제가 생각하는 리팩토링이란

외부 동작을 바꾸지 않으면서 내부 구조를 개선하는 방법입니다.

그렇다면 리팩토링이 필요한 코드들은 어떤게 있을까 하고 살펴보면

1. 리팩토링이 필요한 코드란..?

  • 중복코드 : 같은 코드가 두 군데 이상 존재할 때, 서브 클래스에 같은 코드가 존재할 때
  • 장황한 메소드 : 메소드 안의 내용이 너무 길 때
  • 방대한 클래스 : 한 클래스 안에 기능과 변수가 지나치게 많을 때
  • 과대한 매개변수 : 메소드의 매개변수 길이, 개수가 지나치게 많을 때
  • 수정의 산발 : 하나의 클래스에 대해 잦은 변경이 발생하는 경우
  • 기능의 산재 : 변경이 발생할 때 마다 많은 클래스가 수정되는 경우
  • 직무유기 클래스 : 사용하지 않거나 비용대비 효율이 떨어지는 클래스
  • 막연한 범용코드 : 향후 필요할 것이라는 막연한 생각으로 미리 만들어 둔 코드
  • 임시필드 : 클래스안의 인스턴스 변수가 아주 특정한 상황에서만 사용됨, 대부분 실제 사용하지 않음
  • 지나친 관여 : 클래스간 관계가 지나치게 밀접함, 서로 너무 많이 관여할 때
  • 인터페이스가 다른 대용 클래스 : 기능은 동일한데 메소드 명이나 매개변수 구조가 다른 메소드
  • 방치된 상속물 : 상속받은 메소드나 데이터를 하위 클래스에서 사용하지 않음

위와 같이 많은 리팩토링 대상이 있는데 지금까지 구현한 많은 코드들이 위에 상황에 해당했다.

그렇다면 위와 같은 상황을 해결할 수 있는 리팩토링 기법은 뭐가 있을까..🤔

2. 리팩토링 주요 기법

  • Extract Method : 그룹으로 함께 묶을 수 있는 코드 조각이 있으면 코드의 목적이 잘 드러나도록 메소드의 이름을 지어 별도의 메소드로 추출
  • Move Method : 메소드가 자신이 정의된 클래스보다 다른 클래스의 기능을 더 많이 사용하고 있
    다면, 이 메소드를 가장 많이 사용하고 있는 클래스에 비슷한 몸체를 가진 새로운
    메소드 생성
  • Rename Method : 메소드의 이름이 그 목적을 드러내지 못하고 있다면 메소드의 이름 변경
  • Inline Method : 메소드 몸체가 메소드의 이름 만큼이나 명확할 때는 호출하는 곳에 메소드의 몸체를 넣고 메소드를 삭제
  • Extract Class : 두 개의 클래스가 해야 할 일을 하나의 클래스가 하고 있는 경우 새로운 클래스를 만들어 관련 있는 필드와 메소드를 기존 클래스에서 새로운 클래스로 이동
  • Replace Temp With Query : 수식의 결과값을 저장하기 위해서 임시 변수를 사용하고 있다면, 수식을 추출해서 메소드를 만들고, 임시 변수를 참조하는 곳을 찾아 모두 메소드 호출로 교체
  • Substitute Algorithm : 알고리즘을 보다 명확한 것으로 바꾸고 싶은 경우, 메소드의 몸체를 새로운 알고리즘으로 교체

위와 같은 리팩토링 기법들이 있는데 이름만 들어도 어떤 느낌일지 알 수 있습니다!
몇 가지 코드로 예를 들어보면

Extract Method

그룹으로 함께 묶을 수 있는 코드 조각이 있으면 코드의 목적이 잘 드러나도록 메소드의 이름을 지어 별도의 메소드로 추출하는 기법

fun print() {
    for (i in 0 until count) {
        for (j in 0..i) {
            print("*")
        }
        println("")
    }

    for (i in 0 until count) {
        for (j in 0..i) {
            print("-")
        }
        println("")
    }

    for (i in 0 until count) {
        for (j in 0..i) {
            print("*")
        }
        println("")
    }
}

위의 print()함수는 일반적인 별찍기와 중간에 하이픈를 찍는 코드인데 여기서 보면 별찍는 코드가 중복이 되는걸 볼 수 있습니다. 이걸 메소드 추출 기법을 사용하면

fun print(count: Int) {
    printStar(count)
    printHyphen(count)
    printStar(count)
}

private fun printStar(count: Int) {
    for (i in 0 until count) {
        for (j in 0..i) {
            print("*")
        }
        println("")
    }
}

private fun printHyphen(count: Int) {
    for (i in 0 until count) {
        for (j in 0..i) {
            print("-")
        }
        println("")
    }
}

위와 같이 리팩토링 전에는 세 가지 기능이 print()메소드안에 모두 들어있었지만 printStar(), printHyphen() 별도 메소드로 분리하였습니다.

Inline Method

메소드 몸체가 메소드의 이름 만큼이나 명확할 때는 호출하는 곳에 메소드의 몸체를 넣고 메소드를 삭제하는 기법

private fun isPass() = score > 80
private fun getPass() = if(isPass()) "Pass" else "Non-Pass"

위의 코드를 보면 score가 80 이상이면 Pass 아니면 Non-Pass를 리턴하도록 되어있는 코드입니다.
이 코드에 Inline Method기법을 사용해보면 아래와 같이 리팩토링할 수 있습니다.

private fun getPass() = if (score > 80) "Pass" else "Non-Pass"

Substitute Algorithm

알고리즘을 보다 명확한 것으로 바꾸고 싶은 경우, 메소드의 몸체를 새로운 알고리즘으로 교체하는 기법

fun checkAnimal(animalList: Array<String>):String {
    for(i in animalList) {
        return when (i){
            "Cat" -> "Cat"
            "Dog" -> "Dog"
            "Bird" -> "Bird"
            else -> ""
        }
    }
    return ""
}

위의 코드를 보면 animalList라는 동물의 리스트를 받아 Cat, Dog, Bird가 리스트에 포함되어 있으면 해당 동물 이름을 출력하는 메소드입니다.
이 코드에 Substitute Algorithm기법을 사용해서 리팩토링 해보면 아래와 같이 리팩토링 할 수 있습니다.

fun checkAnimal(animalList: Array<String>):String {
    val animals = arrayOf("Cat","Dog","Bird")
    for(animal in animalList) {
        return if(animals.contains(animal)) animal else ""
    }
    return ""
}

마무리

지금까지는 아키텍처 구조나 유지보수가 쉬운 코드 작성을 생각하지 않고 단순히 기능 구현만을 목표로 하여 코딩을 해왔던 것 같아 많은 깨달음을 얻게 되었습니다. 🙏

Reference

소프트웨어공학 - Refactoring
소프트웨어 구현_리팩토링

0개의 댓글