IntelliJ IDEA
IntelliJ IDEA – the Leading Java and Kotlin IDE, by JetBrains
IntelliJ IDEA에서의 일상적 리팩토링
버그 수정, 가독성 향상, 새로운 기능 추가와 같은 여러 가지 이유로 작업 중인 코드를 변경하는 경우가 종종 있습니다. 이 블로그에서는 일상적인 리팩토링을 다룹니다.
이 블로그 글에서는 동영상과 동일한 내용을 다룹니다. 블로그 게시물은 보는 것보다는 읽는 것을 선호하는 분들이 내용을 빠르게 훑어보고, 독자/시청자에게 코드 샘플과 추가 정보에 대한 링크를 제공하는 간편한 방법이라고 생각합니다.
이 블로그에 사용된 코드는 여기에서 확인할 수 있습니다.
요소 이름 바꾸기
클래스 파일에서 직접 이름을 변경하여 수동으로 이름을 바꾸는 것은 안전하지 않습니다. 예를 들어, 에디터에서 Bug 클래스의 이름을 ABug로 변경하면 이 클래스를 사용하는 다른 모든 코드를 업데이트하지 못할 수 있습니다. IntelliJ IDEA 2020.2부터는 에디터에 “관련 문제” 오류 메시지가 표시됩니다.
제자리에서 적용 가능한 새로운 이름 변경 리팩토링(in-place rename refactoring)이 여백에서 강조 표시됩니다. 여백에서 R 아이콘을 클릭하면 Bug의 모든 사용 위치 이름이 ABug로 변경되는 것을 미리 볼 수 있습니다.
또한 IntelliJ IDEA는 유사한 이름을 가진 변수를 찾아서 사용자에게 업데이트할 변수를 선택할 수 있게 합니다.
또는, 에디터에서 클래스 이름을 변경할 때 단축키 ⌥⏎ 또는 Alt+Enter를 사용하여 이 단계를 반복할 수 있습니다.
또한 macOS에서 ⇧F6 또는 Windows/Linux에서 Shift+F6을 사용하여 이름 변경 리팩토링을 호출할 수 있으며 IntelliJ IDEA는 클래스, 메소드 및 변수에 대한 대체 이름을 제안합니다. 사용자가 새 이름을 선택하면 IntelliJ IDEA가 안전하게 리팩토링을 완료합니다.
public class Bug { private long id; private String description; public Bug(long id, String description) { this.id = id; this.description = description; } public long getId() { return id; } public String getDescription() { return description; } }
예를 들어, 필드 description
의 이름을 desc
로 변경하면 IntelliJ IDEA는 이 필드가 getter 및 setter 메소드에서 사용되는지 여부를 감지하고 사용자에게 업데이트할 것인지 묻습니다. 또한 메소드 매개변수 이름의 사용 위치도 업데이트합니다.
getItems
메소드를 정의하는 View
라는 인터페이스로 작업해 보겠습니다. macOS에서 ⌘B를 사용하거나 Windows/Linux에서 Ctrl+B를 사용하여 사용 위치를 탐색할 수 있습니다. 이 인터페이스는 ViewImpl
클래스에 의해 구현됩니다.
View
라는 인터페이스에서 macOS의 경우 ⇧F6, Windows/Linux의 경우 Shift+F6을 사용하면 getItems
메소드의 이름이 getItemsList
로 변경됩니다. Enter 키를 누르면 ViewImpl
클래스에서 구현이 업데이트됩니다.
또한 macOS의 경우 ⇧F6 또는 Windows/Linux의 경우 Shift+F6를 사용하면, 프로젝트 창에 액세스하고, 클래스, 패키지, 파일 또는 디렉토리의 이름을 변경할 수도 있습니다.
시그니처 변경
클래스 또는 메소드의 시그니처를 안전하게 변경하는 방법을 살펴보겠습니다. BugReport
의 binaryStrings
메소드는 하나의 문자열 인수를 허용합니다.
public boolean binaryStrings(String b) { Stack<Character> s = new Stack<>(); for (char c : b.toCharArray()) if (s.empty() || !s.peek().equals(c)) s.push(c); else s.pop(); return s.empty(); }
예를 들어, int 유형의 카운터와 같이 binaryStrings
메소드에 매개변수를 추가할 수 있습니다. 다시 말하지만 이것은 제자리에서 적용 가능한 리팩토링이며 여백 영역에서 R 아이콘으로 강조 표시됩니다. 여백에 있는 아이콘을 클릭하고 메소드 시그니처를 업데이트하여 메소드 호출자가 디폴트값을 사용하도록 매개변수에 기본값을 추가할 수 있습니다.
또한 macOS에서 ⌥⏎, 또는 Windows/Linux에서 Alt+Enter를 사용하여 메소드 시그니처를 업데이트하고 디폴트 매개변수 값을 추가할 수 있습니다.
또한 macOS에서 ⌘F6, 또는 Windows/Linux에서 Ctrl+F6을 사용하여 시그니처 변경 대화 상자를 호출해서 메소드 시그니처를 변경할 수 있습니다. 디폴트 값이 “true”인 부울 매개변수 상태
를 추가해 보겠습니다. 이 대화 상자를 사용하여 매개변수의 순서를 변경할 수도 있습니다. 또한 오버로드를 통해 위임을 선택할 수 있는 옵션도 있습니다. 리팩토링하면 메소드가 오버로드됩니다.
public class MyMap { int count = 0; public int getCount() { return count; } public void setCount(int Count) { this.count = count; } public static void main(String[] args) { MyMap myMap = new MyMap(); System.out.println(myMap.getCount()); } }
이 MyMap
클래스를 사용하여 클래스 시그니처 변경이 어떤 영향을 미치는지 확인할 수 있습니다. macOS에서 ⌘F6, 또는 Windows/Linux에서 Ctrl+F6을 사용하여 클래스 시그니처를 수정할 수 있습니다. 여기에 두 개의 제네릭 매개변수를 추가해 보겠습니다. 하나는 디폴트 값이 Integer
인 K
이고 다른 하나는 디폴트 값이 String
인 V
입니다. 클래스를 리팩토링하면 MyMap
의 사용 위치가 변경됩니다.
메소드 추출
메소드 추출 리팩토링은 메소드를 더 짧고 읽기 쉽게 만들 수 있습니다.
public boolean binaryStrings(String b) { Stack<Character> s = new Stack<>(); for (char c : b.toCharArray()) if (s.empty() || !s.peek().equals(c)) s.push(c); else s.pop(); return s.empty(); }
그룹화할 수 있는 binaryStrings
메소드에서 코드를 선택해 보겠습니다. macOS에서는 ⌥⌘M, Windows/Linux에서는Ctrl+Alt+M 단축키를 사용하여 이 코드를 다른 메소드로 추출할 수 있습니다.
이 블록에서 사용하는 지역 변수 또는 메소드 매개변수가 여기에 매개변수로 전달됩니다. 메소드 이름(예: manipulateStack)을 입력하면 추출한 코드 블록이 메소드 호출로 대체됩니다.
또한 코드를 선택한 다음, macOS에서는 ⌥⏎, Windows/Linux에서는 Alt+Enter를 사용하여 메소드 추출을 선택하면 메소드를 추출할 수도 있습니다.
메소드 인라인화
메소드 인라인화 리팩토링은 메소드 추출 리팩토링의 반대입니다. macOS에서는 단축키 ⌥⌘N, Windows/Linux에서는 Ctrl+Alt+N을 사용하여 방금 생성한 manipulateStack
메소드를 인라인화할 수 있습니다. 메소드 내용을 인라인화하고 메소드를 삭제하거나 유지할 수 있습니다. 이번 예제에서는 메소드를 삭제했습니다.
그렇다면 메소드를 인라인화해야 하는 이유는 무엇일까요? 예를 들어, 불필요한 리디렉션을 제거하는 데 사용하거나 잘못 리팩토링된 메소드를 인라인화하고 다시 추출해야 할 수도 있습니다.
상수 삽입
코드베이스에서 리터럴 값보다 상수를 사용하는 것이 항상 권장되므로 코드를 리팩토링하여 상수를 삽입하는 방법을 살펴보겠습니다.
public static boolean beautifulBinaryStrings(String s) { while (!s.equals(s = s.replaceAll("AA|BB", ""))) { System.out.println(s); } return s.isEmpty(); }
여기서 beautifulBinaryStrings
메소드는 문자열 리터럴 값을 사용합니다. macOS에서는 단축키 ⌥⌘C, Windows/Linux에서는 Ctrl+Alt+C를 사용하여 이 값을 상수로 추출할 수 있습니다.
IntelliJ IDEA는 리터럴 값을 기반으로 상수 이름을 제안합니다. 첫 번째 권장 사항을 선택하겠습니다. 필요한 경우 이 상수를 다른 클래스로 이동할 수도 있습니다.
필드 삽입
이 리팩토링으로 필드를 삽입 또는 추출하고 초기화할 수 있습니다.
ViewImpl
클래스의 getItemsList
메소드는 상수 0
과 4
를 사용하여 목록의 하위 집합을 가져옵니다.
public class ViewImpl implements View { @Override public List<Integer> getItemsList() { return List.of(1, 2, 3, 4, 5).subList(0,4); } }
이를 상수로 정의하는 대신 macOS에서 ⌥⌘F, Windows/Linux에서 Ctrl+Alt+F를 사용하여 이러한 값을 저장하는 필드를 삽입하여 인스턴스간에 서로 다른 값을 갖도록 할 수 있습니다.
매개변수 추출
매개변수 추출 리팩토링을 사용하면 메소드에서 상수 또는 표현식을 선택하고 이를 메소드에 인수로 전달되도록 정의할 수 있습니다. 이 예에서는 toIndex
라는 getItemsList
메소드에 두 번째 필드도 삽입했습니다.
public class ViewImpl implements View { private final int fromIndex; private final int toIndex; public ViewImpl() { fromIndex = 0; toIndex = 4; } public List<Integer> getItemsList() { return List.of(1, 2, 3, 4, 5).subList(fromIndex, toIndex); } }
getItemsList
메소드에서 List.of(1,2,3,4,5)
표현식을 이 메소드에 대한 인수로 전달할 수 있습니다. 메소드 매개변수의 유형은 선택한 표현식의 유형과 동일합니다.
Delegate via method overloading(오버로드 메소드를 통한 위임) 확인란을 선택하여 원래 메소드를 유지하고 두 번째 메소드를 삽입할 수도 있습니다. 이는 호출자에 따라 두 메소드 중 하나를 사용할 수 있음을 의미합니다.
매개변수 추출은 표현식을 가장 논리적인 위치로 이동하여 메소드 또는 메소드 호출을 더 읽기 쉽게 만드는 데 도움을 줍니다. 이해하기 쉬운 매개변수 이름을 사용하는 것도 가독성에 도움을 줍니다.
변수 삽입
필요한 경우 변수를 추출할 수도 있습니다. 때로는 표현식을 적절하게 이름이 지정된 변수로 이동하여 코드를 더 읽기 쉽게 만들 수 있습니다.
public ContextActionsWithAltEnter(double cityPopulation) { if (cityPopulation > 0x1.2016eb678a2p43 && cityPopulation < 987677.8) { if (cityPopulation % 5 == 0) { this.cityPopulation /= 2; } } this.cityPopulation = cityPopulation; }
ContextActionsWithAltEnter
생성자의 if 문은 약간 복잡해 보입니다. 표현식을 선택하고 macOS에서 ⌥⌘V, Windows/Linux에서 Ctrl+Alt+V 단축키를 입력합니다. lowerLimit
과 같이 유용한 이름을 지정하세요. 두 번째 조건을 변수로 추출하고 이름을 upperLimit
으로 지정할 수도 있습니다. 필요한 경우 추출된 변수를 final
로, Java 10 이상을 사용하는 경우 var
로 정의할 수 있습니다.
이 표현식은 이제 훨씬 더 읽기 쉬워 보입니다.
안전하게 삭제
프로젝트에서 파일이나 심볼을 더 이상 사용하지 않으려면 안전하게 삭제해야 합니다.
삭제할 파일 또는 클래스를 선택하고 macOS에서 ⌘⌦, 또는 Windows/Linux에서 Alt+Delete를 사용하세요. IntelliJ IDEA는 삭제하기 전에 리소스를 삭제해도 안전한지 확인합니다.
요약
IntelliJ IDEA는 코드를 개선하기 위해 매일 안전하게 사용할 수 있는 강력한 리팩토링 기능을 제공합니다.
그러나, 리팩토링이 만능 해결사는 아닙니다. 리팩토링으로 인해 코드의 동작이 달라지지 않도록 테스트에 안전망을 준비해 두세요.
기타 참고 자료:
- IntelliJ IDEA에서 지원되는 가장 인기있는 리팩토링
- 리팩토링: IntelliJ IDEA에서 메소드 인라인화(동영상 포함)
- 리팩토링: IntelliJ IDEA에서 변수 추출 및 동영상
이 게시물은 Helen Scott이 작성한 Everyday Refactorings in IntelliJ IDEA를 번역한 글입니다.