2010-03-23 20 views
9

Tôi không chắc liệu tôi có thích tính năng động của Python hay không. Nó thường kết quả trong tôi quên để kiểm tra một loại, cố gắng gọi một thuộc tính và nhận được NoneType (hoặc bất kỳ khác) không có thuộc tính x lỗi. Rất nhiều người trong số họ khá vô hại nhưng nếu không được xử lý đúng cách, họ có thể giảm toàn bộ ứng dụng/quy trình của bạn/v.v. Theo thời gian, tôi đã dự đoán tốt hơn nơi chúng có thể bật lên và thêm kiểm tra kiểu rõ ràng, nhưng bởi vì tôi chỉ là con người tôi bỏ lỡ một lần và sau đó một số người dùng cuối tìm thấy nó.Chiến lược của bạn để tránh lỗi nhập động trong Python (NoneType không có thuộc tính x) là gì?

Vì vậy, tôi quan tâm đến chiến lược của bạn để tránh những điều này. Bạn có sử dụng trang trí kiểm tra kiểu không? Có thể hàm bao đối tượng đặc biệt?

Vui lòng chia sẻ ...

+0

Ngoài ra, hãy xem http://stackoverflow.com/questions/2014105/null-pattern-in-python-underused – voyager

+2

"NoneType không có thuộc tính x" giống như một NullPointerException trong Java. Nó không phải là một vấn đề gõ động và tĩnh. – FogleBird

+0

@FogleBird: nhưng Python cho phép nhiều hơn * trí tưởng tượng * giải pháp;) – voyager

Trả lời

7

quên để kiểm tra một loại

Điều này không có ý nghĩa nhiều. Bạn hiếm khi cần phải "kiểm tra" một loại. Bạn chỉ cần chạy thử nghiệm đơn vị và nếu bạn đã cung cấp đối tượng loại sai, mọi thứ sẽ thất bại. Bạn không bao giờ cần phải "kiểm tra" nhiều, theo kinh nghiệm của tôi.

cố gắng gọi thuộc tính và nhận loại NoneType (hoặc bất kỳ khác) không có thuộc tính x lỗi.

Bất ngờ None là lỗi cũ. 80% thời gian, tôi bỏ qua số return. Các bài kiểm tra đơn vị luôn tiết lộ những điều này.

Trong số những người còn lại, 80% thời gian, chúng là lỗi cũ đơn giản do "lối ra sớm" trả về None vì ai đó đã viết tuyên bố return chưa hoàn chỉnh. Các cấu trúc if foo: return này dễ phát hiện với các bài kiểm tra đơn vị. Trong một số trường hợp, chúng phải là if foo: return somethingMeaningful và trong các trường hợp khác, chúng phải là if foo: raise Exception("Foo").

Phần còn lại là những sai lầm ngớ ngẩn khi hiểu sai về API. Nói chung, các hàm mutator không trả về bất cứ thứ gì. Đôi khi tôi quên. Kiểm tra đơn vị tìm thấy những điều này một cách nhanh chóng, vì về cơ bản, không có gì hoạt động đúng.

Điều đó bao gồm các trường hợp "bất ngờ None" khá chắc chắn. Dễ dàng để kiểm tra đơn vị cho. Hầu hết các sai lầm liên quan đến các bài kiểm tra khá nhỏ nhặt để viết cho một số lỗi khá rõ ràng của các sai lầm: sai trở lại; không nêu ra một ngoại lệ.

Lỗi "không có thuộc tính X" khác là những sai lầm thực sự hoang dã khi sử dụng loại sai hoàn toàn. Đó là một trong hai câu lệnh chuyển nhượng thực sự sai hoặc các cuộc gọi hàm (hoặc phương thức) thực sự sai. Chúng luôn thất bại một cách công phu trong quá trình thử nghiệm đơn vị, đòi hỏi rất ít nỗ lực để sửa chữa.

Rất nhiều trong số đó là vô hại nhưng nếu không được xử lý đúng cách, chúng có thể giảm toàn bộ ứng dụng/quy trình/v.v.

Um ... Vô hại? Nếu đó là một lỗi, tôi cầu nguyện rằng nó mang xuống toàn bộ ứng dụng của tôi càng nhanh càng tốt để tôi có thể tìm thấy nó. Một lỗi không làm hỏng ứng dụng của tôi là tình huống khủng khiếp nhất có thể tưởng tượng được. "Vô hại" không phải là một từ tôi muốn sử dụng cho một lỗi mà không sụp đổ ứng dụng của tôi.

+1

+1 cho "Một lỗi không làm hỏng ứng dụng của tôi là tình huống khủng khiếp nhất có thể tưởng tượng được". –

+0

+1 Insightful, chắc chắn tôi có nghĩa là vô hại như "sẽ vô hại nếu bạn đối phó với họ, mà thường thực sự dễ dàng". –

4

Nếu bạn viết kiểm tra đơn vị tốt cho tất cả mã, bạn nên tìm lỗi rất nhanh khi kiểm tra mã.

+1

Theo kinh nghiệm của tôi, đó là sự thật đối với hầu hết trong số họ, nhưng không nói 1% cuối cùng vì (đối với chúng tôi) nó không có ý nghĩa (hoặc thậm chí có thể) để thử nghiệm đơn vị _every_ kịch bản có thể xảy ra. –

+1

Kiểm tra trở nên quan trọng hơn trong các ngôn ngữ rất năng động, có khả năng xảy ra lỗi thậm chí không thể có trong nhiều ngôn ngữ tĩnh hơn. –

+0

@Mike: ở mặt tươi sáng, việc kiểm tra đơn vị viết trên ngôn ngữ động dễ dàng hơn so với ngôn ngữ tĩnh. – voyager

-1

tôi có xu hướng sử dụng

if x is None: 
    raise ValueError('x cannot be None') 

Nhưng điều này sẽ chỉ làm việc với giá trị thực tế None.

Cách tiếp cận tổng quát hơn là kiểm tra các thuộc tính cần thiết trước khi bạn cố gắng sử dụng chúng. Ví dụ:

def write_data(f): 
    # Here we expect f is a file-like object. But what if it's not? 
    if not hasattr(f, 'write'): 
     raise ValueError('write_data requires a file-like object') 
    # Now we can do stuff with f that assumes it is a file-like object 

Mấu chốt của mã này là thay vì nhận được một thông báo lỗi như "NoneType không có ghi thuộc tính", bạn nhận được "write_data đòi hỏi một đối tượng tập tin giống như". Lỗi thực tế không nằm trong số write_data() và thực sự không phải là vấn đề với số NoneType. Lỗi thực tế nằm trong mã mà gọiwrite_data(). Điều quan trọng là truyền đạt thông tin đó trực tiếp nhất có thể.

+2

Cách tiếp cận của bạn có ít điểm đến với nó. Bạn không thể nâng cao lỗi của chính bạn hơn là nâng cao Python bằng cách nào đó. –

+0

Có lẽ tôi đã không giải thích rõ ràng đủ. Điểm của tôi là chính xác rằng: nâng cao lỗi Python sẽ tăng lên dù sao, chỉ thất bại nhanh chóng. Miễn là bạn luôn thất bại nhanh chóng, bạn sẽ có thể tìm thấy lỗi (giả sử phạm vi kiểm tra của bạn là hợp lý). Bất cứ điều gì nhiều hơn là thực sự chỉ cần cố gắng để xây dựng một hệ thống kiểu tĩnh trên đầu trang của một năng động của Python. –

+1

có gì sai với 'khẳng định x không phải là None'? –

0

Tôi chưa thực hiện nhiều chương trình Python, nhưng tôi hoàn toàn không lập trình bằng ngôn ngữ được nhập tĩnh, vì vậy tôi không có xu hướng suy nghĩ về mọi thứ về các loại biến. Điều đó có thể giải thích tại sao tôi không gặp vấn đề này nhiều. (Mặc dù số lượng nhỏ lập trình Python mà tôi đã thực hiện cũng có thể giải thích được điều đó.)

Tôi thích xử lý chuỗi sửa đổi của Python 3 (tức là tất cả các chuỗi là unicode, mọi thứ khác chỉ là một luồng byte), bởi vì Python 2 bạn có thể không thông báo TypeError s cho đến khi giao dịch với các giá trị chuỗi bất thường trên thế giới thực.

+1

Python 2 có hai loại chuỗi — 'zipode' và' str'; trước đây là một đại diện trừu tượng của văn bản và sau này là một chuỗi các byte. Python 3 đổi tên 'unicode' thành' str' và thực hiện một số thay đổi nhỏ và đổi tên 'str' thành' bytes' và thực hiện một số thay đổi trung bình. Nếu bạn đang sử dụng 'unicode' ngay trong Python 2, nó sẽ hoạt động gần giống như Python 3. –

-1

Một số thứ bạn có thể sử dụng để đơn giản hóa mã của mình đang sử dụng Null Object Design Pattern (mà tôi đã được giới thiệu trong Python Cookbook).

đại khái, mục tiêu với các đối tượng Null là cung cấp một 'thông minh' thay thế cho các thường được sử dụng kiểu dữ liệu cơ bản Không bằng Python hoặc Null (hoặc con trỏ Null) bằng các ngôn ngữ khác. Chúng được sử dụng cho nhiều mục đích bao gồm trường hợp quan trọng trong đó một thành viên của một số nhóm các yếu tố tương tự khác là đặc biệt vì bất kỳ lý do gì. Hầu hết thường kết quả này trong các câu lệnh có điều kiện để phân biệt giữa các phần tử thông thường và giá trị Null nguyên thủy.

Đối tượng này chỉ ăn thiếu lỗi thuộc tính và bạn có thể tránh kiểm tra sự tồn tại của chúng.

Đó là không có gì hơn

class Null(object): 

    def __init__(self, *args, **kwargs): 
     "Ignore parameters." 
     return None 

    def __call__(self, *args, **kwargs): 
     "Ignore method calls." 
     return self 

    def __getattr__(self, mname): 
     "Ignore attribute requests." 
     return self 

    def __setattr__(self, name, value): 
     "Ignore attribute setting." 
     return self 

    def __delattr__(self, name): 
     "Ignore deleting attributes." 
     return self 

    def __repr__(self): 
     "Return a string representation." 
     return "<Null>" 

    def __str__(self): 
     "Convert to a string and return it." 
     return "Null" 

Với điều này, nếu bạn làm Null("any", "params", "you", "want").attribute_that_doesnt_exists() nó sẽ không phát nổ, nhưng chỉ âm thầm trở thành tương đương với pass.

Thông thường bạn muốn làm điều gì đó như

if obj.attr: 
    obj.attr() 

Với điều này, bạn chỉ cần làm:

obj.attr() 

và quên nó đi. Hãy coi chừng việc sử dụng rộng rãi đối tượng Null có thể ẩn các lỗi trong mã của bạn.

+3

Điều đó có vẻ khá nguy hiểm đối với tôi. Nó đi ngược lại The Zen of Python: "Lỗi không bao giờ nên trôi qua âm thầm." –

+1

Nhưng nếu không, ý tưởng chung của mẫu thiết kế đối tượng Null là một mẫu tốt. Ví dụ: không sử dụng 'None' để biểu thị một biến lặp trống, sử dụng một biến lặp trống thực sự như danh sách trống (hoặc một lớp tùy chỉnh có hiệu ứng tương tự). –

+0

'None' không phải là kiểu dữ liệu nguyên thủy. Nó không phải là một kiểu dữ liệu nào cả. –

1

Một lợi thế của TDD là bạn sẽ viết mã dễ viết hơn.

Viết mã trước và sau đó các thử nghiệm có thể dẫn đến mã hoạt động giống hệt nhau, nhưng sẽ khó hơn khi viết 100% thử nghiệm vùng phủ sóng.

Mỗi trường hợp có khả năng là khác nhau

Nó có thể làm cho tinh thần để có một trang trí để kiểm tra xem một tham số cụ thể là None (hoặc một số giá trị bất ngờ khác) nếu bạn sử dụng nó trong một loạt các nơi.

Có thể thích hợp để sử dụng Null pattern - nếu mã bị thổi vì bạn đang đặt giá trị ban đầu thành Không, thay vào đó bạn có thể đặt giá trị ban đầu thành phiên bản rỗng của đối tượng.

Ngày càng có nhiều giấy gói có thể thêm lên đến khá một hit hiệu suất mặc dù, vì vậy nó luôn luôn tốt hơn để viết mã ngay từ đầu rằng tránh các trường hợp góc

2

Một công cụ để cố gắng giúp bạn giữ cho các mảnh của bạn vừa khít với nhau là giao diện. zope.interface là gói đáng chú ý nhất trong thế giới Python để sử dụng giao diện. Hãy xem http://wiki.zope.org/zope3/WhatAreInterfaceshttp://glyph.twistedmatrix.com/2009/02/explaining-why-interfaces-are-great.html để bắt đầu có ý tưởng về cách giao diện và z.i hoạt động cụ thể. Giao diện có thể chứng minh rất hữu ích trong một codebases Python lớn.

Giao diện không thể thay thế để thử nghiệm. Kiểm tra toàn diện hợp lý là đặc biệt quan trọng trong các ngôn ngữ năng động cao như Python, nơi có các loại lỗi mà không thể tồn tại trong một ngôn ngữ kiểu tĩnh. Các bài kiểm tra cũng sẽ giúp bạn nắm bắt các loại lỗi không phải là duy nhất đối với các ngôn ngữ động. May mắn thay, phát triển bằng Python có nghĩa là việc kiểm tra rất dễ dàng (do tính linh hoạt) và bạn có nhiều thời gian để viết chúng mà bạn đã lưu vì bạn đang sử dụng Python.

+0

Có lẽ câu trả lời tốt nhất để thảo luận về các ngôn ngữ động và kiểu gõ tĩnh và tầm quan trọng của các bài kiểm tra đơn vị trong các ngôn ngữ được nhập động. –

3

Bạn cũng có thể sử dụng decorators to enforce the type of attributes.

>>> @accepts(int, int, int) 
... @returns(float) 
... def average(x, y, z): 
...  return (x + y + z)/2 
... 
>>> average(5.5, 10, 15.0) 
TypeWarning: 'average' method accepts (int, int, int), but was given 
(float, int, float) 
15.25 
>>> average(5, 10, 15) 
TypeWarning: 'average' method returns (float), but result is (int) 
15 

Tôi không thực sự là người hâm mộ của họ, nhưng tôi có thể thấy tính hữu ích của họ.

+0

Đây không phải là một chiến lược tốt để tạo mã mà không có những cạm bẫy OP sợ hãi. –

1

quên để kiểm tra một loại

Với gõ vịt, nó không phải là cần thiết để kiểm tra một loại. Nhưng đó là lý thuyết, trên thực tế bạn thường muốn xác thực các thông số đầu vào (ví dụ: kiểm tra UUID bằng regex). Vì mục đích đó, tôi tự tạo ra một số trang trí tiện dụng cho loại đơn giản và kiểm tra kiểu trả lại được gọi như sau:

@decorators.params(0, int, 2, str) # first parameter must be integer/third a string 
@decorators.returnsOrNone(int, long) # must return an int/long value or None 
def doSomething(integerParam, noMatterWhatParam, stringParam): 
    ... 

Cho mọi thứ khác tôi chủ yếu sử dụng xác nhận. Tất nhiên người ta thường quên kiểm tra một tham số, vì vậy nó cần thiết để kiểm tra và kiểm tra thường xuyên.

cố gắng để gọi một thuộc tính

xảy ra với tôi rất hiếm khi. Trên thực tế, tôi thường sử dụng các phương thức thay vì truy cập trực tiếp vào các thuộc tính (đôi khi là phương thức getter/setter cũ "tốt").

vì tôi chỉ là con người tôi nhớ một đôi và sau đó một số người dùng cuối tìm thấy nó

"Phần mềm luôn được hoàn thành tại của khách hàng." - Một mô hình chống mà bạn nên giải quyết với các bài kiểm tra đơn vị xử lý tất cả các trường hợp có thể có trong một hàm. Nói dễ hơn làm, nhưng nó giúp ...

Đối với các lỗi thường gặp khác của Python (nhập sai tên, nhập sai) ..., tôi đang sử dụng Eclipse với PyDev cho dự án (không cho tập lệnh nhỏ). PyDev cảnh báo bạn về hầu hết các loại sai lầm đơn giản.

+3

Kiểm tra xem UUID có thực sự là — hoặc xác thực dữ liệu khác không phải là đánh máy * mỗi lần *, nó trực tiếp xem xét những gì bạn quan tâm. Nó thường là một điều tốt. Thực tế typechecking cho một loại chữ (như trang trí của bạn làm) là khá khác nhau và làm cho mã của bạn ít linh hoạt mà không có bất cứ điều gì thực sự để đạt được hầu hết thời gian. –

+0

Sử dụng các phương pháp thay vì truy cập trực tiếp vào một thuộc tính khác không giúp gì được. Một phương thức vẫn * là * một thuộc tính và một thuộc tính mà bạn có thể sai chính tả hoặc sử dụng sai. Sử dụng getters và setters, mặc dù, làm cho bạn phải viết thêm boilerplate và lộn xộn API của bạn cho không có gì để đạt được. (Trong các ngôn ngữ như C++, có một cái gì đó để đạt được: bạn sẽ phải thay đổi API của bạn nếu bạn cần thay đổi từ một thành viên sang một phương thức.Trong Python, bạn có thể sử dụng các thuộc tính và do đó không phải thay đổi API của bạn, thực sự không có lý do rõ ràng để sử dụng getters và setters.) –

+0

@Mike Graham: Correcto. Đó là lý do tại sao thường gõ vịt là đủ, như cho vòng lặp bạn sẽ (hầu như) không bao giờ làm 'khẳng định isinstance (param, tuple)'.Nhưng nếu bạn xác định các giao diện chặt chẽ (như các lớp cơ sở trừu tượng), đôi khi bạn nên kiểm tra loại nghiêm ngặt. – AndiDog

0

Bạn có thể gợi ý IDE của mình qua hàm doc, ví dụ: http://www.pydev.org/manual_adv_type_hints.html, trong JavaScript jsDoc giúp theo cách tương tự.

Nhưng tại một số điểm bạn sẽ gặp phải lỗi mà ngôn ngữ đã nhập sẽ tránh ngay lập tức mà không cần kiểm tra đơn vị (thông qua trình biên dịch IDE và loại/suy luận).

Tất nhiên điều này không loại bỏ lợi ích của các bài kiểm tra đơn vị, phân tích tĩnh và xác nhận. Đối với dự án lớn hơn, tôi có xu hướng sử dụng các ngôn ngữ gõ tĩnh bởi vì chúng có hỗ trợ IDE rất tốt (tự động hoàn thành tuyệt vời, tái cấu trúc nặng ...). Bạn vẫn có thể sử dụng kịch bản hoặc DSL cho một số phần phụ của dự án.

Các vấn đề liên quan