NRef.NReq 1.0.0

Suggested Alternatives

NReq

Additional Details

Renamed package to NReq

dotnet add package NRef.NReq --version 1.0.0                
NuGet\Install-Package NRef.NReq -Version 1.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NRef.NReq" Version="1.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add NRef.NReq --version 1.0.0                
#r "nuget: NRef.NReq, 1.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install NRef.NReq as a Cake Addin
#addin nuget:?package=NRef.NReq&version=1.0.0

// Install NRef.NReq as a Cake Tool
#tool nuget:?package=NRef.NReq&version=1.0.0                

NReq

NReq lets you keep track of software artifacts in your code.

Features

  • Define natural language descriptions of your software artifacts, right in the code.
    • Example software artifacts: features, bugs, design decisions, tech debt, requirements, risks, preconditions, postconditions.
  • Use code navigation e.g. Go To Definition/Implementation, Find All References to navigate software artifacts.
  • No runtime overhead.

Example

In the code, define your software artifacts with natural language:


public class DivideRequirement : Requirement
{
  public override string Description { get; } = "Shall divide two integers";
}

public class DivideFeature : Feature
{
  public override string Description => "Divides two integers";
}

public class DivideByZeroRisk : Risk
{
  public override string Description { get; } = "User attempts to divide by zero";
}

public class CheckForZeroDivisionFeature : Feature
{
  public override string Description => "Checks for division by zero";
}

class NonzeroDivisorPrecondition : Precondition
{
    public override string Description => "The divisor shall be nonzero";
}

class NoDivByZeroPostcondition : Postcondition
{
  public override string Description => $"Does not throw {nameof(DivideByZeroException)}";
}

Declare a risk:


public interface ICalculator
{
  [ImplementsRequirement<DivideRequirement>]
  [HasRisk<DivideByZeroRisk>] 
  int Divide(int a, int b);
}

Mitigate the risk with a feature:

public class LenientCalculator : ICalculator
{
  /// <summary>
  /// This calculator mitigates the risk of division by zero by checking the divisor.
  /// Thus it can guarantee the postcondition the method will not throw <see cref="DivideByZeroException"/>
  /// </summary>
  [ImplementsFeature<DivideFeature>]
  [ImplementsFeature<CheckForZeroDivisionFeature>]
  [MitigatesRisk<DivideByZeroRisk>]
  [Postcondition<NoDivByZeroPostcondition>]
  public int Divide(int a, int b)
  {
    if (b == 0) return 0;
    return a / b; // Beware: Integer division
  }
}

[TestCase(16, 0, 0)]
[TestCase(0, 0, 0)]
[VerifiesRiskMitigation<DivideByZeroRisk>]
public void DivideByZero_DoesNotThrow(int a, int b, int want)
{
  ICalculator calc = new Calculators.LenientCalculator();

  Assert.DoesNotThrow(() =>
  {
    int got = calc.Divide(a, b);
    Assert.That(got, Is.EqualTo(want));
  });
}

Mitigate the risk with a precondition:

public class StrictCalculator : ICalculator
{
  /// <summary>
  /// This calculator does not mitgate the risk of division by zero.
  /// Instead, it explicitly declares with the precondition <see cref="NonzeroDivisorPrecondition"/> 
  /// that it assumes the divisor <paramref name="b"/> is nonzero.
  /// Unlike <see cref="LenientCalculator.Divide(int, int)"/> it does not guarantee the postcondition <see cref="NoDivByZeroPostcondition"/>
  /// </summary>
  [ImplementsFeature<DivideFeature>]
  [Precondition<NonzeroDivisorPrecondition>]
  public int Divide(int a, int b) => a / b;
}

[TestCase(16, 0)]
[TestCase(0, 0)]
[VerifiesRiskMitigation<DivideByZeroRisk>]
public void DivideByZero_Throws(int a, int b)
{
  ICalculator calc = new Calculators.StrictCalculator();
  Assert.Throws<DivideByZeroException>(() => _ = calc.Divide(a, b));
}

Example: Annotate buggy code

public class FlippedDivisorDividendBug : Bug
{
  public override string Description => "The divisor and dividend are flipped in this implementation. " +
    "Needs to be fixed but calling code can simply invert the return value e.g. 1/x. " +
    "Note, as a consequence, the dividend must be nonzero.";
}

public class BuggyCalculator : ICalculator
{
  [HasBug<FlippedDivisorDividendBug>]
  public int Divide(int a, int b) => b / a; // Intentional bug for demonstration: Divisor and dividend are flipped
}

Software Artifacts

NReq recognizes four kinds of software artifacts. Artifacts can be positive or negative, and they can be visible or invisible:

Visible Invisible
Good Feature Design
Bad Bug Tech debt

With NReq you can...

  • Name and track positive artifacts: features and design decisions.
  • Name and track negative artifacts: bugs and tech debt.
  • Name and track requirements and risks.
  • Name and track design decisions, much like an ADR, but they are right next to your code.
  • Describe program logic in the form of preconditions and postconditions

Philosophy

Whatever isn't in the code gets lost. There are plenty of project management tools for stories, tasks, bugs, risks, requirements, etc. NReq lets you do all of that in the code.

People talk about implicit or tacit knowledge that gets lost when developers leave. When the code is all that is left, it's hard to change because you don't know the string of decisions.

By embedding the design in the code, you can be confident the design will remain available.

I once worked at a company that got attacked by ransomware. A decade of completed stories, bug trackers, builds, and test runs were lost. All that remained were the .git folders on the developers' workstations. The company is still in business thanks to those folders.

I worked at another company that annotated unit tests with requirement attributes, e.g. [Requirement(12345)]. The number linked the unit test to a formal software requirement in Azure DevOps. Then, the CI scanned the test results declared whether all requirements were satisfied. I want that capability, but without an external tool or klugey CI scripts.

Future Work

  • Calculate requirements coverage
  • Generate reports, e.g. Markdown.
  • Strip annotations from release
  • Add analyzers. For example, warn of unimplemented requirements or unmitigated risks.
  • When tests fail, make note of associated requirements or risks.
  • Requirement tree
  • Publish NuGet package

Non goals

  • This is not a test framework, e.g. not a replacement for NUnit
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.0 135 2/22/2024 1.0.0 is deprecated because it is no longer maintained.