당장 몇 달전에 MVVM 연습한다고 간단하게 만든 앱도 지금 다시보니 X판이라..
외부 동작을 바꾸지 않으면서 내부 구조를 개선하는 방법입니다.
그렇다면 리팩토링이 필요한 코드들은 어떤게 있을까 하고 살펴보면
- 중복코드 : 같은 코드가 두 군데 이상 존재할 때, 서브 클래스에 같은 코드가 존재할 때
- 장황한 메소드 : 메소드 안의 내용이 너무 길 때
- 방대한 클래스 : 한 클래스 안에 기능과 변수가 지나치게 많을 때
- 과대한 매개변수 : 메소드의 매개변수 길이, 개수가 지나치게 많을 때
- 수정의 산발 : 하나의 클래스에 대해 잦은 변경이 발생하는 경우
- 기능의 산재 : 변경이 발생할 때 마다 많은 클래스가 수정되는 경우
- 직무유기 클래스 : 사용하지 않거나 비용대비 효율이 떨어지는 클래스
- 막연한 범용코드 : 향후 필요할 것이라는 막연한 생각으로 미리 만들어 둔 코드
- 임시필드 : 클래스안의 인스턴스 변수가 아주 특정한 상황에서만 사용됨, 대부분 실제 사용하지 않음
- 지나친 관여 : 클래스간 관계가 지나치게 밀접함, 서로 너무 많이 관여할 때
- 인터페이스가 다른 대용 클래스 : 기능은 동일한데 메소드 명이나 매개변수 구조가 다른 메소드
- 방치된 상속물 : 상속받은 메소드나 데이터를 하위 클래스에서 사용하지 않음
위와 같이 많은 리팩토링 대상이 있는데 지금까지 구현한 많은 코드들이 위에 상황에 해당했다.
그렇다면 위와 같은 상황을 해결할 수 있는 리팩토링 기법은 뭐가 있을까..🤔
- Extract Method : 그룹으로 함께 묶을 수 있는 코드 조각이 있으면 코드의 목적이 잘 드러나도록 메소드의 이름을 지어 별도의 메소드로 추출
- Move Method : 메소드가 자신이 정의된 클래스보다 다른 클래스의 기능을 더 많이 사용하고 있
다면, 이 메소드를 가장 많이 사용하고 있는 클래스에 비슷한 몸체를 가진 새로운
메소드 생성- Rename Method : 메소드의 이름이 그 목적을 드러내지 못하고 있다면 메소드의 이름 변경
- Inline Method : 메소드 몸체가 메소드의 이름 만큼이나 명확할 때는 호출하는 곳에 메소드의 몸체를 넣고 메소드를 삭제
- Extract Class : 두 개의 클래스가 해야 할 일을 하나의 클래스가 하고 있는 경우 새로운 클래스를 만들어 관련 있는 필드와 메소드를 기존 클래스에서 새로운 클래스로 이동
- Replace Temp With Query : 수식의 결과값을 저장하기 위해서 임시 변수를 사용하고 있다면, 수식을 추출해서 메소드를 만들고, 임시 변수를 참조하는 곳을 찾아 모두 메소드 호출로 교체
- Substitute Algorithm : 알고리즘을 보다 명확한 것으로 바꾸고 싶은 경우, 메소드의 몸체를 새로운 알고리즘으로 교체
위와 같은 리팩토링 기법들이 있는데 이름만 들어도 어떤 느낌일지 알 수 있습니다!
몇 가지 코드로 예를 들어보면
그룹으로 함께 묶을 수 있는 코드 조각이 있으면 코드의 목적이 잘 드러나도록 메소드의 이름을 지어 별도의 메소드로 추출하는 기법
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()
별도 메소드로 분리하였습니다.
메소드 몸체가 메소드의 이름 만큼이나 명확할 때는 호출하는 곳에 메소드의 몸체를 넣고 메소드를 삭제하는 기법
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"
알고리즘을 보다 명확한 것으로 바꾸고 싶은 경우, 메소드의 몸체를 새로운 알고리즘으로 교체하는 기법
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 ""
}