Các khối khác có làm tăng độ phức tạp của mã không? [đóng cửa]


18

Đây là một ví dụ rất đơn giản . Đây không nhất thiết là một câu hỏi dành riêng cho ngôn ngữ và tôi yêu cầu bạn bỏ qua nhiều cách khác để chức năng có thể được viết và những thay đổi có thể được thực hiện đối với nó. . Màu sắc là một loại duy nhất

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    else
    {
        return "No you can't";
    }
}

Rất nhiều người tôi đã gặp, ReSharper và anh chàng này (có bình luận nhắc nhở tôi rằng tôi đã tìm cách hỏi điều này một thời gian) sẽ khuyên bạn nên cấu trúc lại mã để loại bỏ elsekhối này:

(Tôi không thể nhớ lại những gì đa số đã nói, tôi có thể không hỏi điều này nếu không)

string CanLeaveWithoutUmbrella()
{
    if(sky.Color.Equals(Color.Blue))
    {
        return "Yes you can";
    }
    return "No you can't";
}

Câu hỏi: Có sự gia tăng độ phức tạp được giới thiệu bằng cách không bao gồm elsekhối?

Tôi có ấn tượng về elseý định trực tiếp hơn, bằng cách nêu thực tế rằng mã trong cả hai khối có liên quan trực tiếp.

Ngoài ra, tôi thấy rằng tôi có thể ngăn ngừa các lỗi tinh vi trong logic, đặc biệt là sau khi sửa đổi mã vào một ngày sau đó.

Lấy biến thể này của ví dụ đơn giản hóa của tôi (Bỏ qua thực tế ortoán tử vì đây là ví dụ đơn giản hóa có chủ đích):

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

Bây giờ ai đó có thể thêm một ifkhối mới dựa trên một điều kiện sau ví dụ đầu tiên mà không nhận ra ngay điều kiện đầu tiên là đặt một ràng buộc cho điều kiện của chính họ.

Nếu một elsekhối có mặt, bất cứ ai thêm điều kiện mới sẽ bị buộc phải di chuyển nội dung của elsekhối (và nếu bằng cách nào đó, chúng che phủ nó, heuristic sẽ hiển thị mã không thể truy cập được, trong trường hợp không ifràng buộc mã này) .

Tất nhiên, có những cách khác mà ví dụ cụ thể nên được xác định bằng mọi cách, tất cả đều ngăn chặn tình huống đó, nhưng đó chỉ là một ví dụ.

Độ dài của ví dụ tôi đưa ra có thể làm lệch khía cạnh trực quan của điều này, vì vậy giả sử rằng không gian được đưa lên dấu ngoặc tương đối không đáng kể so với phần còn lại của phương thức.

Tôi đã quên đề cập đến một trường hợp tôi đồng ý với sự thiếu sót của một khối khác và khi sử dụng một ifkhối để áp dụng một ràng buộc phải được thỏa mãn một cách hợp lý cho tất cả các mã sau, chẳng hạn như kiểm tra null (hoặc bất kỳ bộ bảo vệ nào khác) .


Trong thực tế, khi có câu lệnh "if" có "return", nó có thể được xem là "khối bảo vệ" trong> 50% của tất cả các trường hợp. Vì vậy, tôi nghĩ rằng quan niệm sai lầm của bạn ở đây rằng "khối bảo vệ" là trường hợp đặc biệt và ví dụ của bạn là trường hợp thông thường.
Doc Brown

2
@AssortTrailmix: Tôi đồng ý rằng một trường hợp như trên có thể dễ đọc hơn khi viết elsemệnh đề (theo sở thích của tôi, nó sẽ còn dễ đọc hơn khi bỏ dấu ngoặc không cần thiết. Nhưng tôi nghĩ ví dụ này không được chọn tốt, vì trong trường hợp trên tôi sẽ viết return sky.Color == Color.Blue(không có if / other). Một ví dụ với kiểu trả về khác với bool có thể sẽ làm cho điều này rõ ràng hơn.
Doc Brown

1
như được chỉ ra trong các ý kiến ​​ở trên, ví dụ được đề xuất là quá đơn giản, mời các câu trả lời đầu cơ. Về phần câu hỏi bắt đầu bằng "Bây giờ ai đó có thể thêm một khối if mới ..." , nó đã được hỏi và trả lời nhiều lần trước đó, xem ví dụ: Cách tiếp cận để kiểm tra nhiều điều kiện? và nhiều câu hỏi liên quan đến nó.
gnat

1
Tôi không thấy các khối khác phải làm gì với nguyên tắc DRY. DRY có liên quan nhiều hơn đến sự trừu tượng hóa và các tham chiếu đến mã thường được sử dụng dưới dạng hàm, phương thức và đối tượng so với những gì bạn đang yêu cầu. Câu hỏi của bạn liên quan đến khả năng đọc mã và có thể là quy ước hoặc thành ngữ.
Shashank Gupta

2
Bỏ phiếu để mở lại. Các câu trả lời tốt cho câu hỏi này không đặc biệt dựa trên ý kiến. Nếu ví dụ đưa ra trong một câu hỏi là không thỏa đáng, cải thiện nó bằng cách chỉnh sửa nó sẽ là điều hợp lý để làm, không bỏ phiếu để đóng vì một lý do không thực sự đúng.
Michael Shaw

Câu trả lời:


27

Theo quan điểm của tôi, khối khác rõ ràng là thích hợp hơn. Khi tôi thấy điều này:

if (sky.Color != Blue) {
   ...
}  else {
   ...
}

Tôi biết rằng tôi đang đối phó với các lựa chọn loại trừ lẫn nhau. Tôi không cần phải đọc những gì bên trong khối if để có thể nói.

Khi tôi thấy điều này:

if (sky.Color != Blue) {
   ...
} 
return false;

Thoạt nhìn, có vẻ như nó trả về sai bất kể và có tác dụng phụ tùy chọn, bầu trời không có màu xanh.

Như tôi thấy, cấu trúc của mã thứ hai không phản ánh những gì mã thực sự làm. Thật tồi tệ. Thay vào đó, chọn tùy chọn đầu tiên phản ánh cấu trúc logic. Tôi không muốn phải kiểm tra logic trả về / ngắt / tiếp tục trong mỗi khối để biết luồng logic.

Tại sao bất cứ ai thích người khác là một bí ẩn đối với tôi, hy vọng ai đó sẽ giữ vị trí ngược lại sẽ khai sáng cho tôi với câu trả lời của họ.

Tôi đã quên đề cập đến một trường hợp tôi đồng ý với sự thiếu sót của một khối khác và khi sử dụng một khối if để áp dụng một ràng buộc phải được thỏa mãn một cách hợp lý cho tất cả các mã sau, chẳng hạn như kiểm tra null (hoặc bất kỳ bộ bảo vệ nào khác ).

Tôi muốn nói rằng các điều kiện bảo vệ là ổn trong trường hợp bạn rời khỏi con đường hạnh phúc. Một lỗi hoặc lỗi đã xảy ra khiến cho việc thực thi thông thường tiếp tục. Khi điều này xảy ra, bạn nên đưa ra một ngoại lệ, trả lại mã lỗi hoặc bất cứ điều gì phù hợp với ngôn ngữ của bạn.

Ngay sau phiên bản gốc của câu trả lời này, mã như thế này đã được phát hiện trong dự án của tôi:

Foobar merge(Foobar left, Foobar right) {
   if(!left.isEmpty()) {
      if(!right.isEmpty()) {
          Foobar merged = // construct merged Foobar
          // missing return merged statement
      }
      return left;
   }
   return right;
}

Bằng cách không đặt sự trở lại bên trong elses, thực tế là một tuyên bố trở lại bị mất đã bị bỏ qua. Nếu người khác đã được sử dụng, mã thậm chí sẽ không được biên dịch. (Tất nhiên, mối quan tâm lớn hơn nhiều của tôi là các bài kiểm tra viết trên mã này khá tệ khi không phát hiện ra vấn đề này.)


Về việc tổng hợp những suy nghĩ của tôi về nó, tôi rất vui vì tôi không phải là người duy nhất nghĩ như thế này. Tôi làm việc với các lập trình viên, công cụ và IDE thích loại bỏ elsekhối (nó có thể gây khó chịu khi tôi buộc phải làm điều đó chỉ để giữ nội tuyến với các quy ước cho một cơ sở mã). Tôi cũng quan tâm để xem các điểm truy cập ở đây.
Selali Adobor

1
Tôi khác nhau về điều này. Đôi khi, những thứ khác rõ ràng không mang lại cho bạn nhiều vì không thể kích hoạt các lỗi logic tinh vi mà OP đề cập - đó chỉ là tiếng ồn. Nhưng chủ yếu là tôi đồng ý, và thỉnh thoảng tôi sẽ làm một cái gì đó giống như } // elsenơi mà người khác thường đi như một sự thỏa hiệp giữa hai người.
Telastyn

3
@Telastyn, nhưng bạn kiếm được gì từ việc bỏ qua thứ khác? Tôi đồng ý lợi ích rất nhẹ trong nhiều tình huống, nhưng ngay cả đối với lợi ích nhỏ tôi nghĩ rằng nó đáng để làm điều đó. Chắc chắn, nó có vẻ kỳ quái đối với tôi để đưa một cái gì đó vào một bình luận khi nó có thể là mã.
Winston Ewert

2
Tôi nghĩ rằng bạn có cùng hiểu lầm với OP - "nếu" với "trở lại" có thể được xem chủ yếu là khối bảo vệ, vì vậy bạn đang thảo luận ở đây trường hợp đặc biệt, trong khi trường hợp thường xuyên hơn về khối bảo vệ thì IMHO dễ đọc hơn mà không cần "khác".
Doc Brown

... Vì vậy, những gì bạn viết không sai, nhưng IMHO không xứng đáng với nhiều sự ủng hộ bạn có.
Doc Brown

23

Lý do chính để loại bỏ elsekhối mà tôi đã tìm thấy là thụt quá mức. Việc ngắn mạch của elsecác khối cho phép cấu trúc mã phẳng hơn nhiều.

Nhiều iftuyên bố về cơ bản là bảo vệ. Họ đang ngăn chặn việc hủy bỏ các con trỏ null và "không làm điều này!" lỗi. Và chúng dẫn đến sự chấm dứt nhanh chóng của chức năng hiện tại. Ví dụ:

if (obj === NULL) {
    return NULL;
}
// further processing that now can assume obj attributes are set

Bây giờ có vẻ như một elseđiều khoản sẽ rất hợp lý ở đây:

if (obj === NULL) {
    return NULL;
}
else if (obj.color != Blue) {
   // handle case that object is not blue
}

Và thực sự, nếu đó là tất cả những gì bạn đang làm, thì nó không bị thụt vào nhiều hơn ví dụ trên. Tuy nhiên, đây hiếm khi là tất cả những gì bạn đang làm với các cấu trúc dữ liệu đa cấp thực (một điều kiện xảy ra mọi lúc khi xử lý các định dạng phổ biến như JSON, HTML và XML). Vì vậy, nếu bạn muốn sửa đổi văn bản của tất cả các con của một nút HTML đã cho, hãy nói:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
p = elements[0]
if ((p.children === NULL) or (p.children.length == 0)) {
    return NULL;
}
for (c in p.children) {
    c.text = c.text.toUpperCase();
}
return p;

Các lính canh không làm tăng sự thụt vào của mã. Với một elsemệnh đề, tất cả các công việc thực tế bắt đầu chuyển sang bên phải:

elements = tree.xpath(".//p");
if (elements === NULL) {
    return NULL;
}
else {
    p = elements[0]
    if ((p.children === NULL) or (p.children.length == 0)) {
        return NULL;
    }
    else {
        for (c in p.children) {
            c.text = c.text.toUpperCase();
        }
        return p;
    }
}

Điều này đang bắt đầu có chuyển động phải đáng kể, và đây là một ví dụ khá tầm thường, chỉ có hai điều kiện bảo vệ. Mỗi bảo vệ bổ sung thêm một mức thụt đầu dòng khác. Mã thực Tôi đã xử lý bằng văn bản XML hoặc tiêu thụ nguồn cấp dữ liệu JSON chi tiết có thể dễ dàng xếp chồng lên 3, 4 hoặc nhiều điều kiện bảo vệ hơn. Nếu bạn luôn sử dụng else, cuối cùng bạn sẽ thụt lề 4, 5 hoặc 6 cấp. Có thể nhiều hơn. Và điều đó chắc chắn góp phần vào một cảm giác phức tạp về mã, và dành nhiều thời gian hơn để hiểu những gì sắp xếp với những gì. Kiểu thoát nhanh, bằng phẳng, thoát sớm giúp loại bỏ một số "cấu trúc dư thừa" đó và có vẻ đơn giản hơn.

Phụ lục Nhận xét về câu trả lời này khiến tôi nhận ra rằng có thể không rõ ràng rằng NULL / xử lý trống không phải là lý do duy nhất cho các điều kiện bảo vệ. Không gần! Mặc dù ví dụ đơn giản này tập trung vào xử lý NULL / trống và không chứa các lý do khác, ví dụ, tìm kiếm các cấu trúc sâu như XML và AST cho nội dung "có liên quan" thường có một loạt các thử nghiệm cụ thể để loại bỏ các nút không liên quan và không quan tâm các trường hợp. Một loạt các bài kiểm tra "nếu nút này không liên quan, trả về" sẽ gây ra loại trôi dạt ngay tương tự mà NULL / bộ bảo vệ trống sẽ làm. Và trong thực tế, các bài kiểm tra mức độ liên quan tiếp theo thường được kết hợp với NULL / bộ bảo vệ trống cho các cấu trúc dữ liệu sâu hơn tương ứng được tìm kiếm - một mức độ gấp đôi cho sự trôi dạt phải.


2
Đây là một câu trả lời chi tiết và hợp lệ, nhưng tôi sẽ thêm câu hỏi này vào lúc đầu (Nó đã thoát khỏi tâm trí tôi); Có một "ngoại lệ" khi tôi luôn tìm thấy một elsekhối không cần thiết, khi sử dụng một ifkhối để áp dụng một ràng buộc phải được thỏa mãn một cách hợp lý cho tất cả các mã sau, chẳng hạn như kiểm tra null (hoặc bất kỳ bộ bảo vệ nào khác).
Selali Adobor

@AssortTrailmix Tôi tin rằng nếu bạn loại trừ các trường hợp bạn có thể thường xuyên thực hiện một lối thoát sớm trong thenmệnh đề (chẳng hạn như các câu lệnh bảo vệ liên tiếp), thì không có giá trị cụ thể nào để tránh elsemệnh đề cụ thể . Thật vậy, nó sẽ làm giảm sự rõ ràng và có khả năng gây nguy hiểm (bằng cách không nêu rõ cấu trúc thay thế đang / nên có).
Jonathan Eunice

Tôi nghĩ thật hợp lý khi sử dụng các điều kiện bảo vệ để đẩy ra khi bạn rời khỏi con đường hạnh phúc. Tuy nhiên, tôi cũng thấy trong trường hợp xử lý XML / JSON rằng nếu bạn xác thực đầu vào dựa vào lược đồ trước, bạn sẽ nhận được thông báo lỗi tốt hơn và mã sạch hơn.
Winston Ewert

Đây là một lời giải thích hoàn hảo về các trường hợp mà tránh được.
Chris Schiffhauer

2
Thay vào đó, tôi đã bọc API để không tạo ra các NULL ngớ ngẩn.
Winston Ewert

4

Khi tôi thấy điều này:

if(something){
    return lamp;
}
return table;

Tôi thấy một thành ngữ phổ biến . Một mô hình phổ biến , trong đầu tôi chuyển thành:

if(something){
    return lamp;
}
else return table;

Bởi vì đây là một thành ngữ rất phổ biến trong lập trình, nên lập trình viên đã từng thấy loại mã này có một "bản dịch" ẩn trong đầu họ bất cứ khi nào họ nhìn thấy, điều đó "chuyển đổi" nó thành đúng nghĩa.

Tôi không cần sự rõ ràng else, đầu tôi 'đặt nó ở đó' cho tôi vì nó nhận ra toàn bộ mô hình . Tôi hiểu ý nghĩa của toàn bộ mô hình chung, vì vậy tôi không cần các chi tiết nhỏ để làm rõ nó.

Ví dụ, đọc văn bản sau:

nhập mô tả hình ảnh ở đây

Bạn đã đọc nó?

Tôi yêu Paris vào mùa xuân

Nếu vậy, bạn đã sai. Đọc văn bản một lần nữa. Những gì thực sự được viết là

Tôi yêu Paris trong những mùa xuân

hai từ "the" trong văn bản. Vậy tại sao bạn lại bỏ lỡ một trong hai?

Đó là bởi vì bộ não của bạn đã nhìn thấy một mô hình chung , và ngay lập tức dịch nó theo nghĩa đã biết. Nó không cần phải đọc toàn bộ chi tiết điều một cách chi tiết để tìm ra ý nghĩa, bởi vì nó đã nhận ra mô hình khi nhìn thấy nó. Đây là lý do tại sao nó bỏ qua thêm "the ".

Điều tương tự với thành ngữ trên. Các lập trình viên, những người đã đọc rất nhiều mã được sử dụng để nhìn thấy điều này mô hình , của if(something) {return x;} return y;. Họ không cần một thám hiểm elseđể hiểu ý nghĩa của nó, bởi vì bộ não của họ 'đặt nó ở đó cho họ'. Họ nhận ra nó là một mô hình với ý nghĩa đã biết: "những gì sau khi kết thúc sẽ chỉ chạy như một ẩn elseý trước đó if".

Vì vậy, theo tôi, elselà không cần thiết. Nhưng chỉ sử dụng những gì có vẻ dễ đọc hơn.


2

Họ thêm sự lộn xộn thị giác trong mã, vì vậy, họ có thể thêm sự phức tạp.

Tuy nhiên, điều ngược lại cũng đúng, tái cấu trúc quá mức để giảm độ dài mã cũng có thể thêm độ phức tạp (tất nhiên không phải trong trường hợp đơn giản này).

Tôi đồng ý với tuyên bố rằng elsekhối nhà nước có ý định. Nhưng kết luận của tôi thì khác, bởi vì bạn đang thêm độ phức tạp vào mã của mình để đạt được điều này.

Tôi không đồng ý với tuyên bố của bạn rằng nó cho phép bạn ngăn chặn những sai lầm tinh vi trong logic.

Hãy xem một ví dụ, bây giờ nó chỉ trở về đúng nếu Bầu trời có màu xanh và có Độ ẩm cao, Độ ẩm không cao hoặc nếu Bầu trời có Màu đỏ. Nhưng sau đó, bạn nhầm một nẹp và đặt nó sai else:

bool CanLeaveWithoutUmbrella() {
    if(sky.Color == Color.Blue)
    {
        if (sky.Humidity == Humidity.High) 
        {
            return true;
        } 
    else if (sky.Humidity != Humidity.High)
    {
        return true;
    } else if (sky.Color == Color.Red) 
    {
            return true;
    }
    } else 
    {
       return false;
    }
}

Chúng ta đã thấy tất cả các loại sai lầm ngớ ngẩn trong mã sản xuất và có thể rất khó phát hiện.

Tôi muốn đề nghị như sau:

Tôi thấy điều này dễ đọc hơn, bởi vì tôi có thể thấy trong nháy mắt rằng mặc định là false.

Ngoài ra, ý tưởng buộc phải di chuyển nội dung của một khối để chèn một mệnh đề mới, dễ bị lỗi. Điều này bằng cách nào đó liên quan đến nguyên tắc mở / đóng (mã nên được mở để mở rộng nhưng đóng để sửa đổi). Bạn đang buộc phải sửa đổi mã hiện có (ví dụ: bộ mã hóa có thể gây ra lỗi trong việc cân bằng niềng răng).

Cuối cùng, bạn đang dẫn dắt mọi người trả lời theo cách được xác định trước mà bạn nghĩ là đúng. Ví dụ, bạn trình bày một ví dụ rất đơn giản nhưng sau đó, bạn nói rằng mọi người không nên xem xét nó. Tuy nhiên, tính đơn giản của ví dụ ảnh hưởng đến toàn bộ câu hỏi:

  • Nếu trường hợp đơn giản như trường hợp được trình bày, thì câu trả lời của tôi sẽ là bỏ qua mọi if elsemệnh đề.

    return (sky.Color == Color.Blue);

  • Nếu trường hợp phức tạp hơn một chút, với nhiều mệnh đề khác nhau, tôi sẽ trả lời:

    bool CanLeaveWithoutUm giác () {

    if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
        return true;
    } else if (sky.Humidity != Humidity.High) {
        return true;
    } else if (sky.Color == Color.Red) {
        return true;
    }
    
    return false;
    

    }

  • Nếu trường hợp phức tạp và không phải tất cả các mệnh đề đều trả về đúng (có thể chúng trả lại gấp đôi) và tôi muốn giới thiệu một số lệnh breakpoint hoặc loggin, tôi sẽ xem xét một số thứ như:

    double CanLeaveWithoutUmbrella() {
    
        double risk = 0.0;
    
        if(sky.Color == Color.Blue && sky.Humidity == Humidity.High) {
            risk = 1.0;
        } else if (sky.Humidity != Humidity.High) {
            risk = 0.5;
        } else if (sky.Color == Color.Red) {
            risk = 0.8;
        }
    
        Log("value of risk:" + risk);
        return risk;
    

    }

  • Nếu chúng ta không nói về một phương thức trả về một cái gì đó, mà thay vào đó là một khối if-if đặt một số biến hoặc gọi một phương thức, thì có, tôi sẽ bao gồm một elsemệnh đề cuối cùng .

Do đó, sự đơn giản của ví dụ cũng là một yếu tố cần xem xét.


Tôi cảm thấy như bạn đang làm cho cùng một sai lầm khác đã thực hiện và biến dạng ví dụ một chút quá nhiều, do đó kiểm tra một vấn đề mới, nhưng tôi cần phải thực hiện một giây, kiểm tra quá trình nhận lại ví dụ của bạn, vì vậy bây giờ nó trông hợp lệ với tôi Và một lưu ý phụ, điều này không liên quan đến nguyên tắc mở / đóng . Phạm vi quá hẹp để áp dụng thành ngữ này. Nhưng nó là thú vị để lưu ý ứng dụng của nó trên một mức độ cao hơn sẽ xóa toàn bộ khía cạnh này của vấn đề (bằng cách loại bỏ sự cần thiết của bất kỳ sửa đổi của hàm).
Selali Adobor

1
Nhưng nếu chúng ta không thể sửa đổi ví dụ đơn giản hóa quá mức này bằng cách thêm các mệnh đề, cũng không sửa đổi cách viết, cũng như không thực hiện bất kỳ thay đổi nào đối với nó, thì bạn nên xem xét rằng câu hỏi này chỉ liên quan đến các dòng mã chính xác đó và nó không còn là câu hỏi chung nữa. Trong trường hợp đó, bạn hoàn toàn đúng, nó không thêm bất kỳ sự phức tạp nào (cũng không loại bỏ nó) mệnh đề khác vì không có sự phức tạp để bắt đầu. Tuy nhiên, bạn không công bằng, bởi vì bạn là người gợi ý tưởng rằng các mệnh đề mới có thể được thêm vào.
FranMowinckel

Và như tôi lưu ý, tôi không nói rõ điều này liên quan đến nguyên tắc đóng / mở nổi tiếng. Tôi nghĩ rằng ý tưởng dẫn mọi người sửa đổi mã của bạn như bạn đề xuất là dễ bị lỗi. Tôi chỉ lấy ý tưởng này từ nguyên tắc đó.
FranMowinckel

Giữ các chỉnh sửa, bạn đang có một cái gì đó tốt, tôi đang gõ một bình luận ngay bây giờ
Selali Adobor

1

Mặc dù trường hợp if-other được mô tả có độ phức tạp lớn hơn, trong hầu hết (nhưng không phải tất cả) các tình huống thực tế, nó không quan trọng lắm.

Cách đây nhiều năm, khi tôi đang làm việc trên phần mềm được sử dụng cho ngành hàng không (nó phải trải qua quá trình chứng nhận), bạn đã có một câu hỏi được đặt ra. Hóa ra, có một chi phí tài chính cho tuyên bố 'khác' khi nó làm tăng độ phức tạp của mã.

Đây là lý do tại sao.

Sự hiện diện của 'người khác' đã tạo ra trường hợp thứ 3 ngầm định phải được đánh giá - đó không phải là 'nếu' hay 'người khác'. Bạn có thể nghĩ (giống như tôi lúc đó) WTF?

Lời giải thích đã diễn ra như thế này ...

Từ quan điểm logic, chỉ có hai tùy chọn - 'nếu' và 'khác'. Tuy nhiên, những gì chúng tôi đã xác nhận là tệp đối tượng mà trình biên dịch đang tạo. Do đó, khi có một 'cái khác', một phân tích phải được thực hiện để xác nhận rằng mã phân nhánh được tạo là chính xác và đường dẫn thứ 3 bí ẩn đã không xuất hiện.

Tương phản điều này với trường hợp chỉ có 'nếu' và không có 'khác'. Trong trường hợp đó, chỉ có hai tùy chọn - đường dẫn 'nếu' và đường dẫn 'không-nếu'. Hai trường hợp để đánh giá là ít phức tạp hơn ba.

Hi vọng điêu nay co ich.


trong một tệp đối tượng, trường hợp nào sẽ không được hiển thị dưới dạng jmps giống hệt nhau?
Winston Ewert

@WinstonEwert - Người ta sẽ mong họ giống nhau. Tuy nhiên, những gì được kết xuất và những gì người ta mong đợi được kết xuất là hai thứ khác nhau, bất kể chúng trùng nhau bao nhiêu.
Sparky

(Tôi đoán SE đã ăn bình luận của tôi ...) Đây là một câu trả lời rất thú vị. Mặc dù tôi nói rằng trường hợp mà nó phơi bày có phần thích hợp, nhưng nó cho thấy rằng một số công cụ sẽ tìm thấy sự khác biệt giữa hai (Ngay cả số liệu mã phổ biến nhất dựa trên dòng chảy tôi thấy, độ phức tạp theo chu kỳ, sẽ không đo được sự khác biệt.
Selali Adobor

1

Mục tiêu quan trọng nhất của mã là có thể hiểu được như đã cam kết (trái ngược với dễ dàng tái cấu trúc, điều này hữu ích nhưng ít quan trọng hơn). Một ví dụ Python phức tạp hơn một chút có thể được sử dụng để minh họa điều này:

def value(key):
    if key == FROB:
        return FOO
    elif key == NIK:
        return BAR
    [...]
    else:
        return BAZ

Điều này khá rõ ràng - nó tương đương với một casetuyên bố hoặc tra cứu từ điển, phiên bản cấp cao hơn của return foo == bar:

KEY_VALUES = {FROB: FOO, NIK: BAR, [...]}
DEFAULT_VALUE = BAZ

def value(key):
    return KEY_VALUES.get(key, default=DEFAULT_VALUE)

Điều này rõ ràng hơn, bởi vì mặc dù số lượng mã thông báo cao hơn (32 so với 24 đối với mã gốc có hai cặp khóa / giá trị), ngữ nghĩa của hàm hiện rõ ràng: Đây chỉ là một tra cứu.

(Điều này có hậu quả cho việc tái cấu trúc - nếu bạn muốn có tác dụng phụ nếu key == NIK, bạn có ba lựa chọn:

  1. Hoàn nguyên về if/ elif/ elsestyle và chèn một dòng trong key == NIKtrường hợp.
  2. Lưu kết quả tra cứu vào một giá trị và thêm một iftuyên bố valuecho trường hợp đặc biệt trước khi quay lại.
  3. Đặt một iftuyên bố trong người gọi.

Bây giờ chúng ta thấy tại sao chức năng tra cứu là một sự đơn giản hóa mạnh mẽ: Nó làm cho tùy chọn thứ ba rõ ràng là giải pháp đơn giản nhất, vì ngoài việc dẫn đến một sự khác biệt nhỏ hơn so với tùy chọn đầu tiên, đó là cách duy nhất đảm bảo valuechỉ có một điều duy nhất. )

Quay trở lại mã của OP, tôi nghĩ rằng điều này có thể đóng vai trò là một hướng dẫn cho sự phức tạp của các elsecâu lệnh: Mã với một elsecâu lệnh rõ ràng phức tạp hơn về mặt khách quan, nhưng quan trọng hơn là liệu nó có làm cho ngữ nghĩa của mã rõ ràng và đơn giản hay không. Và điều đó phải được trả lời trong từng trường hợp, vì mỗi đoạn mã có một ý nghĩa khác nhau.


Tôi thích bảng tra cứu của bạn viết lại trường hợp chuyển đổi đơn giản. Và tôi biết đó là một thành ngữ Python được ưa chuộng. Trên thực tế, tuy nhiên, tôi thường thấy rằng đa chiều điều kiện (aka casehoặc switchbáo cáo) ít đồng nhất. Một số tùy chọn chỉ là trả về giá trị thẳng, nhưng sau đó một hoặc hai trường hợp cần xử lý thêm. Và khi bạn khám phá ra những chiến lược tra cứu không phù hợp, bạn cần phải viết lại trong một hoàn toàn khác nhau if elif... elsecú pháp.
Jonathan Eunice

Đó là lý do tại sao tôi nghĩ rằng việc tra cứu thường phải là một chức năng hoặc một chức năng, để logic xung quanh kết quả tra cứu được tách ra khỏi logic tìm kiếm ở vị trí đầu tiên.
l0b0

Câu hỏi này mang đến một cái nhìn ở cấp độ cao hơn cho câu hỏi chắc chắn nên được đề cập và ghi nhớ, nhưng tôi cảm thấy rằng có đủ sự ràng buộc đã được đặt ra trong trường hợp bạn có thể thoải mái mở rộng theo lập trường của riêng mình
Selali Adobor

1

Tôi nghĩ rằng nó phụ thuộc vào loại iftuyên bố bạn đang sử dụng. Một số ifcâu lệnh có thể được xem như là biểu thức và những câu lệnh khác chỉ có thể được xem như là câu lệnh điều khiển.

Ví dụ của bạn trông giống như một biểu hiện với tôi. Trong các ngôn ngữ giống như C, toán tử ternary ?:rất giống với một ifcâu lệnh, nhưng nó là một biểu thức và phần khác không thể được bỏ qua. Điều này có nghĩa là một nhánh khác không thể được bỏ qua trong một biểu thức, bởi vì biểu thức phải luôn có một giá trị.

Sử dụng toán tử ternary, ví dụ của bạn sẽ như thế này:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue)
               ? true
               : false;
}

Vì đó là một biểu thức boolean, tuy nhiên, nó thực sự cần được đơn giản hóa tất cả các cách để

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

Bao gồm một điều khoản rõ ràng khác làm cho ý định của bạn rõ ràng hơn. Các lính canh có thể có ý nghĩa trong một số tình huống, nhưng trong việc lựa chọn giữa hai giá trị có thể, không có giá trị nào là đặc biệt hay bất thường, họ không giúp đỡ.


+1, OP là IMHO thảo luận về một trường hợp bệnh lý nên được viết tốt hơn theo cách bạn đã trình bày ở trên. Trường hợp thường xuyên hơn cho "nếu" với "trở lại" theo kinh nghiệm của tôi về trường hợp "bảo vệ".
Doc Brown

Tôi không biết tại sao điều này cứ xảy ra, nhưng mọi người cứ bỏ qua đoạn đầu tiên của câu hỏi. Trong thực tế, tất cả các bạn đang sử dụng dòng mã chính xác mà tôi nói rõ ràng là không sử dụng. I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor

Và đọc lại câu hỏi cuối cùng của bạn, bạn dường như hiểu sai ý định của câu hỏi.
Selali Adobor

Bạn đã sử dụng một ví dụ thực tế đang cầu xin để được đơn giản hóa. Tôi đã đọc và chú ý đến đoạn đầu tiên của bạn, đó là lý do tại sao khối mã đầu tiên của tôi có ở đó, thay vì bỏ qua trực tiếp đến cách tốt nhất để làm ví dụ cụ thể đó. Nếu bạn nghĩ rằng tôi (hoặc bất kỳ ai khác) đã hiểu sai ý định của câu hỏi của bạn, có lẽ bạn nên chỉnh sửa nó để làm cho ý định của bạn rõ ràng.
Michael Shaw

Tôi sẽ chỉnh sửa nó, nhưng tôi sẽ không biết làm thế nào rõ ràng hơn nữa vì tôi sử dụng dòng mã chính xác mà bạn làm và nói, "đừng làm điều này". Có vô số cách bạn có thể viết lại mã và xóa câu hỏi, tôi không thể bao quát hết tất cả, nhưng rất nhiều người đã hiểu câu nói của tôi và tôn trọng nó ...
Selali Adobor

1

Một điều khác để suy nghĩ trong tranh luận này là thói quen mỗi cách tiếp cận thúc đẩy.

  • if / other refram một mẫu mã theo các nhánh. Như bạn nói, đôi khi nhà thám hiểm này là tốt. Thực sự có hai đường dẫn thực thi có thể và chúng tôi muốn làm nổi bật điều đó.

  • Trong các trường hợp khác, chỉ có một đường dẫn thực thi và sau đó là tất cả những điều đặc biệt mà các lập trình viên phải lo lắng. Như bạn đã đề cập, mệnh đề bảo vệ là tuyệt vời cho việc này.

  • Tất nhiên, có 3 trường hợp trở lên (n), trong đó chúng ta thường khuyến khích từ bỏ chuỗi if / khác và sử dụng câu lệnh case / switch hoặc đa hình.

Nguyên tắc hướng dẫn tôi ghi nhớ ở đây là một phương pháp hoặc chức năng chỉ nên có một mục đích. Bất kỳ mã nào có câu lệnh if / other hoặc switch chỉ dành cho phân nhánh. Vì vậy, nó nên ngắn một cách vô lý (4 dòng) và chỉ ủy thác cho phương pháp đúng hoặc tạo ra kết quả rõ ràng (như ví dụ của bạn). Khi mã của bạn quá ngắn, thật khó để bỏ lỡ nhiều lợi nhuận đó :) Giống như "goto coi là có hại", bất kỳ tuyên bố phân nhánh nào cũng có thể bị lạm dụng, do đó, việc đưa ra một số suy nghĩ quan trọng vào các tuyên bố khác thường có giá trị. Như bạn đã đề cập, chỉ cần có một khối khác cung cấp một nơi để mã bị vón cục. Bạn và nhóm của bạn sẽ phải quyết định bạn cảm thấy thế nào về điều đó.

Điều mà công cụ thực sự phàn nàn là thực tế là bạn có trả lại / phá / ném bên trong khối if. Vì vậy, theo như nó liên quan, bạn đã viết một điều khoản bảo vệ nhưng lại làm hỏng nó. Bạn có thể chọn làm cho công cụ hài lòng hoặc bạn có thể "giải trí" đề xuất của nó mà không chấp nhận nó.


Tôi chưa bao giờ nghĩ về thực tế công cụ có thể xem xét nếu là một điều khoản bảo vệ ngay khi nó phù hợp với mô hình cho một người, đó có thể là trường hợp, và nó giải thích lý do cho một "nhóm người đề xướng" cho sự vắng mặt của nó
Selali Adobor

@AssortTrailmix Đúng vậy, bạn đang suy nghĩ về elsengữ nghĩa, các công cụ phân tích mã thường tìm kiếm các hướng dẫn không liên quan vì chúng thường làm nổi bật sự hiểu lầm về luồng điều khiển. Sau elsekhi iftrả về đó là lãng phí bit. if(1 == 1)thường kích hoạt cùng một loại cảnh báo.
Steve Jackson

1

Tôi nghĩ rằng câu trả lời là nó phụ thuộc. Mẫu mã của bạn (như bạn gián tiếp chỉ ra) chỉ đơn giản là trả về đánh giá của một điều kiện và được thể hiện tốt nhất như bạn rõ ràng biết, như là một biểu thức.

Để xác định xem việc thêm một điều kiện khác có làm rõ hoặc che khuất mã không, bạn cần xác định IF / ELSE đại diện cho điều gì. Đây có phải là một (như trong ví dụ của bạn) một biểu thức? Nếu vậy, biểu thức đó nên được trích xuất và sử dụng. Có phải là một điều kiện bảo vệ? Nếu vậy, ELSE là không cần thiết và gây hiểu lầm, vì nó làm cho hai nhánh xuất hiện tương đương. Đây có phải là một ví dụ về đa hình thủ tục? Giải nén nó Có phải nó đang đặt điều kiện tiên quyết? Sau đó, nó có thể là thiết yếu.

Trước khi bạn quyết định bao gồm hoặc loại bỏ ELSE, hãy quyết định những gì nó thể hiện, điều đó sẽ cho bạn biết liệu nó có nên có mặt hay không.


0

Câu trả lời là một chút chủ quan. Một elsekhối có thể hỗ trợ khả năng đọc bằng cách thiết lập rằng một khối mã thực hiện riêng cho một khối mã khác, mà không cần phải đọc qua cả hai khối. Điều này có thể hữu ích nếu, nói, cả hai khối tương đối dài. Có những trường hợp, mặc dù có ifs mà không có elses có thể dễ đọc hơn. So sánh mã sau với một số if/elsekhối:

if(a == b) {
    if(c <= d) {
        if(e != f) {
            a.doSomeStuff();
            c.makeSomeThingsWith(b);
            f.undo(d, e);
            return 9;
        } else {
            e++;
            return 3;
        }
    } else {
        return 1;
    }
} else {
    return 0;
}

với:

if(a != b) {
    return 0;
}

if(c > d) {
    return 1;
}

if(e != f) {
    e++;
    return 3;
}

a.doSomeStuff();
c.makeSomeThingsWith(b);
f.undo(d, e);
return 9;

Khối mã thứ hai thay đổi các ifcâu lệnh lồng nhau thành các mệnh đề bảo vệ. Điều này làm giảm thụt đầu dòng và làm cho một số điều kiện hoàn trả rõ ràng hơn ("nếu a != b, sau đó chúng tôi quay lại 0!")


Nó đã được chỉ ra trong các ý kiến ​​rằng có thể có một số lỗ hổng trong ví dụ của tôi, vì vậy tôi sẽ bổ sung thêm một chút nữa.

Chúng tôi có thể đã viết ra khối mã đầu tiên ở đây như vậy để làm cho nó dễ đọc hơn:

if(a != b) {
    return 0;
} else if(c > d) {
    return 1;
} else if(e != f) {
    e++;
    return 3;
} else {
    a.doSomeStuff();
    c.makeSomeThingsWith(b);
    f.undo(d, e);
    return 9;
}

Mã này tương đương với cả hai khối ở trên, nhưng nó đưa ra một số thách thức. Các elseđiều khoản ở đây kết nối những câu lệnh if với nhau. Trong phiên bản mệnh đề bảo vệ ở trên, các mệnh đề bảo vệ độc lập với nhau. Thêm một cái mới có nghĩa là chỉ cần viết một cái mới ifgiữa cái cuối cùng ifvà phần còn lại của logic. Ở đây, điều đó cũng có thể được thực hiện, chỉ với một chút tải trọng nhận thức. Tuy nhiên, sự khác biệt là khi một số logic bổ sung cần phải xảy ra trước mệnh đề bảo vệ mới đó. Khi các tuyên bố là độc lập, chúng tôi có thể làm:

...
if(e != f) {
    e++;
    return 3;
}

g.doSomeLogicWith(a);

if(!g.isGood()) {
    return 4;
}

a.doSomeStuff();
...

Với if/elsechuỗi kết nối , điều này không dễ thực hiện. Như một vấn đề thực tế, cách dễ nhất để thực hiện là phá vỡ chuỗi để trông giống như phiên bản mệnh đề bảo vệ.

Nhưng thực sự, điều này có ý nghĩa. Sử dụng if/elseyếu ngụ ý một mối quan hệ giữa các bộ phận. Chúng ta có thể phỏng đoán rằng a != bbằng cách nào đó liên quan đến c > dtrongif/else phiên bản xích. Giả định đó sẽ gây hiểu lầm, như chúng ta có thể thấy từ phiên bản mệnh đề bảo vệ rằng trên thực tế, chúng không liên quan. Điều đó có nghĩa là chúng ta có thể làm nhiều hơn với các mệnh đề độc lập, như sắp xếp lại chúng (có lẽ để tối ưu hóa hiệu suất truy vấn hoặc để cải thiện luồng logic). Chúng ta cũng có thể nhận thức bỏ qua chúng một khi chúng ta đã vượt qua chúng. Trong một if/elsechuỗi, tôi ít chắc chắn rằng mối quan hệ tương đương giữa absẽ không xuất hiện trở lại sau này.

Mối quan hệ ngụ ý elsecũng được thể hiện rõ trong mã ví dụ được lồng sâu, nhưng theo một cách khác. Các elsecâu lệnh được tách ra khỏi điều kiện mà chúng được gắn vào và chúng có liên quan theo thứ tự ngược lại với các điều kiện (cái đầu tiên elseđược gắn vào cái cuối cùng if, cái cuối cùngelse được gắn vào cái đầu tiên if). Điều này tạo ra sự bất đồng về nhận thức, nơi chúng ta phải quay lại mã, cố gắng xếp hàng thụt vào trong não. Nó giống như cố gắng kết hợp một loạt dấu ngoặc đơn. Nó thậm chí còn tồi tệ hơn nếu vết lõm sai. Niềng răng tùy chọn cũng không giúp được gì.

Tôi đã quên đề cập đến một trường hợp tôi đồng ý với sự thiếu sót của một khối khác và khi sử dụng một khối if để áp dụng một ràng buộc phải được thỏa mãn một cách hợp lý cho tất cả các mã sau, chẳng hạn như kiểm tra null (hoặc bất kỳ bộ bảo vệ nào khác ).

Đây là một sự nhượng bộ tốt đẹp, nhưng nó thực sự không làm mất hiệu lực bất kỳ câu trả lời nào. Tôi có thể nói rằng trong ví dụ mã của bạn:

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color != Color.Blue)
    {
        return false;
    }
    return true;
}

một cách giải thích hợp lệ cho việc viết mã như thế này có thể là "chúng ta chỉ phải rời đi với một chiếc ô nếu bầu trời không có màu xanh." Ở đây có một ràng buộc là bầu trời phải có màu xanh để mã sau có hiệu lực để chạy. Tôi đã đáp ứng định nghĩa về nhượng bộ của bạn.

Điều gì sẽ xảy ra nếu tôi nói rằng nếu màu trời là màu đen, thì đó là một đêm rõ ràng, vì vậy không cần ô. (Có những cách đơn giản hơn để viết này, nhưng có những cách đơn giản hơn để viết toàn bộ ví dụ.)

bool CanLeaveWithoutUmbrella()
{
    if(sky.Color == Color.Blue)
    {
        return true;
    }

    if(sky.Color == Color.Black)
    {
        return true;
    }

    return false;
}

Như trong ví dụ lớn hơn ở trên, tôi chỉ có thể thêm mệnh đề mới mà không cần suy nghĩ nhiều. Trong mộtif/else , tôi không thể chắc chắn mình sẽ không làm hỏng thứ gì đó, đặc biệt nếu logic phức tạp hơn.

Tôi cũng sẽ chỉ ra rằng ví dụ đơn giản của bạn cũng không đơn giản như vậy. Một thử nghiệm cho một giá trị không nhị phân không giống như thử nghiệm nghịch đảo của thử nghiệm đó với kết quả ngược lại.


1
Chỉnh sửa của tôi cho câu hỏi giải quyết trường hợp này. Khi một ràng buộc phải được thỏa mãn một cách hợp lý cho tất cả các mã sau đây, chẳng hạn như kiểm tra null (hoặc bất kỳ bộ bảo vệ nào khác), tôi đồng ý bỏ qua một elsekhối là điều cần thiết để dễ đọc.
Selali Adobor

1
Phiên bản đầu tiên của bạn rất khó nắm bắt, nhưng tôi không nghĩ rằng điều đó là bắt buộc bằng cách sử dụng if / other. Xem cách tôi viết nó: gist.github.com/winstonewert/c9e0955bc22ce44930fb .
Winston Ewert

@WinstonEwert Xem các chỉnh sửa để phản hồi bình luận của bạn.
cbojar

Chỉnh sửa của bạn làm cho một số điểm hợp lệ. Và tôi chắc chắn nghĩ rằng các điều khoản bảo vệ có một vị trí. Nhưng trong trường hợp chung, tôi ủng hộ các mệnh đề khác.
Winston Ewert

0

Bạn đã đơn giản hóa ví dụ của bạn quá nhiều. Hoặc là sự thay thế có thể dễ đọc hơn, tùy thuộc vào tình huống lớn hơn: cụ thể, nó phụ thuộc vào việc một trong hai nhánh của tình trạng có bất thường hay không . Hãy để tôi chỉ cho bạn: trong trường hợp một trong hai chi nhánh có khả năng như nhau, ...

void DoOneOfTwoThings(bool which)
{
    if (which)
        DoThingOne();
    else
        DoThingTwo();
}

... dễ đọc hơn ...

void DoOneOfTwoThings(bool which)
{
    if (which) {
        DoThingOne();
        return;
    }
    DoThingTwo();
}

... bởi vì cái thứ nhất thể hiện cấu trúc song song và cái thứ hai thì không. Nhưng, khi phần lớn chức năng được dành cho một nhiệm vụ và bạn chỉ cần kiểm tra một số điều kiện tiên quyết trước tiên, ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input)) {
        ProcessInputOfTypeTwo(input);
        return;
    }
    ProcessInputDefinitelyOfTypeOne(input);

}

... dễ đọc hơn ...

void ProcessInputOfTypeOne(String input)
{
    if (InputIsReallyTypeTwo(input))
        ProcessInputOfTypeTwo(input);
    else
        ProcessInputDefinitelyOfTypeOne(input);
}

... bởi vì việc cố tình không thiết lập cấu trúc song song cung cấp manh mối cho người đọc trong tương lai rằng việc nhận đầu vào "thực sự là loại hai" ở đây là bất thường . (Trong cuộc sống thực, ProcessInputDefinitelyOfTypeOnesẽ không phải là một chức năng riêng biệt, do đó, sự khác biệt sẽ còn rõ ràng hơn nữa.)


Có thể tốt hơn để chọn một ví dụ cụ thể hơn cho trường hợp thứ hai. Phản ứng ngay lập tức của tôi với nó là "không thể bất thường nếu bạn tiếp tục và xử lý nó bằng mọi cách."
Winston Ewert

@WinstonEwert bất thường có lẽ là thuật ngữ sai. Nhưng tôi đồng ý với Zack, rằng nếu bạn có một mặc định (case1) và một ngoại lệ, thì nó nên được viết là » if-> Thực hiện đặc biệt và để lại« như một chiến lược hoàn trả sớm . Hãy nghĩ về mã, trong đó mặc định bao gồm nhiều kiểm tra hơn, trước tiên sẽ xử lý trường hợp ngoại lệ nhanh hơn.
Thomas Junk

Điều này dường như rơi vào trường hợp tôi đang nói về when using an if block to apply a constraint that must be logically satisfied for all following code, such as a null-check (or any other guards). Về mặt logic, bất kỳ công việc ProcessInputOfTypeOnenào cũng phụ thuộc vào thực tế đó !InputIsReallyTypeTwo(input), vì vậy chúng ta cần nắm bắt điều đó bên ngoài quá trình xử lý thông thường của chúng ta. Thông thường ProcessInputOfTypeTwo(input);sẽ thực sự throw ICan'tProccessThatExceptionsẽ làm cho sự tương đồng rõ ràng hơn.
Selali Adobor

@WinstonEwert Tôi có thể nói điều đó tốt hơn, vâng. Tôi đã nghĩ đến một tình huống xuất hiện rất nhiều khi mã hóa mã thông báo bằng tay: ví dụ như trong CSS, chuỗi ký tự " u+" thường giới thiệu mã thông báo "unicode-Range", nhưng nếu ký tự tiếp theo không phải là chữ số hex, sau đó, người ta cần sao lưu và xử lý 'u' làm định danh thay thế. Vì vậy, vòng quét chính sẽ gửi "quét mã thông báo phạm vi unicode" một cách không chính xác và bắt đầu bằng "... nếu chúng ta ở trong trường hợp đặc biệt khác thường này, hãy sao lưu, sau đó gọi chương trình con nhận dạng quét."
zwol

@AssortTrailmix Ngay cả khi ví dụ mà tôi nghĩ là đã không được sử dụng một cơ sở mã di sản mà ngoại lệ có thể không được sử dụng, đây không phải là một điều kiện lỗi , chỉ là mã mà các cuộc gọi ProcessInputOfTypeOnesử dụng kiểm tra không chính xác mà đôi khi bắt loại hai thay thế.
zwol

0

Có, có sự gia tăng độ phức tạp vì mệnh đề khác là dư thừa . Vì vậy, tốt hơn hết là bỏ đi để giữ cho mã gọn và có ý nghĩa. Đó là cùng một hợp âm như bỏ qua các thisvòng loại dự phòng (Java, C ++, C #) và bỏ qua dấu ngoặc đơn trong các returncâu lệnh, v.v.

Một lập trình viên giỏi nghĩ rằng "tôi có thể thêm gì?" trong khi một lập trình viên tuyệt vời nghĩ rằng "tôi có thể loại bỏ cái gì?".


1
Khi tôi thấy thiếu sót của elsekhối, nếu nó được giải thích trong một lớp lót (không thường xuyên) thì nó thường với kiểu lý luận này, vì vậy +1 để trình bày đối số "mặc định" mà tôi thấy, nhưng tôi không tìm thấy nó là một lý luận đủ mạnh. A good programmer things "what can I add?" while a great programmer things "what can I remove?".dường như là một câu ngạn ngữ phổ biến rằng rất nhiều người quá mức mà không xem xét cẩn thận, và cá nhân tôi thấy đây là một trường hợp như vậy.
Selali Adobor

Vâng, đọc câu hỏi của bạn tôi ngay lập tức cảm thấy bạn đã quyết định, nhưng muốn xác nhận. Câu trả lời này rõ ràng không phải là điều bạn muốn, nhưng đôi khi điều quan trọng là phải xem xét các ý kiến ​​mà bạn không đồng ý. Có lẽ có một cái gì đó quá nó?
vidstige

-1

Tôi nhận thấy, thay đổi suy nghĩ của tôi về trường hợp này.

Khi được hỏi câu hỏi này khoảng một năm trước, tôi sẽ trả lời như sau:

»Chỉ nên có một entryvà một exitcho một phương thức« Và với ý nghĩ này, tôi đã đề nghị cấu trúc lại mã thành:

bool CanLeaveWithoutUmbrella()
{
    bool result

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    else
    {
        result = false;
    }

    return result;
}

Từ quan điểm giao tiếp thì rõ ràng , nên làm gì. Ifmột cái gì đó là trường hợp, thao túng kết quả theo một cách , else thao tác nó theo cách khác .

Nhưng điều này liên quan đến một không cần thiết else . Các elsebiểu hiện trường hợp mặc định . Vì vậy, trong bước thứ hai, tôi có thể cấu trúc lại nó thành

bool CanLeaveWithoutUmbrella()
{
    bool result=false

    if(sky.Color == Color.Blue)
    {
        result = true;
    }
    return result;
}

Sau đó, câu hỏi đặt ra: Tại sao sử dụng một biến tạm thời ở tất cả? Bước tiếp theo của tái cấu trúc dẫn đến:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue)
    {
        return true;
    }
    return false;
}

Vì vậy, điều này là rõ ràng trong những gì nó làm. Tôi thậm chí sẽ đi thêm một bước nữa:

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) return true;
    return false;
}

Hoặc cho những người yếu tim :

bool CanLeaveWithoutUmbrella()
{

    if(sky.Color == Color.Blue) { return true; }
    return false;
}

Bạn có thể đọc nó như thế: »CanLeaveWithoutUm giác trả về một bool . Nếu không có gì đặc biệt xảy ra, nó sẽ trả về false «Đây là lần đọc đầu tiên, từ trên xuống dưới.

Và sau đó bạn "phân tích" điều kiện »Nếu điều kiện được đáp ứng, nó sẽ trả về đúng « Bạn hoàn toàn có thể bỏ qua điều kiện và đã hiểu, chức năng này làm gì trong trường hợp mặc định . Mắt và tâm trí của bạn chỉ phải lướt qua một số chữ cái ở bên trái, mà không đọc phần bên phải.

Tôi ấn tượng rằng người khác nói rõ hơn về ý định, bằng cách nêu thực tế rằng mã trong cả hai khối có liên quan trực tiếp.

Vâng, đó là đúng. Nhưng thông tin đó là dư thừa . Bạn có một trường hợp mặc định và một "ngoại lệ" được bao phủ bởi phần if-statement.

Khi xử lý các cấu trúc phức tạp hơn, tôi sẽ chọn một chiến lược khác, bỏ qua người ifanh em xấu xí đó switch.


Dự phòng +1 chắc chắn cho thấy sự gia tăng không chỉ phức tạp, mà còn nhầm lẫn. Tôi biết tôi đã luôn luôn phải kiểm tra lại mã được viết như thế này chính xác vì sự dư thừa.
dietbuddha

1
Bạn có thể loại bỏ các trường hợp sau khi vụ án bắt đầu So this is clear in what it does...và mở rộng trên nó? (Các trường hợp trước đây cũng có liên quan nghi vấn). Tôi nói điều này bởi vì đó là những gì câu hỏi đang yêu cầu chúng tôi kiểm tra. Bạn đã tuyên bố lập trường của mình, bỏ qua khối khác và thực sự đó là lập trường cần giải thích thêm (và đó là lý do khiến tôi phải hỏi câu hỏi này). Từ câu hỏi:I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.)
Selali Adobor

@AssortTrailmix Chắc chắn! Chính xác thì tôi nên làm gì? Vì câu hỏi ban đầu của bạn là »Các khối khác có làm tăng độ phức tạp của mã không?« Và câu trả lời của tôi là: có. Trong trường hợp này nó có thể được bỏ qua.
Thomas Junk

Và không! Tôi không hiểu downvote.
Thomas Junk

-1

Để làm cho một câu chuyện dài ngắn, trong trường hợp này, loại bỏ elsetừ khóa là một điều tốt. Nó hoàn toàn dư thừa ở đây và tùy thuộc vào cách bạn định dạng mã của mình, nó sẽ thêm một đến ba dòng hoàn toàn vô dụng vào mã của bạn. Xem xét những gì trong ifkhối:

return true;

Do đó, nếu ifkhối được nhập vào, toàn bộ phần còn lại của hàm, theo định nghĩa, sẽ không được thực thi. Nhưng nếu nó không được nhập vào, vì không có ifcâu lệnh nào nữa , toàn bộ phần còn lại của hàm được thực thi. Sẽ hoàn toàn khác nếu một returntuyên bố không nằm trong ifkhối, trong trường hợp đó, sự hiện diện hay vắng mặt của một elsekhối sẽ có tác động, nhưng ở đây, nó sẽ không có tác động.

Trong câu hỏi của bạn, sau khi đảo ngược iftình trạng của bạn và bỏ qua else, bạn đã nêu như sau:

Bây giờ ai đó có thể thêm một khối if mới dựa trên một điều kiện sau ví dụ đầu tiên mà không nhận ra ngay điều kiện đầu tiên là đặt một ràng buộc cho điều kiện của chính họ.

Họ cần đọc mã kỹ hơn một chút sau đó. Chúng tôi sử dụng các ngôn ngữ cấp cao và tổ chức mã rõ ràng để giảm thiểu các vấn đề này, nhưng việc thêm một elsekhối hoàn toàn dư thừa sẽ thêm "nhiễu" và "lộn xộn" vào mã, và chúng tôi cũng không nên đảo ngược hoặc đảo ngược các ifđiều kiện của mình. Nếu họ không thể nói sự khác biệt, thì họ sẽ không biết họ đang làm gì. Nếu họ có thể cho biết sự khác biệt, thì họ luôn có thể tự đảo ngược các ifđiều kiện ban đầu .

Tôi đã quên đề cập đến một trường hợp tôi đồng ý với sự thiếu sót của một khối khác và khi sử dụng một khối if để áp dụng một ràng buộc phải được thỏa mãn một cách hợp lý cho tất cả các mã sau, chẳng hạn như kiểm tra null (hoặc bất kỳ bộ bảo vệ nào khác ).

Đó thực chất là những gì đang xảy ra ở đây. Bạn đang trở về từ ifkhối, do đó ifđiều kiện đánh giá false một ràng buộc phải được thỏa mãn một cách hợp lý cho tất cả các mã sau.

Có sự gia tăng độ phức tạp được giới thiệu bằng cách không bao gồm các khối khác?

Không, nó sẽ tạo ra sự giảm độ phức tạp trong mã của bạn và thậm chí nó có thể tạo thành sự giảm mã được biên dịch, tùy thuộc vào việc tối ưu hóa siêu nhỏ nhất định sẽ xảy ra hay không.


2
"Họ cần đọc mã kỹ hơn một chút sau đó." Phong cách mã hóa tồn tại để các lập trình viên có thể ít chú ý đến các chi tiết khó chịu và chú ý nhiều hơn đến những gì họ thực sự đang làm. Nếu phong cách của bạn khiến bạn chú ý đến những chi tiết đó một cách không cần thiết, thì đó là một phong cách tồi. "... dòng hoàn toàn vô dụng ..." Chúng không vô dụng - chúng cho bạn xem nhanh cấu trúc là gì, mà không phải lãng phí thời gian để suy nghĩ về những gì sẽ xảy ra nếu phần này hoặc phần đó được thực thi.
Michael Shaw

@MichaelShaw Tôi nghĩ câu trả lời của Manila Cohn đi sâu vào những gì tôi đang nói.
Panzercrisis

-2

Trong ví dụ cụ thể bạn đã đưa ra, nó làm.

  • Nó buộc người đánh giá phải đọc lại mã để đảm bảo đó không phải là lỗi.
  • Nó thêm độ phức tạp (không cần thiết) bởi vì nó thêm một nút vào biểu đồ luồng.

Tôi nghĩ rằng nó không thể đơn giản hơn thế này:

bool CanLeaveWithoutUmbrella()
{
    return (sky.Color == Color.Blue);
}

6
Tôi đặc biệt giải quyết thực tế ví dụ rất đơn giản này có thể được viết lại để xóa ifcâu lệnh. Trong thực tế, tôi rõ ràng không khuyến khích đơn giản hóa hơn nữa bằng cách sử dụng mã chính xác mà bạn đã sử dụng. Ngoài ra, độ phức tạp chu kỳ không được tăng theo elsekhối.
Selali Adobor

-4

Tôi luôn cố gắng viết mã của mình sao cho ý định càng rõ ràng càng tốt. Ví dụ của bạn thiếu một số ngữ cảnh, vì vậy tôi sẽ sửa đổi nó một chút:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

Ý định của chúng tôi dường như là chúng tôi có thể rời đi mà không cần ô khi Sky có màu xanh. Tuy nhiên, ý định đơn giản đó bị mất trong một biển mã. Có một số cách chúng ta có thể làm cho nó dễ hiểu hơn.

Đầu tiên, có câu hỏi của bạn về elsechi nhánh. Tôi cũng không bận tâm, nhưng có xu hướng bỏ đi else. Tôi hiểu lập luận về việc rõ ràng, nhưng ý kiến ​​của tôi là các ifcâu lệnh nên bao bọc các nhánh 'tùy chọn', giống như câu lệnh return true. Tôi sẽ không gọi return falsechi nhánh là 'tùy chọn', vì nó hoạt động như mặc định của chúng tôi. Dù bằng cách nào, tôi thấy đây là một vấn đề rất nhỏ và ít quan trọng hơn những vấn đề sau:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        if(this.color == Color.Blue)
        {
            return true;
        }
        return false;
    }
}

Một nhiều vấn đề quan trọng hơn là chi nhánh của bạn đang làm quá nhiều! Các chi nhánh, giống như mọi thứ, nên "đơn giản nhất có thể, nhưng không hơn". Trong trường hợp này, bạn đã sử dụng một ifcâu lệnh, phân nhánh giữa các khối mã (tập hợp các câu lệnh) khi tất cả những gì bạn thực sự cần phân nhánh giữa là các biểu thức (giá trị). Điều này là rõ ràng vì a) khối mã của bạn chỉ chứa các câu lệnh đơn và b) chúng là cùng một câu lệnh ( return). Chúng ta có thể sử dụng toán tử ternary ?:để thu hẹp nhánh chỉ thành phần khác nhau:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue)
            ? true
            : false;
    }
}

Bây giờ chúng tôi đạt đến sự dư thừa rõ ràng mà kết quả của chúng tôi là giống hệt nhau this.color == Color.Blue. Tôi biết câu hỏi của bạn yêu cầu chúng tôi bỏ qua điều này, nhưng tôi nghĩ rằng điều thực sự quan trọng là chỉ ra rằng đây là cách suy nghĩ theo thủ tục, theo thứ tự đầu tiên: bạn đang phá vỡ các giá trị đầu vào và xây dựng một số giá trị đầu ra. Điều đó tốt, nhưng nếu có thể, sẽ mạnh mẽ hơn nhiều khi nghĩ về các mối quan hệ hoặc biến đổi giữa đầu vào và đầu ra. Trong trường hợp này, mối quan hệ rõ ràng là giống nhau, nhưng tôi thường thấy mã thủ tục phức tạp như thế này, điều này che khuất một số mối quan hệ đơn giản. Hãy tiếp tục và thực hiện đơn giản hóa này:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool CanLeaveWithoutUmbrella()
    {
        return (this.color == Color.Blue);
    }
}

Điều tiếp theo tôi muốn chỉ ra là API của bạn chứa một tiêu cực kép: hoàn toàn có thể xảy CanLeaveWithoutUmbrellara false. Điều này, tất nhiên, đơn giản hóa để NeedUmbrellatrở thành true, vì vậy hãy làm điều đó:

class Sky {
    Sky(Colour c)
    {
        this.color = c;
    }
    bool NeedUmbrella()
    {
        return (this.color != Color.Blue);
    }
}

Bây giờ chúng ta có thể bắt đầu suy nghĩ về phong cách mã hóa của bạn. Có vẻ như bạn đang sử dụng ngôn ngữ Hướng đối tượng, nhưng bằng cách sử dụng các ifcâu lệnh để phân nhánh giữa các khối mã, bạn đã kết thúc việc viết mã thủ tục .

Trong mã hướng đối tượng, tất cả các khối mã là các phương thức và tất cả các nhánh được thực hiện thông qua công văn động , ví dụ. sử dụng các lớp hoặc nguyên mẫu. Người ta tranh cãi liệu điều đó có làm mọi thứ đơn giản hơn không, nhưng nó sẽ làm cho mã của bạn phù hợp hơn với phần còn lại của ngôn ngữ:

class Sky {
    bool NeedUmbrella()
    {
        return true;
    }
}

class BlueSky extends Sky {
    bool NeedUmbrella()
    {
        return false;
    }
}

Lưu ý rằng việc sử dụng các toán tử ternary để phân nhánh giữa các biểu thức là phổ biến trong lập trình chức năng .


Đoạn đầu tiên của câu trả lời dường như đang đi đúng hướng để trả lời câu hỏi, nhưng nó ngay lập tức chuyển sang chủ đề chính xác, tôi hỏi một cách rõ ràng một người bỏ qua cho ví dụ rất đơn giản này . Từ câu hỏi : I ask that you ignore the many other ways the function can be written, and changes that can be made to it. (I know most people are itching to write return sky.Color == Color.Blue.). Trong thực tế, bạn sử dụng dòng mã chính xác mà tôi đề cập. Ngoài ra, trong khi phần này của câu hỏi không có chủ đề, tôi muốn nói rằng bạn đang đi quá xa trong suy luận của mình. Bạn đã thay đổi hoàn toàn mã là gì.
Selali Adobor

@AssorticTrailmix Đây hoàn toàn là một câu hỏi về phong cách. Nó có ý nghĩa để quan tâm đến phong cách và nó có ý nghĩa để bỏ qua nó (chỉ tập trung vào kết quả). Tôi không nghĩ rằng nó làm cho tinh thần để chăm sóc về return false;vs else { return false; }trong khi không quan tâm về cực chi nhánh, văn năng động, ternaries, vv Nếu bạn đang viết mã thủ tục trong một ngôn ngữ OO, thay đổi triệt để là cần thiết;)
Warbo

Bạn có thể nói một cái gì đó giống như vậy nói chung, nhưng nó không thể áp dụng khi bạn trả lời một câu hỏi với các tham số được xác định rõ (đặc biệt là khi không tôn trọng các tham số đó, bạn có thể "trừu tượng hóa" toàn bộ câu hỏi). Tôi cũng nói rằng câu cuối cùng là sai. OOP là về "trừu tượng hóa thủ tục", không phải là "xóa sổ thủ tục". Tôi muốn nói rằng thực tế bạn đã đi từ một lớp lót đến vô số lớp (Một cho mỗi màu) cho thấy lý do tại sao. Ngoài ra, tôi vẫn đang tự hỏi công văn động nên làm gì với một if/elsetuyên bố và phân cực nhánh là gì.
Selali Adobor

Tôi chưa bao giờ nói giải pháp OO tốt hơn (thực tế, tôi thích lập trình chức năng hơn), tôi chỉ nói rằng nó phù hợp hơn với ngôn ngữ. Theo "cực tính", ý tôi là thứ nào là "đúng" và "sai" (tôi đã chuyển nó bằng "NeedUm giác"). Trong thiết kế OO, tất cả các luồng điều khiển được xác định bởi công văn động. Ví dụ: Smalltalk không cho phép bất cứ điều gì khác en.wikipedia.org/wiki/Smalltalk#Control_ cấu trúc (cũng xem c2.com/cgi/wiki?HeInventedTheTerm )
Warbo

2
Tôi đã nâng cấp điều này cho đến khi tôi đi đến đoạn cuối cùng (về OO), điều này đã khiến nó trở thành một downvote thay thế! Đó chính xác là kiểu lạm dụng không suy nghĩ của các cấu trúc OO tạo ra mã ravioli , thứ không thể đọc được như spaghetti.
zwol
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.