如何重构类?(VB.Net)
我最近启动了一个项目,需要通过DirectoryServices与LDAP进行一些集成。我在其他应用程序中也这么做过,所以我进入其中一个应用程序,看看我是如何做到的——为什么要重新发明轮子呢?嗯,虽然这个轮子可以工作,但它是几年前开发的,有点异味(它是木制的,牢固地固定在以前的车辆上,很难修理,并且会产生潜在的颠簸) 所以我对自己说,现在是重构这只小狗的最佳时机,让它变得更便携、可重用、可靠、更易于配置等等。现在一切都很好,但我开始觉得从哪里开始有点不知所措。它应该是一个独立的图书馆吗?它应该如何配置?它应该使用国际奥委会吗?迪 因此,我(承认是主观的)问题是——给定一个相对较小但非常有用的类,比如下面的类,什么是重构它的好方法?您会问什么问题?您如何决定实施或不实施什么?关于配置灵活性,您的底线在哪里 [注意:请不要过分抨击此代码,好吗?它是很久以前编写的,在内部应用程序中运行良好。]如何重构类?(VB.Net),vb.net,.net-2.0,refactoring,Vb.net,.net 2.0,Refactoring,我最近启动了一个项目,需要通过DirectoryServices与LDAP进行一些集成。我在其他应用程序中也这么做过,所以我进入其中一个应用程序,看看我是如何做到的——为什么要重新发明轮子呢?嗯,虽然这个轮子可以工作,但它是几年前开发的,有点异味(它是木制的,牢固地固定在以前的车辆上,很难修理,并且会产生潜在的颠簸) 所以我对自己说,现在是重构这只小狗的最佳时机,让它变得更便携、可重用、可靠、更易于配置等等。现在一切都很好,但我开始觉得从哪里开始有点不知所措。它应该是一个独立的图书馆吗?它应该如
Public Class AccessControl
Public Shared Function AuthenticateUser(ByVal id As String, ByVal password As String) As Boolean
Dim path As String = GetUserPath(id)
If path IsNot Nothing Then
Dim username As String = path.Split("/")(3)
Dim userRoot As DirectoryEntry = New DirectoryEntry(path, username, password, AuthenticationTypes.None)
Try
userRoot.RefreshCache()
Return True
Catch ex As Exception
Return False
End Try
Else
Return False
End If
End Function
Private Shared Function GetUserPath(ByVal id As String) As String
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
Dim searcher As New DirectorySearcher
Dim results As SearchResultCollection
Dim result As SearchResult
Try
With searcher
.SearchRoot = root
.PropertiesToLoad.Add("cn")
.Filter = String.Format("cn={0}", id)
results = .FindAll()
End With
If results.Count > 0 Then
result = results(0)
Return result.Path.ToString()
Else
Return Nothing
End If
Catch ex As Exception
Return Nothing
End Try
End Function
Public Shared Function GetUserInfo(ByVal id As String) As UserInfo
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
Dim searcher As New DirectorySearcher
Dim results As SearchResultCollection
Dim props() As String = {"id", "sn", "mail", "givenname", "container", "cn"}
Try
With searcher
.SearchRoot = root
.PropertiesToLoad.AddRange(props)
.Filter = String.Format("cn={0}", id)
results = .FindAll()
End With
If results.Count > 0 Then
Dim properties As PropertyCollection = results(0).GetDirectoryEntry().Properties
Dim user As New UserInfo(properties("id").Value)
user.EmailAddress = properties("mail").Item(0).ToString
user.FirstName = properties("givenname").Item(0).ToString
user.LastName = properties("sn").Item(0).ToString
user.OfficeLocation = properties("container").Item(0).ToString
Return user
Else
Return New UserInfo
End If
Catch ex As Exception
Return Nothing
End Try
End Function
Public Shared Function IsMemberOfGroup(ByVal id As String, ByVal group As String) As Boolean
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
Dim searcher As New DirectorySearcher
Dim results As SearchResultCollection
Dim result As SearchResult
Dim props() As String = {"cn", "member"}
Try
With searcher
.SearchRoot = root
.PropertiesToLoad.AddRange(props)
.Filter = String.Format("cn={0}", group)
results = .FindAll()
End With
If results.Count > 0 Then
For Each result In results
Dim members As PropertyValueCollection = result.GetDirectoryEntry().Properties("member")
Dim member As String
For i As Integer = 0 To members.Count - 1
member = members.Item(i).ToString
member = member.Substring(3, member.IndexOf(",") - 3).ToLowerInvariant
If member.Contains(id.ToLowerInvariant) Then Return True
Next
Next
End If
Return False
Catch ex As Exception
Return False
End Try
End Function
Public Shared Function GetMembersOfGroup(ByVal group As String) As List(Of String)
Dim groupMembers As New List(Of String)
Dim root As DirectoryEntry = New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
Dim searcher As New DirectorySearcher
Dim results As SearchResultCollection
Dim result As SearchResult
Dim props() As String = {"cn", "member"}
Try
With searcher
.SearchRoot = root
.PropertiesToLoad.AddRange(props)
.Filter = String.Format("cn={0}", group)
results = .FindAll()
End With
If results.Count > 0 Then
For Each result In results
Dim members As PropertyValueCollection = result.GetDirectoryEntry().Properties("member")
Dim member As String
For i As Integer = 0 To members.Count - 1
member = members.Item(i).ToString
member = member.Substring(3, member.IndexOf(",") - 3).ToLowerInvariant
groupMembers.Add(member)
Next
Next
End If
Catch ex As Exception
Return Nothing
End Try
Return groupMembers
End Function
End Class
澄清:-用户有一个单独的类(简单poco)
-没有组类,因为现在使用的只是ID列表,添加这些ID可能很有用。不过,我马上想到的第一件事是,有很多函数涉及的用户似乎与您创建的用户类没有关联 我会尝试以下几种方法:
- 将以下内容添加到用户类:
- 路径
- 身份验证(字符串密码)-这可能是一个静态方法,不确定这里的用例
- 组-我还建议为组创建一个实际的域对象。它可以有一个用户集合作为开始的属性
- 我要做的第一件事是删除任何重复项。将中的常见功能剥离到单独的方法中
另外,考虑每个类都应该有一个单独的角色/职责-您可能希望创建单独的用户和组类
这里有一个常见重构的目录:
<>你只应该考虑控制反转,如果你希望在不同的类中交换测试的原因等等… 它可能不是最重要的重构,但我是早期回归的大粉丝。例如,你有:
If results.Count > 0 Then
Dim properties As PropertyCollection = results(0).GetDirectoryEntry().Properties
Dim user As New UserInfo(properties("id").Value)
user.EmailAddress = properties("mail").Item(0).ToString
user.FirstName = properties("givenname").Item(0).ToString
user.LastName = properties("sn").Item(0).ToString
user.OfficeLocation = properties("container").Item(0).ToString
Return user
Else
Return New UserInfo
End If
相反,我会使用:
If results.Count == 0 Then Return New UserInfo
Dim properties As PropertyCollection = results(0).GetDirectoryEntry().Properties
Dim user As New UserInfo(properties("id").Value)
user.EmailAddress = properties("mail").Item(0).ToString
user.FirstName = properties("givenname").Item(0).ToString
user.LastName = properties("sn").Item(0).ToString
user.OfficeLocation = properties("container").Item(0).ToString
Return user
缩进意味着复杂性,并且在空结果案例的特殊处理中没有足够的复杂性来保证8行缩进。在某一点上,删除缩进实际上可以隐藏真正的复杂性,因此不要过分强调这一规则,但对于提供的代码,我肯定会使用提前返回。我认为修改异常处理也很重要。我在上述方法中看到的模式是:
Try
...
Catch ex As Exception
Return False
End Try
上面的代码本质上隐藏(吞咽)了它的异常。这可能是最初实现的,因为某些类型的异常是由于找不到用户而引发的,返回False或Nothing可能是合适的。但是,您可能会在应用程序中遇到其他类型的异常,您可能永远都不知道这些异常(例如OutOfMemoryException等)
我建议只捕获您可能希望合法返回false/Nothing的特定类型的异常。对于其他人,让异常冒泡,或者至少记录它
有关异常处理的其他提示,请阅读。以下是重构代码示例:
Public Class AccessControl
Public Shared Function AuthenticateUser(ByVal id As String, ByVal password As String) As Boolean
Dim path As String
Dim username As String
Dim userRoot As DirectoryEntry
path = GetUserPath(id)
If path.Length = 0 Then
Return False
End If
username = path.Split("/")(3)
userRoot = New DirectoryEntry(path, username, password, AuthenticationTypes.None)
Try
userRoot.RefreshCache()
Return True
Catch ex As Exception
' Catching Exception might be accepted way of determining user is not authenticated for this case
' TODO: Would be better to test for specific exception type to ensure this is the reason
Return False
End Try
End Function
Private Shared Function GetUserPath(ByVal id As String) As String
Dim results As SearchResultCollection
Dim propertiesToLoad As String()
propertiesToLoad = New String() {"cn"}
results = GetSearchResultsForCommonName(id, propertiesToLoad)
If results.Count = 0 Then
Return String.Empty
Else
Debug.Assert(results.Count = 1)
Return results(0).Path
End If
End Function
Public Shared Function GetUserInfo(ByVal id As String) As UserInfo
Dim results As SearchResultCollection
Dim propertiesToLoad As String()
propertiesToLoad = New String() {"id", "sn", "mail", "givenname", "container", "cn"}
results = GetSearchResultsForCommonName(id, propertiesToLoad)
If results.Count = 0 Then
Return New UserInfo
End If
Debug.Assert(results.Count = 1)
Return CreateUser(results(0).GetDirectoryEntry().Properties)
End Function
Public Shared Function IsMemberOfGroup(ByVal id As String, ByVal group As String) As Boolean
Dim allMembersOfGroup As List(Of String)
allMembersOfGroup = GetMembersOfGroup(group)
Return allMembersOfGroup.Contains(id.ToLowerInvariant)
End Function
Public Shared Function GetMembersOfGroup(ByVal group As String) As List(Of String)
Dim results As SearchResultCollection
Dim propertiesToLoad As String()
propertiesToLoad = New String() {"cn", "member"}
results = GetSearchResultsForCommonName(group, propertiesToLoad)
Return ConvertMemberPropertiesToList(results)
End Function
Private Shared Function GetStringValueForPropertyName(ByVal properties As PropertyCollection, ByVal propertyName As String) As String
Return properties(propertyName).Item(0).ToString
End Function
Private Shared Function CreateUser(ByVal userProperties As PropertyCollection) As UserInfo
Dim user As New UserInfo(userProperties("id").Value)
With user
.EmailAddress = GetStringValueForPropertyName(userProperties, "mail")
.FirstName = GetStringValueForPropertyName(userProperties, "givenname")
.LastName = GetStringValueForPropertyName(userProperties, "sn")
.OfficeLocation = GetStringValueForPropertyName(userProperties, "container")
End With
Return user
End Function
Private Shared Function GetValueFromMemberProperty(ByVal member As String) As String
Return member.Substring(3, member.IndexOf(",") - 3).ToLowerInvariant
End Function
Private Shared Function ConvertMemberPropertiesToList(ByVal results As SearchResultCollection) As List(Of String)
Dim result As SearchResult
Dim memberProperties As PropertyValueCollection
Dim groupMembers As List(Of String)
groupMembers = New List(Of String)
For Each result In results
memberProperties = result.GetDirectoryEntry().Properties("member")
For i As Integer = 0 To memberProperties.Count - 1
groupMembers.Add(GetValueFromMemberProperty(memberProperties.Item(i).ToString))
Next
Next
Return groupMembers
End Function
Private Shared Function GetSearchResultsForCommonName(ByVal commonName As String, ByVal propertiesToLoad() As String) As SearchResultCollection
Dim results As SearchResultCollection
Dim searcher As New DirectorySearcher
With searcher
.SearchRoot = GetDefaultSearchRoot()
.PropertiesToLoad.AddRange(propertiesToLoad)
.Filter = String.Format("cn={0}", commonName)
results = .FindAll()
End With
Return results
End Function
Private Shared Function GetDefaultSearchRoot() As DirectoryEntry
Return New DirectoryEntry("LDAP://XXXXX/O=YYYY", String.Empty, String.Empty, AuthenticationTypes.None)
End Function
End Class
我想你可以通过提取常数等进一步了解这一点,但这会消除大部分重复,等等。让我知道你的想法
太晚了,我意识到。。。但同时也想回答你提出的一些问题。请看在开始破解之前,您可能已经准备好了一组全面的测试?我喜欢Chris在重构方面所做的工作。我可能提出的一个建议是在对Active directory的特定调用周围添加一个适配器。这将使整个类都可以测试,考虑到基础设施方面的代码比较繁重,这可能有点过分,但我已经围绕套接字类等编写了类似的适配器,以简化它们的api。