IntelliJ IDEA에서의 일상적 리팩토링

Read this post in other languages:

버그 수정, 가독성 향상, 새로운 기능 추가와 같은 여러 가지 이유로 작업 중인 코드를 변경하는 경우가 종종 있습니다. 이 블로그에서는 일상적인 리팩토링을 다룹니다.

이 블로그 글에서는 동영상과 동일한 내용을 다룹니다. 블로그 게시물은 보는 것보다는 읽는 것을 선호하는 분들이 내용을 빠르게 훑어보고, 독자/시청자에게 코드 샘플과 추가 정보에 대한 링크를 제공하는 간편한 방법이라고 생각합니다.

이 블로그에 사용된 코드는 여기에서 확인할 수 있습니다.

요소 이름 바꾸기

클래스 파일에서 직접 이름을 변경하여 수동으로 이름을 바꾸는 것은 안전하지 않습니다. 예를 들어, 에디터에서 Bug 클래스의 이름을 ABug로 변경하면 이 클래스를 사용하는 다른 모든 코드를 업데이트하지 못할 수 있습니다. IntelliJ IDEA 2020.2부터는 에디터에 “관련 문제” 오류 메시지가 표시됩니다.

Bug 클래스 이름을 ABug로 바꾸기

제자리에서 적용 가능한 새로운 이름 변경 리팩토링(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를 사용하면, 프로젝트 창에 액세스하고, 클래스, 패키지, 파일 또는 디렉토리의 이름을 변경할 수도 있습니다.

시그니처 변경

클래스 또는 메소드의 시그니처를 안전하게 변경하는 방법을 살펴보겠습니다. BugReportbinaryStrings 메소드는 하나의 문자열 인수를 허용합니다.

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을 사용하여 클래스 시그니처를 수정할 수 있습니다. 여기에 두 개의 제네릭 매개변수를 추가해 보겠습니다. 하나는 디폴트 값이 IntegerK이고 다른 하나는 디폴트 값이 StringV입니다. 클래스를 리팩토링하면 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 메소드는 상수 04를 사용하여 목록의 하위 집합을 가져옵니다.

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는 코드를 개선하기 위해 매일 안전하게 사용할 수 있는 강력한 리팩토링 기능을 제공합니다.

그러나, 리팩토링이 만능 해결사는 아닙니다. 리팩토링으로 인해 코드의 동작이 달라지지 않도록 테스트에 안전망을 준비해 두세요.

기타 참고 자료:

이 게시물은 Helen Scott이 작성한 Everyday Refactorings in IntelliJ IDEA를 번역한 글입니다.

image description

Discover more