Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RevisionDiff: Move out non GUI to RevisionDiffController #4112

Conversation

gerhardol
Copy link
Member

On top of #4087 (only last commit specific)

Requested as part of #4106 #4089, #4087, #4092 by @RussKie
The files are still tightly coupled with for instance _revisionGrid shared
Some calls like resetFileToToolStripMenuItem_DropDownOpening would just have resulted in code duplication
The Controller is not testable right now - but t is a start at least
For instance Module must be extracted from revisionGrid, or pass revisiongrid.Module at every call
Path.Combine is not swapped (4 out of 5 calls applicable) could have been replaced but I feel that this limits the readability and just increases code size.

Changes proposed in this pull request:

  • Refactoring

Screenshots before and after (if PR changes UI):

  • None

How did I test this code:

  • Manually

Has been tested on (remove any that don't apply):

  • GIT 2.14, 2.15
  • Windows 10

…-browse-artificial-stage

Refactoring in master so changes in GitUI/CommandsDialogs/FormBrowse.Designer.cs and GitUI/CommandsDialogs/FormBrowse.cs reverted
Reimplementation after refactoring in master to RevisionDiffThe implementation can currently not refresh the RevisionGrid.
Requested as part of gitextensions#4106 and other
The files are still tightly coupled with _revisionGrid shared
For instance Module must be extracted from revisionGrid, or pass revisiongrid.Module at every call
Copy link
Member

@RussKie RussKie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems as though the purpose of the controller is unclear to you.
We need the controller to decouple the "thinking" (business logic) from the presentation (UI). This separation provides a number of benefits, such as:

  • testability - we can verify the implementation
  • documentation/assurance - this pretty much follows from the previous point - tests allow to capture the requirements and assumptions used during the implementation, and
  • UI portability or plugability - (in the ideal world) we could use the controller for a completely different UI implementation (e.g. web, wpf, etc). The UI layer provides necessary interaction elements and all the logic behind those elements is abstracted by the controller.

I hope it makes sense.

I've pushed a sample of what I envisaged when suggested moving the logic into the controller. It isn't a complete solution by any means (i.e. missing argument validations, tests etc), but is a good start.

@@ -42,6 +43,10 @@ public RevisionDiff()
DiffText.Font = AppSettings.DiffFont;

GotFocus += (s, e) => DiffFiles.Focus();
Load += (s, e) =>
{
_revisionDiffController = new RevisionDiffController(Module);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing Module object does not make sense, it is not updated when the RevisionDiff Module is updated

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module is not required, I put it as an example.

it is not updated when the RevisionDiff Module is updated

that's a very good point

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not updated when the RevisionDiff Module is updated

I've checked it, Module always contains the current information when switching between git repos.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The working for was not updated for me.

@@ -633,16 +664,6 @@ private void saveAsToolStripMenuItem1_Click(object sender, EventArgs e)
}
}


private bool DeleteSelectedDiffFiles()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK to remove, just a comment: This was purposely not removed as I want to have Keyboard shortcut here. A refrence from FormBrowse is needed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this PR is concentrating on moving logic out of the UI.
a following PR can concentrate on adding keyboard shortcuts

//Many options have no meaning for artificial commits or submodules
//Hide the obviously no action options when single selected, handle them in actions if multi select

// disable items that need exactly one selected item
bool isExactlyOneItemSelected = DiffFiles.SelectedItems.Count() == 1;
var isCombinedDiff = isExactlyOneItemSelected &&
DiffFiles.CombinedDiff.Text == DiffFiles.SelectedItemParent;
var isCombinedDiff = isExactlyOneItemSelected && DiffFiles.CombinedDiff.Text == DiffFiles.SelectedItemParent;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I consider this to leave too much logic in the GUI layer. This will not separate the layers, just involve two files instead of one.

@@ -706,7 +726,6 @@ private void diffEditFileToolStripMenuItem_Click(object sender, EventArgs e)
//TBD RefreshRevisions();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some solution to refresh the difflist and revisions is needed here.

@gerhardol
Copy link
Member Author

@RussKie I believe the suggestion you implemented is just making the implementation more complicated:

  • The parameters to the functions are still calculated in the GUI layer, it still needs information about the parameters to pass.
  • None of the executing logic I moved to the controller is moved. (I only moved those without special parameters, could have done more.)
    So two files need to be maintained instead of one. Tests can be added, but they are quite meaningless.

I can live with the changes (except for passing Module), will just not push for such changes.

@RussKie
Copy link
Member

RussKie commented Oct 31, 2017

The parameters to the functions are still calculated in the GUI layer, it still needs information about the parameters to pass.

Ideally the CanShowMenuXxx methods would receive the list of selected revisions and the list of selected items (GitItemStatus). However DiffFiles.SelectedItem returns the last selected items from the list, and I am not totally comfortable with copying this piece of logic into the controller - if one day DiffFiles.SelectedItem changes and starts returning the first item from the list, the RevisionDiffController's implementations may likely become incorrect.
The whole range of things needs changing before the logic is properly decoupled from the UI, it is a long road. But it doesn't mean we shouldn't start moving in that direction.

None of the executing logic I moved to the controller is moved. (I only moved those without special parameters, could have done more.)

As per #4106 (review) I am expecting a refactor to increase maintainability of the new functionality.
The existing functionality can be moved across, but for me this is a secondary objective.

So two files need to be maintained instead of one.

In the current codebase we are maintaining only one file but the price of maintenance is staggering.
Your original proposal couples the controller to UI (by virtue of referencing UI components) which does not make it any more maintainable than the current implementation. And the proposed APIs are not very usable or aesthetic.

Tests can be added, but they are quite meaningless.

I respectfully disagree, tests are anything but meaningless. For example:

  • ShouldShowMenuCherryPick implementations has a cyclomatic complexity of 6,
  • ShouldShowMenuResetFile implementations has a cyclomatic complexity of 5,
  • ShouldShowMenuStage and ShouldShowMenuUnstage - complexity of 4.

This is complex for a simple one/two-line property. It is very hard to reason about these implementations and to make changes.

@gerhardol
Copy link
Member Author

What is missing then? openWithDifftoolToolStripMenuItem_DropDownOpening? (Most logic will remain RevisionDiff though.)

I consider the test case meaningless because the logic is still in RevisionDiff. The test is independent of the functionality. I will look at some sample tests though.

@RussKie
Copy link
Member

RussKie commented Oct 31, 2017

because the logic is still in RevisionDiff.

how so? RevisionDiff collects the data and passes it to RevisionDiffController to make decisions

My proposal isn't perfect nor complete, it is meant as a talking point. There is still a room for improvement (i.e. signatures look very similar - can be refactored further).

@gerhardol
Copy link
Member Author

@RussKie I want you to be happy, you (and current/previous maintainers) are doing a great job.
My second priority is getting my changes (especially gerhardol@7234d28#diff-320265b67fc16a7d7a11a99771f9e1a4L121, not even a PR yet). I have other things in my life too...

So I can do what you suggest, but my arguments against doing it the way it is done:

  • Splitting just CanShow* to Controller will leave dependencies between the CanShow and the "action" required (show blame etc) in two files. The CanShow* is only part of the problem. The implementation looks less complex but is even complexer.
  • My previous comment was for when all arguments were separate to CanShow* too. Then both the calculation of the arguments and the CanShow* declarations had to be updated at changes. After the change it is still likely that "just" implementation of CanShow*, GetSelectionInfo() and ContextMenuSelectionInfo will need to be updated.
  • Tests can be added, but they will just confirm the static implementation. Like "x=1; y=1: Assert(x==y)" which adds no value.

Adding tests will take a little longer (but will come), partly because I have not added tests here before, partly because I do not have a plan for good tests, partly because this does not engage me.

@RussKie RussKie merged commit 0aa2d9d into gitextensions:master Nov 7, 2017
@gerhardol
Copy link
Member Author

Side note: I have started to write tests, but that is slow...

@RussKie
Copy link
Member

RussKie commented Nov 7, 2017 via email

@gerhardol
Copy link
Member Author

There are at least 2^7 combinations so all combinations cannot be tested...
I will think about it.
(So far I have been looking at how to write a test case than what to write. It slows be down some so I rather take this in the background otherwise everything will take like forever.)

@RussKie
Copy link
Member

RussKie commented Nov 7, 2017 via email

@gerhardol gerhardol deleted the feature/n4031-refactor-revisiondiffcontroller branch November 19, 2017 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants