Có nên sử dụng khác không nên sử dụng trong các tình huống mà luồng điều khiển làm cho nó bị dư thừa?


18

Đôi khi tôi vấp phải mã tương tự như ví dụ sau (chức năng này thực hiện chính xác nằm ngoài phạm vi của câu hỏi này):

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  else if (check2(value)) {
    return value;
  }
  else {
    return false;
  }
}

Như bạn có thể thấy, if, else ifelsebáo cáo được sử dụng kết hợp với returntuyên bố. Điều này có vẻ khá trực quan đối với một người quan sát thông thường, nhưng tôi nghĩ nó sẽ thanh lịch hơn (theo quan điểm của một nhà phát triển phần mềm) để bỏ các else-s và đơn giản hóa mã như thế này:

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  if (check2(value)) {
    return value;
  }
  return false;
}

Điều này có ý nghĩa, vì mọi thứ tuân theo một returncâu lệnh (trong cùng phạm vi) sẽ không bao giờ được thực thi, làm cho mã ở trên về mặt ngữ nghĩa bằng với ví dụ đầu tiên.

Điều nào ở trên phù hợp với thực tiễn mã hóa tốt hơn? Có bất kỳ nhược điểm nào đối với một trong hai phương pháp liên quan đến khả năng đọc mã không?

Chỉnh sửa: Một đề xuất trùng lặp đã được đưa ra với câu hỏi này được cung cấp làm tài liệu tham khảo. Tôi tin rằng câu hỏi của tôi chạm vào một chủ đề khác, vì tôi không hỏi về việc tránh các câu trùng lặp như được trình bày trong câu hỏi khác. Cả hai câu hỏi đều tìm cách giảm sự lặp lại, mặc dù theo những cách hơi khác nhau.


14
Đây elselà một vấn đề nhỏ hơn, vấn đề lớn hơn là bạn rõ ràng đang trả lại hai loại dữ liệu từ một hàm duy nhất, điều này làm cho API của hàm không thể đoán trước được.
Andy

3
E_CLEARLY_NOTADUPE
kojiro

3
@DavidPacker: Ít nhất hai; có thể là ba -1là một số, falselà một boolean và valuekhông được chỉ định ở đây vì vậy nó có thể là bất kỳ loại đối tượng nào.
Yay295

1
@ Yay295 Tôi đã hy vọng valuenó thực sự là một số nguyên. Nếu nó có thể là bất cứ điều gì thì điều đó thậm chí còn tồi tệ hơn.
Andy

@DavidPacker Đây là một điểm rất quan trọng, đặc biệt là khi được nêu ra liên quan đến một câu hỏi về thực hành tốt. Tôi đồng ý với bạn và Yay295, tuy nhiên các mẫu mã đóng vai trò là một ví dụ để chứng minh một kỹ thuật mã hóa không liên quan. Tuy nhiên, tôi nhớ rằng đây là một vấn đề quan trọng và không nên bỏ qua trong mã thực tế.
tê giác

Câu trả lời:


23

Tôi thích cái không có elsevà đây là lý do:

function doSomething(value) {
  //if (check1(value)) {
  //  return -1;
  //}
  if (check2(value)) {
    return value;
  }
  return false;
}

Bởi vì làm điều đó đã không phá vỡ bất cứ điều gì nó không được phép.

Ghét sự phụ thuộc lẫn nhau trong tất cả các hình thức của nó (bao gồm cả việc đặt tên một chức năng check2()). Cô lập tất cả những gì có thể được phân lập. Đôi khi bạn cần elsenhưng tôi không thấy điều đó ở đây.


Tôi thường thích điều này cũng vì lý do được nêu ở đây. Tuy nhiên, tôi thường đặt một nhận xét ở cuối mỗi ifkhối } //elseđể làm cho nó rõ ràng, khi đọc mã, việc thực hiện dự định duy nhất sau ifkhối mã là nếu điều kiện trong ifcâu lệnh là sai. Nếu không có cái gì đó như thế này, đặc biệt nếu mã trong ifkhối phát triển phức tạp hơn, có thể không rõ ràng cho bất cứ ai đang duy trì mã mà ý định là không bao giờ thực hiện vượt qua ifkhối khi ifđiều kiện là đúng.
Makyen

@Makyen bạn nâng cao một điểm tốt. Một tôi đã cố gắng bỏ qua. Tôi cũng tin rằng một cái gì đó nên được thực hiện sau khi iflàm rõ rằng phần mã này đã được thực hiện và cấu trúc đã hoàn thành. Để làm điều này, tôi làm những gì tôi chưa làm ở đây (vì tôi không muốn làm sao lãng điểm chính của OP bằng cách thực hiện các thay đổi khác). Tôi làm điều đơn giản nhất mà tôi có thể đạt được tất cả những điều đó mà không bị phân tâm. Tôi thêm một dòng trống.
candied_orange

27

Tôi nghĩ rằng nó phụ thuộc vào ngữ nghĩa của mã. Nếu ba trường hợp của bạn phụ thuộc vào nhau, hãy nói rõ ràng. Điều này làm tăng khả năng đọc mã và giúp mọi người dễ hiểu hơn. Thí dụ:

if (x < 0) {
    return false;
} else if (x > 0) {
    return true;
} else {
    throw new IllegalArgumentException();
}

Ở đây bạn rõ ràng tùy thuộc vào giá trị của xtrong cả ba trường hợp. Nếu bạn bỏ qua lần cuối else, điều này sẽ không rõ ràng.

Nếu các trường hợp của bạn không phụ thuộc trực tiếp vào nhau, hãy bỏ qua nó:

if (x < 0) {
    return false;
}
if (y < 0) {
    return true;
}
return false;

Thật khó để thể hiện điều này trong một ví dụ đơn giản không có ngữ cảnh, nhưng tôi hy vọng bạn hiểu ý tôi.


6

Tôi thích tùy chọn thứ hai (tách biệt ifsvới không else ifvà sớm return) NHƯNG đó là miễn là các khối mã ngắn .

Nếu các khối mã dài thì sử dụng tốt hơn else if, bởi vì những người khác bạn sẽ không hiểu rõ về một số điểm thoát mà mã có.

Ví dụ:

function doSomething(value) {
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    return -1;
  }
  if (check2(value)) {
    // ...
    // imagine 150 lines of code
    // ...
    return value;
  }
  return false;
}

Trong trường hợp đó, nó được sử dụng tốt hơn else if, lưu trữ mã trả về trong một biến và chỉ có một câu trả về ở cuối.

function doSomething(value) {
  retVal=0;
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    retVal=-1;
  } else if (check2(value)) {
    // ...
    // imagne 150 lines of code
    retVal=value;
  }
  return retVal;
}

Nhưng vì người ta nên cố gắng để các chức năng được ngắn, tôi nói hãy giữ nó ngắn và thoát sớm như trong đoạn mã đầu tiên của bạn.


1
Tôi đồng ý với tiền đề của bạn, nhưng miễn là chúng ta đang thảo luận về việc mã phải như thế nào, liệu chúng ta có thể không chỉ đồng ý các khối mã nên ngắn không? Tôi nghĩ điều tồi tệ hơn là có một khối mã dài không được chia thành các hàm hơn là sử dụng khác nếu, nếu tôi phải chọn.
kojiro

1
Một cuộc cãi vã tò mò. returnnằm trong 3 dòng ifvì vậy nó không khác gì so với else ifsau 200 dòng mã. Kiểu trả về duy nhất mà bạn giới thiệu ở đây là truyền thống của c và các ngôn ngữ khác không báo cáo lỗi có ngoại lệ. Vấn đề là tạo ra một nơi duy nhất để dọn sạch tài nguyên. Ngôn ngữ ngoại lệ sử dụng một finallykhối cho điều đó. Tôi cho rằng điều này cũng tạo ra một nơi để đặt điểm dừng nếu trình gỡ lỗi của bạn không cho phép bạn đặt một hàm trên hàm đóng dấu ngoặc nhọn.
candied_orange

4
Tôi không thích sự phân công nào retValcả. Nếu bạn có thể thoát một cách an toàn một hàm làm như vậy ngay lập tức, mà không gán giá trị an toàn được trả về một biến của một lối thoát duy nhất từ ​​hàm. Khi bạn sử dụng điểm trả về duy nhất trong một hàm thực sự dài, tôi thường không tin tưởng các lập trình viên khác và cuối cùng cũng xem qua phần còn lại của hàm để sửa đổi các giá trị trả về khác. Thất bại nhanh chóng và trở về sớm, trong số những người khác, hai quy tắc tôi sống theo.
Andy

2
Theo ý kiến ​​của tôi, nó thậm chí còn tồi tệ hơn đối với các khối dài. Tôi cố gắng thoát các chức năng càng sớm càng tốt cho dù bối cảnh bên ngoài là gì.
Andy

2
Tôi với DavidPacker ở đây. Kiểu trả về đơn buộc chúng ta phải nghiên cứu tất cả các điểm gán cũng như mã sau các điểm gán. Nghiên cứu các điểm thoát của phong cách trở lại sớm sẽ tốt hơn đối với tôi, dài hay ngắn. Miễn là ngôn ngữ cho phép một khối cuối cùng.
candied_orange

4

Tôi sử dụng cả hai trong hoàn cảnh khác nhau. Trong một kiểm tra xác nhận, tôi bỏ qua cái khác. Trong một luồng điều khiển, tôi sử dụng cái khác.

bool doWork(string name) {
  if(name.empty()) {
    return false;
  }

  //...do work
  return true;
}

đấu với

bool doWork(int value) {
  if(value % 2 == 0) {
    doWorkWithEvenNumber(value);
  }
  else {
    doWorkWithOddNumber(value);
  }
}

Trường hợp đầu tiên đọc giống như bạn đang kiểm tra tất cả các điều kiện tiên quyết trước tiên, đưa chúng ra khỏi đường đi, sau đó tiến tới mã thực tế bạn muốn chạy. Như vậy, mã juicy bạn thực sự muốn chạy thuộc phạm vi của hàm trực tiếp; đó là những gì chức năng thực sự là về.
Trường hợp thứ hai đọc giống như cả hai đường dẫn là mã hợp lệ sẽ được chạy có liên quan đến chức năng như các đường dẫn khác dựa trên một số điều kiện. Như vậy, chúng thuộc các cấp phạm vi tương tự với nhau.


0

Tình huống duy nhất tôi từng thấy nó thực sự quan trọng là mã như thế này:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    }
    if (left != null) {
        return Arrays.asList(left);
    }
    if (right != null) {
        return Arrays.asList(right);
    }
    return Arrays.asList();
}

Rõ ràng có điều gì đó sai ở đây. Vấn đề là, không rõ nếureturn nên thêm hoặc Arrays.asListxóa. Bạn không thể sửa lỗi này mà không kiểm tra sâu hơn các phương pháp liên quan. Bạn có thể tránh sự mơ hồ này bằng cách luôn luôn sử dụng các khối if-if đầy đủ. Điều này:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    } else if (left != null) {
        return Arrays.asList(left);
    } else if (right != null) {
        return Arrays.asList(right);
    } else {
        return Arrays.asList();
    }
}

sẽ không biên dịch (bằng các ngôn ngữ được kiểm tra tĩnh) trừ khi bạn thêm trả lại rõ ràng trước if.

Một số ngôn ngữ thậm chí không cho phép phong cách đầu tiên. Tôi cố gắng chỉ sử dụng nó trong bối cảnh bắt buộc nghiêm ngặt hoặc cho các điều kiện tiên quyết trong các phương pháp dài:

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}

-2

Có ba lối thoát từ chức năng như vậy thực sự khó hiểu nếu bạn đang cố gắng tuân theo mã thông qua và thực hành xấu.

Nếu bạn có một điểm thoát duy nhất cho hàm thì các elses là bắt buộc.

var result;
if(check1(value)
{
    result = -1;
}
else if(check2(value))
{
    result = value;
}
else 
{
    result = 0;
}
return result;

Trong thực tế cho phép đi xa hơn nữa.

var result = 0;
var c1 = check1(value);
var c2 = check2(value);

if(c1)
{
    result = -1;
}
else if(c2)
{
    result = value;
}

return result;

Đủ công bằng bạn có thể tranh luận cho một lợi nhuận sớm duy nhất về xác nhận đầu vào. Chỉ cần tránh gói toàn bộ số lượng lớn nếu logic của bạn trong một nếu.


3
Phong cách này đến từ các ngôn ngữ với quản lý tài nguyên rõ ràng. Điểm duy nhất là cần thiết để giải phóng tài nguyên được phân bổ. Trong các ngôn ngữ có quản lý tài nguyên tự động, điểm trả về duy nhất không quan trọng. Bạn nên tập trung vào việc giảm số lượng và phạm vi của các biến.
Banthar

a: không có ngôn ngữ được chỉ định trong câu hỏi. b: Tôi nghĩ bạn đã sai về điều này. có rất nhiều lý do tốt cho sự trở lại duy nhất. nó làm giảm sự phức tạp và tăng cường khả năng đọc
Ewan

1
Đôi khi nó làm giảm sự phức tạp, đôi khi nó làm tăng nó. Xem wiki.c2.com/?ArrowAntiPotype
Robert Johnson

Không, tôi nghĩ rằng nó luôn làm giảm độ phức tạp theo chu kỳ, xem ví dụ thứ hai của tôi. check2 luôn chạy
Ewan

có sự trở lại sớm là một sự tối ưu hóa làm phức tạp mã bằng cách chuyển một số mã thành một điều kiện
Ewan

-3

Tôi thích bỏ qua các tuyên bố khác dư thừa. Bất cứ khi nào tôi nhìn thấy nó (trong đánh giá mã hoặc tái cấu trúc) tôi tự hỏi liệu tác giả có hiểu luồng điều khiển hay không và có thể có lỗi trong mã.

Nếu tác giả tin rằng tuyên bố khác là cần thiết và do đó không hiểu ý nghĩa của dòng điều khiển của tuyên bố hoàn trả thì chắc chắn sự thiếu hiểu biết như vậy là một nguồn chính của lỗi, trong chức năng này và nói chung.


3
Trong khi tôi tình cờ đồng ý với bạn thì đây không phải là một cuộc thăm dò ý kiến. Vui lòng sao lưu các khiếu nại của bạn hoặc cử tri sẽ không đối xử tử tế với bạn.
candied_orange
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.