Cho-nếu antipotype


9

Tôi đã đọc trên bài đăng trên blog này về mô hình chống nếu, và tôi không chắc là tôi hiểu tại sao đó là mô hình chống.

foreach (string filename in Directory.GetFiles("."))
{
    if (filename.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))
    {
        return new StreamReader(filename);
    }
}

Câu hỏi 1:

Có phải vì return new StreamReader(filename);bên trong for loop? hoặc thực tế là bạn không cần một forvòng lặp trong trường hợp này?

Như tác giả của blog đã chỉ ra phiên bản ít điên rồ hơn của nó là:

if (File.Exists("desktop.ini"))
{
    return new StreamReader("desktop.ini");
} 

cả hai đều gặp phải tình trạng chủng tộc vì nếu tệp bị xóa trước khi tạo StreamReader, bạn sẽ nhận được a File­Not­Found­Exception.

Câu hỏi 2:

Để sửa ví dụ thứ hai, bạn có viết lại nó mà không có câu lệnh if hay không, và thay vào đó bao quanh khối StreamReaderthử bắt và nếu nó ném một khối File­Not­Found­Exceptionbạn có xử lý nó trong catchkhối tương ứng không?



1
@amon - Cảm ơn, nhưng tôi không đưa ra bất kỳ điểm bổ sung nào, tôi chỉ đang cố gắng hiểu những gì bài báo đang nói và làm thế nào tôi sẽ sửa nó.

3
Tôi sẽ không suy nghĩ nhiều. Các poster blog đang tạo ra một số mã ngu ngốc mà không ai viết, đặt tên cho nó và gọi nó là một mô hình chống. Đây là một cái khác: "Tôi gọi đây là mô hình gán vô nghĩa: x = x; Tôi thấy điều này được thực hiện mọi lúc. Thật ngu ngốc. Tôi nghĩ rằng tôi sẽ viết một blog về nó."
Martin Maat

4
To fix the second example, would you re-write it without the if statement, and instead surround the StreamReader with a try-catch block, and if it throws a File­Not­Found­Exception you handle it in the catch block accordingly?- Vâng, đó chính xác là những gì tôi sẽ làm. Việc giải quyết điều kiện cuộc đua quan trọng hơn một số khái niệm "ngoại lệ là dòng kiểm soát" và nó giải quyết nó một cách thanh lịch và sạch sẽ.
Robert Harvey

1
Nó làm gì nếu cả hai tệp không đáp ứng các tiêu chí đã cho? Nó có trở lại nullkhông? Bạn thường có thể sử dụng LINQ để dọn sạch mã giống như mã của ví dụ của bạn: return Directory.GetFiles(".").FirstOrDefault(fileName => fileName.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase))?.Select(fileName => new StreamReader(filename)); Lưu ý ?.toán tử giữa hai lệnh gọi LINQ. Ngoài ra mọi người có thể lập luận rằng việc tạo đối tượng như thế này không phải là cách sử dụng LINQ thích hợp nhất, nhưng tôi nghĩ nó ổn ở đây. Đây không phải là một câu trả lời cho câu hỏi của bạn, nhưng lạc đề về một phần của câu hỏi.
Panzercrisis

Câu trả lời:


7

Đây là một antipotype vì nó có dạng:

loop over a set of values
   if current value meets a condition
       do something with value
   end
end

và có thể được thay thế bằng

do something with value

Một ví dụ kinh điển về điều này là mã như:

for (var i=0; i < 5; i++)
{
    switch (i)
        case 1:
            doSomethingWith(1);
            break;
        case 2:
            doSomethingWith(2);
            break;
        case 3:
            doSomethingWith(4);
            break;
        case 4:
            doSomethingWith(4);
            break;
    }
}

Khi hoạt động sau đây tốt:

doSomethingWith(1);
doSomethingWith(2);
doSomethingWith(3);
doSomethingWith(4);

Nếu bạn thấy mình đang thực hiện một vòng lặp và một ifhoặc switch, thì hãy dừng lại và suy nghĩ về những gì bạn đang làm. Bạn có quá phức tạp mọi thứ và có thể thay thế toàn bộ vòng lặp và kiểm tra bằng một dòng "chỉ cần làm" đơn giản. Tuy nhiên, đôi khi, bạn sẽ thấy bạn cần phải thực hiện vòng lặp đó (ví dụ, nhiều mục có thể khớp với một điều kiện), trong trường hợp đó mô hình vẫn ổn.

Đó là lý do tại sao nó là một mô hình chống: nó lấy mô hình "vòng lặp và thử nghiệm" và lạm dụng nó.

Về câu hỏi thứ hai của bạn: có. Mẫu "thử làm" mạnh hơn mẫu "thử sau đó làm" trong mọi trường hợp mã của bạn không phải là luồng duy nhất trên toàn bộ thiết bị có thể thay đổi trạng thái của mục được kiểm tra.

Vấn đề với mã này:

if (File.Exists("desktop.ini"))
{
    return new StreamReader("desktop.ini");
}

là trong thời gian giữa File.ExistsStreamReadercố gắng mở tệp đó, một luồng hoặc quá trình khác có thể xóa tệp. Vì vậy, bạn sẽ nhận được một ngoại lệ. Do đó, ngoại lệ đó cần được bảo vệ thông qua một cái gì đó như:

try
{
    return new StreamReader("desktop.ini");
}
catch (File­Not­Found­Exception)
{
    return null; // or whatever
}

@Flater tăng điểm tốt? Đây có phải là một antipotype? Có phải chúng ta đang sử dụng ngoại lệ như luồng điều khiển?

Nếu mã đọc một cái gì đó như:

try
{
    if (!File.Exists("desktop.ini")
    {
        throw new IniFileMissingException();
        return new StreamReader("desktop.ini");
    }
}
catch (IniFileMissingException)
{
    return null;
}

sau đó tôi thực sự sẽ sử dụng các ngoại lệ như một goto được tôn vinh và nó thực sự sẽ là một mô hình chống. Nhưng trong trường hợp này, chúng tôi chỉ làm việc xung quanh hành vi không mong muốn là tạo một luồng mới, vì vậy đây không phải là một ví dụ về mô hình chống đó. Nhưng nó là một ví dụ về làm việc xung quanh mô hình chống đó.

Tất nhiên, những gì chúng tôi thực sự muốn là một cách thanh lịch hơn để tạo ra luồng. Cái gì đó như:

return TryCreateStream("desktop.ini", out var stream) ? stream : null;

Và tôi khuyên bạn nên gói try catchmã đó trong một phương thức tiện ích như thế này nếu bạn thấy mình sử dụng mã này rất nhiều.


1
@Flater, tôi có xu hướng đồng ý rằng nó không lý tưởng (mặc dù tôi tranh luận đó là một ví dụ về mô hình chống trả lời mà nói về). Mã phải thể hiện ý định của nó, không phải là cơ học. Vì vậy, tôi thích một cái gì đó như Scala Try, ví dụ return Try(() => new StreamReader("desktop.ini")).OrElse(null);, nhưng C # không hỗ trợ cấu trúc đó (không sử dụng thư viện của bên thứ 3), vì vậy chúng tôi phải làm việc với phiên bản cồng kềnh.
David Arno

1
@Flater, tôi hoàn toàn đồng ý rằng các ngoại lệ nên là ngoại lệ. Nhưng họ hiếm khi như vậy. Ví dụ, một tệp không tồn tại hầu như không phải là ngoại lệ, nhưng File.Opensẽ ném một tệp nếu nó không thể tìm thấy tệp. Vì chúng ta đã có một ngoại lệ ngoại lệ bị ném, nên bẫy nó như một phần của luồng điều khiển là một điều cần thiết (trừ khi người ta chỉ muốn để ứng dụng bị sập).
David Arno

1
@Flater: mẫu mã thứ hai là cách đúng để mở tệp. Bất cứ điều gì khác giới thiệu một điều kiện cuộc đua. Ngay cả khi bạn kiểm tra sự tồn tại của tệp, giữa kiểm tra đó và khi bạn thực sự mở nó, một cái gì đó có thể khiến tệp đó không khả dụng với bạn và lệnh gọi Open () sẽ phát nổ. Vì vậy, bạn phải sẵn sàng cho ngoại lệ nào. Vì vậy, bạn cũng có thể không bận tâm kiểm tra và chỉ cần thử và mở nó. Ngoại lệ là công cụ giúp chúng tôi hoàn thành công việc và việc sử dụng chúng không nên được xem là giáo điều tôn giáo.
tên gì

1
@Flater: mọi cách để tránh thử / bắt xung quanh các thao tác tệp giới thiệu điều kiện cuộc đua. Hệ thống tệp bạn muốn ghi có thể trở nên không khả dụng ngay lập tức trước khi bạn gọi File.Create, khiến mọi kiểm tra không hợp lệ.
tên của

1
@Flater " Bạn đang tranh luận một cách hiệu quả rằng mọi cuộc gọi phương thức đều cần một kiểu trả về ... đó là cách hiệu quả để cố gắng phát minh lại các ngoại lệ theo một cách khác ". Chính xác. Mặc dù sự tái tạo đó không phải là của tôi. Nó đã được sử dụng trong các ngôn ngữ chức năng trong nhiều năm. Nói chung, họ tránh toàn bộ "trường hợp ngoại lệ là vấn đề dòng kiểm soát" (mà bạn cho rằng nhầm lẫn là một kiểu chống lại trong một hơi thở và sau đó tranh luận có lợi cho lần tiếp theo) bằng cách sử dụng các loại kết hợp, ví dụ trong trường hợp này chúng tôi sẽ sử dụng một Maybe<Stream>loại trả về nothingnếu tệp không tồn tại và luồng nếu có.
David Arno

1

Câu hỏi 1: (đây có phải là một vòng lặp)

Có, bởi vì bạn không cần phải tự tìm kiếm các mục được lưu trữ trong hệ thống con có thể truy vấn.

Trong bản tóm tắt, hệ thống tệp, giống như một cơ sở dữ liệu, có khả năng trả lời các truy vấn. Khi tương tác với một hệ thống con có thể truy vấn, chúng tôi có một lựa chọn cơ bản là liệu chúng tôi có muốn một hệ thống con đó liệt kê nội dung của nó cho chúng tôi để chúng tôi thực hiện việc khớp bên ngoài hệ thống con hay sử dụng các khả năng truy vấn gốc của hệ thống con.

Hãy giả vờ một chút rằng bạn đang tìm kiếm một bản ghi trong cơ sở dữ liệu thay vì một tệp trong một thư mục trong một hệ thống tệp. Bạn có muốn xem

SELECT * FROM SomeTable;

và sau đó trong vòng lặp (ví dụ: trong C #) qua con trỏ được trả về đang tìm ID = 100 hoặc để hệ thống con có thể truy vấn làm những gì có thể để tìm chính xác những gì bạn đang tìm kiếm thay thế?

SELECT * FROM SomeTable WHERE ID = 100;

Tôi nên nghĩ rằng hầu hết chúng ta sẽ chọn đúng để cho hệ thống con thực hiện truy vấn chính xác. Giải pháp thay thế bao gồm rất nhiều chuyến đi khứ hồi với hệ thống con, kiểm tra công bằng không hiệu quả và từ bỏ việc sử dụng bất kỳ chỉ mục hoặc trình tăng tốc tìm kiếm nào khác, mà cả cơ sở dữ liệu và hệ thống tệp cung cấp cho chúng tôi.


Câu hỏi 2: Để sửa ví dụ thứ hai, bạn có viết lại nó mà không có câu lệnh if hay không, và thay vào đó bao quanh StreamReader bằng một khối bắt thử và nếu nó ném FileNotFoundException, bạn có xử lý nó trong khối bắt không?

Vâng, bởi vì đó chỉ là cách API cụ thể hoạt động - đó không thực sự là lựa chọn của chúng tôi vì đây là chức năng thư viện. Nếu kiểm tra, trước cuộc gọi, không cung cấp giá trị bổ sung nào: chúng tôi phải sử dụng thử / bắt bằng mọi cách, vì (1) các lỗi khác ngoài FileNotFound có thể xảy ra và (2) điều kiện cuộc đua.


0

Câu hỏi 1:

Có phải vì trả lại StreamReader mới (tên tệp); bên trong vòng lặp for? hoặc thực tế là bạn không cần một vòng lặp for trong trường hợp này?

Streamreader không có gì để làm với nó. Các anti-pattern xuất hiện vì một mâu thuẫn rõ ràng về ý định giữa foreachif:

Mục đích của là foreachgì?

Tôi giả sử câu trả lời của bạn sẽ giống như: "Tôi muốn liên tục thực thi một đoạn mã cụ thể"

Có bao nhiêu tệp bạn muốn xử lý?

Vì bạn chỉ có thể có một tên tệp cụ thể (bao gồm cả phần mở rộng) trong một thư mục cụ thể, điều này chứng tỏ rằng mã của bạn được dự định để tìm một tệp áp dụng.

Điều này cũng được xác nhận bởi thực tế là bạn đang trả lại một giá trị ngay lập tức. Bạn không thực sự quan tâm đến trận đấu thứ hai, ngay cả khi nó tồn tại.


Có tình huống đây không phải là một mô hình chống.

  • Nếu bạn cũng tìm trong thư mục con ( Directory.GetFiles(".", SearchOption.AllDirectories)), thì có thể tìm thấy nhiều hơn một tệp có cùng tên tệp (bao gồm cả phần mở rộng)
  • Nếu bạn tìm tên tệp trùng khớp một phần (ví dụ: mọi tệp có tên bắt đầu bằng "Test_"hoặc mỗi "*.zip"tệp.

Lưu ý rằng cả hai trường hợp này sẽ yêu cầu bạn thực sự xử lý nhiều kết quả khớp và do đó không trả về giá trị ngay lập tức.


Câu hỏi 2:

Để sửa ví dụ thứ hai, bạn có viết lại nó mà không có câu lệnh if hay không, và thay vào đó bao quanh StreamReader bằng một khối bắt thử và nếu nó ném FileNotFoundException, bạn có xử lý nó trong khối bắt phù hợp không?

Ngoại lệ là đắt tiền. Chúng không bao giờ nên được sử dụng thay cho logic dòng chảy thích hợp. Ngoại lệ là, như tên của họ cho thấy hoàn cảnh đặc biệt .

Vì lý do đó, bạn không nên loại bỏ if.

Theo câu trả lời này trên SoftwareEngineering.SE :

Nói chung, việc sử dụng các ngoại lệ cho luồng điều khiển là một mô hình chống đối, với các trường hợp đáng chú ý là ngoại lệ ho và ngôn ngữ cụ thể.

Như một tóm tắt nhanh về lý do tại sao, nói chung, đó là một mô hình chống:

  • Các ngoại lệ về bản chất là các tuyên bố GOTO tinh vi
  • Do đó, lập trình với các ngoại lệ, dẫn đến khó đọc và hiểu mã hơn
  • Hầu hết các ngôn ngữ đều có cấu trúc điều khiển hiện có được thiết kế để giải quyết vấn đề của bạn mà không sử dụng ngoại lệ
  • Các lập luận về hiệu quả có xu hướng được đưa ra cho các trình biên dịch hiện đại, có xu hướng tối ưu hóa với giả định rằng các ngoại lệ không được sử dụng cho luồng điều khiển.

Đọc cuộc thảo luận tại wiki của Ward để biết thêm thông tin chuyên sâu.

Cho dù bạn cần bọc cái này trong một lần thử / bắt, phụ thuộc rất nhiều vào tình huống của bạn:

  • Bạn có khả năng gặp phải tình trạng đua như thế nào?
  • Bạn có thực sự có thể xử lý tình huống này không, hoặc bạn muốn vấn đề này nổi lên với người dùng vì bạn không biết cách xử lý.

Không có gì là vấn đề "luôn luôn sử dụng nó". Để chứng minh quan điểm của tôi:

Các nghiên cứu riêng biệt đã chứng minh rằng bạn sẽ ít bị thương hơn khi đội mũ bảo hiểm, kính bảo hộ và áo chống đạn.

Vậy tại sao tất cả chúng ta không đeo thiết bị an toàn này mọi lúc?

Câu trả lời đơn giản là vì có những hạn chế khi mặc chúng:

  • Nó tốn tiền
  • Nó làm cho chuyển động của bạn trở nên cồng kềnh hơn
  • Nó có thể khá ấm để mặc nó.

Bây giờ chúng tôi đang nhận được ở đâu đó: có pro và nhược điểm . Nói cách khác, chỉ có ý nghĩa khi mặc thiết bị này trong trường hợp người chuyên nghiệp vượt trội hơn .

  • Công nhân xây dựng có nhiều khả năng bị chấn thương trong công việc của họ. Họ được hưởng lợi từ một chiếc mũ bảo hiểm an toàn.
  • Nhân viên văn phòng, mặt khác, có khả năng bị thương thấp hơn nhiều. Mũ bảo hiểm không đáng
  • Một thành viên nhóm SWAT có nhiều khả năng bị bắn hơn, so với một nhân viên văn phòng.

Bạn có nên kết thúc cuộc gọi trong một thử / bắt? Điều đó phụ thuộc rất nhiều vào việc lợi ích của việc đó có vượt quá chi phí thực hiện hay không.

Lưu ý rằng những người khác có thể lập luận rằng chỉ cần một vài lần nhấn phím để bọc nó, vì vậy rõ ràng nó nên được thực hiện. Nhưng đó không phải là toàn bộ lập luận:

  • Bạn cần phải quyết định phải làm gì khi bạn bắt được ngoại lệ.
  • Nếu có rất nhiều lệnh gọi khác nhau đến các tệp khác nhau trên toàn bộ cơ sở mã, việc quyết định gói một trong một lần thử / bắt thường có nghĩa là bạn phải bọc tất cả các trường hợp này. Điều này có thể có một tác động mạnh mẽ đến số lượng nỗ lực cần thiết để thực hiện nó.
  • Nó hoàn toàn có thể là bạn cố ý muốn không xử lý một ngoại lệ.
    • Lưu ý rằng ứng dụng của bạn sẽ phải xử lý ngoại lệ tại một số điểm, nhưng điều đó không nhất thiết ngay lập tức sau khi ngoại lệ được nêu ra.

Vì vậy, sự lựa chọn là của bạn. Có một lợi ích để làm như vậy? Bạn có nghĩ rằng nó cải thiện ứng dụng, hơn cả chi phí nỗ lực để thực hiện nó không?

Cập nhật - Từ một bình luận tôi đã viết cho câu trả lời khác, vì tôi nghĩ đó cũng là một sự cân nhắc phù hợp với bạn:

Nó rất nhiều bản lề về bối cảnh xung quanh.

  • Nếu việc mở bộ đọc luồng được đi trước if(!File.Exists) File.Create(), thì sự vắng mặt của tệp khi mở bộ đọc luồng thực sự là đặc biệt .
  • Nếu tên tệp được chọn từ danh sách các tệp hiện có, sự vắng mặt đột ngột của nó lại là ngoại lệ .
  • Nếu bạn đang làm việc với một chuỗi mà bạn chưa thực sự kiểm tra thư mục; sau đó sự vắng mặt của tập tin là một kết quả hoàn toàn hợp lý, và do đó không phải là ngoại lệ .
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.