Access
 sql >> Teknologi Basis Data >  >> RDS >> Access

Menulis Kode yang Dapat Dibaca untuk VBA – Coba* pola

Menulis Kode yang Dapat Dibaca untuk VBA – Pola Coba*

Akhir-akhir ini, saya menemukan diri saya menggunakan Try pola semakin banyak. Saya sangat menyukai pola ini karena membuat kode lebih mudah dibaca. Ini sangat penting ketika memprogram dalam bahasa pemrograman yang matang seperti VBA di mana penanganan kesalahan terkait dengan aliran kontrol. Secara umum, saya menemukan prosedur apa pun yang mengandalkan penanganan kesalahan sebagai aliran kontrol lebih sulit untuk diikuti.

Skenario

Mari kita mulai dengan sebuah contoh. Model objek DAO adalah kandidat yang sempurna karena cara kerjanya. Lihat, semua objek DAO memiliki Properties koleksi, yang berisi Property objek. Namun, siapa pun dapat menambahkan properti khusus. Bahkan, Access akan menambahkan beberapa properti ke berbagai objek DAO. Oleh karena itu, kami mungkin memiliki properti yang mungkin tidak ada dan harus menangani kasus perubahan nilai properti yang ada dan kasus penambahan properti baru.

Mari gunakan Subdatasheet properti sebagai contoh. Secara default, semua tabel yang dibuat melalui Access UI akan memiliki properti yang disetel ke Auto , tapi kita mungkin tidak menginginkan itu. Tetapi jika kita memiliki tabel yang dibuat dalam kode atau cara lain, itu mungkin tidak memiliki properti. Jadi kita bisa mulai dengan versi awal kode untuk memperbarui properti semua tabel dan menangani kedua kasus.

Sub Publik EditTableSubdatasheetProperty( _ Opsional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="SubdatasheetName" On Error GoTo ErrHandler Tetapkan db =CurrentDb Untuk Setiap tdf Dalam db.TableDefs If (tdf.Attributes And dbSystemObject) =0 Kemudian If Len(tdf.Connect) =0 Dan (Not tdf.Name Like "~*") Kemudian 'Tidak terpasang, atau temp . Set prp =tdf.Properties(SubDatasheetPropertyName) If prp.Value <> NewValue Kemudian prp.Value =NewValue End If End If End IfContinue:NextExitProc:Exit SubErrHandler:If Err.Number =3270 Kemudian Set prp =tdf.CreatePropertyName(SubDatasheetProperty(SubData dbText, NewValue) tdf.Properties.Append prp Resume Continue End If MsgBox Err.Number &":" &Err.Description Resume ExitProc End Sub

Kode mungkin akan berfungsi. Namun, untuk memahaminya, kita mungkin harus membuat diagram beberapa diagram alir. Baris Set prp = tdf.Properties(SubDatasheetPropertyName) berpotensi menimbulkan kesalahan 3270. Dalam kasus ini, kontrol melompat ke bagian penanganan kesalahan. Kami kemudian membuat properti dan melanjutkan pada titik loop yang berbeda menggunakan label Continue . Ada beberapa pertanyaan…

  • Bagaimana jika 3270 dinaikkan pada baris lain?
  • Misalkan baris Set prp =... tidak melempar kesalahan 3270 tetapi sebenarnya ada kesalahan lain?
  • Bagaimana jika saat kita berada di dalam pengendali kesalahan, kesalahan lain terjadi saat menjalankan Append atau CreateProperty ?
  • Haruskah fungsi ini menampilkan Msgbox ? Pikirkan tentang fungsi yang seharusnya bekerja pada sesuatu atas nama formulir atau tombol. Jika fungsi menampilkan kotak pesan, lalu keluar secara normal, kode panggilan tidak mengetahui ada yang tidak beres dan mungkin terus melakukan hal yang seharusnya tidak dilakukan.
  • Dapatkah Anda melihat sekilas kodenya dan langsung memahami fungsinya? saya tidak bisa. Saya harus menyipitkannya, lalu berpikir tentang apa yang harus terjadi jika terjadi kesalahan dan secara mental membuat sketsa jalan. Itu tidak mudah dibaca.

Tambahkan HasProperty prosedur

Bisakah kita melakukan yang lebih baik? Ya! Beberapa programmer sudah mengenali masalah dengan menggunakan penanganan kesalahan seperti yang saya ilustrasikan dan dengan bijak mengabstraksikannya ke dalam fungsinya sendiri. Ini versi yang lebih baik:

Sub Publik EditTableSubdatasheetProperty( _ Opsional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="SubdatasheetName" Set db =Untuk Setiap tdf Dalam db.TableDefs Jika (tdf.Attributes Dan dbSystemObject) =0 Kemudian Jika Len(tdf.Connect) =0 Dan (Tidak tdf.Name Seperti "~*") Kemudian 'Tidak terpasang, atau temp. Jika Bukan HasProperty(tdf, SubDatasheetPropertyName) Kemudian Tetapkan prp =tdf.CreateProperty(SubDatasheetPropertyName , dbText, NewValue) tdf.Properties.Tambahkan prp Lain Jika tdf.Properties(SubDatasheetPropertyName) <> NewValuePropertyNameSub) If.PropertyNameSubData Baru (SubDatasheetPropertyValue tdf) End If End If End If NextEnd SubPublic Function HasProperty(TargetObject As Object, PropertyName As String) As Boolean Dim Diabaikan Sebagai Varian Pada Error Lanjutkan Next Ignored =TargetObject.Properties(PropertyName) HasProperty =(Err.Number =0)End Function 

Alih-alih mencampuradukkan alur eksekusi dengan penanganan kesalahan, kami sekarang memiliki fungsi HasFunction yang dengan rapi mengabstraksi pemeriksaan rawan kesalahan untuk properti yang mungkin tidak ada. Akibatnya, kita tidak memerlukan penanganan error/alur eksekusi yang rumit seperti yang kita lihat pada contoh pertama. Ini adalah peningkatan besar dan membuat kode yang agak mudah dibaca. Tapi…

  • Kami memiliki satu cabang yang menggunakan variabel prp dan kami memiliki cabang lain yang menggunakan tdf.Properties(SubDatasheetPropertyName) yang sebenarnya mengacu pada properti yang sama. Mengapa kita mengulangi diri kita sendiri dengan dua cara berbeda untuk merujuk properti yang sama?
  • Kami menangani properti cukup banyak. HasProperty harus menangani properti untuk mengetahui apakah itu ada maka cukup kembalikan Boolean hasilnya, serahkan pada kode panggilan untuk mencoba lagi dan mendapatkan properti yang sama lagi untuk mengubah nilainya.
  • Demikian pula, kami menangani NewValue lebih dari yang diperlukan. Kami meneruskannya di CreateProperty atau atur Value properti properti.
  • HasProperty fungsi secara implisit mengasumsikan bahwa objek memiliki Properties member dan menyebutnya late-bound, yang berarti error runtime jika jenis objek yang salah diberikan padanya.

Gunakan TryGetProperty sebagai gantinya

Bisakah kita melakukan yang lebih baik? Ya! Di situlah kita perlu melihat pola Try. Jika Anda pernah memprogram dengan .NET, Anda mungkin pernah melihat metode seperti TryParse di mana alih-alih meningkatkan kesalahan pada kegagalan, kita dapat mengatur kondisi untuk melakukan sesuatu untuk sukses dan sesuatu yang lain untuk kegagalan. Tetapi yang lebih penting, kami memiliki hasil yang tersedia untuk sukses. Jadi, bagaimana kami meningkatkan HasProperty fungsi? Untuk satu hal, kita harus mengembalikan Property obyek. Mari kita coba kode ini:

Public Function TryGetProperty( _ ByVal SourceProperties As DAO.Properties, _ ByVal PropertyName As String, _ ByRef OutProperty As DAO.Property _) As Boolean On Error Resume Next Set OutProperty =SourceProperties(PropertyName) Jika Err.Number Kemudian Tetapkan OutProperty =Tidak Ada Yang Berakhir Jika Pada Kesalahan GoTo 0 TryGetProperty =(Tidak KeluarProperty Bukan Apa-apa) Fungsi Akhir

Dengan sedikit perubahan, kami telah mencetak beberapa kemenangan besar:

  • Akses ke Property tidak lagi terikat terlambat. Kita tidak perlu berharap bahwa suatu objek memiliki properti bernama Properties dan itu dari DAO.Properties . Ini dapat diverifikasi pada waktu kompilasi.
  • Alih-alih hanya Boolean hasilnya, kita juga bisa mendapatkan Property . yang diambil objek, tetapi hanya pada keberhasilan. Jika kita gagal, OutProperty parameternya adalah Nothing . Kami masih akan menggunakan Boolean result untuk membantu menyetel alur naik seperti yang akan segera Anda lihat.
  • Dengan menamai fungsi baru kita dengan Try awalan, kami menunjukkan bahwa ini dijamin tidak menimbulkan kesalahan dalam kondisi operasi normal. Jelas, kami tidak dapat mencegah kesalahan memori atau sesuatu seperti itu, tetapi pada saat itu, kami memiliki masalah yang jauh lebih besar. Namun di bawah kondisi operasi normal, kami menghindari kekusutan penanganan kesalahan kami dengan alur eksekusi. Kode sekarang dapat dibaca dari atas ke bawah tanpa melompat maju atau mundur.

Perhatikan bahwa menurut konvensi, saya mengawali properti "keluar" dengan Out . Itu membantu memperjelas bahwa kita seharusnya meneruskan variabel ke fungsi yang tidak diinisialisasi. Kami juga mengharapkan bahwa fungsi akan menginisialisasi parameter. Itu akan menjadi jelas ketika kita melihat kode panggilan. Jadi, mari kita siapkan kode panggilannya.

Revisi kode panggilan menggunakan TryGetProperty

Sub Publik EditTableSubdatasheetProperty( _ Opsional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="SubdatasheetName" Set db =Untuk Setiap tdf Dalam db.TableDefs Jika (tdf.Attributes Dan dbSystemObject) =0 Kemudian Jika Len(tdf.Connect) =0 Dan (Tidak tdf.Name Seperti "~*") Kemudian 'Tidak terpasang, atau temp. If TryGetProperty(tdf, SubDatasheetPropertyName, prp) Kemudian If prp.Value <> NewValue Then prp.Value =NewValue End If Else Set prp =tdf.CreateProperty(SubDatasheetPropertyName , dbText, NewValue) tdf.Property End If EndAppend prp Jika NextEnd Sub

Kode sekarang sedikit lebih mudah dibaca dengan pola Coba pertama. Kami telah berhasil mengurangi penanganan prp . Perhatikan bahwa kami melewati prp variabel ke dalam true , prp akan diinisialisasi dengan properti yang ingin kita manipulasi. Jika tidak, prp tetap Nothing . Kami kemudian dapat menggunakan CreateProperty untuk menginisialisasi prp variabel.

Kami juga membalik negasi sehingga kode menjadi lebih mudah dibaca. Namun, kami belum benar-benar mengurangi penanganan NewValue parameter. Kami masih memiliki blok bersarang lain untuk memeriksa nilainya. Bisakah kita melakukan yang lebih baik? Ya! Mari tambahkan fungsi lain:

Menambahkan TrySetPropertyValue prosedur

Fungsi Publik TrySetPropertyValue( _ ByVal SourceProperty As DAO.Property, _ ByVal NewValue As Variant_) Sebagai Boolean Jika SourceProperty.Value =PropertyValue Kemudian TrySetPropertyValue =True Else On Error Resume Next SourceProperty.Value =PropertyValue GoToValue =NewValue 0 SourceProperty.Value =NewValue) Akhiri Fungsi IfEnd

Karena kami menjamin bahwa fungsi ini tidak akan membuat kesalahan saat mengubah nilai, kami menyebutnya TrySetPropertyValue . Lebih penting lagi, fungsi ini membantu merangkum semua detail mengerikan seputar perubahan nilai properti. Kami memiliki cara untuk menjamin bahwa nilainya adalah nilai yang kami harapkan. Mari kita lihat bagaimana kode panggilan akan diubah dengan fungsi ini.

Memperbarui kode panggilan menggunakan keduanya TryGetProperty dan TrySetPropertyValue

Sub Publik EditTableSubdatasheetProperty( _ Opsional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="SubdatasheetName" Set db =Untuk Setiap tdf Dalam db.TableDefs Jika (tdf.Attributes Dan dbSystemObject) =0 Kemudian Jika Len(tdf.Connect) =0 Dan (Tidak tdf.Name Seperti "~*") Kemudian 'Tidak terpasang, atau temp. If TryGetProperty(tdf, SubDatasheetPropertyName, prp) Kemudian TrySetPropertyValue prp, NewValue Else Set prp =tdf.CreateProperty(SubDatasheetPropertyName , dbText, NewValue) tdf.Properties.Append prp End If End If End If NextEnd Sub
 Kami telah menghilangkan seluruh If memblokir. Kami sekarang dapat dengan mudah membaca kode dan segera bahwa kami mencoba untuk menetapkan nilai properti dan jika terjadi kesalahan, kami terus melanjutkan. Itu jauh lebih mudah dibaca dan nama fungsinya menggambarkan dirinya sendiri. Nama yang baik membuatnya tidak perlu lagi mencari definisi fungsi untuk memahami apa yang dilakukannya.

Membuat TryCreateOrSetProperty prosedur

Kode lebih mudah dibaca tetapi kami masih memiliki Else memblokir pembuatan properti. Bisakah kita melakukan yang lebih baik lagi? Ya! Mari kita pikirkan apa yang perlu kita capai di sini. Kami memiliki properti yang mungkin ada atau mungkin tidak ada. Jika tidak, kami ingin membuatnya. Apakah sudah ada atau belum, kita perlu mengaturnya ke nilai tertentu. Jadi yang kita butuhkan adalah fungsi yang akan membuat properti atau memperbarui nilainya jika sudah ada. Untuk membuat properti, kita harus memanggil CreateProperty yang sayangnya tidak ada di Properties melainkan objek DAO yang berbeda. Jadi, kita harus terlambat mengikat dengan menggunakan Object tipe data. Namun, kami masih dapat menyediakan beberapa pemeriksaan runtime untuk menghindari kesalahan. Mari buat TryCreateOrSetProperty fungsi:

Public Function TryCreateOrSetProperty( _ ByVal SourceDaoObject As Object, _ ByVal PropertyName As String, _ ByVal PropertyType As DAO.DataTypeEnum, _ ByVal PropertyValue As Variant, _ ByRef OutProperty As DAO.Property _) Sebagai Boolean Select SourceDao True Case Type Adalah DAO.TableDef, _ TypeOf SourceDaoObject Adalah DAO.QueryDef, _ TypeOf SourceDaoObject Adalah DAO.Field, _ TypeOf SourceDaoObject Adalah DAO.Database Jika TryGetProperty(SourceDaoObject.Properties, PropertyName, OutProperty) Lalu TryCreateOrSetPropertyV Error Resume Next Set OutProperty =SourceDaoObject.CreateProperty(PropertyName, PropertyType, PropertyValue) SourceDaoObject.Properties.Append OutProperty If Err.Number Kemudian Set OutProperty =Tidak Ada yang Berakhir Jika Aktif Error GoTo 0 TryCreateOrSetProperty =(OutProperty Is Nothing) End If Case Else Err.Raise 5, , "Objek tidak valid disediakan untuk parameter SourceDaoObject. Itu harus berupa objek DAO yang berisi anggota CreateProperty." End SelectEnd Function

Beberapa hal yang perlu diperhatikan:

  • Kami dapat mengembangkan Try* sebelumnya fungsi yang kami definisikan, yang membantu mengurangi pengkodean badan fungsi, memungkinkannya untuk lebih fokus pada pembuatan jika tidak ada properti seperti itu.
  • Ini tentu lebih bertele-tele karena pemeriksaan runtime tambahan, tetapi kami dapat mengaturnya sehingga kesalahan tidak mengubah alur eksekusi dan kami masih dapat membaca dari atas ke bawah tanpa melompat.
  • Alih-alih melempar MsgBox entah dari mana, kami menggunakan Err.Raise dan mengembalikan kesalahan yang berarti. Penanganan kesalahan yang sebenarnya didelegasikan ke kode panggilan yang kemudian dapat memutuskan apakah akan menampilkan kotak pesan kepada pengguna atau melakukan sesuatu yang lain.
  • Karena penanganan kami yang cermat dan ketentuan bahwa SourceDaoObject parameter valid, semua jalur yang mungkin menjamin bahwa setiap masalah dengan membuat atau menyetel nilai properti yang ada akan ditangani dan kami akan mendapatkan false hasil. Itu mempengaruhi kode panggilan seperti yang akan kita lihat sebentar lagi.

Versi terakhir dari kode panggilan

Mari perbarui kode panggilan untuk menggunakan fungsi baru:

Sub Publik EditTableSubdatasheetProperty( _ Opsional NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="SubdatasheetName" Set db =Untuk Setiap tdf Dalam db.TableDefs Jika (tdf.Attributes Dan dbSystemObject) =0 Kemudian Jika Len(tdf.Connect) =0 Dan (Tidak tdf.Name Seperti "~*") Kemudian 'Tidak terpasang, atau temp. TryCreateOrSetProperty tdf, SubDatasheetPropertyName, dbText, NewValue End If End If NextEnd Sub

Itu cukup meningkatkan keterbacaan. Dalam versi aslinya, kita harus meneliti sejumlah If blok dan bagaimana penanganan kesalahan mengubah aliran eksekusi. Kami harus mencari tahu apa sebenarnya yang dilakukan konten untuk menyimpulkan bahwa kami mencoba mendapatkan properti atau membuatnya jika tidak ada dan menyetelnya ke nilai tertentu. Dengan versi saat ini, semuanya ada atas nama fungsi, TryCreateOrSetProperty . Sekarang kita dapat melihat apa yang diharapkan dari fungsi tersebut.

Kesimpulan

Anda mungkin bertanya-tanya, “tetapi kami menambahkan lebih banyak fungsi dan lebih banyak baris. Bukankah itu banyak pekerjaan?" Memang benar bahwa dalam versi saat ini, kami mendefinisikan 3 fungsi lagi. Namun, Anda dapat membaca setiap fungsi secara terpisah dan masih dengan mudah memahami apa yang harus dilakukan. Anda juga melihat bahwa TryCreateOrSetProperty fungsi dapat dibangun di atas 2 Try* lainnya fungsi. Itu berarti kami memiliki lebih banyak fleksibilitas dalam menyusun logika bersama.

Jadi, jika kita menulis fungsi lain yang melakukan sesuatu dengan properti objek, kita tidak perlu menulis semuanya dan juga tidak menyalin-tempel kode dari EditTableSubdatasheetProperty asli. ke dalam fungsi baru. Bagaimanapun, fungsi baru mungkin memerlukan beberapa varian yang berbeda dan dengan demikian memerlukan urutan yang berbeda. Terakhir, perlu diingat bahwa penerima manfaat sebenarnya adalah kode panggilan yang perlu melakukan sesuatu. Kami ingin menjaga agar kode panggilan tetap cukup tinggi tanpa terperosok ke dalam detail yang dapat berdampak buruk bagi pemeliharaan.

Anda juga dapat melihat bahwa penanganan kesalahan disederhanakan secara signifikan, meskipun kami menggunakan On Error Resume Next . Kita tidak perlu lagi mencari kode kesalahan karena pada kebanyakan kasus, kita hanya tertarik pada apakah berhasil atau tidak. Lebih penting lagi, penanganan kesalahan tidak mengubah alur eksekusi di mana Anda memiliki beberapa logika di badan dan logika lain dalam penanganan kesalahan. Yang terakhir adalah situasi yang pasti ingin kita hindari karena jika ada kesalahan di dalam pengendali kesalahan, maka perilakunya bisa mengejutkan. Sebaiknya hindari hal itu agar tidak menjadi kemungkinan.

Ini semua tentang abstraksi

Tetapi skor terpenting yang kami menangkan di sini adalah tingkat abstraksi yang sekarang dapat kami capai. Versi asli EditTableSubdatasheetProperty berisi banyak detail tingkat rendah tentang objek DAO sebenarnya bukan tentang tujuan inti dari fungsi tersebut. Pikirkan tentang hari-hari di mana Anda telah melihat prosedur yang panjangnya ratusan baris dengan loop atau kondisi yang sangat bersarang. Apakah Anda ingin men-debug itu? Saya tidak.

Jadi ketika saya melihat sebuah prosedur, hal pertama yang benar-benar ingin saya lakukan adalah merobek bagian-bagian itu menjadi fungsinya sendiri, sehingga saya dapat meningkatkan tingkat abstraksi untuk prosedur itu. Dengan memaksa diri kita untuk mendorong tingkat abstraksi, kita juga dapat menghindari kelas besar bug di mana penyebabnya adalah satu perubahan di bagian mega-prosedur memiliki konsekuensi yang tidak diinginkan untuk bagian lain dari prosedur. Saat kita memanggil fungsi dan meneruskan parameter, kita juga mengurangi kemungkinan efek samping yang tidak diinginkan mengganggu logika kita.

Oleh karena itu mengapa saya menyukai pola “Coba*”. Saya harap ini berguna untuk proyek Anda juga.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Apakah Karyawan Anda Bekerja dari Jarak Jauh? Inilah Cara Menjaga Keamanan Data Anda.

  2. Parameter String Koneksi untuk Spesifikasi Tersimpan

  3. Cara Membuat Database Inventory di Microsoft Access

  4. CARA:Jalankan Tugas Terjadwal dengan Microsoft Access

  5. Akses ODBC dari Windows Server Core