patterncsharpMinor
Exploring the code of the Code Explorer
Viewed 0 times
codeexploringexplorerthe
Problem
The Code Explorer is a dockable toolwindow that displays a TreeView that shows all opened VBA projects and their respective modules, but unlike the "native" Project Explorer, Rubberduck's explorer also drills down to individual module members, and allows quickly navigating them:
While it looks pretty, the code behind it is among the most painful code in the project - clearly, it needs to be refactored and optimized. I'm particularly interested in ways to improve the
```
namespace Rubberduck.UI.CodeExplorer
{
public class CodeExplorerDockablePresenter : DockablePresenterBase
{
private readonly IRubberduckParser _parser;
private CodeExplorerWindow Control { get { return UserControl as CodeExplorerWindow; } }
public CodeExplorerDockablePresenter(IRubberduckParser parser, VBE vbe, AddIn addIn, CodeExplorerWindow view)
: base(vbe, addIn, view)
{
_parser = parser;
RegisterControlEvents();
}
public override void Show()
{
base.Show();
Task.Run(() => RefreshExplorerTreeView());
}
private void RegisterControlEvents()
{
if (Control == null)
{
return;
}
Control.RefreshTreeView += RefreshExplorerTreeView;
Control.NavigateTreeNode += NavigateExplorerTreeNode;
Control.AddComponent += AddComponent;
Control.AddTestModule += AddTestModule;
Control.ToggleFolders += ToggleFolders;
Control.ShowDesigner += ShowDesigner;
Control.DisplayStyleChanged += DisplayStyleChanged;
Control.RunAllTests += ContextMenuRunAllTests;
Control.RunInspections += ContextMenuRunInspections;
Control.SelectionChanged += SelectionChanged;
Control.Rename += RenameSelection;
Control.FindAl
While it looks pretty, the code behind it is among the most painful code in the project - clearly, it needs to be refactored and optimized. I'm particularly interested in ways to improve the
async void RefreshExplorerTreeView() method and the whole node-generation code.```
namespace Rubberduck.UI.CodeExplorer
{
public class CodeExplorerDockablePresenter : DockablePresenterBase
{
private readonly IRubberduckParser _parser;
private CodeExplorerWindow Control { get { return UserControl as CodeExplorerWindow; } }
public CodeExplorerDockablePresenter(IRubberduckParser parser, VBE vbe, AddIn addIn, CodeExplorerWindow view)
: base(vbe, addIn, view)
{
_parser = parser;
RegisterControlEvents();
}
public override void Show()
{
base.Show();
Task.Run(() => RefreshExplorerTreeView());
}
private void RegisterControlEvents()
{
if (Control == null)
{
return;
}
Control.RefreshTreeView += RefreshExplorerTreeView;
Control.NavigateTreeNode += NavigateExplorerTreeNode;
Control.AddComponent += AddComponent;
Control.AddTestModule += AddTestModule;
Control.ToggleFolders += ToggleFolders;
Control.ShowDesigner += ShowDesigner;
Control.DisplayStyleChanged += DisplayStyleChanged;
Control.RunAllTests += ContextMenuRunAllTests;
Control.RunInspections += ContextMenuRunInspections;
Control.SelectionChanged += SelectionChanged;
Control.Rename += RenameSelection;
Control.FindAl
Solution
(Not a full review, just focusing on one method.)
I think
First of all I'm not a fan of the "use a switch to set a value that you then return"-approach: just do the return directly, especially if there are a lot of cases.
I'd group all of these:
So that would become:
Each of these I'd change to a method call:
So that would become something along these lines:
Which means this method is now half its original length:
I'm still inclined to move this to a helper class though, considering that
I think
private string GetImageKeyForDeclaration(Declaration declaration) contains a lot of repeated code that can be significantly reduced.First of all I'm not a fan of the "use a switch to set a value that you then return"-approach: just do the return directly, especially if there are a lot of cases.
I'd group all of these:
case DeclarationType.Module:
break;
case DeclarationType.Class:
break;
case DeclarationType.Parameter:
break;So that would become:
case DeclarationType.Module:
case DeclarationType.Class:
case DeclarationType.Parameter:
return string.Empty;Each of these I'd change to a method call:
if (declaration.Accessibility == Accessibility.Private)
{
result = "PrivateMethod";
break;
}
if (declaration.Accessibility == Accessibility.Friend)
{
result = "FriendMethod";
break;
}
result = "PublicMethod";So that would become something along these lines:
case DeclarationType.Function:
return GetImageKeyForDeclaration(declaration, "Method");
case DeclarationType.PropertyGet:
case DeclarationType.PropertyLet:
case DeclarationType.PropertySet:
return GetImageKeyForDeclaration(declaration, "Property");
private string GetImageKeyForDeclaration(Declaration declaration, string suffix)
{
if (declaration.Accessibility == Accessibility.Private)
{
return "Private" + suffix;
}
if (declaration.Accessibility == Accessibility.Friend)
{
return "Friend" + suffix;
}
return "Public" + suffix;
}Which means this method is now half its original length:
private string GetImageKeyForDeclaration(Declaration declaration)
{
switch (declaration.DeclarationType)
{
case DeclarationType.Module:
case DeclarationType.Class:
case DeclarationType.Parameter:
return string.Empty;
case DeclarationType.Procedure:
case DeclarationType.Function:
return GetImageKeyForDeclaration(declaration, "Method");
case DeclarationType.PropertyGet:
case DeclarationType.PropertyLet:
case DeclarationType.PropertySet:
return GetImageKeyForDeclaration(declaration, "Property");
case DeclarationType.Variable:
return GetImageKeyForDeclaration(declaration, "Field");
case DeclarationType.Constant:
return GetImageKeyForDeclaration(declaration, "Const");
case DeclarationType.Enumeration:
return GetImageKeyForDeclaration(declaration, "Enum");
case DeclarationType.Event:
return GetImageKeyForDeclaration(declaration, "Event");
case DeclarationType.UserDefinedType:
return GetImageKeyForDeclaration(declaration, "Type");
case DeclarationType.EnumerationMember:
return "EnumItem";
case DeclarationType.UserDefinedTypeMember:
return "PublicField";
case DeclarationType.LibraryProcedure:
case DeclarationType.LibraryFunction:
return "Identifier";
default:
throw new ArgumentOutOfRangeException();
}
}I'm still inclined to move this to a helper class though, considering that
CodeExplorerDockablePresenter is 500+ lines long.Code Snippets
case DeclarationType.Module:
break;
case DeclarationType.Class:
break;
case DeclarationType.Parameter:
break;case DeclarationType.Module:
case DeclarationType.Class:
case DeclarationType.Parameter:
return string.Empty;if (declaration.Accessibility == Accessibility.Private)
{
result = "PrivateMethod";
break;
}
if (declaration.Accessibility == Accessibility.Friend)
{
result = "FriendMethod";
break;
}
result = "PublicMethod";case DeclarationType.Function:
return GetImageKeyForDeclaration(declaration, "Method");
case DeclarationType.PropertyGet:
case DeclarationType.PropertyLet:
case DeclarationType.PropertySet:
return GetImageKeyForDeclaration(declaration, "Property");
private string GetImageKeyForDeclaration(Declaration declaration, string suffix)
{
if (declaration.Accessibility == Accessibility.Private)
{
return "Private" + suffix;
}
if (declaration.Accessibility == Accessibility.Friend)
{
return "Friend" + suffix;
}
return "Public" + suffix;
}private string GetImageKeyForDeclaration(Declaration declaration)
{
switch (declaration.DeclarationType)
{
case DeclarationType.Module:
case DeclarationType.Class:
case DeclarationType.Parameter:
return string.Empty;
case DeclarationType.Procedure:
case DeclarationType.Function:
return GetImageKeyForDeclaration(declaration, "Method");
case DeclarationType.PropertyGet:
case DeclarationType.PropertyLet:
case DeclarationType.PropertySet:
return GetImageKeyForDeclaration(declaration, "Property");
case DeclarationType.Variable:
return GetImageKeyForDeclaration(declaration, "Field");
case DeclarationType.Constant:
return GetImageKeyForDeclaration(declaration, "Const");
case DeclarationType.Enumeration:
return GetImageKeyForDeclaration(declaration, "Enum");
case DeclarationType.Event:
return GetImageKeyForDeclaration(declaration, "Event");
case DeclarationType.UserDefinedType:
return GetImageKeyForDeclaration(declaration, "Type");
case DeclarationType.EnumerationMember:
return "EnumItem";
case DeclarationType.UserDefinedTypeMember:
return "PublicField";
case DeclarationType.LibraryProcedure:
case DeclarationType.LibraryFunction:
return "Identifier";
default:
throw new ArgumentOutOfRangeException();
}
}Context
StackExchange Code Review Q#90029, answer score: 8
Revisions (0)
No revisions yet.