CodeGym /Blog Java /Ngẫu nhiên /Chống mẫu là gì? Cùng xem một số ví dụ (Phần 2)
John Squirrels
Mức độ
San Francisco

Chống mẫu là gì? Cùng xem một số ví dụ (Phần 2)

Xuất bản trong nhóm
Chống mẫu là gì? Hãy xem xét một số ví dụ (Phần 1) Hôm nay chúng ta tiếp tục xem xét các anti-pattern phổ biến nhất. Nếu bạn bỏ lỡ phần đầu tiên, nó đây . Chống mẫu là gì?  Cùng xem một số ví dụ (Phần 2) - 1Vì vậy, các mẫu thiết kế là phương pháp hay nhất. Nói cách khác, chúng là những ví dụ về những cách giải quyết vấn đề cụ thể đã được thử nghiệm qua thời gian. Đổi lại, các mô hình phản đối hoàn toàn ngược lại với chúng, theo nghĩa chúng là các mô hình của những cạm bẫy hoặc sai lầm khi giải quyết các vấn đề khác nhau (các mô hình xấu xa). Hãy chuyển sang mô hình chống phát triển phần mềm tiếp theo.

8. Búa vàng

Búa vàng là một phản mẫu được xác định bởi sự tự tin rằng một giải pháp cụ thể có thể áp dụng phổ biến. Ví dụ:
  1. Sau khi gặp một vấn đề và tìm ra một mô hình cho giải pháp hoàn hảo, một lập trình viên cố gắng áp dụng mô hình này vào mọi nơi, áp dụng nó cho các dự án hiện tại và tương lai, thay vì tìm kiếm các giải pháp thích hợp cho các trường hợp cụ thể.

  2. Một số nhà phát triển đã từng tạo biến thể bộ đệm của riêng họ cho một tình huống cụ thể (vì không có gì khác phù hợp). Sau đó, trong dự án tiếp theo không liên quan đến logic bộ đệm đặc biệt, họ đã sử dụng lại biến thể của mình thay vì sử dụng các thư viện được tạo sẵn (ví dụ: Ehcache). Kết quả là một loạt các lỗi và sự không tương thích, cũng như rất nhiều thời gian lãng phí và căng thẳng.

    Bất cứ ai cũng có thể rơi vào mô hình chống này. Nếu bạn là người mới bắt đầu, bạn có thể không am hiểu về các mẫu thiết kế. Điều này có thể khiến bạn cố gắng giải quyết mọi vấn đề theo một cách mà bạn đã thành thạo. Nếu chúng ta đang nói về các chuyên gia, thì chúng ta gọi đây là sự biến dạng chuyên nghiệp hoặc mọt sách. Bạn có các mẫu thiết kế ưa thích của riêng mình và thay vì sử dụng mẫu phù hợp, bạn sử dụng mẫu yêu thích của mình, giả sử rằng sự phù hợp tốt trong quá khứ sẽ đảm bảo kết quả tương tự trong tương lai.

    Cạm bẫy này có thể tạo ra những kết quả rất đáng buồn — từ việc triển khai tồi tệ, không ổn định và khó duy trì cho đến thất bại hoàn toàn của dự án. Cũng giống như không có một viên thuốc nào cho mọi bệnh tật, không có một mẫu thiết kế nào cho mọi trường hợp.

9. Tối ưu hóa sớm

Tối ưu hóa sớm là một anti-pattern mà cái tên của nó đã nói lên điều đó.
"Các lập trình viên dành rất nhiều thời gian để suy nghĩ và lo lắng về những vị trí không quan trọng trong mã và cố gắng tối ưu hóa chúng, điều này chỉ ảnh hưởng tiêu cực đến việc gỡ lỗi và hỗ trợ sau này. Nói chung, chúng ta nên quên việc tối ưu hóa trong 97% trường hợp. Hơn nữa, , tối ưu hóa sớm là gốc rễ của mọi tội lỗi. Nói như vậy, chúng ta phải hết sức chú ý đến 3% còn lại." — Donald Knuth
Ví dụ: thêm sớm các chỉ mục vào cơ sở dữ liệu. Tại sao điều đó là xấu? Chà, thật tệ ở chỗ, các chỉ mục được lưu trữ dưới dạng cây nhị phân. Do đó, mỗi khi một giá trị mới được thêm và xóa, cây sẽ được tính toán lại và điều này tiêu tốn tài nguyên và thời gian. Do đó, chỉ nên thêm các chỉ mục khi có nhu cầu cấp bách (nếu bạn có một lượng lớn dữ liệu và truy vấn mất quá nhiều thời gian) và chỉ dành cho các trường quan trọng nhất (các trường được truy vấn thường xuyên nhất).

10. Mã spaghetti

Mã spaghetti là một mã chống mẫu được xác định bởi mã có cấu trúc kém, khó hiểu và khó hiểu, chứa tất cả các loại phân nhánh, chẳng hạn như gói ngoại lệ, điều kiện và vòng lặp. Trước đây, toán tử goto là đồng minh chính của anti-pattern này. Các câu lệnh Goto không thực sự được sử dụng nữa, điều này giúp loại bỏ một số khó khăn và vấn đề liên quan.

public boolean someDifficultMethod(List<String> XMLAttrList) {
           ...
   int prefix = stringPool.getPrefixForQName(elementType);
   int elementURI;
   try {
       if (prefix == -1) {
        ...
           if (elementURI != -1) {
               stringPool.setURIForQName(...);
           }
       } else {
        ...
           if (elementURI == -1) {
           ...
           }
       }
   } catch (Exception e) {
       return false;
   }
   if (attrIndex != -1) {
       int index = attrList.getFirstAttr(attrIndex);
       while (index != -1) {
           int attName = attrList.getAttrName(index);
           if (!stringPool.equalNames(...)){
           ...
               if (attPrefix != namespacesPrefix) {
                   if (attPrefix == -1) {
                    ...
                   } else {
                       if (uri == -1) {
                       ...
                       }
                       stringPool.setURIForQName(attName, uri);
                   ...
                   }
                   if (elementDepth >= 0) {
                   ...
                   }
                   elementDepth++;
                   if (elementDepth == fElementTypeStack.length) {
                   ...
                   }
               ...
                   return contentSpecType == fCHILDRENSymbol;
               }
           }
       }
   }
}
Nó trông khủng khiếp, phải không? Thật không may, đây là anti-pattern phổ biến nhất :( Ngay cả người viết mã như vậy cũng sẽ không thể hiểu nó trong tương lai. Các nhà phát triển khác nhìn thấy mã sẽ nghĩ, "Chà, nếu nó hoạt động, thì được thôi — tốt hơn hết là đừng chạm vào nó". Thông thường, một phương pháp ban đầu rất đơn giản và rất minh bạch, nhưng khi các yêu cầu mới được thêm vào, phương pháp đó dần dần được thêm vào với ngày càng nhiều câu lệnh điều kiện, biến nó thành một thứ quái dị như thế này. Nếu một phương pháp như vậy xuất hiện, bạn cần phải cấu trúc lại nó hoàn toàn hoặc ít nhất là những phần khó hiểu nhất. Thông thường, khi lên lịch cho một dự án, thời gian được phân bổ cho việc tái cấu trúc, ví dụ, 30% thời gian chạy nước rút là dành cho việc tái cấu trúc và kiểm tra. Tất nhiên, điều này giả định rằng không có bất kỳ sự vội vàng nào (nhưng khi nào điều đó xảy ra).đây .

11. Những con số kỳ diệu

Số ma thuật là một dạng phản mẫu trong đó tất cả các loại hằng số được sử dụng trong một chương trình mà không có bất kỳ lời giải thích nào về mục đích hoặc ý nghĩa của chúng. Đó là, chúng thường được đặt tên kém hoặc trong những trường hợp cực đoan, không có bình luận nào giải thích những bình luận đó là gì hoặc tại sao. Giống như mã spaghetti, đây là một trong những anti-pattern phổ biến nhất. Một người nào đó không viết mã có thể có hoặc không có manh mối về những con số kỳ diệu hoặc cách chúng hoạt động (và theo thời gian, chính tác giả sẽ không thể giải thích chúng). Do đó, việc thay đổi hoặc xóa một số sẽ khiến mã ngừng hoạt động cùng nhau một cách kỳ diệu. Ví dụ: 3673. Để chống lại mô hình chống đối này, tôi khuyên bạn nên xem lại mã. Mã của bạn cần được xem xét bởi các nhà phát triển không tham gia vào các phần có liên quan của mã. Đôi mắt của họ sẽ tươi sáng và họ sẽ có câu hỏi: đây là gì và tại sao bạn lại làm như vậy? Và tất nhiên, bạn cần sử dụng tên giải thích hoặc để lại nhận xét.

12. Lập trình sao chép và dán

Lập trình sao chép và dán là một kiểu chống đối trong đó mã của người khác được sao chép và dán một cách thiếu suy nghĩ, có thể dẫn đến các tác dụng phụ không lường trước được. Ví dụ: các phương pháp sao chép và dán bằng các phép tính toán học hoặc các thuật toán phức tạp mà chúng tôi không hiểu đầy đủ. Nó có thể hiệu quả đối với trường hợp cụ thể của chúng ta, nhưng trong một số trường hợp khác, nó có thể dẫn đến rắc rối. Giả sử tôi cần một phương thức để xác định số lượng tối đa trong một mảng. Lục lọi trên Internet, tôi tìm thấy giải pháp này:

public static int max(int[] array) {
   int max = 0;
   for(int i = 0; i < array.length; i++) {
       if (Math.abs(array[i]) > max){
           max = array[i];
       }
   }
   return max;
}
Chúng tôi nhận được một mảng với các số 3, 6, 1, 4 và 2, và phương thức trả về 6. Tuyệt vời, hãy tiếp tục! Nhưng sau đó, chúng tôi nhận được một mảng bao gồm 2,5, -7, 2 và 3, và kết quả của chúng tôi là -7. Và kết quả này là không tốt. Vấn đề ở đây là Math.abs() trả về giá trị tuyệt đối. Sự thiếu hiểu biết về điều này dẫn đến thảm họa, nhưng chỉ trong một số tình huống nhất định. Nếu không tìm hiểu sâu về giải pháp, có nhiều trường hợp bạn sẽ không kiểm chứng được. Mã được sao chép cũng có thể vượt ra ngoài cấu trúc bên trong của ứng dụng, cả về mặt phong cách lẫn mức độ kiến ​​trúc, cơ bản hơn. Mã như vậy sẽ khó đọc và bảo trì hơn. Và tất nhiên, chúng ta không được quên rằng việc sao chép trực tiếp mã của người khác là một loại đạo văn đặc biệt.

13. Phát minh lại bánh xe

Phát minh lại bánh xe là một phản mẫu, đôi khi còn được gọi là phát minh lại bánh xe hình vuông. Về bản chất, mẫu này đối lập với mẫu chống sao chép và dán được xem xét ở trên. Trong mô hình chống đối này, nhà phát triển thực hiện giải pháp của riêng mình cho một vấn đề mà các giải pháp đã tồn tại. Đôi khi những giải pháp hiện có này tốt hơn những gì lập trình viên phát minh ra. Thông thường, điều này chỉ dẫn đến mất thời gian và năng suất thấp hơn: lập trình viên có thể không tìm ra giải pháp nào cả hoặc có thể tìm ra giải pháp không phải là giải pháp tốt nhất. Điều đó nói rằng, chúng ta không thể loại trừ khả năng tạo ra một giải pháp độc lập, bởi vì làm điều đó là con đường trực tiếp dẫn đến lập trình sao chép và dán. Lập trình viên nên được hướng dẫn bởi các nhiệm vụ lập trình cụ thể phát sinh để giải quyết chúng một cách thành thạo, cho dù bằng cách sử dụng các giải pháp làm sẵn hoặc bằng cách tạo các giải pháp tùy chỉnh. Rất thường xuyên, lý do sử dụng mô hình chống này chỉ đơn giản là vội vàng. Kết quả là một phân tích nông về (tìm kiếm) các giải pháp làm sẵn. Phát minh lại bánh xe hình vuông là trường hợp phản mẫu đang được xem xét có kết quả tiêu cực. Đó là, dự án yêu cầu một giải pháp tùy chỉnh và nhà phát triển tạo ra nó, nhưng thật tệ. Đồng thời, một tùy chọn tốt đã tồn tại và những người khác đang sử dụng nó thành công. Điểm mấu chốt: một lượng lớn thời gian bị mất. Đầu tiên, chúng tôi tạo ra thứ gì đó không hoạt động. Sau đó, chúng tôi cố gắng cấu trúc lại nó, và cuối cùng chúng tôi thay thế nó bằng thứ đã tồn tại. Một ví dụ là triển khai bộ đệm tùy chỉnh của riêng bạn khi đã có nhiều triển khai. Cho dù bạn là một lập trình viên tài năng đến đâu, bạn nên nhớ rằng việc phát minh lại bánh xe vuông ít nhất cũng là một sự lãng phí thời gian. Và, như bạn đã biết, thời gian là tài nguyên quý giá nhất.

14. Bài toán yo-yo

Vấn đề yo-yo là một phản mẫu trong đó cấu trúc của ứng dụng quá phức tạp do bị phân mảnh quá mức (ví dụ: chuỗi thừa kế được chia nhỏ quá mức). "Vấn đề yo-yo" phát sinh khi bạn cần hiểu một chương trình có hệ thống phân cấp kế thừa dài và phức tạp, tạo ra các lệnh gọi phương thức được lồng sâu. Do đó, các lập trình viên cần điều hướng giữa nhiều lớp và phương thức khác nhau để kiểm tra hành vi của chương trình. Tên của mô hình chống này xuất phát từ tên của đồ chơi. Ví dụ, hãy xem chuỗi kế thừa sau: Chúng ta có giao diện Công nghệ:

public interface Technology {
   void turnOn();
}
Giao diện Transport kế thừa nó:

public interface Transport extends Technology {
   boolean fillUp();
}
Và sau đó chúng ta có một giao diện khác, GroundTransport:

public interface GroundTransportation extends Transport {
   void startMove();
   void brake();
}
Và từ đó, chúng ta lấy được một lớp Car trừu tượng:

public abstract class Car implements GroundTransportation {
   @Override
   public boolean fillUp() {
       /* some implementation */
       return true;
   }
   @Override
   public void turnOn() {
       /* some implementation */
   }
   public boolean openTheDoor() {
       /* some implementation */
       return true;
   }
   public abstract void fixCar();
}
Tiếp theo là lớp Volkswagen trừu tượng:

public abstract class Volkswagen extends Car {
   @Override
   public void startMove() {
       /* some implementation */
   }
   @Override
   public void brake() {
       /* some implementation */
   }
}
Và cuối cùng, một mô hình cụ thể:

public class VolkswagenAmarok extends Volkswagen {
   @Override
   public void fixCar(){
       /* some implementation */
   }
}
Chuỗi này buộc chúng ta phải tìm kiếm câu trả lời cho những câu hỏi như:
  1. Có bao nhiêu phương pháp VolkswagenAmarok?

  2. Loại nào nên được chèn thay vì dấu chấm hỏi để đạt được mức độ trừu tượng tối đa:

    
    ? someObj = new VolkswagenAmarok();
           someObj.brake();
    
Thật khó để nhanh chóng trả lời những câu hỏi như vậy — nó đòi hỏi chúng ta phải xem xét và điều tra, và rất dễ nhầm lẫn. Và điều gì sẽ xảy ra nếu hệ thống phân cấp lớn hơn, dài hơn và phức tạp hơn nhiều, với đủ loại quá tải và ghi đè? Cấu trúc mà chúng ta có sẽ bị che khuất do sự phân mảnh quá mức. Giải pháp tốt nhất sẽ là giảm bớt sự phân chia không cần thiết. Trong trường hợp của chúng tôi, chúng tôi sẽ để Công nghệ → Ô tô → VolkswagenAmarok.

15. Sự phức tạp ngẫu nhiên

Sự phức tạp không cần thiết là một phản mẫu trong đó các biến chứng không cần thiết được đưa vào một giải pháp.
"Bất kỳ kẻ ngốc nào cũng có thể viết mã mà máy tính có thể hiểu được. Lập trình viên giỏi viết mã mà con người có thể hiểu được." — Martin Fowler
Vậy độ phức tạp là gì? Nó có thể được định nghĩa là mức độ khó mà mỗi thao tác được thực hiện trong chương trình. Theo quy định, sự phức tạp có thể được chia thành hai loại. Loại phức tạp đầu tiên là số chức năng mà một hệ thống có. Nó chỉ có thể được giảm theo một cách - bằng cách loại bỏ một số chức năng. Các phương pháp hiện có cần được theo dõi. Một phương thức nên được loại bỏ nếu nó không còn được sử dụng hoặc vẫn được sử dụng nhưng không mang lại bất kỳ giá trị nào. Hơn nữa, bạn cần đánh giá cách tất cả các phương pháp trong ứng dụng được sử dụng, để hiểu nơi nào đáng để đầu tư (tái sử dụng nhiều mã) và điều gì bạn có thể từ chối. Loại phức tạp thứ hai là phức tạp không cần thiết. Nó có thể được chữa khỏi chỉ thông qua một phương pháp chuyên nghiệp. Thay vì làm điều gì đó "hay ho" (các nhà phát triển trẻ không phải là những người duy nhất dễ mắc phải căn bệnh này), bạn cần nghĩ cách thực hiện nó đơn giản nhất có thể, vì giải pháp tốt nhất luôn đơn giản. Ví dụ: giả sử chúng ta có các bảng nhỏ liên quan với mô tả về một số thực thể, chẳng hạn như người dùng: Chống mẫu là gì?  Cùng xem một số ví dụ (Phần 2) - 3Vì vậy, chúng tôi có id của người dùng, id của ngôn ngữ mà mô tả được tạo và chính mô tả đó. Tương tự, chúng ta có các bộ mô tả phụ trợ cho các bảng car, files, plan và customers. Sau đó, sẽ như thế nào khi chèn các giá trị mới vào các bảng như vậy?

public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description)throws Exception {
   switch (type){
       case CAR:
           jdbcTemplate.update(CREATE_RELATION_WITH_CAR, languageId, serviceId, description);
       case USER:
           jdbcTemplate.update(CREATE_RELATION_WITH_USER, languageId, serviceId, description);
       case FILE:
           jdbcTemplate.update(CREATE_RELATION_WITH_FILE, languageId, serviceId, description);
       case PLAN:
           jdbcTemplate.update(CREATE_RELATION_WITH_PLAN, languageId, serviceId, description);
       case CUSTOMER:
           jdbcTemplate.update(CREATE_RELATION_WITH_CUSTOMER, languageId, serviceId, description);
       default:
           throw new Exception();
   }
}
Và theo đó, chúng ta có enum này:

public enum ServiceType {
   CAR(),
   USER(),
   FILE(),
   PLAN(),
   CUSTOMER()
}
Mọi thứ có vẻ đơn giản và tốt... Nhưng còn những phương pháp khác thì sao? Thật vậy, tất cả chúng cũng sẽ có một loạt switchcác câu lệnh và một loạt các truy vấn cơ sở dữ liệu gần như giống hệt nhau, do đó sẽ làm lớp của chúng ta phức tạp và phình to lên rất nhiều. Làm thế nào tất cả điều này có thể được thực hiện dễ dàng hơn? Hãy nâng cấp enum của chúng ta một chút:

@Getter
@AllArgsConstructor
public enum ServiceType {
   CAR("cars_descriptions", "car_id"),
   USER("users_descriptions", "user_id"),
   FILE("files_descriptions", "file_id"),
   PLAN("plans_descriptions", "plan_id"),
   CUSTOMER("customers_descriptions", "customer_id");
   private String tableName;
   private String columnName;
}
Bây giờ mỗi loại có tên của các trường ban đầu của bảng. Do đó, phương pháp tạo mô tả trở thành:

private static final String CREATE_RELATION_WITH_SERVICE = "INSERT INTO %s(language_id, %s, description) VALUES (?, ?, ?)";
public void createDescriptionForElement(ServiceType type, Long languageId, Long serviceId, String description) {
   jdbcTemplate.update(String.format(CREATE_RELATION_WITH_SERVICE, type.getTableName(), type.getColumnName()), languageId, serviceId, description);
   }
Thật tiện lợi, đơn giản và nhỏ gọn phải không nào? Dấu hiệu của một nhà phát triển giỏi thậm chí không phải là tần suất anh ta hoặc cô ta sử dụng các mẫu, mà là tần suất anh ta hoặc cô ta tránh các phản mẫu. Sự thiếu hiểu biết là kẻ thù tồi tệ nhất, bởi vì bạn cần biết kẻ thù của mình bằng mắt thường. Vâng, đó là tất cả những gì tôi có cho ngày hôm nay. Cảm ơn mọi người! :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION