Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 48 additions & 22 deletions CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,77 @@ The Visual Studio extension for Force Feedback Programming provides visual plus

Since not all code is created equal and your team's coding style might differ from that of another team you can configure the extension's "sensitivity" and its "use of force".

As the metric to determine how clean/dirty your code is the extension (currently only) uses the number of lines of a method.
Currently the extension uses the *number of lines* in a function/methode as its only metric to determine how clean/dirty your code is. We know this might sound simplistic, but we've thought long and hard about it and believe that more sophisticated metrics would only deliver pseudo accuracy.

This method consists of 10 lines of code starting from the initial `{` and ending with `}`. A background color is chosen accordingly:
The following method consists of 10 lines of code between the initial `{` and the final `}`. A background color is chosen accordingly:

![](images/config_fig1.png)

Adding a line (by breaking the last statement apart) pushes it into the next "brownfield category", though, and the background color changes:
Now, adding a line (by breaking the last statement apart) pushes it into the next "brownfield category", though, and the background color changes:

![](images/config_fig2.png)

## config.json
The visual feedback is immediate, as you see. So is the tactile feedback which consist of spurts of random characters inserted while you type and delaying the effect of your keystrokes.

Currently the information about the "brownfield categories" is stored in a global JSON config file _.forcefeedbackprogramming_ located at `c:\ProgramData`:
The metric is simple, as you see, but how is the feedback chosen? That's what you can control with FFP config files:

![](images/config_fig3.png)
## Locating the config file
The rules to assess methods and define the feedback to give are read from a config file with the name `.forcefeedbackprogramming`.

A [sample config file](example/.forcefeedbackprogramming) is stored next to this documentation in the repo. It's structure is simple:
The FFP extension will use the first config file it finds while searching for it in a number of places in this order:

1. directory of current source file
2. directory of project the source file belongs to
3. directory of solution the project belongs to

If no config file is found, one is created from a default in the solution directory.

## Structure of config file
A config file is a JSON file with a simple structure. Here's an excerpt from the default config:

```
{
"methodTooLongLimits": [
"Version": "1.0",

"FeedbackRules": [
{
"lines": 6,
"color": "#ffffe5",
"transparency": 0.25
"MinimumNumberOfLinesInMethod": 11,

"BackgroundColor": "Beige",
"BackgroundColorTransparency": 0.0,

"NoiseDistance": 0,
"NoiseLevel": 0,

"Delay": 0
},
{
"lines": 11,
"color": "#fee391",
"transparency": 0.25,
"noiseDistance": 10
"MinimumNumberOfLinesInMethod": 26,

"BackgroundColor": "Burlywood",
"BackgroundColorTransparency": 0.0,

"NoiseDistance": 50,
"NoiseLevel": 3,

"Delay": 0
},
...
]
}
```
The `methodTooLongLimits` list contains entries for each "brownfield category".

In each category you can set the number of lines (`lines`) it starts at. In the example above the first category starts with 6 lines of code in a methode. Once 11 lines of code are reached the second category takes over and so on. That means code with less than 6 lines of code falls into no category. It's not colored. It's considered clean. But from 6 lines of code on code is deemed to grow dirty...

The `color` property determines how to color a method's background once it falls into a certain category. If you want to adapt the colors to your liking find inspiration (and hex codes) here: [http://www.rapidtables.com/web/color/gray-color.htm](http://www.rapidtables.com/web/color/gray-color.htm)
Currently it's just a list of rules defining **levels of what's considered clean/dirty**.

`transparency` determines how "thick" the color is applied. A lower number means less "thickness"/opacity. For most colors/categories a value of 0.25 seems ok.
* Levels relate to the number of lines in a method only (`MinimumNumberOfLinesInMethod`). The example states that less that 11 lines of code (LOC) is considered perfectly clean. No feedback is given. But from 11 LOC on you'll see/feel feedback. First a little - 11 to 25 LOC -, then more - 26 LOC and up. All methods with more LOC than the highest number in the rules state are covered by that rule.

So much for the visual feedback. The final value is about the tactile feedback:
**Visual feedback** is given for each level based on two properties:

`noiseDistance` determines how often the extension is supposed to add noise to your input. Currently we use the character ⌫ to irritate you. A value of 10 means "every 10 characters you type" you'll see an addition noise character in your code - which you then need to delete, which slows you down, which hopefully motivates your to reduce the number of lines of code in the method to avoid such tactile noise.
* Visual feedback is given by coloring the backhground of a method's body (`BackgroundColor`). You can define the color by name, e.g. `Beige` or `Maroon` or any color from [this list](http://www.99colors.net/dot-net-colors). Or you can define it with a hex code, e.g. `#F0FFFF` instead of `Azure`; use a [tool like this](https://www.rapidtables.com/web/color/RGB_Color.html) to mix your color.
* Some colors might lead to bad contrast for the source text when applied in their default "strength" or "thickness". To adjust for better readability you can change the transparency with which they are applied (`BackgroundColorTransparency`). The default is `0.0`, i.e. no transparency/"full strength"/maximum opacity. Choose a value between `0.0` and `1.0` to make the background color more light, more translucent.

For **tactile feedback** you can tweak three properties:

* If keystrokes/changes should be delayed (to simulate wading through a brownfield), then set `Delay` to a millisecond value of your choice, e.g. `100` to delay every keystroke by 0.1 seconds.
* In addition you can add "noise" to the input. That means additional random characters will be inserted to hamper typing progress. `NoiseDistance` defines how many keystrokes/changes should pass without disturbance, e.g. `10` for "insert noise every 10 keystrokes".
* How many noise characters are inserted is defined by `NoiseLevel`, e.g. `3` to insert a random string like "♥☺❉".
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ And not only will FFP tell you how clean your code is, it will actively hinder y

And all this is made possible through a simple Visual Studio extension you can even [adapt to your coding style](CONFIG.md) and level of brownfield.

Take as an example this screenshot from [a video by contributor Robin](https://vimeo.com/171889390) where he's explaining how FFP works:

![](images/example.png)

As you see, methods in the source code are colored differently according to how clean/dirty FFP deems them. But this color is not static! It will change while you're editing your code because with each keystroke it might become more or less clean.

That's not all, however. In addition to this kind of visual feedback the FFP extension gives tactile feedback. [Read Robin's article](https://robinsedlaczek.com/2016/06/23/introducing-force-feedback-programming/) and watch his video to see that in action.

Enjoy!

PS: If you like what you see and have time to spare, you can join us in moving the FFP extension forward. Please [check the wiki](https://github.com/robinsedlaczek/ForceFeedbackProgramming/wiki) for details.
Expand All @@ -24,22 +32,22 @@ You can download the precompiled Visual Studio installer package from the [relea

### Microsoft Visual Studio

Force Feedback is currently available in Visual Studio only (exactly for VS 2015, 2017 and 2019). Therefore, we deliver a Visual Studio extension that can be found in the VS marketplace [here](https://marketplace.visualstudio.com/items?itemName=RobinSedlaczek.ForceFeedback) and that can be installed via the main menu entry "Tools\Manage Extensions". We update the extension in the marketplace with every stable version. Stable versions on the [releases page](https://github.com/robinsedlaczek/ForceFeedbackProgramming/releases/ "Visual Studio Installer Package releases") are those versions, that are free of any additional version status info (e.g. -alpha, -beta etc.).
Force Feedback Programming is currently available for C# in Visual Studio only (VS 2015, 2017 and 2019). It's delivered as a Visual Studio extension that can be found in the VS marketplace [here](https://marketplace.visualstudio.com/items?itemName=RobinSedlaczek.ForceFeedback) and that can be installed via the main menu entry "Tools|Manage Extensions". We update the extension in the marketplace with every stable version. Stable versions on the [releases page](https://github.com/robinsedlaczek/ForceFeedbackProgramming/releases/ "Visual Studio Installer Package releases") are those versions, which are free of any additional version status info (e.g. -alpha, -beta etc.).

### ABAB

The phrase "Visual Studio only" is not really correct. There are some guys who ported the Force Feedback Programming to SAP's [ABAB](https://en.wikipedia.org/wiki/ABAP). We got the hint via Twitter [here](https://twitter.com/ceedee666/status/1106887766221180929). You can find the GitHub repo for the ABAB implementation [here](https://github.com/css-ch/abap-code-feedback).
To be honest, the phrase "Visual Studio only" might not really be correct anymore. It seems there are some guys who have taken the Force Feedback Programming idea to SAP's [ABAB](https://en.wikipedia.org/wiki/ABAP). [Here's a Tweet](https://twitter.com/ceedee666/status/1106887766221180929) hinting at that. A repo for their ABAB implementation [ABAB implementation](https://github.com/css-ch/abap-code-feedback) is on GitHub.

[!Please be aware that their implementation is no fork of our repo. The guys there are working independent from us currently. Their ABAB AiE integration is not part of our project and we are not responsible. In case of issues and/or questions, please contact them directly.]
*Please note: Their implementation is no fork of our repo. It's an independent project. Their ABAB AiE integration is not part of our effort here.*

### Roadmap

We have the integration for Visual Studio Code and JetBrains Rider on the list!
We have the integration for Visual Studio Code and JetBrains Rider on our list!

## Health of master (Release|x86):

[![Build status](https://ci.appveyor.com/api/projects/status/mrnvhtnf9k2xrs4g/branch/master?svg=true)](https://ci.appveyor.com/project/robinsedlaczek/forcefeedbackprogramming/branch/master)

## Wanna chat with us?

You can meet us here: [![Gitter](https://badges.gitter.im/robinsedlaczek/ForceFeedbackProgramming.svg)](https://gitter.im/robinsedlaczek/ForceFeedbackProgramming?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
You can meet us here: [![Gitter](https://badges.gitter.im/robinsedlaczek/ForceFeedbackProgramming.svg)](https://gitter.im/robinsedlaczek/ForceFeedbackProgramming?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Binary file added images/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/.idea/.idea.ForceFeedback.Core/.idea/contentModel.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 32 additions & 23 deletions src/ForceFeedback.Core.Tests_dnc/ConfigrationProvider_tests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using ForceFeedback.Core.adapters.configuration;
Expand All @@ -11,6 +13,9 @@ namespace ForceFeedback.Core.Tests_dnc
*/
public class ConfigrationProvider_tests
{
private readonly string TEST_SLN_PATH = "slnfolder-" + Guid.NewGuid().ToString();


[Fact]
public void Load_default_config_if_no_paths_are_given() {
var sut = new ConfigurationProvider("", "", "");
Expand All @@ -24,12 +29,12 @@ public void Load_default_config_if_no_paths_are_given() {
[Fact]
public void Load_config_from_solution_path()
{
if (Directory.Exists("slnfolder_2"))
Directory.Delete("slnfolder_2", true);
Directory.CreateDirectory("slnfolder_2");
File.Copy("../../../sampledata/ConfigurationDefaults.json", "slnfolder_2/.forcefeedbackprogramming");
if (Directory.Exists(TEST_SLN_PATH))
Directory.Delete(TEST_SLN_PATH, true);
Directory.CreateDirectory(TEST_SLN_PATH);
File.Copy("../../../sampledata/ConfigurationDefaults.json", Path.Combine(TEST_SLN_PATH,".forcefeedbackprogramming"));

var sut = new ConfigurationProvider("slnfolder_2/my.sln", "", "");
var sut = new ConfigurationProvider(Path.Combine(TEST_SLN_PATH, "my.sln"), "", "");
var result = sut.Configuration;

Assert.True(result.TryFindRule(1, out var rule));
Expand All @@ -40,15 +45,15 @@ public void Load_config_from_solution_path()
[Fact]
public void Create_config_in_sln_folder_from_defaults_if_none_found()
{
if (Directory.Exists("slnfolder_3"))
Directory.Delete("slnfolder_3", true);
Directory.CreateDirectory("slnfolder_3");
if (Directory.Exists(TEST_SLN_PATH))
Directory.Delete(TEST_SLN_PATH, true);
Directory.CreateDirectory(TEST_SLN_PATH);

var sut = new ConfigurationProvider("slnfolder_3/my.sln", "", "");
var sut = new ConfigurationProvider(Path.Combine(TEST_SLN_PATH, "my.sln"), "", "");
var result = sut.Configuration;

Assert.True(result.TryFindRule(30, out var rule));
Assert.True(File.Exists("slnfolder_3/.forcefeedbackprogramming"));
Assert.True(File.Exists(Path.Combine(TEST_SLN_PATH, ".forcefeedbackprogramming")));
Assert.Equal(26, rule.MinimumNumberOfLinesInMethod);
Assert.Equal(Color.BurlyWood, rule.BackgroundColor);
}
Expand All @@ -57,14 +62,16 @@ public void Create_config_in_sln_folder_from_defaults_if_none_found()
[Fact]
public void Pick_first_config_found_from_file_path()
{
if (Directory.Exists("slnfolder_4"))
Directory.Delete("slnfolder_4", true);
Directory.CreateDirectory("slnfolder_4/prjfolder/filefolder");
if (Directory.Exists(TEST_SLN_PATH))
Directory.Delete(TEST_SLN_PATH, true);
Directory.CreateDirectory(Path.Combine(TEST_SLN_PATH, "prjfolder", "filefolder"));

File.Copy("../../../sampledata/ConfigurationDefaults.json", "slnfolder_4/prjfolder/filefolder/.forcefeedbackprogramming");
File.Copy("../../../sampledata/ConfigurationEmpty.json", "slnfolder_4/prjfolder/.forcefeedbackprogramming");
File.Copy("../../../sampledata/ConfigurationDefaults.json", Path.Combine(TEST_SLN_PATH, "prjfolder/filefolder/.forcefeedbackprogramming"));
File.Copy("../../../sampledata/ConfigurationEmpty.json", Path.Combine(TEST_SLN_PATH, "prjfolder/.forcefeedbackprogramming"));

var sut = new ConfigurationProvider("slnfolder_4/my.sln", "slnfolder_4/prjfolder/my.csproj", "slnfolder_4/prjfolder/filefolder/my.cs");
var sut = new ConfigurationProvider(Path.Combine(TEST_SLN_PATH, "my.sln"),
Path.Combine(TEST_SLN_PATH, "prjfolder/my.csproj"),
Path.Combine(TEST_SLN_PATH, "prjfolder/filefolder/my.cs"));
var result = sut.Configuration;

Assert.True(result.TryFindRule(1, out var rule));
Expand All @@ -75,18 +82,20 @@ public void Pick_first_config_found_from_file_path()
[Fact]
public void Pick_first_config_found_from_project_path()
{
if (Directory.Exists("slnfolder_5"))
Directory.Delete("slnfolder_5", true);
Directory.CreateDirectory("slnfolder_5/prjfolder/filefolder");
if (Directory.Exists(TEST_SLN_PATH))
Directory.Delete(TEST_SLN_PATH, true);
Directory.CreateDirectory(Path.Combine(TEST_SLN_PATH, "prjfolder", "filefolder"));

File.Copy("../../../sampledata/ConfigurationDefaults.json", "slnfolder_5/prjfolder/.forcefeedbackprogramming");
File.Copy("../../../sampledata/ConfigurationEmpty.json", "slnfolder_5/.forcefeedbackprogramming");
File.Copy("../../../sampledata/ConfigurationDefaults.json", Path.Combine(TEST_SLN_PATH, "prjfolder/.forcefeedbackprogramming"));
File.Copy("../../../sampledata/ConfigurationEmpty.json", Path.Combine(TEST_SLN_PATH, ".forcefeedbackprogramming"));

var sut = new ConfigurationProvider("slnfolder_5/my.sln", "slnfolder_5/prjfolder/my.csproj", "slnfolder_5/prjfolder/filefolder/my.cs");
var sut = new ConfigurationProvider(Path.Combine(TEST_SLN_PATH, "my.sln"),
Path.Combine(TEST_SLN_PATH, "prjfolder/my.csproj"),
Path.Combine(TEST_SLN_PATH, "prjfolder/filefolder/my.cs"));
var result = sut.Configuration;

Assert.True(result.TryFindRule(1, out var rule));
Assert.Equal(Color.Beige, rule.BackgroundColor);
}
}
}
}
17 changes: 17 additions & 0 deletions src/ForceFeedback.Core.Tests_dnc/DebugFact.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Diagnostics;
using Xunit;

namespace ForceFeedback.Core.Tests_dnc
{
// Original source and idea: https://lostechies.com/jimmybogard/2013/06/20/run-tests-explicitly-in-xunit-net/
internal class DebugFact : FactAttribute
{
public DebugFact()
{
if (!Debugger.IsAttached)
{
Skip = "Only running in interactive mode with debugger attached.";
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<ItemGroup>
<EmbeddedResource Include="sampledata\ConfigurationDefaults.json" />
<EmbeddedResource Include="sampledata\ConfigurationEmpty.json" />
<None Include="sampledata\ConfigurationWithError.json" />
</ItemGroup>

</Project>
Loading