Là phong cách xấu để kiểm tra dư thừa một điều kiện?


10

Tôi thường nhận được các vị trí trong mã của mình, nơi tôi thấy mình kiểm tra một điều kiện cụ thể nhiều lần.

Tôi muốn cho bạn một ví dụ nhỏ: giả sử có một tệp văn bản chứa các dòng bắt đầu bằng "a", các dòng bắt đầu bằng "b" và các dòng khác và tôi thực sự chỉ muốn làm việc với hai dòng đầu tiên. Mã của tôi sẽ trông giống như thế này (sử dụng python, nhưng đọc nó dưới dạng mã giả):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a")):
        # do stuff
    elif (line.startsWith("b")):
        # magic
    else:
        # this else is redundant, I already made sure there is no else-case
        # by using clear_lines()
# ...

Bạn có thể tưởng tượng tôi sẽ không chỉ kiểm tra tình trạng này ở đây, mà còn có thể trong các chức năng khác, v.v.

Bạn có nghĩ nó là tiếng ồn hay nó thêm một số giá trị vào mã của tôi?


5
Về cơ bản là về việc bạn có mã hóa phòng thủ hay không. Bạn có thấy mã này được chỉnh sửa rất nhiều không? Có khả năng đây sẽ là một phần của một hệ thống cần phải cực kỳ đáng tin cậy? Tôi không thấy nhiều tác hại trong việc đẩy một cái assert()vào đó để giúp kiểm tra, nhưng vượt quá điều đó có lẽ là quá mức. Điều đó nói rằng, nó sẽ thay đổi tùy thuộc vào tình huống.
Latty

trường hợp 'khác' của bạn về cơ bản là mã chết / không thể truy cập được. Hãy kiểm tra rằng không có yêu cầu toàn hệ thống cấm điều này.
NWS

@NWS: bạn đang nói rằng tôi nên giữ trường hợp khác? Xin lỗi tôi không hiểu bạn hoàn toàn.
marktani

2
không đặc biệt liên quan đến câu hỏi - nhưng tôi sẽ biến 'khẳng định' đó thành một bất biến - đòi hỏi một lớp "Line" mới (có lẽ với các lớp dẫn xuất cho A & B), thay vì coi các dòng là chuỗi và nói với chúng những gì họ đại diện từ bên ngoài. Tôi rất vui được giải thích về vấn đề này tại CodeReview
MattDavey

ý bạn là elif (line.startsWith("b"))sao bằng cách này, bạn có thể loại bỏ một cách an toàn các dấu ngoặc đơn trên các điều kiện, chúng không phải là thành ngữ trong Python.
tokland

Câu trả lời:


14

Đây là một thực tiễn phổ biến và cách đối phó với nó là thông qua các bộ lọc bậc cao .

Về cơ bản, bạn chuyển một hàm cho phương thức lọc, cùng với danh sách / chuỗi mà bạn muốn lọc và danh sách / chuỗi kết quả chỉ chứa các phần tử mà bạn muốn.

Tôi không quen với cú pháp python (mặc dù, nó có chứa một hàm như đã thấy trong liên kết ở trên), nhưng trong c # / f # thì nó trông như thế này:

c #:

var linesWithAB = lines.Where(l => l.StartsWith("a") || l.StartsWith("b"));
foreach (var line in linesWithAB)
{
    /* line is guaranteed to ONLY start with a or b */
}

f # (giả sử không thể đếm được, nếu không List.filter sẽ được sử dụng):

let linesWithAB = lines
    |> Seq.filter (fun l -> l.StartsWith("a") || l.StartsWith("b"))

for line in linesWithAB do
    /* line is guaranteed to ONLY start with a or b */

Vì vậy, để rõ ràng: nếu bạn sử dụng mã / mẫu đã thử và thử nghiệm, thì đó là phong cách xấu. Điều đó và làm thay đổi danh sách trong bộ nhớ theo cách bạn xuất hiện thông qua Clear_lines () làm bạn mất an toàn luồng và bất kỳ hy vọng nào về sự song song mà bạn có thể có.


3
Lưu ý, cú pháp python cho điều này sẽ là một biểu thức trình tạo : (line for line in lines if line.startswith("a") or line.startswith("b")).
Latty

1
+1 để chỉ ra rằng việc thực hiện bắt buộc (không cần thiết) thực clear_linessự là một ý tưởng tồi. Trong Python có thể bạn sẽ sử dụng các trình tạo để tránh tải tập tin hoàn chỉnh vào bộ nhớ.
tokland

Điều gì xảy ra khi tệp đầu vào lớn hơn bộ nhớ khả dụng?
Blrfl

@Blrfl: tốt, nếu trình tạo thuật ngữ phù hợp giữa c # / f # / python, thì những gì @tokland và @Lattyware chuyển thành c # / f # suất và / hoặc sản lượng! các câu lệnh. Rõ ràng hơn một chút trong ví dụ f # của tôi vì Seq.filter chỉ có thể được áp dụng cho các bộ sưu tập IEnumerable <T> nhưng cả hai ví dụ mã sẽ hoạt động nếu lineslà bộ sưu tập được tạo.
Steven Evers

@mcwise: Khi bạn bắt đầu xem xét tất cả các chức năng có sẵn khác hoạt động theo cách này, nó bắt đầu trở nên thực sự gợi cảm và cực kỳ biểu cảm bởi vì tất cả chúng có thể được kết nối và kết hợp với nhau. Nhìn vào skip, take, reduce( aggregatetrong .NET), map( selecttrong .NET), và có nhiều nhưng đó là một sự khởi đầu thực sự vững chắc.
Steven Evers

14

Gần đây tôi đã phải thực hiện một chương trình phần mềm sử dụng định dạng Motorola S-record , rất giống với những gì bạn mô tả. Vì chúng tôi đã có một số áp lực thời gian, dự thảo đầu tiên của tôi đã bỏ qua các khoản dự phòng và thực hiện đơn giản hóa dựa trên tập hợp con tôi thực sự cần sử dụng trong ứng dụng của mình. Nó vượt qua các bài kiểm tra của tôi một cách dễ dàng, nhưng thất bại nặng nề ngay khi người khác thử nó. Không có manh mối vấn đề là gì. Nó đã đi tất cả các cách nhưng cuối cùng đã thất bại.

Vì vậy, tôi không có lựa chọn nào khác ngoài việc thực hiện tất cả các kiểm tra dư thừa, để thu hẹp vấn đề ở đâu. Sau đó, tôi mất khoảng hai giây để tìm ra vấn đề.

Tôi phải mất thêm hai giờ để làm điều đó đúng cách, nhưng lãng phí một ngày thời gian của người khác cũng như trong việc khắc phục sự cố. Rất hiếm khi một vài chu kỳ bộ xử lý đáng giá một ngày để xử lý sự cố.

Điều đó đang được nói, khi đọc các tập tin có liên quan, thường có ích khi thiết kế phần mềm của bạn để làm việc với việc đọc và xử lý từng dòng một, thay vì đọc toàn bộ tệp vào bộ nhớ và xử lý nó trong bộ nhớ. Bằng cách đó, nó vẫn sẽ hoạt động trên các tệp rất lớn.


"Rất hiếm khi một vài chu kỳ bộ xử lý đáng giá một ngày lãng phí." Cảm ơn câu trả lời, bạn có một điểm tốt.
marktani

5

Bạn có thể đưa ra một ngoại lệ trong elsetrường hợp. Cách này không thừa. Trường hợp ngoại lệ phát sinh những điều không nên xảy ra nhưng dù sao cũng được kiểm tra.

clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if (line.startsWith("a)):
        # do stuff
    if (line.startsWith("b")):
        # magic
    else:
        throw BadLineException
# ...

Tôi cho rằng cái sau là một ý tưởng tồi, vì nó ít rõ ràng hơn - nếu sau này bạn quyết định thêm một "c", nó có thể ít rõ ràng hơn.
Latty

Gợi ý đầu tiên có công ... thứ hai (giả sử "b") là một ý tưởng tồi
Andrew

@Lattyware Tôi đã cải thiện câu trả lời. Cảm ơn ý kiến ​​của bạn.
Tulains Córdova

1
@Andrew Tôi đã cải thiện câu trả lời. Cảm ơn ý kiến ​​của bạn.
Tulains Córdova

3

Trong thiết kế theo hợp đồng , người ta đoán mỗi chức năng phải thực hiện công việc của nó như được mô tả trong tài liệu của nó. Vì vậy, mỗi hàm có một danh sách các điều kiện trước, nghĩa là các điều kiện trên các đầu vào của hàm cũng như các điều kiện sau, nghĩa là các điều kiện của đầu ra của hàm.

Hàm phải đảm bảo với khách hàng của mình rằng, nếu các đầu vào tôn trọng các điều kiện trước, thì đầu ra sẽ được mô tả bởi các điều kiện sau. Nếu ít nhất một trong các điều kiện trước không được tôn trọng, hàm có thể thực hiện những gì nó muốn (sự cố, trả về bất kỳ kết quả nào, ...). Do đó, trước và sau điều kiện là một mô tả ngữ nghĩa của hàm.

Nhờ hợp đồng, một chức năng chắc chắn rằng khách hàng của nó sử dụng chính xác và khách hàng chắc chắn rằng chức năng đó thực hiện đúng chức năng của mình.

Một số ngôn ngữ xử lý hợp đồng nguyên bản hoặc thông qua một khung chuyên dụng. Đối với những người khác, tốt nhất là kiểm tra các điều kiện trước và sau nhờ các khẳng định, như @Lattyware nói. Nhưng tôi sẽ không gọi đó là lập trình phòng thủ, vì trong suy nghĩ của tôi, khái niệm này tập trung nhiều hơn vào việc bảo vệ chống lại đầu vào của con người (con người).

Nếu bạn khai thác hợp đồng, bạn có thể tránh điều kiện được kiểm tra dự phòng vì chức năng được gọi hoàn toàn hoạt động và bạn không cần kiểm tra kép, hoặc chức năng được gọi là không hoạt động và chức năng gọi có thể hoạt động như mong muốn.

Phần khó hơn sau đó là xác định chức năng nào chịu trách nhiệm về cái gì, và ghi lại đúng các vai trò này.


1

Bạn thực sự không cần Clear_lines () khi bắt đầu. Nếu dòng không phải là "a" hoặc "b", các điều kiện đơn giản sẽ không kích hoạt. Nếu bạn muốn loại bỏ những dòng đó thì hãy biến những dòng khác thành một Clear_line (). Khi nó đứng, bạn đang thực hiện hai lần đi qua tài liệu của bạn. Nếu bạn bỏ qua Clear_lines () lúc đầu và thực hiện nó như một phần của vòng lặp foreach thì bạn đã cắt giảm thời gian xử lý của mình xuống một nửa.

Đó không chỉ là phong cách xấu, mà còn là tính toán xấu.


2
Có thể là những dòng đó đang được sử dụng cho mục đích khác và chúng cần được xử lý trước khi xử lý "a"/ "b"dòng. Không nói rằng nó có khả năng ( tên rõ ràng ngụ ý rằng chúng đang bị loại bỏ), chỉ là có khả năng nó cần thiết. Nếu tập hợp các dòng đó được lặp đi lặp lại trong tương lai, thì cũng có thể đáng để loại bỏ chúng trước để tránh rất nhiều lần lặp lại vô nghĩa.
Latty

0

Nếu bạn thực sự muốn làm bất cứ điều gì nếu bạn tìm thấy một chuỗi không hợp lệ (ví dụ văn bản gỡ lỗi đầu ra) thì tôi sẽ nói điều đó hoàn toàn tốt. Một vài dòng bổ sung và một vài tháng xuống dòng khi nó ngừng hoạt động vì một số lý do không xác định, bạn có thể nhìn vào đầu ra để tìm hiểu lý do tại sao.

Tuy nhiên, nếu an toàn, chỉ cần bỏ qua nó, hoặc bạn biết chắc chắn bạn sẽ không bao giờ có được chuỗi không hợp lệ thì không cần thêm nhánh.

Cá nhân tôi luôn luôn đưa ra ít nhất một đầu ra theo dõi cho bất kỳ tình trạng bất ngờ nào - nó giúp cuộc sống dễ dàng hơn nhiều khi bạn gặp lỗi với đầu ra kèm theo cho bạn biết chính xác những gì đã sai.


0

... giả sử có một tệp văn bản chứa các dòng bắt đầu bằng "a", các dòng bắt đầu bằng "b" và các dòng khác và tôi thực sự chỉ muốn làm việc với hai dòng đầu tiên. Mã của tôi sẽ trông giống như thế này (sử dụng python, nhưng đọc nó dưới dạng mã giả):

# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
    if ...

Tôi ghét các if...then...elsecông trình xây dựng. Tôi sẽ tránh toàn bộ vấn đề:

process_lines_by_first_character (lines,  
                                  'a' => { |line| ... a code ... },
                                  'b' => { |line| ... b code ... } )
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.