Mengetik bebek, validasi data, dan pemrograman asertif dengan Python

10

Tentang mengetik bebek :

Mengetik bebek dibantu oleh biasanya tidak menguji untuk jenis argumen dalam metode dan fungsi badan, mengandalkan dokumentasi, kode yang jelas dan pengujian untuk memastikan penggunaan yang benar.

Tentang validasi argumen (EAFP: Lebih mudah untuk meminta maaf daripada izin). Contoh yang diadaptasi dari sini :

... dianggap lebih pythonic untuk dilakukan:

def my_method(self, key):
    try:
        value = self.a_dict[member]
    except TypeError:
        # do something else

Ini berarti bahwa orang lain yang menggunakan kode Anda tidak harus menggunakan kamus atau subkelas nyata - mereka dapat menggunakan objek apa pun yang mengimplementasikan antarmuka pemetaan.

Sayangnya dalam praktiknya tidak sesederhana itu. Bagaimana jika anggota dalam contoh di atas mungkin bilangan bulat? Integer tidak dapat diubah - jadi sangat masuk akal untuk menggunakannya sebagai kunci kamus. Namun mereka juga digunakan untuk mengindeks objek jenis urutan. Jika anggota kebetulan merupakan bilangan bulat maka contoh dua bisa membiarkan daftar dan string serta kamus.

Tentang pemrograman asertif :

Pernyataan adalah cara sistematis untuk memeriksa bahwa keadaan internal suatu program adalah seperti yang diharapkan oleh programmer, dengan tujuan menangkap bug. Secara khusus, mereka bagus untuk menangkap asumsi palsu yang dibuat saat menulis kode, atau penyalahgunaan antarmuka oleh programmer lain. Selain itu, mereka dapat bertindak sebagai dokumentasi in-line sampai batas tertentu, dengan membuat asumsi programmer jelas. ("Eksplisit lebih baik daripada implisit.")

Konsep-konsep yang disebutkan kadang-kadang bertentangan, jadi saya mengandalkan faktor-faktor berikut ketika memilih jika saya tidak melakukan validasi data sama sekali, melakukan validasi yang kuat atau menggunakan pernyataan:

  1. Validasi yang kuat. Dengan validasi yang kuat maksud saya meningkatkan Pengecualian khusus ( ApiErrormisalnya). Jika fungsi / metode saya adalah bagian dari API publik, lebih baik memvalidasi argumen untuk menampilkan pesan kesalahan yang baik tentang tipe yang tidak terduga. Dengan memeriksa tipe yang saya maksud bukan hanya menggunakan isinstance, tetapi juga jika objek yang lewat mendukung antarmuka yang diperlukan (duck typing). Sementara saya mendokumentasikan API dan menentukan jenis yang diharapkan dan pengguna mungkin ingin menggunakan fungsi saya dengan cara yang tidak terduga, saya merasa lebih aman ketika saya memeriksa asumsi. Saya biasanya menggunakan isinstancedan jika nanti saya ingin mendukung jenis atau bebek lain, saya mengubah logika validasi.

  2. Pemrograman asertif. Jika kode saya baru, saya menggunakan banyak pernyataan. Apa saran Anda tentang ini? Apakah nanti Anda menghapus pernyataan dari kode?

  3. Jika fungsi / metode saya bukan bagian dari API, tetapi meneruskan beberapa argumennya ke kode lain yang tidak ditulis, dipelajari atau diuji oleh saya, saya melakukan banyak penegasan sesuai dengan antarmuka yang dipanggil. Logika saya di balik ini - lebih baik gagal dalam kode saya, kemudian di suatu tempat 10 tingkat lebih dalam di stacktrace dengan kesalahan yang tidak dapat dimengerti yang memaksa untuk banyak debug dan kemudian menambahkan pernyataan ke kode saya.

Komentar dan saran kapan harus menggunakan atau tidak menggunakan validasi tipe / nilai, tegaskan? Maaf karena bukan rumusan pertanyaan terbaik.

Sebagai contoh, pertimbangkan fungsi berikut, di mana Customermodel deklaratif SQLAlchemy:

def add_customer(self, customer):
    """Save new customer into the database.
    @param customer: Customer instance, whose id is None
    @return: merged into global session customer
    """
    # no validation here at all
    # let's hope SQLAlchemy session will break if `customer` is not a model instance
    customer = self.session.add(customer)
    self.session.commit()
    return customer

Jadi, ada beberapa cara untuk menangani validasi:

def add_customer(self, customer):
    # this is an API method, so let's validate the input
    if not isinstance(customer, Customer):
        raise ApiError('Invalid type')
    if customer.id is not None:
        raise ApiError('id should be None')

    customer = self.session.add(customer)
    self.session.commit()
    return customer

atau

def add_customer(self, customer):
    # this is an internal method, but i want to be sure
    # that it's a customer model instance
    assert isinstance(customer, Customer), 'Achtung!'
    assert customer.id is None

    customer = self.session.add(customer)
    self.session.commit()
    return customer

Kapan dan mengapa Anda menggunakan masing-masing ini dalam konteks pengetikan bebek, pengecekan tipe, validasi data?

warvariuc
sumber
1
Anda tidak boleh menghapus pernyataan seperti halnya unit test kecuali karena alasan kinerja
Bryan Chen

Jawaban:

4

Biarkan saya memberikan beberapa prinsip panduan.

Prinsip # 1. Seperti diuraikan dalam http://docs.python.org/2/reference/simple_stmts.html overhead kinerja dari aserters dapat dihapus dengan opsi baris perintah, sementara masih berada di sana untuk debugging. Jika kinerja adalah masalah, lakukan itu. Tinggalkan keterangannya. (Tapi jangan lakukan hal yang penting dalam pernyataan!)

Prinsip # 2. Jika Anda menegaskan sesuatu, dan akan memiliki kesalahan fatal, maka gunakan pernyataan. Sama sekali tidak ada nilai dalam melakukan sesuatu yang lain. Jika seseorang nanti ingin mengubahnya, mereka dapat mengubah kode Anda atau menghindari panggilan metode itu.

Prinsip # 3. Jangan melarang sesuatu hanya karena Anda pikir itu adalah hal yang bodoh untuk dilakukan. Jadi bagaimana jika metode Anda memungkinkan string melalui? Jika berhasil, itu berfungsi.

Prinsip # 4. Larang hal-hal yang kemungkinan merupakan tanda kesalahan. Misalnya pertimbangkan untuk meneruskan kamus opsi. Jika kamus itu berisi hal-hal yang bukan opsi yang valid, maka itu pertanda bahwa seseorang tidak memahami API Anda, atau ada kesalahan ketik. Meledakkan itu lebih mungkin untuk menangkap kesalahan ketik daripada menghentikan seseorang dari melakukan sesuatu yang masuk akal.

Berdasarkan 2 prinsip pertama, versi kedua Anda dapat dibuang. Yang mana dari dua lainnya yang Anda sukai adalah masalah selera. Menurut Anda, mana yang lebih mungkin? Bahwa seseorang akan meneruskan non-pelanggan ke add_customerdan hal-hal akan rusak (dalam hal ini versi 3 lebih disukai), atau seseorang pada suatu saat ingin mengganti pelanggan Anda dengan objek proxy dari beberapa jenis yang menanggapi semua metode yang tepat (dalam hal ini versi 1 lebih disukai).

Secara pribadi saya telah melihat kedua mode kegagalan. Saya cenderung menggunakan versi 1 dari prinsip umum bahwa saya malas dan kurang mengetik. (Juga kegagalan semacam itu biasanya cenderung muncul cepat atau lambat dengan cara yang cukup jelas. Dan ketika saya ingin menggunakan objek proxy, saya benar-benar kesal pada orang-orang yang mengikat tangan saya.) akan pergi ke arah lain.

btilly
sumber
Saya lebih suka v.3, terutama ketika mendesain antarmuka - menulis kelas dan metode baru. Saya juga menganggap v.3 berguna untuk metode API - karena kode saya baru untuk orang lain. Saya pikir pendekatan tegas adalah kompromi yang baik, karena akan dihapus dalam produksi ketika berjalan dalam mode yang dioptimalkan. > Meledakkan sesuatu yang lebih mungkin menangkap kesalahan ketik daripada menghentikan seseorang dari melakukan sesuatu yang masuk akal. <Jadi, kamu tidak keberatan memiliki validasi seperti itu?
warvariuc
Mari kita begini. Saya menemukan bahwa peta warisan buruk untuk bagaimana saya ingin mengembangkan desain. Saya lebih suka komposisi. Jadi saya menghindar untuk menyatakan bahwa ini harus dari kelas itu. Tapi saya tidak menentang pernyataan di mana saya pikir mereka menyelamatkan saya sesuatu.
btilly