Là bài tập trong điều kiện một phần của điều kiện là một thực hành xấu?


35

Giả sử tôi muốn viết một hàm nối hai chuỗi trong C. Cách tôi sẽ viết là:

void concat(char s[], char t[]){
    int i = 0;
    int j = 0;

    while (s[i] != '\0'){
        i++;
    }

    while (t[j] != '\0'){
        s[i] = t[j];
        i++;
        j++;
    }

    s[i] = '\0';

}

Tuy nhiên, K & R trong cuốn sách của họ đã triển khai nó theo cách khác, đặc biệt bao gồm càng nhiều trong phần điều kiện của vòng lặp while càng tốt:

void concat(char s[], char t[]){
    int i, j;
    i = j = 0;
    while (s[i] != '\0') i++;

    while ((s[i++]=t[j++]) != '\0');

}

Cách nào được ưa thích? Được khuyến khích hay không khuyến khích viết mã theo cách K & R làm? Tôi tin rằng phiên bản của tôi sẽ dễ đọc hơn bởi những người khác.


38
Đừng quên, K & R được xuất bản lần đầu tiên vào năm 1978. Có một vài thay đổi nhỏ trong cách chúng tôi viết mã kể từ đó.
corsiKa

28
Khả năng đọc trở lại rất khác trong thời của các nhà xuất bản và biên tập viên định hướng dòng. Nấm tất cả những thứ đó vào một dòng duy nhất được sử dụng để dễ đọc hơn .
user2357112 hỗ trợ Monica

15
Tôi bị sốc khi họ có các chỉ số và so sánh với '\ 0' thay vì một cái gì đó như while (*s++ = *t++); (My C rất gỉ, tôi có cần parens ở đó để ưu tiên cho nhà điều hành không?) K & R có phát hành phiên bản mới của cuốn sách của họ không? Cuốn sách gốc của họ có mã cực kỳ súc tích và thành ngữ.
dùng949300

4
Khả năng đọc là một điều rất riêng tư - ngay cả trong thời của teletypes. Những người khác nhau thích phong cách khác nhau. Rất nhiều hướng dẫn nhồi nhét phải làm với việc tạo mã. Trong những ngày đó, một số bộ hướng dẫn (ví dụ: Data General) có thể nhồi nhét một số thao tác vào một lệnh. Ngoài ra, vào đầu những năm 80, có một huyền thoại rằng sử dụng dấu ngoặc đơn tạo ra nhiều hướng dẫn hơn. Tôi đã phải tạo trình biên dịch để chứng minh cho người đánh giá mã rằng đó là một huyền thoại.
cốc

10
Lưu ý rằng hai khối mã không tương đương. Khối mã đầu tiên sẽ không sao chép kết thúc '\0'từ t( whilethoát ra trước). Điều này sẽ để lại schuỗi kết quả mà không kết thúc '\0'(trừ khi vị trí bộ nhớ đã bị xóa). Khối mã thứ hai sẽ tạo bản sao kết thúc '\0'trước khi thoát khỏi whilevòng lặp.
Makyen

Câu trả lời:


80

Luôn thích sự rõ ràng hơn sự thông minh. Trong năm qua, lập trình viên giỏi nhất là người mà mã không ai có thể hiểu được. "Tôi không thể hiểu mã của anh ấy, anh ấy phải là một thiên tài" , họ nói. Ngày nay, lập trình viên tốt nhất là người mà ai cũng có thể hiểu được. Giờ máy tính rẻ hơn thời gian lập trình viên.

Bất kỳ kẻ ngốc nào cũng có thể viết mã mà máy tính có thể hiểu. lập trình viên giỏi viết mã mà con người có thể hiểu được. (M. Fowler)

Vì vậy, không còn nghi ngờ gì nữa, tôi sẽ chọn phương án A. Và đó là câu trả lời dứt khoát của tôi.


8
Hệ tư tưởng rất sâu sắc, nhưng thực tế vẫn không có gì sai khi gán trong điều kiện. Tốt hơn hết là thoát sớm khỏi một vòng lặp hoặc sao chép mã trước và bên trong một vòng lặp.
Miles Rout

26
@MilesRout Có. Có điều gì đó không đúng với bất kỳ mã nào có tác dụng phụ mà bạn không mong đợi, tức là truyền các đối số hàm hoặc đánh giá các điều kiện. Thậm chí không đề cập đến if (a=b)có thể dễ dàng bị nhầm lẫn if (a==b).
Arthur Havlicek

12
@Luke: "IDE của tôi có thể dọn sạch X, do đó nó không phải là vấn đề" khá thiếu thuyết phục. Nếu nó không phải là một vấn đề, tại sao IDE làm cho nó dễ dàng "sửa chữa?"
Kevin

6
@ArthurHavlicek Tôi đồng ý với quan điểm chung của bạn, nhưng mã có tác dụng phụ trong điều kiện thực sự không quá hiếm gặp: while ((c = fgetc(file)) != EOF)như là điều đầu tiên xuất hiện trong đầu tôi.
Daniel Jour

3
+1 "Xem xét rằng việc gỡ lỗi khó gấp đôi so với việc viết chương trình ở nơi đầu tiên, nếu bạn thông minh như bạn có thể khi bạn viết nó, bạn sẽ gỡ lỗi như thế nào?" BWKernighan
Christophe

32

Nguyên tắc vàng, giống như trong câu trả lời của Tulains Córdova, là đảm bảo viết mã dễ hiểu. Nhưng tôi không đồng ý với kết luận này. Quy tắc vàng đó có nghĩa là viết mã mà lập trình viên điển hình cuối cùng sẽ duy trì mã của bạn có thể hiểu được. Và bạn là người đánh giá tốt nhất về việc lập trình viên điển hình sẽ duy trì mã của bạn là ai.

Đối với các lập trình viên không bắt đầu với C, phiên bản đầu tiên có lẽ dễ hiểu hơn, vì những lý do bạn đã biết.

Đối với những người lớn lên với phong cách C đó, phiên bản thứ hai có thể dễ hiểu hơn: đối với họ, điều dễ hiểu không kém là mã làm gì, đối với họ, nó để lại ít câu hỏi hơn tại sao nó được viết theo cách đó và đối với họ , không gian dọc ít hơn có nghĩa là có thể hiển thị nhiều ngữ cảnh hơn trên màn hình.

Bạn sẽ phải dựa vào ý thức tốt của riêng bạn. Đối tượng nào bạn muốn làm cho mã của bạn dễ hiểu nhất? Là mã này được viết cho một công ty? Sau đó, đối tượng mục tiêu có lẽ là các lập trình viên khác trong công ty đó. Đây có phải là một dự án sở thích cá nhân mà không ai sẽ làm việc ngoài chính bạn? Sau đó, bạn là đối tượng mục tiêu của riêng bạn. Đây có phải là mã bạn muốn chia sẻ với người khác? Sau đó, những người khác là đối tượng mục tiêu của bạn. Chọn phiên bản phù hợp với khán giả đó. Thật không may, không có cách duy nhất ưa thích để khuyến khích.


14

EDIT: Dòng s[i] = '\0';đã được thêm vào phiên bản đầu tiên, do đó sửa nó như được mô tả trong biến thể 1 bên dưới, vì vậy điều này không áp dụng cho phiên bản hiện tại của mã câu hỏi nữa.

Phiên bản thứ hai có ưu điểm đặc biệt là chính xác , trong khi phiên bản thứ nhất thì không - nó không kết thúc chính xác chuỗi mục tiêu.

"Gán trong điều kiện" cho phép thể hiện khái niệm "sao chép mọi ký tự trước khi kiểm tra ký tự null" rất chính xác và theo cách giúp tối ưu hóa trình biên dịch dễ dàng hơn, mặc dù nhiều kỹ sư phần mềm ngày nay thấy kiểu mã này ít đọc hơn . Nếu bạn khăng khăng sử dụng phiên bản đầu tiên, bạn phải

  1. thêm chấm dứt null sau khi kết thúc vòng lặp thứ hai (thêm mã, nhưng bạn có thể lập luận rằng khả năng đọc làm cho nó đáng giá) hoặc
  2. thay đổi thân vòng lặp thành "gán đầu tiên, sau đó kiểm tra hoặc lưu char được gán, sau đó tăng chỉ số". Kiểm tra điều kiện ở giữa vòng lặp có nghĩa là thoát ra khỏi vòng lặp (làm giảm sự rõ ràng, được hầu hết những người theo chủ nghĩa thuần túy cau mày). Lưu char được gán có nghĩa là giới thiệu một biến tạm thời (giảm độ rõ ràng và hiệu quả). Theo tôi, cả hai thứ đó sẽ hủy diệt lợi thế.

Đúng là tốt hơn đọc và súc tích.
dùng949300

5

Các câu trả lời của Tulains Córdova và hvd bao gồm các khía cạnh rõ ràng / dễ đọc khá tốt. Hãy để tôi ném vào phạm vi như một lý do khác có lợi cho các bài tập trong điều kiện. Một biến được khai báo trong điều kiện chỉ khả dụng trong phạm vi của câu lệnh đó. Bạn không thể sử dụng biến đó sau đó một cách tình cờ. Các cho vòng lặp đã được làm điều này cho các lứa tuổi. Và điều quan trọng là C ++ 17 sắp ra mắt giới thiệu một cú pháp tương tự cho ifswitch :

if (int foo = bar(); foo > 42) {
    do_stuff();
}

foo = 23;   // compiler error: foo is not in scope

3

Không. Nó rất chuẩn và kiểu C bình thường. Ví dụ của bạn là một ví dụ xấu, bởi vì nó chỉ là một vòng lặp for, nhưng nói chung không có gì sai với

if ((a = f()) != NULL)
    ...

ví dụ (hoặc với thời gian).


7
Có cái gì đó sai với nó; `! = NULL` và họ hàng của nó trong điều kiện C là khác nhau, chỉ cần ở đó để xoa dịu các nhà phát triển không thoải mái với khái niệm giá trị là đúng hay sai (hoặc ngược lại).
Jonathan Cast

1
@jcast Không, nó bao gồm rõ ràng hơn != NULL.
Miles Rout

1
Không, nó rõ ràng hơn để nói (x != NULL) != 0. Rốt cuộc, đó là những gì C thực sự đang kiểm tra, phải không?
Jonathan Cast

@jcast Không, không phải vậy. Kiểm tra xem một cái gì đó không bằng với sai không phải là cách bạn viết điều kiện bằng bất kỳ ngôn ngữ nào.
Miles Rout

"Kiểm tra xem một cái gì đó không bằng với sai không phải là cách bạn viết điều kiện bằng bất kỳ ngôn ngữ nào." Chính xác.
Jonathan Cast

2

Trong thời của K & R

  • 'C' là mã lắp ráp di động
  • Nó được sử dụng bởi các lập trình viên nghĩ về mã lắp ráp
  • Trình biên dịch không làm tối ưu hóa nhiều
  • Hầu hết các máy tính đều có bộ hướng dẫn phức tạp của tập tin, ví dụ, while ((s[i++]=t[j++]) != '\0')ánh xạ tới một lệnh trên hầu hết các CPU (tôi mong đợi VAC tháng 12)

Những ngày kia

  • Hầu hết mọi người đọc mã C không phải là lập trình viên mã lắp ráp
  • Trình biên dịch C thực hiện rất nhiều tối ưu hóa, do đó việc đọc mã đơn giản hơn có khả năng được dịch sang cùng mã máy.

(Lưu ý về luôn sử dụng niềng răng - tập 1 mã chiếm nhiều không gian hơn do có một số “không cần thiết” {}, theo kinh nghiệm của tôi những thường ngăn chặn mã mà đã nặng sáp nhập từ trình biên dịch và cho phép lỗi với sai “;” Vị trí được được phát hiện bởi các công cụ.)

Tuy nhiên, vào thời xưa, phiên bản thứ 2 của mã sẽ được đọc. (Nếu tôi hiểu đúng!)

concat(char* s, char *t){      
    while (*s++);
    --s;
    while (*s++=*t++);
}

2

Thậm chí có thể làm điều này là một ý tưởng rất xấu. Nó thường được gọi là "Lỗi cuối cùng của thế giới", như vậy:

if (alert = CODE_RED)
{
   launch_nukes();
}

Mặc dù bạn không có khả năng mắc một lỗi khá nghiêm trọng, nhưng rất dễ vô tình làm hỏng và gây ra lỗi khó tìm trong cơ sở mã của bạn. Hầu hết các trình biên dịch hiện đại sẽ chèn một cảnh báo cho các bài tập bên trong một điều kiện. Họ ở đó vì một lý do, và bạn sẽ làm tốt việc chú ý đến họ và chỉ cần tránh cấu trúc này.


Trước những cảnh báo này, chúng tôi sẽ viết CODE_RED = alertđể nó báo lỗi trình biên dịch.
Ian

4
@Ian Yoda Điều kiện được gọi là. Khó đọc chúng là. Thật không may sự cần thiết cho họ là.
Mason Wheeler

Sau một thời gian giới thiệu "làm quen với nó" rất ngắn gọn, điều kiện của Yoda không khó đọc hơn những điều bình thường. Đôi khi chúng dễ đọc hơn . Ví dụ: nếu bạn có một chuỗi ifs / otherifs, việc điều kiện được kiểm tra ở bên trái để có sự nhấn mạnh cao hơn là một IMO cải thiện nhẹ.
user949300

2
@ user949300 Hai từ: Hội chứng Stockholm: P
Mason Wheeler

2

Cả hai phong cách đều được hình thành tốt, chính xác, và phù hợp. Cái nào phù hợp hơn phụ thuộc phần lớn vào nguyên tắc phong cách của công ty bạn. Các IDE hiện đại sẽ tạo điều kiện thuận lợi cho việc sử dụng cả hai phong cách thông qua việc sử dụng cú pháp trực tiếp, làm nổi bật rõ ràng các khu vực có thể trở thành một nguồn gây nhầm lẫn.

Ví dụ: biểu thức sau được Netbeans tô sáng :

if($a = someFunction())

với lý do "phân công tình cờ".

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

Để nói rõ với Netbeans rằng "vâng, tôi thực sự muốn làm điều đó ...", biểu thức có thể được gói trong một dấu ngoặc đơn.

if(($a = someFunction()))

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

Vào cuối ngày, tất cả tập trung vào các hướng dẫn về phong cách của công ty và sự sẵn có của các công cụ hiện đại để tạo thuận lợi cho quá trình phát triển.

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.