Đây có phải là sử dụng điều kiện một mô hình chống?


14

Tôi đã thấy điều này rất nhiều trong hệ thống di sản của chúng tôi tại nơi làm việc - các chức năng hoạt động giống như thế này:

bool todo = false;
if(cond1)
{
  ... // lots of code here
  if(cond2)
    todo = true;
  ... // some other code here
}

if(todo)
{
  ...
}

Nói cách khác, chức năng có hai phần. Phần đầu tiên thực hiện một số loại xử lý (có khả năng chứa các vòng lặp, tác dụng phụ, v.v.) và trên đường đi, nó có thể đặt cờ "việc cần làm". Phần thứ hai chỉ được thực hiện nếu cờ "việc cần làm" đã được đặt.

Nó có vẻ như là một cách khá xấu xí để làm mọi thứ, và tôi nghĩ rằng hầu hết các trường hợp mà tôi thực sự đã dành thời gian để hiểu, có thể được tái cấu trúc để tránh sử dụng cờ. Nhưng đây có phải là một mô hình chống thực tế, một ý tưởng tồi, hoặc hoàn toàn chấp nhận được?

Tái cấu trúc rõ ràng đầu tiên sẽ là cắt nó thành hai phương pháp. Tuy nhiên, câu hỏi của tôi là về việc có bao giờ cần (bằng ngôn ngữ OO hiện đại) để tạo biến cờ cục bộ hay không, có khả năng đặt nó ở nhiều nơi và sau đó sử dụng nó để quyết định có thực thi khối mã tiếp theo hay không.


2
Làm thế nào để bạn tái cấu trúc nó?
Tamás Szelei

13
Giả sử việc cần làm được đặt ở một số nơi, theo một số điều kiện không độc quyền không tầm thường, tôi khó có thể nghĩ đến một phép tái cấu trúc có ý nghĩa nhỏ nhất. Nếu không có tái cấu trúc, thì không có antipotype. Ngoại trừ việc đặt tên biến cần làm; nên được đặt tên biểu cảm hơn, như "doSecurityCheck".
user281377

3
@ammoQ: +1; nếu mọi thứ phức tạp thì đó là như thế. Một biến cờ có thể có ý nghĩa hơn nhiều trong một số trường hợp vì nó làm cho rõ ràng hơn rằng quyết định đã được đưa ra và bạn có thể tìm kiếm nó để tìm ra quyết định đó được đưa ra ở đâu.
Donal Fellows

1
@Donal Fellows: Nếu tìm kiếm lý do là cần thiết, tôi sẽ biến biến thành một danh sách; miễn là nó trống rỗng, nó "sai"; bất cứ nơi nào cờ được đặt, mã lý do sẽ được thêm vào danh sách. Vì vậy, bạn có thể kết thúc với một danh sách như thế ["blacklisted-domain","suspicious-characters","too-long"]cho thấy một số lý do được áp dụng.
user281377

2
Tôi không nghĩ đó là một mô hình chống, nhưng nó chắc chắn là một mùi
Binary Worrier

Câu trả lời:


23

Tôi không biết về chống mẫu, nhưng tôi sẽ trích xuất ba phương pháp từ đây.

Việc đầu tiên sẽ thực hiện một số công việc và trả về giá trị boolean.

Thứ hai sẽ thực hiện bất cứ công việc nào được thực hiện bởi "một số mã khác"

Thứ ba sẽ thực hiện công việc phụ trợ nếu boolean trả về là đúng.

Các phương thức được trích xuất có thể là riêng tư nếu điều quan trọng là chỉ thứ hai (và luôn luôn) được gọi nếu phương thức đầu tiên trả về đúng.

Bằng cách đặt tên cho các phương thức tốt, tôi hy vọng nó sẽ làm cho mã rõ ràng hơn.

Một cái gì đó như thế này:

public void originalMethod() {
    bool furtherProcessingRequired = lotsOfCode();
    someOtherCode();
    if (furtherProcessingRequired) {
        doFurtherProcessing();
    }
    return;
}

private boolean lotsOfCode() {
    if (cond1) {
        ... // lots of code here
        if(cond2) {
            return true;
        }
    }
    return false;
}

private void someOtherCode() {
    ... // some other code here
}

private void doFurtherProcessing() {
    // Do whatever is needed
}

Rõ ràng có tranh luận về việc liệu lợi nhuận sớm có được chấp nhận hay không, nhưng đó là một chi tiết thực hiện (như tiêu chuẩn định dạng mã).

Điểm đáng chú ý là ý định của mã trở nên rõ ràng hơn, điều này tốt ...

Một trong những ý kiến ​​về câu hỏi cho thấy mô hình này đại diện cho một mùi , và tôi sẽ đồng ý với điều đó. Thật đáng để nhìn vào nó để xem bạn có thể làm cho ý định rõ ràng hơn không.


Việc chia thành 2 chức năng vẫn sẽ cần một todobiến và có lẽ sẽ khó hiểu hơn.
Pubby

Vâng, tôi cũng sẽ làm điều đó, nhưng câu hỏi của tôi là về cách sử dụng cờ "việc cần làm".
Kricket

2
Nếu bạn kết thúc với if (check_if_needed ()) do_whatever ();, không có cờ rõ ràng ở đó. Tôi nghĩ rằng điều này có thể phá vỡ mã quá nhiều và có khả năng gây hại cho khả năng đọc nếu mã khá đơn giản. Rốt cuộc, các chi tiết về những gì bạn làm do_whatevercó thể ảnh hưởng đến cách bạn kiểm tra check_if_needed, do đó thật hữu ích khi giữ tất cả các mã trong cùng một màn hình. Ngoài ra, điều này không đảm bảo check_if_neededcó thể tránh sử dụng cờ - và nếu có, nó có thể sẽ sử dụng nhiềureturn câu lệnh để làm điều đó, có thể làm đảo lộn những người ủng hộ lối thoát đơn nghiêm ngặt.
Steve314

3
@ Pubby8, ông nói "trích xuất 2 phương pháp từ đây" , dẫn đến 3 phương thức. 2 phương thức thực hiện xử lý thực tế và phương thức ban đầu điều phối quy trình công việc. Đây sẽ là một thiết kế sạch hơn nhiều.
MattDavey

Điều này bỏ qua ... // some other code heretrong trường hợp trả lại sớm
Caleth

6

Tôi nghĩ rằng sự xấu xí là do có rất nhiều mã trong một phương thức duy nhất và / hoặc các biến được đặt tên xấu (cả hai đều là mã có mùi theo cách riêng của chúng - antipotype là những thứ trừu tượng và phức tạp hơn IMO).

Vì vậy, nếu bạn trích xuất hầu hết mã vào các phương thức cấp thấp hơn như @Bill gợi ý, phần còn lại sẽ trở nên sạch sẽ (ít nhất là với tôi). Ví dụ

bool registrationNeeded = installSoftware(...);
if (registrationNeeded) {
  registerUser(...)
}

Hoặc thậm chí bạn có thể loại bỏ hoàn toàn cờ cục bộ bằng cách ẩn kiểm tra cờ vào phương thức thứ hai và sử dụng một biểu mẫu như

calculateTaxRefund(isTaxRefundable(...), ...)

Nhìn chung, tôi không thấy có một biến cờ cục bộ là nhất thiết phải xấu mỗi se. Tùy chọn nào ở trên dễ đọc hơn (= thích hợp hơn với tôi) phụ thuộc vào số lượng tham số phương thức, tên được chọn và hình thức nào phù hợp hơn với logic bên trong của mã.


4

todo là một tên thực sự xấu cho biến, nhưng tôi nghĩ đó có thể là tất cả sai. Thật khó để hoàn toàn chắc chắn nếu không có bối cảnh.

Giả sử phần thứ hai của hàm sắp xếp một danh sách, được xây dựng bởi phần thứ nhất. Điều này sẽ dễ đọc hơn nhiều:

bool requiresSorting = false;
if(cond1)
{
    ... // lots of code here
    if(cond2)
        requiresSorting = true;
    ... // some other code here
}

if(requiresSorting)
{
    ...
}

Tuy nhiên, đề nghị của Bill cũng đúng. Điều này vẫn dễ đọc hơn:

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

Tại sao không chỉ đi thêm một bước nữa: if (BuildList (list)) SortList (list);
Phil N DeBlanc

2

Các mẫu máy trạng thái có vẻ tốt với tôi. Các mẫu chống trong đó là "todo" (tên xấu) và "rất nhiều mã".


Tôi chắc chắn rằng đó chỉ là để minh họa.
Loren Pechtel

1
Đã đồng ý. Những gì tôi đã cố gắng truyền đạt là các mẫu tốt bị chìm trong mã kém không nên đổ lỗi cho chất lượng của mã.
ptyx

1

Nó phụ thuộc thực sự. Nếu mã được bảo vệ bởitodo (tôi hy vọng bạn không sử dụng tên đó vì nó hoàn toàn không có ý nghĩa!) Là mã dọn sạch về mặt khái niệm, thì bạn đã có một mẫu chống và nên sử dụng một cái gì đó như RAII hoặc C # của C ++ usingxây dựng để xử lý mọi thứ thay vào đó.

Mặt khác, nếu về mặt khái niệm không phải là giai đoạn dọn dẹp mà chỉ là xử lý bổ sung đôi khi cần thiết và khi quyết định thực hiện cần phải được thực hiện sớm hơn, thì những gì được viết là tốt. Xem xét liệu các đoạn mã riêng lẻ có được tái cấu trúc tốt hơn thành các hàm riêng của chúng hay không, và liệu bạn đã nắm bắt được ý nghĩa của biến cờ trong tên của nó hay chưa, nhưng mẫu mã cơ bản này có ổn không. Cụ thể, việc cố gắng đưa quá nhiều vào các chức năng khác có thể khiến những gì đang diễn ra không rõ ràng và đó chắc chắn sẽ là một mô hình chống đối.


Đây rõ ràng không phải là một dọn dẹp - nó không luôn luôn chạy. Tôi đã gặp các trường hợp như thế này trước đây - trong khi xử lý một cái gì đó bạn có thể sẽ vô hiệu hóa một số loại kết quả được tính toán trước. Nếu tính toán đắt tiền, bạn chỉ muốn chạy nó nếu cần.
Loren Pechtel

1

Nhiều câu trả lời ở đây sẽ gặp khó khăn khi vượt qua kiểm tra độ phức tạp, một số ít nhìn> 10.

Tôi nghĩ rằng đây là phần "chống mẫu" của những gì bạn đang xem. Tìm một công cụ đo độ phức tạp theo chu kỳ của mã của bạn - có các plugin để nhật thực. Về cơ bản, đây là phép đo mức độ khó của mã để kiểm tra và liên quan đến số lượng và cấp độ của các nhánh mã.

Như một phỏng đoán tổng thể về một giải pháp khả thi, cách bố trí loại mã của bạn khiến tôi phải suy nghĩ trong "Nhiệm vụ", nếu điều này xảy ra ở nhiều nơi có lẽ điều bạn thực sự muốn là một kiến ​​trúc hướng nhiệm vụ - mỗi tác vụ là của riêng nó đối tượng và ở giữa nhiệm vụ, bạn có khả năng chinh phục nhiệm vụ tiếp theo bằng cách khởi tạo một đối tượng nhiệm vụ khác và ném nó vào hàng đợi. Đây có thể là đơn giản đáng kinh ngạc để thiết lập và chúng làm giảm đáng kể sự phức tạp của một số loại mã nhất định - nhưng như tôi đã nói đây là một cú đâm hoàn toàn trong bóng tối.


1

Sử dụng ví dụ của pdr ở trên, vì đây là một ví dụ hay, tôi sẽ tiến thêm một bước nữa.

Anh ấy đã có:

bool requiresSorting = BuildList(list);
if (requiresSorting)
    SortList(list);

Vì vậy, tôi nhận ra rằng những điều sau đây sẽ làm việc:

if(BuildList(list)) 
    SortList(list)

Nhưng không rõ ràng lắm.

Vì vậy, với câu hỏi ban đầu, tại sao không có:

BuildList(list)
SortList(list)

Và để SortList quyết định nếu nó yêu cầu sắp xếp?

Bạn thấy phương thức BuildList của bạn dường như biết về cách sắp xếp, vì nó trả về một bool chỉ ra như vậy, nhưng điều đó không có nghĩa đối với một phương thức được thiết kế để xây dựng một danh sách.


Và tất nhiên bước tiếp theo là hỏi tại sao đây là quá trình hai bước. Bất cứ nơi nào tôi thấy mã như thế tôi đều cấu trúc lại một phương thức gọi là BuildAndSortList (danh sách)
Ian

Đây không phải là một câu trả lời. Bạn đã thay đổi hành vi của mã.
D Drmmr

Không hẳn vậy. Một lần nữa, tôi không thể tin rằng tôi đang trả lời một cái gì đó tôi đã đăng 7 năm trước, nhưng cái quái gì vậy :) Điều tôi đang tranh luận là SortList sẽ chứa điều kiện. Nếu bạn có một bài kiểm tra đơn vị khẳng định rằng danh sách chỉ được sắp xếp nếu điều kiện x được đáp ứng, nó vẫn sẽ vượt qua. Bằng cách di chuyển điều kiện vào SortList, bạn tránh phải luôn luôn viết (nếu (một cái gì đó) sau đó SortList (...))
Ian

0

Có, đây có vẻ là một vấn đề vì bạn phải tiếp tục theo dõi tất cả các địa điểm bạn đang đánh dấu cờ BẬT / TẮT. Tốt hơn là bao gồm logic ngay bên trong dưới dạng lồng nếu điều kiện thay vì lấy logic ra.

Ngoài ra các mô hình miền phong phú, trong trường hợp đó chỉ cần một lớp lót sẽ làm những việc lớn bên trong đối tượng.


0

Nếu cờ chỉ được đặt một lần thì hãy di chuyển
...
mã lên trực tiếp sau
... // một số mã khác tại đây
sau đó loại bỏ cờ.

Nếu không thì chia
... // rất nhiều mã ở đây
... // một số mã khác ở đây
...
mã ra thành các hàm riêng biệt nếu có thể, vì vậy rõ ràng hàm này có một trách nhiệm là logic nhánh.

Bất cứ nơi nào có thể ngăn cách mã trong
... // rất nhiều mã ở đây
thành hai hoặc nhiều hàm, một số thực hiện một số công việc (là lệnh) và các mã khác trả về giá trị việc cần làm (là truy vấn) hoặc thực hiện rất rõ ràng họ đang sửa đổi nó (một truy vấn sử dụng các hiệu ứng phụ)

Bản thân mã không phải là kiểu chống mẫu đang diễn ra ở đây ... Tôi nghi ngờ rằng việc trộn lẫn logic phân nhánh với việc thực hiện các công cụ (lệnh) là mô hình chống mà bạn đang tìm kiếm.


bài đăng này thêm gì mà câu trả lời hiện có bị thiếu?
esoterik ngày

@esoterik Đôi khi cơ hội để thêm một chút CQRS thường bị bỏ qua khi nói đến cờ ... logic để quyết định thay đổi cờ đại diện cho một truy vấn, trong khi thực hiện công việc đại diện cho một lệnh. Đôi khi tách biệt hai có thể làm cho mã rõ ràng hơn. Ngoài ra, đáng để chỉ ra trong đoạn mã trên rằng nó có thể được đơn giản hóa vì cờ chỉ được đặt trong một nhánh. Tôi cảm thấy cờ không phải là một antipotype và nếu tên của chúng thực sự làm cho mã trở nên biểu cảm hơn thì chúng là một điều tốt. Tôi cảm thấy các cờ được tạo, đặt và sử dụng phải gần nhau trong mã nếu có thể.
pate

0

Đôi khi tôi thấy tôi cần phải thực hiện mô hình này. Đôi khi bạn muốn thực hiện nhiều kiểm tra trước khi tiến hành một thao tác. Vì lý do hiệu quả, các tính toán liên quan đến một số kiểm tra nhất định không được thực hiện trừ khi có vẻ cần thiết phải kiểm tra. Vì vậy, bạn thường thấy mã như:

// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(valuesExist) {
    try {
      // Attempt insertion
      trx.commit();
    } catch (DatabaseException dbe) {
      trx.rollback();
      throw dbe;
    }
  } else {
    closeConnection(db);
    throwException();
  }
} else {
  closeConnection(db);
  throwException();
}

Điều này có thể được đơn giản hóa bằng cách tách xác thực khỏi quy trình thực hiện thao tác thực tế, vì vậy bạn sẽ thấy nhiều hơn như:

boolean proceed = true;
// Check individual fields for proper input

if(fieldsValidated) {
  // Perform cross-checks to see if input contains values which exist in the database

  if(!valuesExist) {
    proceed = false;
  }
} else {
  proceed = false;
}

// The moment of truth
if(proceed) {
  try {
    // Attempt insertion
    trx.commit();
  } catch (DatabaseException dbe) {
    trx.rollback();
    throw dbe;
  }
} else {
  if(db.isOpen()) {
    closeConnection(db);
  }
  throwException();
}

Rõ ràng nó thay đổi tùy theo những gì bạn đang cố gắng đạt được, mặc dù được viết như thế này, cả mã "thành công" và mã "thất bại" của bạn đều được viết một lần, giúp đơn giản hóa logic và duy trì cùng mức hiệu suất. Từ đó, sẽ là một ý tưởng tốt để phù hợp với toàn bộ các cấp độ xác nhận bên trong các phương thức bên trong, trả về các dấu hiệu thành công hay thất bại boolean đơn giản hóa mọi thứ, mặc dù một số lập trình viên thích các phương pháp cực kỳ dài vì một lý do kỳ lạ.


Trong ví dụ bạn đã đưa ra, tôi nghĩ rằng tôi muốn có một hàm ShouldIDoIt (FieldValidated, valueExist) trả về câu trả lời. Điều này là do tất cả quyết định có / không được đưa ra cùng một lúc, trái ngược với mã mà tôi thấy ở đây tại nơi làm việc, nơi quyết định tiến hành được phân tán thành một vài điểm không tiếp giáp khác nhau.
Kricket

@KelseyRider, đó chính xác là vấn đề. Việc tách xác thực khỏi thực thi cho phép bạn nhồi logic vào một phương thức để đơn giản hóa logic tổng thể của chương trình thành if (isValidated ()) doOperation ()
Neil

0

Đây không phải là một mô hình . Giải thích chung nhất là bạn đang thiết lập một biến boolean và phân nhánh trên giá trị của nó sau này. Đó là lập trình thủ tục bình thường, không có gì hơn.

Bây giờ, ví dụ cụ thể của bạn có thể được viết lại thành:

if(cond1)
{
    ... // lots of code here
    ... // some other code here
    if (cond2)
    {
        ...
    }
}

Điều đó có thể dễ đọc hơn. Hoặc có thể không. Nó phụ thuộc vào phần còn lại của mã mà bạn bỏ qua. Tập trung vào làm cho mã đó ngắn gọn hơn.


-1

Các cờ cục bộ được sử dụng cho luồng điều khiển phải được công nhận là một dạng goto ngụy trang. Nếu một cờ chỉ được sử dụng trong một chức năng, nó có thể được loại bỏ bằng cách viết ra hai bản sao của chức năng, gắn nhãn một là "cờ là đúng" và một là "cờ là sai" và thay thế mọi thao tác đặt cờ khi nó rõ ràng hoặc xóa nó khi nó được đặt, với một bước nhảy giữa hai phiên bản của chức năng.

Trong nhiều trường hợp, mã sử dụng cờ sẽ sạch hơn bất kỳ giải pháp thay thế nào có thể sử dụng gotothay thế, nhưng điều đó không phải lúc nào cũng đúng. Trong một số trường hợp, sử dụng gotođể bỏ qua một đoạn mã có thể sạch hơn sử dụng cờ để làm như vậy [mặc dù một số người có thể chèn một phim hoạt hình raptor nào đó ở đây].

Tôi nghĩ rằng nguyên tắc hướng dẫn chính phải là luồng logic chương trình sẽ giống với mô tả logic nghiệp vụ đến mức có thể. Nếu các yêu cầu logic nghiệp vụ được mô tả theo các trạng thái phân tách và hợp nhất theo những cách kỳ lạ, việc logic chương trình thực hiện tương tự có thể sạch hơn so với cố gắng sử dụng cờ để ẩn logic đó. Mặt khác, nếu cách mô tả quy tắc kinh doanh tự nhiên nhất sẽ nói rằng một hành động nên được thực hiện nếu một số hành động khác đã được thực hiện, cách thể hiện tự nhiên nhất có thể là sử dụng cờ được đặt khi thực hiện các hành động sau và rõ ràng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.