Là một biểu thức boolean lớn dễ đọc hơn biểu thức tương tự được chia thành các phương thức vị ngữ? [đóng cửa]


63

Điều gì dễ hiểu hơn, một câu lệnh boolean lớn (khá phức tạp) hoặc cùng một câu lệnh được chia thành các phương thức vị ngữ (rất nhiều mã bổ sung để đọc)?

Tùy chọn 1, biểu thức boolean lớn:

    private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
    {

        return propVal.PropertyId == context.Definition.Id
            && !repo.ParentId.HasValue || repo.ParentId == propVal.ParentId
            && ((propVal.SecondaryFilter.HasValue && context.SecondaryFilter.HasValue && propVal.SecondaryFilter.Value == context.SecondaryFilter) || (!context.SecondaryFilter.HasValue && !propVal.SecondaryFilter.HasValue));
    }

Tùy chọn 2, Các điều kiện được chia thành các phương thức vị ngữ:

    private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
    {
        return MatchesDefinitionId(context, propVal)
            && MatchesParentId(propVal)
            && (MatchedSecondaryFilter(context, propVal) || HasNoSecondaryFilter(context, propVal));
    }

    private static bool HasNoSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
    {
        return (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);
    }

    private static bool MatchedSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
    {
        return (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
    }

    private bool MatchesParentId(TValToMatch propVal)
    {
        return (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
    }

    private static bool MatchesDefinitionId(CurrentSearchContext context, TValToMatch propVal)
    {
        return propVal.PropertyId == context.Definition.Id;
    }

Tôi thích cách tiếp cận thứ hai, vì tôi xem tên phương thức là nhận xét, nhưng tôi hiểu rằng nó có vấn đề vì bạn phải đọc tất cả các phương thức để hiểu mã làm gì, vì vậy nó trừu tượng hóa ý định của mã.


13
Phương án 2 tương tự như những gì Martin Fowler đề xuất trong cuốn sách tái cấu trúc của mình. Cộng với tên phương thức của bạn đóng vai trò là mục đích của tất cả các biểu thức ngẫu nhiên, nội dung của các phương thức chỉ là chi tiết triển khai có thể thay đổi theo thời gian.
lập trình viên

2
Có thực sự là cùng một biểu hiện? "Hoặc" có mức độ ưu tiên thấp hơn "Và", Dù sao thì cái thứ hai nói lên ý định của bạn, cái còn lại (thứ nhất) là kỹ thuật.
đóng gói

3
Những gì @thepacker nói. Thực tế là làm theo cách đầu tiên đã khiến bạn mắc lỗi là một manh mối khá hay mà cách đầu tiên không dễ hiểu đối với một lĩnh vực rất quan trọng trong đối tượng mục tiêu của bạn. Bản thân bạn!
Steve Jessop

3
Tùy chọn 3: Tôi không thích một trong hai. Thứ hai là dài dòng lố bịch, thứ nhất không tương đương với thứ hai. Dấu ngoặc giúp.
David Hammen

3
Điều này có thể là phạm vi, nhưng bạn không có bất kỳ if câu lệnh nào trong cả hai khối mã. Câu hỏi của bạn là về biểu thức Boolean .
Kyle Strand

Câu trả lời:


88

Những gì dễ hiểu hơn

Cách tiếp cận sau. Nó không chỉ dễ hiểu hơn mà còn dễ viết, kiểm tra, tái cấu trúc và mở rộng hơn. Mỗi điều kiện cần có thể được tách rời và xử lý một cách an toàn theo cách riêng của nó.

nó có vấn đề vì bạn phải đọc tất cả các phương pháp để hiểu mã

Sẽ không có vấn đề gì nếu các phương thức được đặt tên đúng. Trong thực tế, nó sẽ dễ hiểu hơn vì tên phương thức sẽ mô tả ý định của điều kiện.
Đối với một người xem if MatchesDefinitionId()là giải thích nhiều hơnif (propVal.PropertyId == context.Definition.Id)

[Cá nhân, cách tiếp cận đầu tiên làm tôi lo lắng.]


12
Nếu tên phương thức là tốt, thì nó cũng dễ hiểu hơn.
Bовић 10/03/2016

Và xin vui lòng, làm cho chúng (tên phương thức) có ý nghĩa và ngắn. 20+ tên phương pháp chars đau mắt của tôi. MatchesDefinitionId()là đường biên giới.
Mindwin

2
@Mindwin Nếu có một sự lựa chọn giữa việc giữ các tên phương thức "ngắn" và giữ cho chúng có ý nghĩa, tôi nói hãy dùng cái sau mỗi lần. Ngắn là tốt, nhưng không phải chi phí dễ đọc.
Ajedi32

@ Ajedi32 người ta không phải viết một bài luận về những gì phương thức làm trên tên phương thức, hoặc có các tên phương thức đúng ngữ pháp. Nếu một trong những tiêu chuẩn viết tắt rõ ràng (trong toàn bộ nhóm hoặc tổ chức công việc) sẽ không thành vấn đề với tên ngắn và dễ đọc.
Mindwin 11/03/2016

Sử dụng luật của Zipf: làm cho mọi thứ dài dòng hơn để ngăn cản việc sử dụng chúng.
hoosierEE

44

Nếu đây là nơi duy nhất các hàm vị ngữ này sẽ được sử dụng, bạn cũng có thể sử dụng các boolbiến cục bộ:

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    bool matchesDefinitionId = (propVal.PropertyId == context.Definition.Id);
    bool matchesParentId = (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
    bool matchesSecondaryFilter = (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
    bool hasNoSecondaryFilter = (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);

    return matchesDefinitionId
        && matchesParentId
        && matchesSecondaryFilter || hasNoSecondaryFilter;
}

Chúng cũng có thể được chia nhỏ hơn và sắp xếp lại để làm cho chúng dễ đọc hơn, ví dụ như với

bool hasSecondaryFilter = propVal.SecondaryFilter.HasValue;

và sau đó thay thế tất cả các trường hợp propVal.SecondaryFilter.HasValue. Một điều ngay lập tức được đưa ra là hasNoSecondaryFiltersử dụng logic AND trên các HasValuethuộc tính bị phủ định , trong khi matchesSecondaryFiltersử dụng logic AND không bị phủ định HasValue- vì vậy nó không hoàn toàn ngược lại.


3
Giải pháp này khá tốt và tôi chắc chắn đã viết rất nhiều mã tương tự. Nó rất dễ đọc. Nhược điểm, so với giải pháp tôi đã đăng, là tốc độ. Với phương pháp này, bạn thực hiện một đống các bài kiểm tra có điều kiện bất kể điều gì. Trong giải pháp của tôi, các hoạt động có thể được giảm đáng kể dựa trên các giá trị được xử lý.
BuvinJ

5
@BuvinJ Các thử nghiệm như những cái được hiển thị ở đây nên khá rẻ, vì vậy trừ khi tôi biết một số điều kiện đắt tiền hoặc trừ khi đây là mã nhạy cảm hiệu năng cực kỳ, tôi sẽ dùng phiên bản dễ đọc hơn.
Svick

1
@svick Không có nghi ngờ gì về điều này không có khả năng giới thiệu một vấn đề hiệu suất hầu hết thời gian. Tuy nhiên, nếu bạn có thể giảm các hoạt động mà không mất khả năng đọc, thì tại sao không làm như vậy? Tôi không tin rằng điều này dễ đọc hơn nhiều so với giải pháp của tôi. Nó tự cung cấp tài liệu "tên" cho các bài kiểm tra - thật tuyệt ... Tôi nghĩ rằng nó thuộc về trường hợp sử dụng cụ thể và mức độ dễ hiểu của các bài kiểm tra theo cách riêng của chúng.
BuvinJ

Thêm bình luận cũng có thể giúp dễ đọc ...
BuvinJ

@BuvinJ Điều tôi thực sự thích về giải pháp này là bằng cách bỏ qua mọi thứ trừ dòng cuối cùng, tôi có thể nhanh chóng hiểu những gì nó đang làm. Tôi nghĩ rằng điều này là dễ đọc hơn.
Svick 10/03/2016

42

Nói chung, sau này được ưa thích.

Nó làm cho các trang web cuộc gọi tái sử dụng nhiều hơn. Nó hỗ trợ DRY (có nghĩa là bạn có ít nơi để thay đổi khi tiêu chí thay đổi và có thể làm điều đó đáng tin cậy hơn). Và rất thường những tiêu chí phụ đó là những thứ sẽ được sử dụng lại một cách độc lập ở nơi khác, cho phép bạn làm điều đó.

Ồ, và nó làm cho công cụ này dễ dàng hơn rất nhiều để kiểm tra đơn vị, giúp bạn tự tin rằng bạn đã thực hiện đúng.


1
Có, mặc dù câu trả lời của bạn cũng sẽ giải quyết việc sửa lỗi sử dụng repo, xuất hiện trường / thuộc tính tĩnh, tức là biến toàn cục. Các phương pháp thống kê nên có tính xác định và không sử dụng các biến toàn cục.
David Arno

3
@DavidArno - trong khi điều đó không tuyệt vời, nó có vẻ tiếp tuyến với câu hỏi trong tầm tay. Và không có nhiều mã hơn, thật hợp lý khi có một lý do bán hợp lệ để thiết kế hoạt động như vậy.
Telastyn

1
Vâng, không bao giờ repo. Tôi đã phải xáo trộn mã một chút, không muốn chia sẻ mã khách hàng như nó vốn có trên interwebs :)
Willem

23

Nếu đó là giữa hai lựa chọn này, thì lựa chọn sau là tốt hơn. Đây không phải là sự lựa chọn duy nhất, tuy nhiên! Làm thế nào về việc chia nhỏ chức năng đơn thành nhiều if? Kiểm tra các cách để thoát khỏi chức năng để tránh các thử nghiệm bổ sung, mô phỏng gần như "ngắn mạch" trong thử nghiệm một dòng.

Điều này dễ đọc hơn (bạn có thể cần kiểm tra lại logic cho ví dụ của mình, nhưng khái niệm này vẫn đúng):

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    if( propVal.PropertyId != context.Definition.Id ) return false;

    if( repo.ParentId.HasValue || repo.ParentId != propVal.ParentId ) return false;

    if( propVal.SecondaryFilter.HasValue && 
        context.SecondaryFilter.HasValue && 
        propVal.SecondaryFilter.Value == context.SecondaryFilter ) return true;

    if( !context.SecondaryFilter.HasValue && 
        !propVal.SecondaryFilter.HasValue) return true;

    return false;   
}

3
Tại sao tôi nhận được một downvote cho điều này trong vòng vài giây sau khi đăng nó? Vui lòng thêm một bình luận khi bạn downvote! Câu trả lời này hoạt động nhanh chóng và dễ đọc hơn. Vậy vấn đề là gì?
BuvinJ

2
@BuvinJ: Hoàn toàn không có gì sai với nó. Giống như mã gốc, ngoại trừ bạn không phải chiến đấu với hàng tá dấu ngoặc đơn và một dòng duy nhất kéo dài đến cuối màn hình. Tôi có thể đọc mã đó từ trên xuống dưới và hiểu nó ngay lập tức. Số lượng WTF = 0.
gnasher729

1
Trả về khác với ở cuối hàm làm cho mã ít đọc hơn, không dễ đọc hơn, IMO. Tôi thích điểm thoát duy nhất. Một số đối số tốt cả hai cách tại liên kết này. stackoverflow.com/questions/36707/ trên
Brad Thomas

5
@Brad thomas Tôi không thể đồng ý với điểm thoát duy nhất. Nó thường dẫn đến dấu ngoặc đơn lồng sâu. Sự trở lại kết thúc con đường vì vậy đối với tôi dễ đọc hơn nhiều.
Borjab 10/03/2016

1
@BradThomas Tôi hoàn toàn đồng ý với Borjab. Tránh làm tổ sâu thực sự là lý do tại sao tôi sử dụng phong cách này thường xuyên hơn là để phá vỡ các tuyên bố điều kiện dài. Tôi sử dụng để tìm cho mình viết mã với hàng tấn tổ. Sau đó, tôi bắt đầu tìm cách để hầu như không bao giờ đi sâu hơn một hoặc hai tổ, và mã của tôi đã trở nên dễ dàng hơn để đọc và duy trì do đó. Nếu bạn có thể tìm cách thoát khỏi chức năng của mình, hãy làm điều đó càng sớm càng tốt! Nếu bạn có thể tìm ra cách để tránh làm tổ sâu và điều kiện dài, hãy làm như vậy!
BuvinJ

10

Tôi thích tùy chọn 2 tốt hơn, nhưng sẽ đề xuất một thay đổi cấu trúc. Kết hợp hai kiểm tra trên dòng cuối cùng của điều kiện thành một cuộc gọi duy nhất.

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
    return MatchesDefinitionId(context, propVal)
        && MatchesParentId(propVal)
        && MatchesSecondaryFilterIfPresent(context, propVal);
}

private static bool MatchesSecondaryFilterIfPresent(CurrentSearchContext context, 
                                                    TValToMatch propVal)
{
    return MatchedSecondaryFilter(context, propVal) 
               || HasNoSecondaryFilter(context, propVal);
}

Lý do tôi đề nghị điều này là hai kiểm tra là một đơn vị chức năng duy nhất và dấu ngoặc đơn lồng trong một điều kiện dễ bị lỗi: Cả hai từ quan điểm ban đầu viết mã và từ quan điểm của người đọc nó. Điều này đặc biệt xảy ra nếu các thành phần phụ của biểu thức không theo cùng một mẫu.

Tôi không chắc MatchesSecondaryFilterIfPresent()là tên tốt nhất cho sự kết hợp; nhưng không có gì tốt hơn là ngay lập tức đến với tâm trí.


Rất hay, cố gắng giải thích những gì đang được thực hiện bên trong các phương thức thực sự tốt hơn là chỉ tái cấu trúc các cuộc gọi.
klaar 11/03/2016

2

Mặc dù trong C #, mã không hướng đối tượng. Nó đang sử dụng các phương thức tĩnh và trông giống như các trường tĩnh (ví dụ repo). Thông thường người ta cho rằng các số liệu thống kê làm cho mã của bạn khó tái cấu trúc và khó kiểm tra, trong khi cản trở khả năng sử dụng lại, và, cho câu hỏi của bạn: sử dụng tĩnh như thế này ít đọc và duy trì hơn so với xây dựng hướng đối tượng.

Bạn nên chuyển đổi mã này sang một hình thức hướng đối tượng hơn. Khi bạn làm như vậy, bạn sẽ thấy rằng có những nơi hợp lý để đặt mã so sánh các đối tượng, của các trường, v.v. Có khả năng sau đó bạn có thể yêu cầu các đối tượng tự so sánh, điều này sẽ làm giảm câu lệnh if lớn của bạn yêu cầu đơn giản để so sánh (ví dụ if ( a.compareTo (b) ) { }, có thể bao gồm tất cả các so sánh trường.)

C # có một bộ giao diện và tiện ích hệ thống phong phú để thực hiện so sánh trên các đối tượng và các lĩnh vực của chúng. Ngoài rõ ràng .Equalsphương pháp, đối với người mới bắt đầu, nhìn vào IEqualityComparer, IEquatablevà các tiện ích như System.Collections.Generic.EqualityComparer.Default.


0

Loại thứ hai chắc chắn được ưa thích, tôi đã thấy các trường hợp với cách đầu tiên và hầu như không thể đọc được. Tôi đã phạm sai lầm khi thực hiện nó theo cách đầu tiên và được yêu cầu thay đổi nó thành các phương thức vị ngữ.


0

Tôi sẽ nói rằng hai cái này giống nhau, NẾU bạn thêm một số khoảng trắng để dễ đọc và một số bình luận để giúp người đọc vượt qua những phần khó hiểu hơn.

Hãy nhớ rằng: bình luận tốt cho người đọc biết bạn đang nghĩ gì khi viết mã.

Với những thay đổi như tôi đã đề xuất, tôi có thể sẽ đi theo cách tiếp cận trước đây, vì nó ít lộn xộn và lan tỏa hơn. Các cuộc gọi chương trình con giống như chú thích: chúng cung cấp thông tin hữu ích nhưng làm gián đoạn dòng đọc. Nếu các vị từ phức tạp hơn, thì tôi sẽ chia chúng thành các phương thức riêng biệt để các khái niệm mà chúng thể hiện có thể được xây dựng thành các phần dễ hiểu.


Xứng đáng là +1. Thực phẩm tốt cho suy nghĩ, mặc dù không phải ý kiến ​​phổ biến dựa trên các câu trả lời khác. Cảm ơn :)
willem

1
@willem Không, nó không xứng đáng +1. Hai cách tiếp cận không giống nhau. Các ý kiến ​​bổ sung là ngu ngốc và không cần thiết.
Bовић 10/03/2016

2
Một mã tốt KHÔNG BAO GIỜ phụ thuộc vào ý kiến ​​để có thể hiểu được. Trong thực tế, các ý kiến ​​là sự lộn xộn tồi tệ nhất mà một mã có thể có. Các mã nên nói cho chính nó. Ngoài ra, hai cách tiếp cận mà OP muốn đánh giá không bao giờ có thể là "giống nhau", bất kể có bao nhiêu khoảng trắng mà người ta thêm vào.
wonderbell 10/03/2016

Nó là tốt hơn để có một tên hàm đầy đủ ý nghĩa hơn là phải đọc bình luận. Như đã nêu trong cuốn sách "Clean Code", một bình luận là không thể hiện mã ném. Tại sao giải thích những gì bạn đang làm khi chức năng có thể đã nói rõ hơn nhiều.
Borjab

0

Chà, nếu có những phần bạn có thể muốn sử dụng lại, tách chúng ra thành các chức năng được đặt tên riêng biệt rõ ràng là ý tưởng tốt nhất.
Ngay cả khi bạn có thể không bao giờ sử dụng lại chúng, làm như vậy có thể cho phép bạn cấu trúc tốt hơn các điều kiện của mình và cung cấp cho chúng một nhãn mô tả ý nghĩa của chúng .

Bây giờ, hãy xem xét tùy chọn đầu tiên của bạn và thừa nhận rằng cả thụt lề và ngắt dòng của bạn đều không hữu ích, cũng không phải là điều kiện có cấu trúc tốt như vậy:

private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal) {
    return propVal.PropertyId == context.Definition.Id && !repo.ParentId.HasValue
        || repo.ParentId == propVal.ParentId
        && propVal.SecondaryFilter.HasValue == context.SecondaryFilter.HasValue
        && (!propVal.SecondaryFilter.HasValue || propVal.SecondaryFilter.Value == context.SecondaryFilter.Value);
}

0

Điều đầu tiên là hoàn toàn khủng khiếp. Bạn đã và đang sử dụng | | cho hai điều trên cùng một dòng; đó là một lỗi trong mã của bạn hoặc có ý định làm xáo trộn mã của bạn.

    return (   (   propVal.PropertyId == context.Definition.Id
                && !repo.ParentId.HasValue)
            || (   repo.ParentId == propVal.ParentId
                && (   (   propVal.SecondaryFilter.HasValue
                        && context.SecondaryFilter.HasValue 
                        && propVal.SecondaryFilter.Value == context.SecondaryFilter)
                    || (   !context.SecondaryFilter.HasValue
                        && !propVal.SecondaryFilter.HasValue))));

Điều đó ít nhất là nửa chừng được định dạng (nếu định dạng phức tạp, đó là vì điều kiện if phức tạp) và bạn có ít nhất một cơ hội để tìm hiểu xem có gì vô lý không. So với rác của bạn được định dạng nếu, bất cứ điều gì khác là tốt hơn. Nhưng dường như bạn chỉ có thể thực hiện các thái cực: Hoặc là một mớ hỗn độn của một câu lệnh if hoặc bốn phương thức hoàn toàn vô nghĩa.

Lưu ý rằng (cond1 && cond2) || (! cond1 && cond3) có thể được viết là

cond1 ? cond2 : cond3

Điều này sẽ làm giảm sự lộn xộn. Tôi sẽ viết

if (propVal.PropertyId == context.Definition.Id && !repo.ParentId.HasValue) {
    return true;
} else if (repo.ParentId != propVal.ParentId) {
    return false;
} else if (propVal.SecondaryFilter.HasValue) {
    return (   context.SecondaryFilter.HasValue
            && propVal.SecondaryFilter.Value == context.SecondaryFilter); 
} else {
    return !context.SecondaryFilter.HasValue;
}

-4

Tôi không thích một trong những giải pháp đó, cả hai đều khó lý luận và khó đọc. Trừu tượng hóa các phương thức nhỏ hơn chỉ dành cho các phương thức nhỏ hơn vì không phải lúc nào cũng giải quyết được vấn đề.

Lý tưởng nhất, tôi nghĩ rằng bạn sẽ so sánh các thuộc tính siêu hình, vì vậy bạn không có định nghĩa một phương thức mới hoặc nếu nhánh mỗi khi bạn muốn so sánh một tập các thuộc tính mới.

Tôi không chắc chắn về c #, nhưng trong javascript, một cái gì đó như thế này sẽ tốt hơn và ít nhất có thể thay thế MatchesDefDefId và MatchesParentId

function compareContextProp(obj, property, value){
  if(obj[property])
    return obj[property] == value
  return false
}

1
Không nên là một vấn đề để thực hiện một cái gì đó như thế này trong C #.
Snoop

Tôi không thấy cách kết hợp boolean của ~ 5 cuộc gọi compareContextProp(propVal, "PropertyId", context.Definition.Id)sẽ dễ đọc hơn kết hợp boolean của OP với ~ 5 so sánh về hình thức propVal.PropertyId == context.Definition.Id. Nó dài hơn đáng kể và thêm một lớp bổ sung mà không thực sự che giấu bất kỳ sự phức tạp nào từ trang web cuộc gọi. (nếu có vấn đề, tôi đã không downvote)
Ixrec 22/03/2016
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.