CodeGym /Java Blog /무작위의 /Java에서 리팩토링이 작동하는 방식
John Squirrels
레벨 41
San Francisco

Java에서 리팩토링이 작동하는 방식

무작위의 그룹에 게시되었습니다
프로그래밍 방법을 배우면서 코드를 작성하는 데 많은 시간을 할애합니다. 대부분의 초보 개발자는 이것이 그들이 미래에 할 일이라고 믿습니다. 이것은 부분적으로 사실이지만 프로그래머의 작업에는 코드 유지 및 리팩터링도 포함됩니다. 오늘은 리팩토링에 대해 알아보겠습니다. Java에서 리팩토링이 작동하는 방식 - 1

CodeGym에서 리팩토링

리팩토링은 CodeGym 과정에서 두 번 다룹니다. 큰 작업은 연습을 통해 실제 리팩토링에 익숙해질 수 있는 기회를 제공하며, IDEA의 리팩토링에 대한 강의는 삶을 훨씬 더 쉽게 만들어 줄 자동화 도구에 뛰어드는 데 도움이 됩니다.

리팩토링이란 무엇입니까?

기능을 변경하지 않고 코드 구조를 변경합니다. 예를 들어 2개의 숫자를 비교하여 첫 번째 숫자가 크면 true를 반환하고 그렇지 않으면 false를 반환하는 메서드가 있다고 가정합니다 .

    public boolean max(int a, int b) {
        if(a > b) {
            return true;
        } else if (a == b) {
            return false;
        } else {
            return false;
        }
    }
다소 다루기 힘든 코드입니다. 초보자라도 이런 글을 쓰는 경우는 거의 없지만 기회가 있습니다. if-else6줄 방법을 더 간결하게 작성할 수 있다면 블록을 사용하는 이유는 무엇입니까 ?

 public boolean max(int a, int b) {
      return a > b;
 }
이제 위의 예제와 동일한 작업을 수행하는 간단하고 우아한 방법이 있습니다. 이것이 리팩토링이 작동하는 방식입니다. 코드의 본질에 영향을 주지 않고 코드 구조를 변경합니다. 자세히 살펴볼 많은 리팩토링 방법과 기술이 있습니다.

왜 리팩토링이 필요한가요?

몇 가지 이유가 있습니다. 예를 들어, 코드에서 단순성과 간결성을 달성하기 위해. 이 이론의 지지자들은 코드를 이해하는 데 수십 줄의 주석이 필요하더라도 가능한 한 간결해야 한다고 믿습니다. 다른 개발자들은 최소한의 주석으로 코드를 이해할 수 있도록 리팩토링해야 한다고 확신합니다. 각 팀은 자체 입장을 채택하지만 리팩토링이 축소를 의미하지는 않는다는 점을 기억하십시오 . 주요 목적은 코드 구조를 개선하는 것입니다. 이 전반적인 목적에는 다음과 같은 몇 가지 작업이 포함될 수 있습니다.
  1. 리팩토링은 다른 개발자가 작성한 코드에 대한 이해를 향상시킵니다.
  2. 버그를 찾고 수정하는 데 도움이 됩니다.
  3. 소프트웨어 개발 속도를 높일 수 있습니다.
  4. 전반적으로 소프트웨어 설계를 개선합니다.
장기간 리팩토링을 하지 않으면 작업이 완전히 중단되는 등 개발에 어려움을 겪을 수 있습니다.

"코드 냄새"

코드에 리팩토링이 필요한 경우 "냄새"가 난다고 합니다. 물론 문자 그대로는 아니지만 그러한 코드는 그다지 매력적으로 보이지 않습니다. 아래에서는 초기 단계에 대한 기본 리팩토링 기술을 살펴보겠습니다.

부당하게 큰 클래스 및 메서드

클래스와 메서드는 번거로울 수 있으며 크기가 크기 때문에 효율적으로 작업하기가 불가능합니다.

대규모 수업

이러한 클래스에는 엄청난 수의 코드 행과 다양한 메서드가 있습니다. 일반적으로 개발자는 새 클래스를 만드는 것보다 기존 클래스에 기능을 추가하는 것이 더 쉽기 때문에 클래스가 커집니다. 일반적으로 이러한 클래스에는 너무 많은 기능이 포함되어 있습니다. 이 경우 기능의 일부를 별도의 클래스로 이동하는 것이 도움이 됩니다. 이에 대해서는 리팩토링 기술 섹션에서 자세히 설명하겠습니다.

긴 방법

이 "냄새"는 개발자가 메서드에 새로운 기능을 추가할 때 발생합니다. 배열의 요소? 여기에 보관합시다. 이렇게 하면 코드가 더 명확해질 것입니다." 및 기타 이러한 오해.

긴 메서드를 리팩터링하는 데는 두 가지 규칙이 있습니다.

  1. 메소드를 작성할 때 주석을 추가하고 싶다면 별도의 메소드에 기능을 넣어야 합니다.
  2. 메서드가 10-15줄 이상의 코드를 사용하는 경우 수행하는 작업과 하위 작업을 식별하고 하위 작업을 별도의 메서드에 넣어야 합니다.

긴 메서드를 제거하는 몇 가지 방법이 있습니다.

  • 메서드 기능의 일부를 별도의 메서드로 이동
  • 지역 변수로 인해 일부 기능을 이동할 수 없는 경우 전체 개체를 다른 메서드로 이동할 수 있습니다.

많은 기본 데이터 유형 사용

이 문제는 일반적으로 클래스의 필드 수가 시간이 지남에 따라 증가할 때 발생합니다. 예를 들어 작은 객체 대신 기본 유형이나 상수에 모든 것(통화, 날짜, 전화번호 등)을 저장하는 경우입니다. 이 경우 필드의 논리적 그룹을 별도의 클래스(추출 클래스)로 이동하는 것이 좋습니다. 클래스에 메서드를 추가하여 데이터를 처리할 수도 있습니다.

너무 많은 매개변수

이것은 특히 긴 메서드와 함께 사용할 때 상당히 흔한 실수입니다. 일반적으로 메서드에 기능이 너무 많거나 메서드가 여러 알고리즘을 구현하는 경우에 발생합니다. 긴 매개변수 목록은 이해하기 매우 어려우며 이러한 목록과 함께 메서드를 사용하는 것은 불편합니다. 결과적으로 전체 개체를 전달하는 것이 좋습니다. 개체에 충분한 데이터가 없으면 보다 일반적인 개체를 사용하거나 각 메서드가 논리적으로 관련된 데이터를 처리하도록 메서드의 기능을 나누어야 합니다.

데이터 그룹

논리적으로 관련된 데이터 그룹은 종종 코드에 나타납니다. 예를 들어 데이터베이스 연결 매개변수(URL, 사용자 이름, 비밀번호, 스키마 이름 등). 필드 목록에서 단일 필드를 제거할 수 없는 경우 이러한 필드를 별도의 클래스(추출 클래스)로 이동해야 합니다.

OOP 원칙을 위반하는 솔루션

이러한 "냄새"는 개발자가 적절한 OOP 설계를 위반할 때 발생합니다. 이는 그 또는 그녀가 OOP 기능을 완전히 이해하지 못하고 이를 완전히 또는 적절하게 사용하지 못할 때 발생합니다.

상속 사용 실패

하위 클래스가 상위 클래스 기능의 작은 하위 집합만 사용하는 경우 잘못된 계층 구조 냄새가 납니다. 이런 일이 발생하면 일반적으로 불필요한 메서드는 재정의되지 않거나 예외가 발생합니다. 다른 클래스를 상속하는 클래스는 자식 클래스가 부모 클래스의 거의 모든 기능을 사용함을 의미합니다. 올바른 계층 구조의 예: Java에서 리팩토링이 작동하는 방식 - 2잘못된 계층 구조의 예: Java에서 리팩토링이 작동하는 방식 - 3

스위치 문

진술 에 어떤 문제가 있을 수 있습니까 switch? 그것이 매우 복잡해지면 나쁘다. 관련된 문제는 많은 수의 중첩된 if문입니다.

인터페이스가 다른 대체 클래스

여러 클래스가 동일한 작업을 수행하지만 메서드 이름이 다릅니다.

임시 필드

클래스에 값이 설정될 때 개체에 가끔 필요한 임시 필드가 있고 비어 있거나 null나머지 시간에는 금지되어 있으면 코드에서 냄새가 납니다. 이것은 의심스러운 디자인 결정입니다.

수정이 어려운 냄새

이 냄새는 더 심각합니다. 다른 냄새는 주로 코드를 이해하기 어렵게 만들지만 코드를 수정하는 데 방해가 됩니다. 새로운 기능을 도입하려고 하면 개발자의 절반은 그만두고 절반은 미쳐 버립니다.

병렬 상속 계층

이 문제는 클래스를 서브클래싱할 때 다른 클래스에 대한 또 다른 서브클래스를 생성해야 할 때 나타납니다.

균일하게 분산된 종속성

모든 수정 사항은 클래스의 모든 사용(종속성)을 찾고 많은 작은 변경 사항을 적용해야 합니다. 하나의 변경 — 많은 클래스에서 편집합니다.

복잡한 수정 트리

이 냄새는 이전 냄새와 반대입니다. 변경 사항은 한 클래스의 많은 메서드에 영향을 미칩니다. 일반적으로 이러한 코드에는 계단식 종속성이 있습니다. 한 메서드를 변경하려면 다른 메서드에서 무언가를 수정한 다음 세 번째 메서드에서 수정해야 합니다. 하나의 클래스 - 많은 변화.

"쓰레기 냄새"

두통을 유발하는 다소 불쾌한 냄새 범주. 쓸모없고 불필요하며 오래된 코드입니다. 다행스럽게도 최신 IDE와 linter는 이러한 냄새에 대해 경고하는 법을 배웠습니다.

메서드에 있는 많은 수의 주석

메서드에는 거의 모든 줄에 많은 설명 주석이 있습니다. 이는 일반적으로 복잡한 알고리즘으로 인해 발생하므로 코드를 여러 개의 작은 메서드로 분할하고 설명 이름을 지정하는 것이 좋습니다.

중복 코드

다른 클래스 또는 메서드는 동일한 코드 블록을 사용합니다.

게으른 클래스

클래스는 대규모로 계획되었지만 거의 기능을 수행하지 않습니다.

미사용 코드

클래스, 메소드 또는 변수는 코드에서 사용되지 않으며 사중입니다.

과도한 연결성

이 악취 범주는 코드에서 많은 부당한 관계가 특징입니다.

외부 방법

메서드는 자체 데이터보다 훨씬 더 자주 다른 개체의 데이터를 사용합니다.

부적절한 친밀감

클래스는 다른 클래스의 구현 세부 사항에 따라 다릅니다.

긴 수업 통화

한 클래스가 다른 클래스를 호출하면 세 번째 클래스에서 데이터를 요청하고 네 번째 클래스에서 데이터를 가져오는 식입니다. 이러한 긴 호출 체인은 현재 클래스 구조에 대한 의존도가 높다는 것을 의미합니다.

태스크 딜러 클래스

클래스는 다른 클래스에 작업을 보낼 때만 필요합니다. 제거해야할까요?

리팩토링 기술

아래에서는 설명된 코드 냄새를 제거하는 데 도움이 되는 기본 리팩토링 기술에 대해 설명합니다.

클래스 추출

클래스는 너무 많은 기능을 수행합니다. 그들 중 일부는 다른 클래스로 이동해야 합니다. 예를 들어 Human집 주소도 저장하고 전체 주소를 반환하는 메서드가 있는 클래스가 있다고 가정합니다.

class Human {
    private String name;
    private String age;
    private String country;
    private String city;
    private String street;
    private String house;
    private String quarter;
 
    public String getFullAddress() {
        StringBuilder result = new StringBuilder();
        return result
                        .append(country)
                        .append(", ")
                        .append(city)
                        .append(", ")
                        .append(street)
                        .append(", ")
                        .append(house)
                        .append(" ")
                        .append(quarter).toString();
    }
 }
주소 정보 및 관련 메서드(데이터 처리 동작)를 별도의 클래스에 넣는 것이 좋습니다.

 class Human {
    private String name;
    private String age;
    private Address address;
 
    private String getFullAddress() {
        return address.getFullAddress();
    }
 }
 class Address {
    private String country;
    private String city;
    private String street;
    private String house;
    private String quarter;
 
    public String getFullAddress() {
        StringBuilder result = new StringBuilder();
        return result
                        .append(country)
                        .append(", ")
                        .append(city)
                        .append(", ")
                        .append(street)
                        .append(", ")
                        .append(house)
                        .append(" ")
                        .append(quarter).toString();
    }
 }

메서드 추출

메서드에 격리할 수 있는 일부 기능이 있는 경우 별도의 메서드에 배치해야 합니다. 예를 들어, 이차 방정식의 근을 계산하는 방법은 다음과 같습니다.

    public void calcQuadraticEq(double a, double b, double c) {
        double D = b * b - 4 * a * c;
        if (D > 0) {
            double x1, x2;
            x1 = (-b - Math.sqrt(D)) / (2 * a);
            x2 = (-b + Math.sqrt(D)) / (2 * a);
            System.out.println("x1 = " + x1 + ", x2 = " + x2);
        }
        else if (D == 0) {
            double x;
            x = -b / (2 * a);
            System.out.println("x = " + x);
        }
        else {
            System.out.println("Equation has no roots");
        }
    }
세 가지 가능한 옵션을 각각 별도의 방법으로 계산합니다.

    public void calcQuadraticEq(double a, double b, double c) {
        double D = b * b - 4 * a * c;
        if (D > 0) {
            dGreaterThanZero(a, b, D);
        }
        else if (D == 0) {
            dEqualsZero(a, b);
        }
        else {
            dLessThanZero();
        }
    }
 
    public void dGreaterThanZero(double a, double b, double D) {
        double x1, x2;
        x1 = (-b - Math.sqrt(D)) / (2 * a);
        x2 = (-b + Math.sqrt(D)) / (2 * a);
        System.out.println("x1 = " + x1 + ", x2 = " + x2);
    }
 
    public void dEqualsZero(double a, double b) {
        double x;
        x = -b / (2 * a);
        System.out.println("x = " + x);
    }
 
    public void dLessThanZero() {
        System.out.println("Equation has no roots");
    }
각 메서드의 코드가 훨씬 더 짧아지고 이해하기 쉬워졌습니다.

전체 개체 전달

메서드가 매개 변수와 함께 호출되면 때때로 다음과 같은 코드가 표시될 수 있습니다.

 public void employeeMethod(Employee employee) {
     // Some actions
     double yearlySalary = employee.getYearlySalary();
     double awards = employee.getAwards();
     double monthlySalary = getMonthlySalary(yearlySalary, awards);
     // Continue processing
 }
 
 public double getMonthlySalary(double yearlySalary, double awards) {
      return (yearlySalary + awards)/12;
 }
employeeMethod에는 값을 수신하고 기본 변수에 저장하는 데 사용되는 2개의 전체 라인이 있습니다 . 때때로 그러한 구성은 최대 10줄을 차지할 수 있습니다. 개체 자체를 전달하고 필요한 데이터를 추출하는 데 사용하는 것이 훨씬 쉽습니다.

 public void employeeMethod(Employee employee) {
     // Some actions
     double monthlySalary = getMonthlySalary(employee);
     // Continue processing
 }
 
 public double getMonthlySalary(Employee employee) {
     return (employee.getYearlySalary() + employee.getAwards())/12;
 }

간단하고 간결하며 간결합니다.

필드를 논리적으로 묶고 분리해서 옮기는 것은 classDespite위의 예제가 매우 간단하다는 사실과, 보고나면 "누가 이러는 거지?" 하는 분들이 많으실 텐데, 많은 개발자들이 부주의로 이런 구조적 오류를 범하고, 코드 리팩터링을 꺼리거나 단순히 "그것만으로도 충분하다"는 태도.

리팩토링이 효과적인 이유

좋은 리팩토링의 결과로 프로그램은 읽기 쉬운 코드를 가지고 있고, 로직을 변경할 가능성이 두렵지 않으며, 새로운 기능을 도입하는 것이 코드 분석 지옥이 되지 않고 대신 며칠 동안 즐거운 경험입니다. . 처음부터 프로그램을 작성하는 것이 더 쉬우면 리팩토링을 해서는 안 됩니다. 예를 들어 팀에서 코드를 이해, 분석 및 리팩터링하는 데 필요한 노동력이 동일한 기능을 처음부터 구현하는 것보다 더 많을 것이라고 추정한다고 가정합니다. 또는 리팩터링할 코드에 디버깅하기 어려운 문제가 많은 경우. 코드 구조를 개선하는 방법을 아는 것은 프로그래머의 작업에 필수적입니다. Java 프로그래밍 학습은 실습을 강조하는 온라인 과정인 CodeGym에서 가장 잘 수행됩니다. 즉각적인 검증을 통한 1200개 이상의 작업, 약 20개의 미니 프로젝트, 게임 작업 — 이 모든 것이 코딩에 자신감을 갖도록 도와줍니다. 시작하기에 가장 좋은 시기는 바로 지금입니다 :)

리팩토링에 더욱 몰입할 수 있는 리소스

리팩토링에 관한 가장 유명한 책은 Martin Fowler의 "Refactoring. Improving the Design of Existing Code"입니다. Joshua Kerievsky의 "Refactoring Using Patterns"라는 이전 책을 기반으로 한 리팩토링에 대한 흥미로운 간행물도 있습니다. 패턴에 대해 말하자면... 리팩토링을 할 때 기본 디자인 패턴을 아는 것은 항상 매우 유용합니다. 다음과 같은 훌륭한 책이 도움이 될 것입니다. 패턴에 대해 말하자면... 리팩토링할 때 기본 디자인 패턴을 아는 것은 항상 매우 유용합니다. 다음과 같은 훌륭한 책이 도움이 될 것입니다.
  1. Head First 시리즈의 Eric Freeman, Elizabeth Robson, Kathy Sierra 및 Bert Bates의 "Design Patterns"
  2. Dustin Boswell과 Trevor Foucher의 "가독 코드의 기술"
  3. 아름답고 우아한 코드의 원칙을 제시하는 Steve McConnell의 "Code Complete".
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION