Access
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Access

Viết mã có thể đọc được cho VBA - Mẫu thử *

Viết mã có thể đọc cho VBA - Thử * mẫu

Gần đây, tôi đã tìm thấy chính mình bằng cách sử dụng Try mẫu ngày càng nhiều. Tôi thực sự thích mẫu này vì nó làm cho mã dễ đọc hơn nhiều. Điều này đặc biệt quan trọng khi lập trình bằng một ngôn ngữ lập trình thuần thục như VBA, nơi việc xử lý lỗi được đan xen với luồng điều khiển. Nói chung, tôi thấy khó thực hiện hơn bất kỳ quy trình nào dựa vào việc xử lý lỗi như một luồng kiểm soát.

Tình huống

Hãy bắt đầu với một ví dụ. Mô hình đối tượng DAO là một ứng cử viên hoàn hảo vì nó hoạt động như thế nào. Hãy xem, tất cả các đối tượng DAO đều có Properties bộ sưu tập, chứa Properties các đối tượng. Tuy nhiên, bất kỳ ai cũng có thể thêm thuộc tính tùy chỉnh. Trên thực tế, Access sẽ thêm một số thuộc tính vào các đối tượng DAO khác nhau. Do đó, chúng tôi có thể có một thuộc tính có thể không tồn tại và phải xử lý cả trường hợp thay đổi giá trị của một sản phẩm hiện có và trường hợp bổ sung một sản phẩm mới.

Hãy sử dụng Subdatasheet tài sản làm ví dụ. Theo mặc định, tất cả các bảng được tạo qua Access UI sẽ có thuộc tính được đặt thành Auto , nhưng chúng tôi có thể không muốn điều đó. Nhưng nếu chúng ta có các bảng được tạo bằng mã hoặc một số cách khác, nó có thể không có thuộc tính. Vì vậy, chúng tôi có thể bắt đầu với phiên bản đầu tiên của mã để cập nhật thuộc tính của tất cả các bảng và xử lý cả hai trường hợp.

 Public Sub EditTableSubdatasheetProperty (_ Tùy chọn 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 Đặt db =CurrentDb cho mỗi tdf Trong db.TableDefs If (tdf.Attributes And dbSystemObject) =0 Then If Len (tdf.Connect) =0 And (Not tdf.Name Like "~ *") Then 'Không được đính kèm, hoặc tạm thời . Đặt prp =tdf.Properties (SubDatasheetPropertyName) If prp.Value <> NewValue Then prp.Value =NewValue End If End If End IfContinue:NextExitProc:Thoát SubErrHandler:If Err.Number =3270 Sau đó đặt prp =tdf.CreateProperty (SubpertyNameheet dbText, NewValue) tdf.Properties.Append prp Resume Tiếp tục Kết thúc Nếu MsgBox Err.Number &":" &Err.Description Resume ExitProc End Sub 

Mã có thể sẽ hoạt động. Tuy nhiên, để hiểu được nó, có lẽ chúng ta phải vẽ sơ đồ một số lưu đồ. Dòng Set prp = tdf.Properties(SubDatasheetPropertyName) có khả năng tạo ra lỗi 3270. Trong trường hợp này, điều khiển sẽ chuyển đến phần xử lý lỗi. Sau đó, chúng tôi tạo một thuộc tính và sau đó tiếp tục tại một điểm khác của vòng lặp bằng cách sử dụng nhãn Continue . Có một số câu hỏi…

  • Điều gì sẽ xảy ra nếu 3270 được nâng lên trên một số dòng khác?
  • Giả sử rằng dòng Set prp =... không ném lỗi 3270 nhưng thực sự là một số lỗi khác?
  • Điều gì xảy ra nếu trong khi chúng ta đang ở bên trong trình xử lý lỗi, một lỗi khác xảy ra khi thực thi Append hoặc CreateProperty ?
  • Nếu chức năng này thậm chí đang hiển thị một Msgbox ? Hãy nghĩ về các chức năng được cho là hoạt động trên một cái gì đó thay mặt cho các biểu mẫu hoặc nút. Nếu các chức năng hiển thị một hộp thông báo, sau đó thoát ra bình thường, mã gọi không có ý tưởng rằng đã xảy ra sự cố và có thể tiếp tục làm những việc mà nó không nên làm.
  • Bạn có thể xem qua mã và hiểu ngay mã hoạt động không? Tôi không thể. Tôi phải nheo mắt nhìn nó, sau đó suy nghĩ về những gì sẽ xảy ra trong trường hợp có lỗi và phác thảo đường đi. Điều đó không dễ đọc.

Thêm HasProperty thủ tục

Chúng ta có thể làm tốt hơn không? Đúng! Một số lập trình viên đã nhận ra vấn đề với việc sử dụng xử lý lỗi như tôi đã minh họa và đã khôn ngoan trừu tượng hóa điều này thành chức năng của riêng nó. Đây là một phiên bản tốt hơn:

 Public Sub EditTableSubdatasheetProperty (_ Tùy chọn NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="SubdatasheetName" Đặt db =CurrentDb Đối với mỗi tdf Trong db.TableDefs If (tdf.Attributes Và dbSystemObject) =0 Then If Len (tdf.Connect) =0 And (Không phải tdf.Name Like "~ *") Then 'Không được đính kèm, hoặc tạm thời. If Not HasProperty (tdf, SubDatasheetPropertyName) then Set prp =tdf.CreateProperty (SubDatasheetPropertyName, dbText, NewValue) tdf.Properties.Append prp Else If tdf.Properties (SubDatasheetPropertyName End If End If End If NextEnd SubPublic Function HasProperty (TargetObject As Object, PropertyName As String) As Boolean Dim Bỏ qua như một biến thể Do lỗi Tiếp tục Tiếp theo Bỏ qua =TargetObject.Properties (PropertyName) HasProperty =(Err.Number =0) End Function  

Thay vì trộn lẫn luồng thực thi với việc xử lý lỗi, bây giờ chúng ta có một hàm HasFunction trong đó tóm tắt gọn gàng việc kiểm tra dễ xảy ra lỗi cho một thuộc tính có thể không tồn tại. Do đó, chúng tôi không cần quy trình xử lý / thực thi lỗi phức tạp mà chúng tôi đã thấy trong ví dụ đầu tiên. Đây là một cải tiến lớn và làm cho mã có thể đọc được. Nhưng…

  • Chúng tôi có một nhánh sử dụng biến prp và chúng tôi có một nhánh khác sử dụng tdf.Properties(SubDatasheetPropertyName) mà trên thực tế đề cập đến cùng một tài sản. Tại sao chúng ta lặp lại chính mình với hai cách khác nhau để tham chiếu cùng một thuộc tính?
  • Chúng tôi đang xử lý tài sản khá nhiều. HasProperty phải xử lý thuộc tính để tìm xem nó có tồn tại hay không, sau đó chỉ cần trả về Boolean kết quả là hãy để nó tùy thuộc vào mã gọi điện để thử lại và lấy lại cùng một thuộc tính để thay đổi giá trị.
  • Tương tự, chúng tôi đang xử lý NewValue Hơn cần thiết. Chúng tôi chuyển nó vào CreateProperty hoặc đặt Value tài sản của tài sản.
  • HasProperty hàm mặc nhiên giả định rằng đối tượng có Properties thành viên và gọi nó là giới hạn muộn, có nghĩa là lỗi thời gian chạy nếu một loại đối tượng sai được cung cấp cho nó.

Sử dụng TryGetProperty thay vào đó

Chúng ta có thể làm tốt hơn không? Đúng! Đó là lúc chúng ta cần xem xét mẫu Thử. Nếu bạn đã từng lập trình với .NET, có thể bạn đã thấy các phương pháp như TryParse Nơi thay vì nêu ra lỗi khi thất bại, chúng ta có thể thiết lập một điều kiện để làm điều gì đó để thành công và điều gì đó khác cho thất bại. Nhưng quan trọng hơn, chúng tôi có sẵn kết quả để thành công. Vậy chúng ta sẽ cải thiện HasProperty như thế nào hàm số? Đối với một điều, chúng ta nên trả về Properties vật. Hãy thử mã này:

 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) If Err.Number Sau đó đặt OutProperty =Không có gì kết thúc nếu gặp lỗi GoTo 0 TryGetProperty =(Không có gì vượt trội thì không có gì) Kết thúc chức năng 

Với một vài thay đổi, chúng tôi đã ghi được một số chiến thắng lớn:

  • Quyền truy cập vào Properties không còn bị ràng buộc muộn. Chúng ta không cần phải hy vọng rằng một đối tượng có thuộc tính có tên là Properties và đó là của DAO.Properties . Điều này có thể được xác minh tại thời điểm biên dịch.
  • Thay vì chỉ một Boolean kết quả là chúng ta cũng có thể lấy Properties đã truy xuất đối tượng, nhưng chỉ dựa trên sự thành công. Nếu chúng tôi thất bại, OutProperty tham số sẽ là Nothing . Chúng tôi vẫn sẽ sử dụng Boolean kết quả giúp thiết lập quy trình như bạn sẽ thấy ngay sau đây.
  • Bằng cách đặt tên cho hàm mới của chúng tôi bằng Try tiền tố, chúng tôi chỉ ra rằng điều này được đảm bảo không gây ra lỗi trong điều kiện hoạt động bình thường. Rõ ràng là chúng ta không thể tránh được lỗi hết bộ nhớ hoặc những thứ tương tự nhưng tại thời điểm đó, chúng ta gặp phải những vấn đề lớn hơn nhiều. Nhưng trong điều kiện hoạt động bình thường, chúng tôi đã tránh được việc xử lý lỗi với quy trình thực thi. Giờ đây, bạn có thể đọc mã từ trên xuống dưới mà không cần phải nhảy qua hay lùi lại.

Lưu ý rằng theo quy ước, tôi đặt tiền tố thuộc tính “out” bằng Out . Điều đó giúp làm rõ ràng rằng chúng ta phải chuyển biến vào hàm chưa được khởi tạo. Chúng tôi cũng đang mong đợi rằng hàm sẽ khởi tạo tham số. Điều đó sẽ rõ ràng khi chúng ta nhìn vào mã cuộc gọi. Vì vậy, hãy thiết lập mã gọi điện.

Mã gọi điện được sửa đổi sử dụng TryGetProperty

 Public Sub EditTableSubdatasheetProperty (_ Tùy chọn NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="SubdatasheetName" Đặt db =CurrentDb Đối với mỗi tdf Trong db.TableDefs If (tdf.Attributes Và dbSystemObject) =0 Then If Len (tdf.Connect) =0 And (Không phải tdf.Name Like "~ *") Then 'Không được đính kèm, hoặc tạm thời. If TryGetProperty (tdf, SubDatasheetPropertyName, prp) Then If prp.Value <> NewValue Then prp.Value =NewValue End If Else Set prp =tdf.CreateProperty (SubDatasheetPropertyName, dbText, NewValue) tdf If End.Properties If End. If NextEnd Sub 

Mã hiện dễ đọc hơn một chút với mẫu Thử đầu tiên. Chúng tôi đã cố gắng giảm bớt việc xử lý prp . Lưu ý rằng chúng tôi vượt qua prp biến thành true , prp sẽ được khởi tạo với thuộc tính mà chúng ta muốn thao tác. Nếu không, prp còn lại Nothing . Sau đó, chúng tôi có thể sử dụng CreateProperty để khởi tạo prp biến.

Chúng tôi cũng lật phủ định để mã trở nên dễ đọc hơn. Tuy nhiên, chúng tôi chưa thực sự giảm thiểu việc xử lý NewValue tham số. Chúng tôi vẫn còn một khối lồng nhau khác để kiểm tra giá trị. Chúng ta có thể làm tốt hơn không? Đúng! Hãy thêm một chức năng khác:

Thêm TrySetPropertyValue thủ tục

 Public Function TrySetPropertyValue (_ ByVal SourceProperty As DAO.Property, _ ByVal NewValue As Variant_) As Boolean If SourceProperty.Value =PropertyValue Sau đó TrySetPropertyValue =True Else On Error Resume Next SourceProperty.Value =NewValue On Error GoTo SourceProperty.Value =NewValue) Kết thúc hàm IfEnd 

Vì chúng tôi đảm bảo rằng hàm này sẽ không gây ra lỗi khi thay đổi giá trị, chúng tôi gọi nó là TrySetPropertyValue . Quan trọng hơn, chức năng này giúp tổng hợp tất cả các chi tiết đẫm máu xung quanh việc thay đổi giá trị của thuộc tính. Chúng tôi có một cách để đảm bảo rằng giá trị đó là giá trị mà chúng tôi mong đợi. Hãy xem cách mã gọi điện sẽ được thay đổi với chức năng này.

Mã gọi điện được cập nhật bằng cả TryGetPropertyTrySetPropertyValue

 Public Sub EditTableSubdatasheetProperty (_ Tùy chọn NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="SubdatasheetName" Đặt db =CurrentDb Đối với mỗi tdf Trong db.TableDefs If (tdf.Attributes Và dbSystemObject) =0 Then If Len (tdf.Connect) =0 And (Không phải tdf.Name Like "~ *") Then 'Không được đính kèm, hoặc tạm thời. Nếu TryGetProperty (tdf, SubDatasheetPropertyName, prp) thì TrySetPropertyValue prp, NewValue Else Set prp =tdf.CreateProperty (SubDatasheetPropertyName, dbText, NewValue) tdf.Properties.Append prp End If End Sub Nếu End If Next If Next 

Chúng tôi đã loại bỏ toàn bộ If khối. Bây giờ chúng tôi có thể chỉ cần đọc mã và ngay lập tức rằng chúng tôi đang cố gắng đặt một giá trị thuộc tính và nếu có vấn đề gì xảy ra, chúng tôi chỉ cần tiếp tục. Điều đó dễ đọc hơn nhiều và tên của hàm có thể tự mô tả. Một cái tên hay giúp bạn ít cần phải tra cứu định nghĩa của hàm để hiểu nó đang làm gì.

Tạo TryCreateOrSetProperty thủ tục

Mã dễ đọc hơn nhưng chúng ta vẫn có Else đó khối tạo thuộc tính. Chúng ta vẫn có thể làm tốt hơn chứ? Đúng! Hãy nghĩ về những gì chúng ta cần hoàn thành ở đây. Chúng tôi có một tài sản có thể tồn tại hoặc không. Nếu không, chúng tôi muốn tạo nó. Cho dù nó đã tồn tại hay chưa, chúng ta cần nó được đặt ở một giá trị nhất định. Vì vậy, những gì chúng ta cần là một hàm sẽ tạo một thuộc tính hoặc cập nhật giá trị nếu nó đã tồn tại. Để tạo một thuộc tính, chúng ta phải gọi CreateProperty tiếc là không có trên Properties mà là các đối tượng DAO khác nhau. Do đó, chúng ta phải liên kết muộn bằng cách sử dụng Object loại dữ liệu. Tuy nhiên, chúng tôi vẫn có thể cung cấp một số kiểm tra thời gian chạy để tránh lỗi. Hãy tạo một TryCreateOrSetProperty chức năng:

 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 _) True Case TypeObject CaseDao Type Là DAO.TableDef, _ TypeOf SourceDaoObject Là DAO.QueryDef, _ TypeOf SourceDaoObject Là DAO.Field, _ TypeOf SourceDaoObject Là DAO.Database Nếu TryGetProperty (SourceDaoObject.Properties, PropertyName, OutProperty) Thì TryCreatepertypertyOrSet thuộc tính OutProperty Error Resume Next Set OutProperty =SourceDaoObject.CreateProperty (PropertyName, PropertyType, PropertyValue) SourceDaoObject.Properties.Append OutProperty If Err.Number Then Set OutProperty =Nothing End If On Lỗi GoTo 0 TryCreateOrSetProperty =(OutProperty Is Nothing) End If Case Else Err.Raise 5, "Đối tượng không hợp lệ được cung cấp cho tham số SourceDaoObject. Nó phải là một đối tượng DAO có chứa thành viên CreateProperty. "Kết thúc Hàm SelectEnd 

Một số điều cần lưu ý:

  • Chúng tôi đã có thể xây dựng dựa trên Try* trước đó chức năng mà chúng tôi đã xác định, giúp cắt giảm phần mã hóa phần thân của hàm, cho phép nó tập trung nhiều hơn vào việc tạo trong trường hợp không có thuộc tính đó.
  • Điều này nhất thiết phải dài dòng hơn do kiểm tra thời gian chạy bổ sung, nhưng chúng tôi có thể thiết lập nó để các lỗi không làm thay đổi quy trình thực thi và chúng tôi vẫn có thể đọc từ trên xuống dưới mà không bị nhảy.
  • Thay vì ném MsgBox không đâu, chúng tôi sử dụng Err.Raise và trả về một lỗi có ý nghĩa. Việc xử lý lỗi thực tế được giao cho mã gọi, mã này sau đó có thể quyết định xem có hiển thị hộp thư cho người dùng hay làm việc gì khác.
  • Do chúng tôi xử lý cẩn thận và cung cấp rằng SourceDaoObject tham số là hợp lệ, tất cả các đường dẫn có thể đảm bảo rằng mọi vấn đề với việc tạo hoặc đặt giá trị của thuộc tính hiện tại sẽ được xử lý và chúng tôi sẽ nhận được false kết quả. Điều đó ảnh hưởng đến mã gọi như chúng ta sẽ thấy ngay sau đây.

Phiên bản cuối cùng của mã gọi điện

Hãy cập nhật mã gọi điện để sử dụng chức năng mới:

 Public Sub EditTableSubdatasheetProperty (_ Tùy chọn NewValue As String ="[None]" _) Dim db As DAO.Database Dim tdf As DAO.TableDef Dim prp As DAO.Property Const SubDatasheetPropertyName As String ="SubdatasheetName" Đặt db =CurrentDb Đối với mỗi tdf Trong db.TableDefs If (tdf.Attributes Và dbSystemObject) =0 Then If Len (tdf.Connect) =0 And (Không phải tdf.Name Like "~ *") Then 'Không được đính kèm, hoặc tạm thời. TryCreateOrSetProperty tdf, SubDatasheetPropertyName, dbText, NewValue End If End If NextEnd Sub 

Đó là một cải thiện khá về khả năng đọc. Trong phiên bản gốc, chúng tôi sẽ phải xem xét kỹ lưỡng một số If các khối và cách xử lý lỗi làm thay đổi luồng thực thi. Chúng tôi sẽ phải tìm ra nội dung chính xác đang làm gì để kết luận rằng chúng tôi đang cố gắng lấy một thuộc tính hoặc tạo ra nó nếu nó không tồn tại và đặt nó ở một giá trị nhất định. Với phiên bản hiện tại, tất cả đều có trong tên của hàm, TryCreateOrSetProperty . Bây giờ chúng ta có thể thấy chức năng này sẽ làm gì.

Kết luận

Bạn có thể tự hỏi, “nhưng chúng tôi đã thêm nhiều chức năng hơn và nhiều dòng hơn nữa. Có phải là rất nhiều việc không? " Đúng là trong phiên bản hiện tại này, chúng tôi đã xác định thêm 3 chức năng. Tuy nhiên, bạn có thể đọc từng hàm đơn lẻ một cách riêng biệt và vẫn dễ dàng hiểu nó phải làm gì. Bạn cũng thấy rằng TryCreateOrSetProperty hàm có thể xây dựng trên 2 Try* khác chức năng. Điều đó có nghĩa là chúng tôi linh hoạt hơn trong việc lắp ráp logic với nhau.

Vì vậy, nếu chúng ta viết một hàm khác thực hiện điều gì đó với thuộc tính của các đối tượng, chúng ta không cần phải viết lại nó cũng như không phải sao chép-dán mã từ EditTableSubdatasheetProperty ban đầu vào chức năng mới. Rốt cuộc, hàm mới có thể cần một số biến thể khác nhau và do đó yêu cầu một trình tự khác. Cuối cùng, hãy nhớ rằng những người thụ hưởng thực sự là mã gọi điện cần phải làm điều gì đó. Chúng tôi muốn giữ cho mã gọi điện ở mức khá cao mà không bị khai thác các chi tiết có thể gây hại cho việc bảo trì.

Bạn cũng có thể thấy rằng việc xử lý lỗi được đơn giản hóa đáng kể, mặc dù chúng tôi đã sử dụng On Error Resume Next . Chúng ta không cần phải tra cứu mã lỗi nữa vì trong phần lớn trường hợp, chúng ta chỉ quan tâm đến việc nó có thành công hay không. Quan trọng hơn, việc xử lý lỗi đã không thay đổi quy trình thực thi nơi bạn có một số logic trong phần thân và logic khác trong việc xử lý lỗi. Sau này là một tình huống mà chúng tôi chắc chắn muốn tránh vì nếu có lỗi trong trình xử lý lỗi, thì hành vi có thể gây ngạc nhiên. Tốt nhất là tránh điều đó thành khả năng xảy ra.

Đó là tất cả về sự trừu tượng

Nhưng điểm số quan trọng nhất mà chúng tôi giành được ở đây là mức độ trừu tượng mà chúng tôi có thể đạt được. Phiên bản gốc của EditTableSubdatasheetProperty chứa rất nhiều chi tiết cấp thấp về đối tượng DAO thực sự không phải về mục tiêu cốt lõi của hàm. Hãy nghĩ về những ngày bạn đã thấy một quy trình dài hàng trăm dòng với các điều kiện hoặc vòng lặp lồng nhau sâu sắc. Bạn có muốn gỡ lỗi đó không? Tôi không.

Vì vậy, khi tôi nhìn thấy một thủ tục, điều đầu tiên tôi thực sự muốn làm là tách các phần đó thành chức năng riêng của chúng, để tôi có thể nâng cao mức độ trừu tượng cho thủ tục đó. Bằng cách buộc bản thân phải nâng cao mức độ trừu tượng, chúng ta cũng có thể tránh được các lớp lỗi lớn mà nguyên nhân là do một thay đổi trong một phần của quy trình lớn gây ra sự phân nhánh không mong muốn cho các phần khác của quy trình. Khi chúng tôi gọi các hàm và chuyển các tham số, chúng tôi cũng giảm khả năng xảy ra các tác dụng phụ không mong muốn can thiệp vào logic của chúng tôi.

Do đó, tại sao tôi yêu thích mẫu "Thử *". Tôi hy vọng bạn cũng thấy nó hữu ích cho các dự án của mình.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. 25 Phím tắt Microsoft Access để tiết kiệm thời gian trong bảng ở dạng xem biểu dữ liệu

  2. Xem tin tức mới nhất về Microsoft Access, bao gồm cả Access 2022!

  3. Chức năng tĩnh và chức năng phụ

  4. 4 cách để bảo vệ thông tin nhạy cảm khỏi khách hàng của bạn

  5. Sử dụng Điều hướng Chỉ Bàn phím trong Word, Excel và PowerPoint (Phần 1:Ruy-băng)