JavaRush /Blog Java /Random-VI /Cách tái cấu trúc hoạt động trong Java

Cách tái cấu trúc hoạt động trong Java

Xuất bản trong nhóm
Khi học lập trình, người ta dành rất nhiều thời gian để viết mã. Hầu hết các nhà phát triển mới bắt đầu đều tin rằng đây là hoạt động trong tương lai của họ. Điều này đúng một phần, nhưng nhiệm vụ của lập trình viên cũng bao gồm việc duy trì và tái cấu trúc mã. Hôm nay chúng ta sẽ nói về việc tái cấu trúc. Cách tái cấu trúc hoạt động trong Java - 1

Tái cấu trúc trong khóa học JavaRush

Khóa học JavaRush bao gồm chủ đề tái cấu trúc hai lần: Nhờ nhiệm vụ lớn, bạn có cơ hội làm quen với việc tái cấu trúc thực tế trong thực tế và bài giảng về tái cấu trúc trong IDEA sẽ giúp bạn hiểu các công cụ tự động giúp cuộc sống trở nên dễ dàng hơn đáng kinh ngạc.

Tái cấu trúc là gì?

Đây là sự thay đổi trong cấu trúc của mã mà không làm thay đổi chức năng của nó. Ví dụ: có một phương thức so sánh 2 số và trả về true nếu số đầu tiên lớn hơn và trả về false nếu ngược lại:
public boolean max(int a, int b) {
    if(a > b) {
        return true;
    } else if(a == b) {
        return false;
    } else {
        return false;
    }
}
Kết quả là mã rất cồng kềnh. Ngay cả những người mới bắt đầu cũng hiếm khi viết những thứ như thế này, nhưng vẫn có rủi ro. Có vẻ như, tại sao lại có một khối ở đây if-elsenếu bạn có thể viết một phương thức ngắn hơn 6 dòng:
public boolean max(int a, int b) {
     return a>b;
}
Bây giờ phương pháp này trông đơn giản và thanh lịch, mặc dù nó thực hiện tương tự như ví dụ trên. Đây là cách hoạt động của quá trình tái cấu trúc: nó thay đổi cấu trúc của mã mà không ảnh hưởng đến bản chất của mã. Có nhiều phương pháp và kỹ thuật tái cấu trúc mà chúng tôi sẽ xem xét chi tiết hơn.

Tại sao cần tái cấu trúc?

Có một số lý do. Ví dụ: việc theo đuổi sự đơn giản và ngắn gọn của mã. Những người ủng hộ lý thuyết này tin rằng mã phải ngắn gọn nhất có thể, ngay cả khi nó cần hàng chục dòng bình luận để hiểu nó. Các nhà phát triển khác tin rằng mã nên được cấu trúc lại để có thể hiểu được với số lượng nhận xét tối thiểu. Mỗi nhóm chọn vị trí riêng của mình, nhưng chúng ta phải nhớ rằng tái cấu trúc không phải là giảm bớt . Mục tiêu chính của nó là cải thiện cấu trúc của mã. Một số mục tiêu có thể được bao gồm trong mục tiêu toàn cầu này:
  1. Tái cấu trúc cải thiện sự hiểu biết về mã do nhà phát triển khác viết;
  2. Giúp tìm và sửa lỗi;
  3. Cho phép bạn tăng tốc độ phát triển phần mềm;
  4. Nhìn chung cải thiện thành phần phần mềm.
Nếu việc tái cấu trúc không được thực hiện trong một thời gian dài, những khó khăn trong quá trình phát triển có thể nảy sinh, có thể dẫn đến việc ngừng hoàn toàn công việc.

“Mã có mùi”

Khi mã yêu cầu tái cấu trúc, họ nói nó “có mùi”. Tất nhiên, không phải theo nghĩa đen, nhưng mã như vậy thực sự trông không đẹp lắm. Dưới đây chúng tôi sẽ xem xét các kỹ thuật tái cấu trúc chính cho giai đoạn đầu.

Các phần tử lớn không cần thiết

Có những lớp và phương thức cồng kềnh không thể làm việc một cách hiệu quả một cách chính xác vì kích thước khổng lồ của chúng.

Lớp học lớn

Một lớp như vậy có số lượng dòng mã khổng lồ và nhiều phương thức khác nhau. Nhà phát triển thường dễ dàng thêm tính năng vào lớp hiện có hơn là tạo một tính năng mới, đó là lý do tại sao nó phát triển. Theo quy định, chức năng của lớp này bị quá tải. Trong trường hợp này, việc tách một phần chức năng thành một lớp riêng biệt sẽ giúp ích. Chúng ta sẽ nói về điều này chi tiết hơn trong phần kỹ thuật tái cấu trúc.

Phương pháp lớn

“Mùi” này xảy ra khi nhà phát triển thêm chức năng mới vào một phương thức. “Tại sao tôi phải đặt việc kiểm tra tham số trong một phương thức riêng nếu tôi có thể viết nó ở đây?”, “Tại sao cần phải tách phương thức tìm phần tử lớn nhất trong mảng, hãy để nó ở đây. Bằng cách này, mã sẽ rõ ràng hơn” và những quan niệm sai lầm khác. Có hai quy tắc để tái cấu trúc một phương thức lớn:
  1. Nếu khi viết một phương thức, bạn muốn thêm nhận xét vào mã, bạn cần tách chức năng này thành một phương thức riêng;
  2. Nếu một phương thức có nhiều hơn 10-15 dòng mã, bạn nên xác định các tác vụ và nhiệm vụ phụ mà nó thực hiện và cố gắng tách các nhiệm vụ phụ thành một phương thức riêng biệt.
Một số cách để loại bỏ một phương pháp lớn:
  • Tách một phần chức năng của một phương thức thành một phương thức riêng biệt;
  • Nếu các biến cục bộ không cho phép bạn trích xuất một phần chức năng, bạn có thể chuyển toàn bộ đối tượng sang một phương thức khác.

Sử dụng nhiều kiểu dữ liệu nguyên thủy

Thông thường, sự cố này xảy ra khi số lượng trường lưu trữ dữ liệu trong một lớp tăng lên theo thời gian. Ví dụ: nếu bạn sử dụng các kiểu nguyên thủy thay vì các đối tượng nhỏ để lưu trữ dữ liệu (tiền tệ, ngày tháng, số điện thoại, v.v.) hoặc các hằng số để mã hóa bất kỳ thông tin nào. Cách thực hành tốt trong trường hợp này là nhóm các trường một cách hợp lý và đặt chúng vào một lớp riêng biệt (chọn một lớp). Bạn cũng có thể bao gồm các phương thức xử lý dữ liệu này trong lớp.

Danh sách dài các tùy chọn

Một lỗi khá phổ biến, đặc biệt là khi kết hợp với một phương pháp lớn. Nó thường xảy ra nếu chức năng của phương thức bị quá tải hoặc phương thức kết hợp một số thuật toán. Danh sách dài các tham số rất khó hiểu và các phương pháp như vậy rất bất tiện khi sử dụng. Vì vậy, tốt hơn là chuyển toàn bộ đối tượng. Nếu đối tượng không có đủ dữ liệu, bạn nên sử dụng một đối tượng tổng quát hơn hoặc tách chức năng của phương thức để nó xử lý dữ liệu liên quan một cách logic.

Nhóm dữ liệu

Các nhóm dữ liệu liên quan đến logic thường xuất hiện trong mã. Ví dụ: các tham số kết nối với cơ sở dữ liệu (URL, tên người dùng, mật khẩu, tên lược đồ, v.v.). Nếu không thể xóa một trường nào khỏi danh sách các phần tử thì danh sách đó là một nhóm dữ liệu phải được đặt trong một lớp riêng biệt (lựa chọn lớp).

Các giải pháp làm hỏng khái niệm OOP

Loại “mùi” này xảy ra khi nhà phát triển vi phạm thiết kế OOP. Điều này xảy ra nếu anh ta không hiểu đầy đủ các khả năng của mô hình này, sử dụng chúng không đầy đủ hoặc không chính xác.

Từ chối thừa kế

Nếu một lớp con sử dụng một phần tối thiểu các chức năng của lớp cha thì nó có vẻ giống như một hệ thống phân cấp không chính xác. Thông thường, trong trường hợp này, các phương thức không cần thiết sẽ không bị ghi đè hoặc đưa ra các ngoại lệ. Nếu một lớp được kế thừa từ một lớp khác, điều này hàm ý việc sử dụng gần như hoàn toàn chức năng của nó. Ví dụ về hệ thống phân cấp đúng: Cách tái cấu trúc hoạt động trong Java - 2 Ví dụ về hệ thống phân cấp không chính xác: Cách tái cấu trúc hoạt động trong Java - 3

câu lệnh chuyển đổi

Điều gì có thể xảy ra với một nhà điều hành switch? Thật tệ khi thiết kế của nó rất phức tạp. Điều này cũng bao gồm nhiều khối lồng nhau if.

Các lớp thay thế với giao diện khác nhau

Một số lớp thực sự làm điều tương tự, nhưng phương thức của chúng được đặt tên khác nhau.

Trường tạm thời

Nếu lớp chứa một trường tạm thời mà đối tượng chỉ thỉnh thoảng cần, khi nó chứa đầy các giá trị và thời gian còn lại nó trống hoặc, Chúa ơi, null, thì mã "có mùi" và thiết kế như vậy là không rõ ràng phán quyết.

Mùi gây khó khăn cho việc sửa đổi

Những “mùi” này nghiêm trọng hơn. Phần còn lại chủ yếu làm suy giảm sự hiểu biết về mã, trong khi những điều này không giúp bạn có thể sửa đổi mã. Khi giới thiệu bất kỳ tính năng nào, một nửa số nhà phát triển sẽ bỏ cuộc và một nửa sẽ phát điên.

Hệ thống phân cấp kế thừa song song

Khi bạn tạo một lớp con của một lớp, bạn phải tạo một lớp con khác của lớp khác.

Phân phối phụ thuộc thống nhất

Khi thực hiện bất kỳ sửa đổi nào, bạn phải tìm kiếm tất cả các phần phụ thuộc (công dụng) của lớp này và thực hiện nhiều thay đổi nhỏ. Một thay đổi - chỉnh sửa ở nhiều lớp.

Cây sửa đổi phức tạp

Mùi này trái ngược với mùi trước: những thay đổi ảnh hưởng đến một số lượng lớn các phương thức của cùng một lớp. Theo quy định, sự phụ thuộc trong mã như vậy là xếp tầng: sau khi thay đổi một phương thức, bạn cần sửa một cái gì đó ở phương thức khác, sau đó ở phương thức thứ ba, v.v. Một lớp - nhiều thay đổi.

“Mùi rác”

Một loại mùi khá khó chịu gây đau đầu. Mã cũ, vô dụng, không cần thiết. May mắn thay, các IDE và linters hiện đại đã học được cách cảnh báo về những mùi như vậy.

Một số lượng lớn ý kiến ​​​​trong phương pháp

Phương pháp này có rất nhiều bình luận giải thích trên hầu hết các dòng. Điều này thường liên quan đến một thuật toán phức tạp, vì vậy tốt hơn là chia mã thành nhiều phương thức nhỏ hơn và đặt cho chúng những cái tên có ý nghĩa.

Sao chép mã

Các lớp hoặc phương thức khác nhau sử dụng cùng một khối mã.

Lớp lười biếng

Lớp học đảm nhận rất ít chức năng, mặc dù phần lớn trong số đó đã được lên kế hoạch.

Mã chưa sử dụng

Một lớp, phương thức hoặc biến không được sử dụng trong mã và được coi là "trọng lượng chết".

Khớp nối quá mức

Loại mùi này được đặc trưng bởi một số lượng lớn các kết nối không cần thiết trong mã.

Phương pháp của bên thứ ba

Một phương thức sử dụng dữ liệu của đối tượng khác thường xuyên hơn nhiều so với việc sử dụng dữ liệu của chính nó.

Sự thân mật không phù hợp

Một lớp sử dụng các trường dịch vụ và các phương thức của một lớp khác.

Cuộc gọi dài hạn

Lớp này gọi lớp khác, yêu cầu dữ liệu từ lớp thứ ba, lớp kia yêu cầu dữ liệu từ lớp thứ tư, v.v. Một chuỗi cuộc gọi dài như vậy có nghĩa là mức độ phụ thuộc cao vào cấu trúc lớp hiện tại.

Lớp-nhiệm vụ-người chia bài

Một lớp chỉ cần thiết để chuyển một nhiệm vụ cho một lớp khác. Có lẽ nó nên được gỡ bỏ?

Kỹ thuật tái cấu trúc

Dưới đây chúng ta sẽ nói về các kỹ thuật tái cấu trúc ban đầu sẽ giúp loại bỏ mùi mã được mô tả.

Lựa chọn lớp học

Lớp thực hiện quá nhiều chức năng, một số chức năng cần được chuyển sang lớp khác. Ví dụ: có một lớp Humancũng chứa địa chỉ cư trú và một phương thức cung cấp địa chỉ đầy đủ:
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();
   }
}
Sẽ là một ý tưởng hay nếu đặt thông tin địa chỉ và phương thức (hành vi xử lý dữ liệu) trong một lớp riêng:
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();
   }
}

Lựa chọn phương pháp

Nếu bất kỳ chức năng nào có thể được nhóm lại trong một phương thức thì nó phải được đặt trong một phương thức riêng. Ví dụ: một phương pháp tính nghiệm của phương trình bậc hai:
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");
    }
}
Hãy chuyển việc tính toán cả ba tùy chọn có thể thành các phương pháp riêng biệt:
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");
}
Mã cho mỗi phương thức đã trở nên ngắn hơn và rõ ràng hơn nhiều.

Chuyển toàn bộ đối tượng

Khi gọi một phương thức có tham số, đôi khi bạn có thể thấy đoạn mã như thế này:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double yearlySalary = employee.getYearlySalary();
    double awards = employee.getAwards();
    double monthlySalary = getMonthlySalary(yearlySalary, awards);
    // Продолжение обработки
}

public double getMonthlySalary(double yearlySalary, double awards) {
     return (yearlySalary + awards)/12;
}
Trong phương thức này, employeeMethodcó tới 2 dòng được phân bổ để lấy các giá trị và lưu trữ chúng trong các biến nguyên thủy. Đôi khi những thiết kế như vậy mất tới 10 dòng. Việc truyền chính đối tượng đó vào phương thức sẽ dễ dàng hơn nhiều, từ đó bạn có thể trích xuất dữ liệu cần thiết:
public void employeeMethod(Employee employee) {
    // Некоторые действия
    double monthlySalary = getMonthlySalary(employee);
    // Продолжение обработки
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}
Đơn giản, ngắn gọn và súc tích.

Nhóm các trường hợp lý và đặt chúng vào một lớp riêng biệt

Mặc dù thực tế là các ví dụ trên rất đơn giản và khi nhìn vào nhiều người có thể đặt câu hỏi “Ai thực sự làm điều này?”, nhưng nhiều nhà phát triển, do không chú ý, không muốn refactor mã, hoặc đơn giản là “Nó sẽ làm được”, đã khiến lỗi cấu trúc tương tự.

Tại sao tái cấu trúc lại hiệu quả

Kết quả của việc tái cấu trúc tốt là một chương trình có mã dễ đọc, các sửa đổi logic chương trình không trở thành mối đe dọa và việc giới thiệu các tính năng mới không biến thành địa ngục phân tích mã mà là một hoạt động thú vị trong vài ngày . Không nên sử dụng phương pháp tái cấu trúc nếu việc viết lại chương trình từ đầu sẽ dễ dàng hơn. Ví dụ: nhóm ước tính chi phí lao động cho việc phân tích cú pháp, phân tích và tái cấu trúc mã sẽ cao hơn so với việc triển khai cùng một chức năng từ đầu. Hoặc đoạn code cần refactor lại có rất nhiều lỗi khó debug. Biết cách cải thiện cấu trúc mã là điều bắt buộc trong công việc của một lập trình viên. Chà, tốt hơn hết bạn nên học lập trình Java tại JavaRush - một khóa học trực tuyến chú trọng vào thực hành. Hơn 1200 nhiệm vụ được xác minh tức thì, khoảng 20 dự án nhỏ, nhiệm vụ trò chơi - tất cả những điều này sẽ giúp bạn cảm thấy tự tin khi viết mã. Thời điểm tốt nhất để bắt đầu là bây giờ :) Как устроен рефакторинг в Java - 4

Tài nguyên để đi sâu hơn vào tái cấu trúc

Cuốn sách nổi tiếng nhất về tái cấu trúc là “Refactoring. Cải thiện thiết kế mã hiện có” của Martin Fowler. Ngoài ra còn có một ấn phẩm thú vị về tái cấu trúc, được viết dựa trên cuốn sách trước đó - “Tái cấu trúc với các mẫu” của Joshua Kiriewski. Nói về mẫu. Khi tái cấu trúc, việc biết các mẫu thiết kế ứng dụng cơ bản luôn rất hữu ích. Những cuốn sách tuyệt vời này sẽ giúp giải quyết vấn đề này:
  1. “Mẫu thiết kế” - của Eric Freeman, Elizabeth Freeman, Kathy Sierra, Bert Bates từ loạt phim Head First;
  2. “Mã có thể đọc được hoặc lập trình như một nghệ thuật” - Dustin Boswell, Trevor Faucher.
  3. “Mã hoàn hảo” của Steve McConnell, trong đó nêu ra các nguyên tắc của mã đẹp và thanh lịch.
Vâng, một vài bài viết về tái cấu trúc:
  1. Một nhiệm vụ thật khó khăn: hãy bắt đầu tái cấu trúc mã kế thừa ;
  2. Tái cấu trúc ;
  3. Tái cấu trúc cho mọi người .
    Bình luận
    TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
    GO TO FULL VERSION