Phong cách mã tốt để giới thiệu kiểm tra dữ liệu ở khắp mọi nơi?


10

Tôi có một dự án có kích thước đủ lớn mà tôi không thể giữ mọi khía cạnh trong đầu nữa. Tôi đang xử lý một số lớp và hàm trong đó và tôi đang truyền dữ liệu xung quanh.

Theo thời gian, tôi nhận thấy rằng tôi liên tục gặp lỗi, vì tôi đã quên dữ liệu phải có dạng chính xác khi tôi chuyển nó sang các hàm khác nhau ( ví dụ: một hàm chấp nhận và đưa ra một chuỗi các chuỗi, một hàm khác, mà tôi đã viết nhiều sau đó, chấp nhận các chuỗi được giữ trong từ điển, v.v., vì vậy tôi phải chuyển đổi các chuỗi tôi đang làm việc từ việc có chúng trong một mảng thành có chúng trong từ điển ).

Để tránh luôn phải tìm ra cái gì đã phá vỡ ở đâu, tôi bắt đầu coi mỗi hàm và lớp là "thực thể cô lập" theo nghĩa là nó không thể dựa vào mã bên ngoài để cho nó nhập đúng và phải tự kiểm tra đầu vào (hoặc, trong một số trường hợp, hãy lấy lại dữ liệu, nếu dữ liệu được cung cấp ở dạng sai).

Điều này đã giảm đáng kể thời gian tôi dành để đảm bảo rằng dữ liệu mà tôi chuyển xung quanh "phù hợp" với mọi chức năng, bởi vì các lớp và chính các chức năng hiện cảnh báo tôi khi một số đầu vào xấu (và đôi khi chính xác là như vậy) và tôi không phải đi với một trình sửa lỗi thông qua toàn bộ mã nữa để tìm ra nơi nào đó đã đi haywire.

Mặt khác, điều này cũng đã tăng mã tổng thể.
Câu hỏi của tôi là, nếu kiểu mã này phù hợp để giải quyết vấn đề này?
Tất nhiên, giải pháp tốt nhất là tái cấu trúc lại hoàn toàn dự án và đảm bảo dữ liệu có cấu trúc thống nhất cho tất cả các chức năng - nhưng vì dự án này đang phát triển liên tục, cuối cùng tôi sẽ chi tiêu nhiều hơn và lo lắng về mã sạch hơn là thực sự thêm công cụ mới .

(FYI: Tôi vẫn là người mới bắt đầu, vì vậy xin vui lòng nếu câu hỏi này là ngây thơ; dự án của tôi là bằng Python.)



3
@gnat Tương tự, nhưng thay vì trả lời câu hỏi của tôi, nó cung cấp lời khuyên ("hãy phòng thủ như bạn có thể") cho trường hợp cụ thể mà OP đã đề cập, khác với truy vấn chung hơn của tôi.
dùng7088941

2
"Nhưng vì dự án này đang phát triển liên tục, cuối cùng tôi sẽ chi tiêu nhiều hơn và lo lắng về mã sạch hơn là thực sự thêm các công cụ mới" - điều này nghe có vẻ giống như bạn cần bắt đầu lo lắng về mã sạch. Nếu không, bạn sẽ thấy năng suất của mình chậm lại và chậm lại vì mỗi bit chức năng mới càng khó thêm vào vì mã hiện có. Không phải tất cả tái cấu trúc đều cần phải "hoàn thành", nếu việc thêm một cái gì đó mới là khó vì mã hiện có mà nó chạm vào, hãy cấu trúc lại mã đó và ghi lại những gì bạn muốn xem lại sau
matt freake

3
Đây là một vấn đề mọi người thường gặp phải khi sử dụng các ngôn ngữ được đánh máy yếu. Nếu bạn không muốn hoặc có thể chuyển sang ngôn ngữ được gõ nghiêm ngặt hơn, câu trả lời chỉ đơn giản là "có, kiểu mã này phù hợp để giải quyết vấn đề này" . Câu hỏi tiếp theo?
Doc Brown

1
Trong một ngôn ngữ được gõ đúng, với các loại dữ liệu phù hợp được xác định, trình biên dịch sẽ thực hiện điều này cho bạn.
SD

Câu trả lời:


4

Một giải pháp tốt hơn là tận dụng nhiều hơn các tính năng và công cụ ngôn ngữ Python.

Ví dụ, trong hàm 1, đầu vào dự kiến ​​là một chuỗi các chuỗi, trong đó chuỗi đầu tiên biểu thị tiêu đề của một cái gì đó và thứ hai là một tham chiếu thư mục. Trong hàm 2, đầu vào dự kiến ​​vẫn là một chuỗi các chuỗi, nhưng bây giờ vai trò của các chuỗi bị đảo ngược.

Vấn đề này được giảm nhẹ với a namedtuple. Nó nhẹ và mang lại ý nghĩa ngữ nghĩa dễ dàng cho các thành viên trong mảng của bạn.

Để lấy lợi ích của một số loại kiểm tra tự động mà không cần chuyển đổi ngôn ngữ, bạn có thể tận dụng gợi ý loại . Một IDE tốt có thể sử dụng điều này để cho bạn biết khi bạn làm điều gì đó ngớ ngẩn.

Bạn cũng có vẻ lo lắng về các chức năng sẽ cũ khi yêu cầu thay đổi. Điều này có thể được bắt bằng thử nghiệm tự động .

Mặc dù tôi không nói rằng kiểm tra thủ công là không bao giờ phù hợp, việc sử dụng tốt hơn các tính năng ngôn ngữ có sẵn có thể giúp bạn giải quyết vấn đề này theo cách dễ bảo trì hơn.


+1 để chỉ cho tôi namedtuplevà tất cả những điều tốt đẹp khác. Tôi đã không nói về namedtuple- và trong khi tôi biết về thử nghiệm tự động, tôi chưa bao giờ thực sự sử dụng nó nhiều và không nhận ra nó sẽ giúp tôi bao nhiêu trong trường hợp này. Tất cả những điều này thực sự có vẻ tốt như một phân tích tĩnh. (Kiểm tra tự động thậm chí có thể tốt hơn, vì tôi có thể nắm bắt tất cả những điều tinh tế sẽ không bị bắt trong phân tích tĩnh!) Nếu bạn biết bất kỳ điều gì khác, vui lòng cho tôi biết. Tôi sẽ tiếp tục mở câu hỏi lâu hơn, nhưng nếu không có câu trả lời nào khác, tôi sẽ chấp nhận câu hỏi của bạn.
dùng7088941

9

OK, vấn đề thực tế được mô tả trong một bình luận bên dưới câu trả lời này:

Ví dụ, trong hàm 1, đầu vào dự kiến ​​là một chuỗi các chuỗi, trong đó chuỗi đầu tiên biểu thị tiêu đề của một cái gì đó và thứ hai là một tham chiếu thư mục. Trong hàm 2, đầu vào dự kiến ​​vẫn là một chuỗi các chuỗi, nhưng bây giờ vai trò của các chuỗi bị đảo ngược

Vấn đề ở đây là việc sử dụng danh sách các chuỗi trong đó thứ tự biểu thị ngữ nghĩa. Đây là một cách tiếp cận thực sự dễ bị lỗi. Thay vào đó, bạn nên tạo một lớp tùy chỉnh với hai trường được đặt tên titlebibliographical_reference. Bằng cách đó, bạn sẽ không trộn lẫn chúng và bạn sẽ tránh được vấn đề này trong tương lai. Tất nhiên điều này đòi hỏi một số tái cấu trúc nếu bạn đã sử dụng danh sách các chuỗi ở nhiều nơi, nhưng tin tôi đi, về lâu dài sẽ rẻ hơn.

Cách tiếp cận phổ biến trong các ngôn ngữ loại động là "gõ vịt", có nghĩa là bạn không thực sự quan tâm đến "loại" của đối tượng được truyền, bạn chỉ quan tâm nếu nó hỗ trợ các phương thức bạn gọi trên đó. Trong trường hợp của bạn, bạn sẽ chỉ cần đọc trường được gọi bibliographical_referencekhi bạn cần. Nếu trường này không tồn tại trên đối tượng được truyền, bạn sẽ gặp lỗi và điều này cho biết loại sai được truyền cho hàm. Đây là một loại kiểm tra tốt như bất kỳ.


Đôi khi vấn đề thậm chí còn tinh tế hơn: Tôi đang chuyển đúng loại, nhưng "cấu trúc bên trong" của đầu vào của tôi làm rối chức năng: Ví dụ, trong hàm 1, đầu vào dự kiến ​​là một chuỗi các chuỗi, trong đó chuỗi đầu tiên biểu thị tiêu đề của một cái gì đó và thứ hai một tài liệu tham khảo thư mục. Trong hàm 2, đầu vào dự kiến ​​vẫn là một chuỗi các chuỗi, nhưng bây giờ vai trò của các chuỗi bị đảo ngược: Chuỗi đầu tiên phải là tham chiếu thư mục và chuỗi thứ hai phải là tham chiếu thư mục. Tôi đoán cho kiểm tra này là thích hợp?
dùng7088941

1
@ user7088941: Vấn đề bạn mô tả có thể được giải quyết dễ dàng bằng cách có một lớp có hai trường: "title" và "bibliographic_Vference". Bạn sẽ không trộn nó lên. Dựa vào thứ tự trong một danh sách các chuỗi có vẻ rất dễ bị lỗi. Có lẽ đây là vấn đề tiềm ẩn?
JacquesB

3
Đây là câu trả lời. Python là một ngôn ngữ hướng đối tượng, không phải là ngôn ngữ liệt kê từ-chuỗi-từ-chuỗi-sang-số-hướng (hoặc bất cứ điều gì). Vì vậy, sử dụng các đối tượng. Các đối tượng chịu trách nhiệm quản lý trạng thái của chính họ và thực thi các bất biến của chính họ, các đối tượng khác không thể làm hỏng chúng, bao giờ (nếu được thiết kế chính xác). Nếu dữ liệu phi cấu trúc hoặc bán cấu trúc xâm nhập vào hệ thống của bạn từ bên ngoài, bạn xác nhận và phân tích một lần tại ranh giới hệ thống và chuyển đổi sang các đối tượng phong phú càng sớm càng tốt.
Jörg W Mittag

3
"Tôi thực sự sẽ tránh tái cấu trúc liên tục" - khối tâm thần này là vấn đề của bạn. Mã tốt chỉ phát sinh từ tái cấu trúc. Rất nhiều tái cấu trúc. Được hỗ trợ bởi các bài kiểm tra đơn vị. Đặc biệt là khi các thành phần cần phải được mở rộng hoặc phát triển.
Doc Brown

2
Tôi hiểu rồi +1 cho tất cả những hiểu biết và bình luận tốt đẹp. Và cảm ơn tất cả những bình luận vô cùng hữu ích của họ! (Trong khi tôi đang sử dụng một số lớp / đối tượng tôi đã xen kẽ chúng với các danh sách được đề cập, như tôi thấy bây giờ, không phải là một ý tưởng hay. Câu hỏi vẫn là cách tốt nhất để thực hiện điều này, trong đó tôi đã sử dụng các đề xuất cụ thể từ câu trả lời của JETMs , điều thực sự tạo ra sự khác biệt căn bản về tốc độ đạt được trạng thái không có lỗi.)
user7088941

3

Trước hết, những gì bạn đang trải nghiệm ngay bây giờ là mùi mã - hãy cố gắng nhớ điều gì dẫn đến việc bạn có ý thức về mùi và cố gắng làm mũi "tâm thần" của bạn, vì bạn càng sớm nhận thấy mùi mã càng sớm - và dễ dàng hơn - bạn có thể khắc phục vấn đề cơ bản.

Để tránh luôn phải tìm ra cái gì đã phá vỡ ở đâu, tôi bắt đầu coi mỗi hàm và lớp là "thực thể cô lập" theo nghĩa là nó không thể dựa vào mã bên ngoài để đưa ra đầu vào chính xác và phải tự kiểm tra đầu vào.

Lập trình phòng thủ - vì kỹ thuật này được gọi là - là một công cụ hợp lệ và thường được sử dụng. Tuy nhiên, như với tất cả mọi thứ, điều quan trọng là sử dụng đúng số lượng, quá ít kiểm tra và bạn sẽ không bắt gặp vấn đề, quá nhiều và mã của bạn sẽ bị quá tải.

(hoặc, trong một số trường hợp, hãy lấy lại dữ liệu, nếu dữ liệu được cung cấp ở dạng sai).

Đó có thể là một ý tưởng ít tốt hơn. Nếu bạn nhận thấy một phần chương trình của bạn đang gọi một chức năng với dữ liệu được định dạng không chính xác, CỐ ĐỊNH PHẦN , không thay đổi chức năng được gọi để có thể tiêu hóa dữ liệu xấu.

Điều này đã giảm đáng kể thời gian tôi dành để đảm bảo rằng dữ liệu mà tôi chuyển xung quanh "phù hợp" với mọi chức năng, bởi vì các lớp và chính các chức năng hiện cảnh báo tôi khi một số đầu vào xấu (và đôi khi chính xác là như vậy) và tôi không phải đi với một trình sửa lỗi thông qua toàn bộ mã nữa để tìm ra nơi nào đó đã đi haywire.

Cải thiện chất lượng và khả năng duy trì mã của bạn là một trình tiết kiệm thời gian trong thời gian dài (theo nghĩa đó tôi phải một lần nữa cảnh báo chống lại chức năng tự sửa lỗi mà bạn tích hợp trong một số chức năng của mình - chúng có thể là một nguồn gây ra lỗi cho bạn. chương trình không bị sập & ghi không có nghĩa là nó hoạt động đúng ...)

Để cuối cùng trả lời câu hỏi của bạn: Có, lập trình phòng thủ (tức là xác minh tính hợp lệ của các tham số được cung cấp) là - ở mức độ lành mạnh - một chiến lược tốt. Điều đó nói rằng , như bạn đã nói, mã của bạn không phù hợp và tôi đặc biệt khuyên bạn nên dành thời gian để cấu trúc lại các phần có mùi - bạn nói rằng bạn không muốn lo lắng về mã sạch mọi lúc, dành nhiều thời gian hơn cho "Dọn dẹp" hơn các tính năng mới ... Nếu bạn không giữ mã của mình sạch sẽ, bạn có thể mất gấp đôi thời gian để "tiết kiệm" từ việc không giữ mã sạch đối với các lỗi xáo trộn VÀ sẽ gặp khó khăn khi thực hiện các tính năng mới - nợ kỹ thuật có thể đè bẹp bạn.


1

Không sao đâu. Tôi đã từng viết mã trong FoxPro, nơi tôi có một khối TRY..CATCH gần như trong mọi chức năng lớn. Bây giờ, tôi viết mã bằng JavaScript / LiveScript và hiếm khi kiểm tra các tham số trong các hàm "nội bộ" hoặc "riêng tư".

"Bao nhiêu để kiểm tra" phụ thuộc vào dự án / ngôn ngữ được chọn nhiều hơn tùy thuộc vào kỹ năng mã của bạn.


1
Tôi đoán, đó là TRY ... CATCH ... IGNORE. Bạn đã làm về điều ngược lại với những gì OP đang yêu cầu. IMHO quan điểm của họ là tránh sự không nhất quán trong khi bạn đảm bảo rằng chương trình không nổ tung khi đánh một cái.
maaartinus

1
@maaartinus đúng vậy. Các ngôn ngữ lập trình thường cung cấp cho chúng ta các cấu trúc đơn giản để sử dụng để ngăn chặn ứng dụng nổ tung - nhưng các ngôn ngữ lập trình cho chúng ta để ngăn chặn sự không nhất quán dường như khó sử dụng hơn: theo hiểu biết của tôi, liên tục cấu trúc lại mọi thứ và sử dụng các lớp chứa tốt nhất luồng thông tin trong ứng dụng của bạn. Đây chính xác là những gì tôi đang hỏi về - có cách nào dễ dàng hơn để khắc phục điều này.
dùng7088941

@ user7088941 Đó là lý do tại sao tôi tránh các ngôn ngữ được gõ yếu. Python thật tuyệt vời, nhưng đối với bất cứ điều gì lớn hơn, tôi không thể theo dõi những gì tôi đã làm ở nơi khác. Do đó, tôi thích Java, khá dài dòng (không quá nhiều với các tính năng Lombok và Java 8), có các công cụ và gõ nghiêm ngặt để phân tích tĩnh. Tôi khuyên bạn nên thử một số loại ngôn ngữ nghiêm ngặt vì tôi không biết làm thế nào để giải quyết nó.
maaartinus

Đây không phải là về tham số gõ chặt chẽ / lỏng lẻo. Đó là về việc biết tham số đó là chính xác. Ngay cả khi bạn sử dụng (số nguyên 4 byte), bạn có thể cần kiểm tra xem nó có nằm trong phạm vi 0..10 không. Nếu bạn biết tham số đó luôn là 0..10 thì bạn không cần phải kiểm tra nó. Ví dụ, FoxPro không có mảng kết hợp, rất khó để hoạt động với các biến, phạm vi của chúng, v.v. đó là lý do tại sao bạn phải kiểm tra kiểm tra ..
Michael Quad

1
@ user7088941 Không phải OO, nhưng có quy tắc "thất bại nhanh". Mỗi phương thức không riêng tư phải kiểm tra các đối số của nó và ném khi có bất cứ điều gì sai. Không thử bắt, không cố gắng sửa nó, chỉ cần thổi nó lên trời. Chắc chắn, ở cấp độ cao hơn, ngoại lệ được ghi lại và xử lý. Vì các thử nghiệm của bạn tìm thấy hầu hết các vấn đề trước đó và không có vấn đề nào bị ẩn, mã này hội tụ đến một giải pháp không có lỗi nhanh hơn nhiều so với khi có khả năng chịu lỗi.
maaartinus
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.