CodeGym/Blog Java/Ngẫu nhiên/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 bạn học cách lập trình, bạn dành nhiều thời gian để viết mã. Hầu hết các nhà phát triển mới bắt đầu tin rằng đây là những gì họ sẽ làm trong tương lai. Điều này đúng một phần, nhưng công việc của một 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ề 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 trên CodeGym

Tái cấu trúc được đề cập hai lần trong khóa học CodeGym: Nhiệm vụ lớn mang đến cơ hội làm quen với tái cấu trúc thực tế thông qua thực hành và bài học về tái cấu trúc trong IDEA giúp bạn đi sâu vào các công cụ tự động giúp cuộc sống của bạn dễ dàng hơn rất nhiều.

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

Nó đang thay đổi cấu trúc mã mà không thay đổi chức năng của nó. Ví dụ: giả sử chúng ta 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;
    }
}
Đây là một mã khá khó sử dụng. 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ó cơ hội. Tại sao lại sử dụng một if-elsekhối nếu bạn có thể viết phương thức 6 dòng ngắn gọn hơn?
public boolean max(int a, int b) {
     return a > b;
}
Bây giờ chúng ta có một phương thức đơn giản và thanh lịch thực hiện thao tác tương tự như ví dụ trên. Đây là cách tái cấu trúc hoạt động: bạn thay đổi cấu trúc mã mà không ảnh hưởng đến bản chất của nó. Có nhiều phương pháp và kỹ thuật tái cấu trúc mà chúng ta sẽ xem xét kỹ hơn.

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

Có một số lý do. Ví dụ, để đạt được sự đơn giản và ngắn gọn trong mã. Những người ủng hộ lý thuyết này tin rằng mã phải càng ngắn gọn càng tốt, ngay cả khi cần vài chục dòng chú thích để 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 để dễ hiểu với số lượng nhận xét tối thiểu. Mỗi nhóm chấp nhận quan điểm riêng của mình, nhưng hãy nhớ rằng tái cấu trúc không có nghĩa là giảm bớt . Mục đích chính của nó là để cải thiện cấu trúc của mã. Một số nhiệm vụ có thể được bao gồm trong mục đích tổng thể này:
  1. Tái cấu trúc cải thiện sự hiểu biết về mã được viết bởi các nhà phát triển khác.
  2. Nó giúp tìm và sửa lỗi.
  3. Nó có thể đẩy nhanh tốc độ phát triển phần mềm.
  4. Nhìn chung, nó cải thiện thiết kế 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, quá trình phát triển có thể gặp khó khăn, bao gồm cả việc dừng hoàn toàn công việc.

"Mùi mã"

Khi mã yêu cầu tái cấu trúc, nó được cho là 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 hấp dẫn lắm. Dưới đây chúng ta sẽ khám phá các kỹ thuật tái cấu trúc cơ bản cho giai đoạn ban đầu.

Các lớp và phương thức lớn bất hợp lý

Các lớp và phương thức có thể cồng kềnh, không thể làm việc 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 lớn

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

phương pháp dài

"Mùi" này phát sinh 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 kiểm tra tham số thành một phương thức riêng nếu tôi có thể viết mã ở đây?", "Tại sao tôi cần một phương pháp tìm kiếm riêng để tìm tối đa phần tử trong một mảng? Hãy giữ nó ở đây. Mã sẽ rõ ràng hơn theo cách này" 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 dài:

  1. Nếu bạn muốn thêm nhận xét khi viết một phương thức, bạn nên đặt chức năng đó trong một phương thức riêng.
  2. Nếu một phương thức chiếm hơn 10-15 dòng mã, bạn nên xác định các nhiệm vụ và nhiệm vụ phụ mà nó thực hiện và cố gắng đặt các nhiệm vụ phụ vào một phương thức riêng biệt.

Có một số cách để loại bỏ một phương pháp dài:

  • Di chuyển một phần chức năng của phương thức sang một phương thức riêng biệt
  • Nếu các biến cục bộ ngăn bạn di chuyển một phần chức năng, bạn có thể di 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

Sự cố này thường xảy ra khi số trường trong một lớp tăng theo thời gian. Ví dụ: nếu bạn lưu trữ mọi thứ (tiền tệ, ngày tháng, số điện thoại, v.v.) ở dạng nguyên thủy hoặc hằng số thay vì các đối tượng nhỏ. Trong trường hợp này, một cách thực hành tốt là di chuyển một nhóm trường logic vào một lớp riêng biệt (lớp trích xuất). Bạn cũng có thể thêm các phương thức vào lớp để xử lý dữ liệu.

Quá nhiều thông số

Đây là 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 dài. Thông thường, nó xảy ra nếu một phương thức có quá nhiều chức năng hoặc nếu một phương thức thực hiện nhiều thuật toán. Danh sách dài các tham số rất khó hiểu và việc sử dụng các phương pháp với danh sách như vậy là bất tiện. Do đó, tốt hơn là chuyển toàn bộ đối tượng. Nếu một đố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 phân chia chức năng của phương thức để mỗi phương thức xử lý dữ liệu liên quan đến 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 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ể loại bỏ một trường nào khỏi danh sách các trường, thì các trường này sẽ được chuyển sang một lớp riêng biệt (lớp trích xuất).

Các giải pháp vi phạm nguyên tắc OOP

Những "mùi" này xảy ra khi nhà phát triển vi phạm thiết kế OOP phù hợp. Điều này xảy ra khi người đó không hiểu đầy đủ về các khả năng OOP và không sử dụng chúng một cách đầy đủ hoặc đúng cách.

Không sử dụng thừa kế

Nếu một lớp con chỉ sử dụng một tập hợp con nhỏ các chức năng của lớp cha, thì nó có mùi của hệ thống phân cấp sai. Khi điều này xảy ra, thông thường các phương thức không cần thiết sẽ không bị ghi đè hoặc chúng đưa ra các ngoại lệ. Một lớp kế thừa một lớp khác ngụ ý rằng lớp con sử dụng gần như tất cả các chức năng của lớp cha. Ví dụ về hệ thống phân cấp chính xác: Cách tái cấu trúc hoạt động trong Java - 2Ví 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

Tuyên bố chuyển đổi

Điều gì có thể sai với một switchtuyên bố? Thật tệ khi nó trở nên rất phức tạp. Một vấn đề liên quan là một số lượng lớn ifcác báo cáo lồng nhau.

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

Nhiều lớp làm cùng một việc, nhưng các phương thức của chúng có các tên khác nhau.

trường tạm thời

Nếu một lớp có một trường tạm thời mà một đối tượng chỉ thỉnh thoảng cần khi giá trị của nó được đặt và nó trống hoặc, Chúa cấm, nullthời gian còn lại, thì mã có mùi. Đây là một quyết định thiết kế đáng ngờ.

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. Các mùi khác chủ yếu làm cho mã khó hiểu hơn, nhưng chúng ngăn cản bạn sửa đổi nó. Khi bạn cố gắng giới thiệu bất kỳ tính năng mới nào, một nửa số nhà phát triển sẽ bỏ cuộc và một nửa phát điên.

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

Vấn đề này tự biểu hiện khi phân lớp một lớp yêu cầu bạn tạo một lớp con khác cho một lớp khác.

Phụ thuộc phân phối đồng đều

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

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

Mùi này trái ngược với mùi trước: các thay đổi ảnh hưởng đến một số lượng lớn các phương thức trong một lớp. Theo quy định, mã như vậy có sự phụ thuộc theo tầng: thay đổi một phương thức yêu cầu bạn sửa một thứ gì đó trong một 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. Vô dụng, không cần thiết, mã cũ. May mắn thay, các IDE và kẻ nói dối hiện đại đã học cách cảnh báo về những mùi như vậy.

Một số lượng lớn các bình luận trong một phương pháp

Một phương pháp 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 là do 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 tên giải thích cho chúng.

mã trùng lặp

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 học lười biếng

Một lớp đảm nhận rất ít chức năng, mặc dù nó được lên kế hoạch lớn.

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à không có trọng số.

Kết 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 mối quan hệ không chính đáng trong mã.

phương pháp bên ngoài

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

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

Một lớp phụ thuộc vào các chi tiết triển khai của lớp khác.

Cuộc gọi lớp học dài

Một lớp gọi một lớp khác, lớp này yêu cầu dữ liệu từ lớp thứ ba, lớp này lấy 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à phụ thuộc nhiều vào cấu trúc lớp hiện tại.

Lớp đại lý nhiệm vụ

Một lớp chỉ cần thiết để gửi một nhiệm vụ đến 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ẽ thảo luận về các kỹ thuật tái cấu trúc cơ bản có thể giúp loại bỏ mùi mã được mô tả.

Trích xuất một lớp

Một lớp thực hiện quá nhiều chức năng. Một số trong số họ phải được chuyển đến lớp khác. Ví dụ: giả sử chúng ta có một Humanlớp cũng lưu địa chỉ nhà riêng và có một phương thức trả về đị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();
    }
 }
Cách tốt nhất là đặt thông tin địa chỉ và phương thức liên quan (hành vi xử lý dữ liệu) vào một lớp riêng biệt:
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();
   }
}

Trích xuất một phương thức

Nếu một phương thức có một số chức năng có thể được tách biệt, bạn nên đặt nó trong một phương thức riêng biệt. 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");
    }
}
Chúng tôi tính toán từng tùy chọn trong số ba tùy chọn có thể theo 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ã của mỗi phương thức đã trở nên ngắn hơn và dễ hiểu hơn nhiều.

Truyền toàn bộ đối tượng

Khi một phương thức được gọi với các tham số, đôi khi bạn có thể thấy mã như sau:
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;
}
Toàn employeeMethodbộ có 2 dòng dành cho việc nhận các giá trị và lưu trữ chúng trong các biến nguyên thủy. Đôi khi các cấu trúc như vậy có thể mất tới 10 dòng. Việc truyền chính đối tượng đó và sử dụng nó để trích xuất dữ liệu cần thiết sẽ dễ dàng hơn nhiều:
public void employeeMethod(Employee employee) {
    // Some actions
    double monthlySalary = getMonthlySalary(employee);
    // Continue processing
}

public double getMonthlySalary(Employee employee) {
    return (employee.getYearlySalary() + employee.getAwards())/12;
}

Đơn giản, ngắn gọn và súc tích.

Việc nhóm các trường một cách hợp lý và chuyển chúng thành một khu vực riêng biệt classDespiteThực tế là các ví dụ trên rất đơn giản và khi nhìn vào chúng, nhiều bạn có thể hỏi: "Ai làm việc này?", nhiều nhà phát triển mắc lỗi cấu trúc như vậy do bất cẩn, không sẵn sàng cấu trúc lại mã hoặc đơn giản là thái độ "thế là đủ tố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à chương trình có mã dễ đọc, khả năng thay đổi logic của nó không đáng sợ và việc giới thiệu các tính năng mới không trở thành địa ngục phân tích mã mà thay vào đó là một trải nghiệm thú vị trong vài ngày . Bạn không nên cấu trúc lại nếu việc viết chương trình từ đầu sẽ dễ dàng hơn. Ví dụ: giả sử nhóm của bạn ước tính rằng nhân công cần thiết để hiểu, phân tích và cấu trúc lại mã sẽ nhiều hơn so với việc thực hiện chức năng tương tự từ đầu. Hoặc nếu mã được tái cấu trúc có nhiều vấn đề khó gỡ lỗi. Biết cách cải thiện cấu trúc mã là điều cần thiết trong công việc của một lập trình viên. Và học cách lập trình bằng Java được thực hiện tốt nhất trên CodeGym, khóa học trực tuyến nhấn mạnh vào thực hành. Hơn 1200 nhiệm vụ với xác minh ngay lập tức, khoảng 20 dự án nhỏ, nhiệm vụ trò chơi — tất cả đ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ờ :)

Tài nguyên để tiếp tục đắm mình trong tái cấu trúc

Cuốn sách nổi tiếng nhất về tái cấu trúc là "Tái cấu trúc. 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, dựa trên cuốn sách trước đó: "Tái cấu trúc bằng cách sử dụng các mẫu" của Joshua Kerievsky. Nói về các mẫu... Khi tái cấu trúc, việc biết các mẫu thiết kế 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 bạn điều này: Nói về các mẫu... Khi tái cấu trúc, việc biết các mẫu thiết kế 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 với điều này:
  1. "Các mẫu thiết kế" của Eric Freeman, Elizabeth Robson, Kathy Sierra và Bert Bates, từ sê-ri Head First
  2. "Nghệ thuật mã có thể đọc được" của Dustin Boswell và Trevor Foucher
  3. "Code Complete" của Steve McConnell, đặt ra các nguyên tắc để viết mã đẹp và thanh lịch.
Bình luận
  • Phổ biến
  • Mới
Bạn phải đăng nhập để đăng nhận xet
Trang này chưa có bất kỳ bình luận nào