FURPSS with two SSes – The simplest choice of software quality taxonomy?

To design the software architecture for a system you must consider the software qualities it must have.

To think through that, you will want a checklist or framework.

An oldie-but-nearly-goody is FURPS, which listed qualities under five top-level headings:

  • Functionality
  • Usability
  • Reliability
  • Performance
  • Supportability
    but anyone working in software since the invention of the internet cannot ignore Security (which in FURPS was a sub-heading under functionality) as a top level concern, which gets us to:

FURPSS with two SSes

  • Functionality
  • Usability
  • Reliability
  • Performance
  • Supportability
  • Security

A 3-way split of design thinking

In practise the responsibility for achieving these qualities is using broken down as:

  • Functionality: this is what analysts and developers devote much of their attention to
  • Usability: this is what designers, together with those who care, pay attention to
  • Reliability, Performance, Supportability, Security: when these go wrong, everyone turns to the project architect, or principal engineer, or the tech lead.

I think this 3-way split matches the competencies required to deal with each area well. That is, being an expert in one of these areas does not make you an expert in the other two. To be a good, all-round lead in software requires some competence in all three. You must make the effort to learn some of the body of knowledge of all three areas, not just rely on picking stuff up as you go along.

When FURPSS is not enough

FURPSS is quiet minimal taxonomy and works very well for a lot (maybe most?) vertical application development, aka enterprise software.

But for bigger or more complex projects you need a bigger checklist. You need …

ISO/IEC 25010 (SQuaRE)

SQuaRE stands for Systems and software Quality Requirements and Evaluation. It defines about 40 qualities, under eight main quality headings:
• Functional suitability
• Performance efficiency
• Compatibility
• Usability
• Reliability
• Security
• Maintainability
• Portability

In addition, SQuaRE considers quality across stages of the product lifecycle, which FURPSS does not highlight, from early use and development to quality in use. For even a medium size project, maintainability matters already before you've finished building release 1; some projects never see the light of day because they become unbuildable.

Reading and learn some Square. But for most projects, it is more of a framework to pick from (which, exactly, of these 40 ways of looking at software quality are helpful to my project?). But it's a good way to review wether there is anything important that you might have missed.

All That is Old Is New Again. ISO 5055

SQuaRE has not altogether taken the world by storm. In fact, ISO5055 has subsequently been developed, which moves a little away from SQuarRE's focus on behaviour (you might think that is the right focus, but sometimes you just have to get technical) and back to the techy inside view of the system.
It's key headings are:

  • Security
  • Reliability
  • Performance Efficiency
  • Maintainability

Which is all but identical to the RPSS of FURRPSS.

But that is not the point of ISO5055. It's actual title is “Information technology — Software measurement — Software quality measurement — Automated source code quality measures” and it intends to be a way to automate the detection of quality failures before the system is deployed. Specifically it proposes to automate checking for the 138 issues listed in Mitre's Common Weakness Enumeration.

Conclusions for a project architect

  • FURPSS is good.
    It is enough for most application development. ISO5055 is something of a confirmation that RPSS are the four top-level quality requirements that are most wanted, alongside Functionality and Usability.
  • SQuaRE is good, for when you want to peruse, or use, a bigger checklist, or help for thinking about quality across stages of the lifecycle.

References

EntityFramework Core, Dependency Injection, and how to inject more dependencies when you can’t use the constructor

Perhaps having dependency injection in .Net for fifteen years has spoilt us a little. We expect any class to simply declare some dependency or other as a constructor parameter and by DI magic it all just works.

It doesn't work for everything though. Notably, it doesn't ‘just work’ in the dependency injection factories provided by Microsoft for EntityFramework. The DI factory .AddDbContext<T>(...) method expects your constructor to take a single DbContextOptions<T> options parameter. No other dependencies can be declared.

The injection technique offered instead by Entity Framework is to add IDbContextOptionsExtension instances to the DbContextOptions. This requires some boilerplate.

The Problem

You use microsoft.extensions.dependencyinjection. You use EntityFrameworkCore. You want to inject dependencies into a DbContext created by the DI factory. You can’t add constructor parameters. How to do it?

How To Create and Use an IDbContextOptionsExtension

As best I can see, the boilerplate for this is about five separate pieces of code. The final piece is a single line in your DI setup, in the same place you add out-of-the-box extensions like EnableDetailErrors:

services.AddDbContext<MyApplicationsDbContext>(
  (s,o) =>  {
               o.UseSqlServer(dbString)
                .EnableDetailedErrors()
                .EnableSensitiveDataLogging(isDevOrTest)
                // -----------------------------------------
                // 👇 add one line to inject your dependency
                .InjectMyThings( s.GetRequiredService<ThingToInject>() );
                // 👆 --------------------------------------
});

The Boilerplate

To make that one line work, you write four more pieces of code.

1. The DbContextOptionsBuilder extension method

/// <summary>
/// Boilerplate to make <see cref="DbOptionsMyInjectedThingExtension"/> available.
/// </summary>
public static class MyInjectedThingExtensionDbContextOptionsBuilderExtensions
{
    public static DbContextOptionsBuilder InjectMyThings(this DbContextOptionsBuilder builder, ThingToInject myInjectedThing)
    {
        (builder as IDbContextOptionsBuilderInfrastructure)
            .AddOrUpdateExtension(new DbOptionsMyInjectedThingExtension(myInjectedThing));
        return builder;
    }
}

2. Your custom IDbContextOptionsExtension class which holds the dependencies to inject

/// <summary>
/// Add MyInjectedThing to the <see cref="DbContextOptions"/>.
/// </summary>
public class DbOptionsMyInjectedThingExtension : IDbContextOptionsExtension
{
    ///<summary>This class carries the thing you want to inject</summary>
    public bool MyInjectedBool { get; }
    public ThingToInject EvenMoreInjectedThings { get; }

    public DbOptionsMyInjectedThingExtension(ThingToInject myInjectedThing)
    {
        MyInjectedBool = myInjectedThing.MyInjectedBool;
        EvenMoreInjectedThings = myInjectedThing;
        Info = new DbOptionsMyInjectedThingExtensionInfo(this);
    }
    public void ApplyServices(IServiceCollection services)
    {
        services.AddSingleton(this); 
    }
    public void Validate(IDbContextOptions options) { }
    public DbContextOptionsExtensionInfo Info { get; }
}

3. The EFCore MetaData

/// <summary>
/// Boilerplate to make <see cref="DbOptionsMyInjectedThingExtension"/> available.
/// </summary>
public class DbOptionsMyInjectedThingExtensionInfo : DbContextOptionsExtensionInfo
{
    /// <inheritdoc/>
    public DbOptionsMyInjectedThingExtensionInfo(DbOptionsMyInjectedThingExtension extension) : base(extension) { }

    /// <inheritdoc/>
    public override bool IsDatabaseProvider => false;

    /// <inheritdoc/>
    public override string LogFragment => "MyInjectedThing: {MyInjectedThing}";

    /// <inheritdoc/>
    public override int GetServiceProviderHashCode() => 0;

    /// <inheritdoc/>
    public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
        => (other.Extension as DbOptionsMyInjectedThingExtension)?.MyInjectedBool == (Extension as DbOptionsMyInjectedThingExtension)?.MyInjectedBool;

    /// <inheritdoc/>
    public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
    {
        debugInfo["MyInjectedThing:"] = $"MyInjectedThing={(Extension as DbOptionsMyInjectedThingExtension)}";
    }
}

4. Finally, your DbContext constructor

    public MyApplicationsDbContext(DbContextOptions<MyApplicationsDbContext> options) 
          : base(options)
    {
        MyInjectedThing = options.FindExtension<DbOptionsMyInjectedThingExtension>()?.EvenMoreInjectedThings;
        MyInjectedBool = EvenMoreInjectedThings?.MyInjectedBool ?? false ;
    }
    protected bool MyInjectedBool;
    protected ThingToInject MyInjectedThing;

Blazor Cascading Parameters don’t “just work” with lambdas or method callback

TL;DR: Blazor Cascading Parameters are matched by Type not necessarily by Name. The simplest solution is to declare the Parameter type as Delegate. If you need to distinguish more than one delegate cascading parameter, then declare a named delegate type (that's a one-liner in C#) and use that as the parameter type.

The problem

You want to pass a callback as a cascading parameter. You try passing a lambda, or possibly a method, and create a CascadingParameter property, with matching name, of type Action or Func<...> or similar, to receive it. But the cascading parameter is always set to null.

Cascading parameters are matched up by Type, not by name, and who knows what the compiled type of a lambda will be? The C# reference tells you that that lambdas can be converted to delegates or expression trees but 'can be converted to' isn't good enough for matching up by Type.

The quickest fix

Declare your parameter to be of type Delegate. Most callable things in C# are of type Delegate.

// Declaration in the child component
[CascadingParameter]public Delegate OnChange { get; set; }

// Use in the child component. The null is what DynamicInvoke requires for 'no parameters'
OnChange.DynamicInvoke(null);

# In the parent container
<CascadingValue Value="StateHasChanged" >

If that's not good enough

If you must distinguish between several Delegate parameters, or just want to better express intent in your code, you can define a named delegate:

public delegate void StateHasChangedHook();

and use that as the Type of your CascadingParameter:

// Declaration in the child component
[CascadingParameter]public StateHasChangedHook OnChange { get; set; }

// Use in the child component is slightly simpler
OnChange();

# In the parent container
<CascadingValue Value="(StateHasChangedHook)this.StateHasChanged" >

When you use named delegates, you can invoke them with onChange() syntax instead of .Invoke() or DynamicInvoke(null).

You can inspect what was set in the child parameter

log.LogDebug("OnChange was set {OnChange}.MethodInfo={MethodInfo}",OnChange,OnChange?.GetMethodInfo())

Recall that Action<> and Func<> are themselves named delegates, not types. They are declared in the System assembly as for instance: public delegate void Action<in T>(T obj)

What are the ARIA roles & accessible names for HTML Elements?

To work with HTML elements via their ARIA attributes, you must learn a minimum about two ARIA attributes:

  1. The ARIA role
  2. The ARIA accessible name, which can and should be a human-readable piece of text such as Your Name, not an html id like your-name.

But it is said that no ARIA is better than bad ARIA! So here is your bare-minimum ‘must-know’ to work successfully with aria role and aria accessible name.

An example: aria roles and names for a small form

testing-library pushes you to target elements in your tests by aria role and name, in the hope that you are then testing what your user interface actually presents to a user. Here's a testable example of the role and accessible names for a small form which you can run from an npm project with devDependencies @jest and @testing-library:

import '@jest/globals'
import '@testing-library/jest-dom'
import {screen, fireEvent} from '@testing-library/dom'

test('Example role and accessible names', ()=>{

      const div= document.createElement('div')
      div.innerHTML=`<form data-testid='form1'>
        <fieldset>
          <legend>Form Alpha</legend>
          <label>Search : <input type='search' value="something" /></label>
          <label><input type='checkbox' /> In Stock Only</label>
          <button>The Go Button</button>
        </fieldset>
      </form>`

      document.body.appendChild(div)

      const fieldSet= screen.getByRole('group',{name:"Form Alpha"})
      expect(fieldSet).toBeVisible()

      const searchBox=screen.getByRole('searchbox', {name:/Search/i})
      expect(searchBox).toHaveValue('something')

      const checkBox=screen.getByRole('checkbox', {name:/in stock only/i})
      expect(checkBox).not.toBeChecked()
      fireEvent.click(checkBox)
      expect(checkBox).toBeChecked()

      const form=screen.getByTestId('form1')
      expect(form).toBeVisible()
})

Now, some of these names and roles you could have guessed and some of it you couldn't. Let's take out the guesswork.

What Role and what Accessible Name do my HTML elements have?

HTML Element Roles

You can get a quick idea of roles just by looking at some of the defined ARIA roles:

  • article, banner, button, cell, checkbox, columnheader, combobox, command, comment, complementary, composite, contentinfo, definition, dialog, document, figure, form, generic, grid, gridcell, group, heading, img, input, landmark, link, list, listbox, listitem, log, main, mark, marquee, math, menu, menubar, menuitem, menuitemcheckbox, menuitemradio, meter, navigation, option, presentation, progressbar, radio, radiogroup, range, region, roletype, row, rowgroup, rowheader, scrollbar, search, searchbox, section, sectionhead, select, separator, slider, spinbutton, status, structure, suggestion, switch, tab, table, tablist, tabpanel, term, textbox, timer, toolbar, tooltip, tree, treegrid, treeitem, widget, window

Most of them split into document structure and landmark roles vs widget and composite widget roles and you can probably guess most of which html elements implicitly have which roles by default, but some you would guess wrong. The other categories of roles are Abstract, Live Region, and Window. Some of the roles form hierarchies, with sub-roles.

Here is a table of the implicit ARIA role given by default to some commonly used html elements, along with the permitted overrides.

Implicit ARIA Roles for some common HTML elements

HTML Element Implicit ARIA Role Permissible explicit Roles
nav navigation none
article article document, main, presentation &c
a href="..." link button,checkbox,menuitem &c
a without href none any
button button tab, checkbox, combobox, link, menuitem, &c
form form but only if it has an accessible name search, none or presentation
fieldset group radiogroup, presentation, none
img alt="..." img button, checkbox, link, menuitem, option, tab &c
img alt="" presentation presentation, none
input[type=button] button checkbox, combobox, link, &c
input[type=checkbox] checkbox button with aria-pressed, &c
input[type=image] button link, menuitem, radio, switch &c
input[type=number] spinbutton
input[type=radio] radio menuitemradio
input[type=range] slider
input[type=reset] button
input[type=submit] button
input[type=search] with no list attribute searchbox none
input[type=search] with list attribute combobox none
input[type=email] with no list attribute textbox none
input[type=email] with list attribute combobox none
input[type=tel] with no list attribute textbox none
input[type=tel] with list attribute combobox none
input[type=text] with no list attribute textbox combobox, searchbox, &c
input[type=text] with list attribute combobox none
input[type=url] with no list attribute textbox none
input[type=url] with list attribute combobox none
ul,ol list directory, group, listbox, menu, menubar, &c
label,legend,caption none none
main main none
menu list directory, group, listbox, menu, menubar, &c
optgroup group none
option option none
select with multiple attribute & no size greater than 1 combobox menu
select otherwise listbox none
textbox textbox none
table table any
td cell, if a descendant of a table any
th columnheader or rowheader any

You can see some patterns:

  • Many elements such as main, article, option, textbox have a fixed semantic meaning which is pretty much the same as their role. They can only have that fixed role and it can't be overridden.
  • Some elements have no implicit Aria role but can be given any explicit role. These elements include div,span,code,blockquote,most of text elements such as em,strong, &c. Only give them a role if they really will play that role! For instance if you use them as parts of a custom widget. Otherwise they are best without a role. Not everything should have a role.
  • Invisible elements such as style and script have no aria role and are not permitted an explicit role either.
  • Label, legend, caption are in a sense special elements whose aria job is to label some other element with an accessible name. They can't be given any other role.
  • Some element roles including a,img,select (and of course input) depend on how their attributes are set.
  • See the HTML element reference pages to find the full list of html elements and the full list of permitted explicit roles for those that aren't in the above table.

The table mentions what explicit roles can be given. An explicit role is given to an HTML element by giving it a role="rolename" attribute. But the advice is to only do this when you have to. Work with the native HTML roles.

HTML Element Accessible Names

An accessible name can and should be a human-readable piece of text such as 'Your Name', not an html id like your-name.

To understand what the accessible name of a component is, you must understand the accessible name computation. Here are, roughly, the first 3 steps of the accessible name computation for HTML. This is enough to get you off the ground and calculate most of your accessible names.

The Accessible Name Computation for HTML Elements

  1. The aria-labelledby property is used if present.
  2. If the name is still empty, the aria-label property is used if present.
  3. If the name is still empty, then the 3rd step depends on the element and is described in the w3.org Accessible Name Computation Section By HTML Element. Here is a summary table:
HTML Element Accessible Name Calculation if the element has no aria-label or aria-labelledby
<input type="text">, <input type="password">, <input type="number">, <input type="search">, <input type="tel">, <input type="email">, <input type="url"> and <textarea> Use the associated label element or elements accessible name(s) - if more than one label is associated; concatenate by DOM order, delimited by spaces.
If the accessible name is still empty, then: use the control's title attribute.
Use the control's placeholder value.
<input type="button">, <input type="submit"> and <input type="reset"> Accessible Name Computation Use the associated label element(s) accessible name(s) - if more than one label is associated; concatenate by DOM order, delimited by spaces.
Use the value attribute.
For input type=submit and type=reset: if the prior steps do not yield a usable text string, and the value attribute is unspecified use the implementation defined string respective to the input type.
Otherwise, if the control still has no accessible name use title attribute.
<input type="image"> Accessible Name Computation Use the associated label element(s) accessible name(s) - if more than one label is associated; concatenate by DOM order, delimited by spaces.
Use alt attribute if present and its value is not the empty string.
Use title attribute if present and its value is not the empty string.
Otherwise if the previous steps do not yield a usable text string, use the implementation defined string respective to the input type (an input in the image state represents a submit button). For instance, a localized string of the word "submit" or the words "Submit Query".
<button> Use the associated label element(s) accessible name(s) - if more than one label is associated; concatenate by DOM order, delimited by spaces.
Use the button element subtree.
Use title attribute.
<fieldset> If the accessible name is still empty, then: if the fieldset element has a child that is a legend element, then use the subtree of the first such element.
If the accessible name is still empty, then:, if the fieldset element has a title attribute, then use that attribute.
Otherwise, there is no accessible name.
<output> Use the associated label element or elements accessible name(s) - if more than one label is associated; concatenate by DOM order, delimited by spaces.
Use title attribute.
Other Form Elements Use label element.
Use title attribute.
<summary> If the first summary element, which is a direct child of the details element, has an aria-label or an aria-labelledby attribute the accessible name is to be calculated using the algorithm defined in Accessible Name and Description: Computation and API Mappings.
Use summary element subtree.
Use title attribute.
If there is no summary element as a direct child of the details element, the user agent should provide one with a subtree containing a localized string of the word "details".
If there is a summary element as a direct child of the details element, but none of the above yield a usable text string, there is no accessible name.
<figure> If the accessible name is still empty, then: if the figure element has a child that is a figcaption element, then use the subtree of the first such element.
If the accessible name is still empty, then: if the figure element has a title attribute, then use that attribute.
<img> Use alt attribute, even if its value is the empty string.
NOTE
An img with an alt attribute whose value is the empty string is mapped to the presentation role. It has no accessible name.
Otherwise, if there is no alt attribute use the title attribute.
<table> if the table element has a child that is a caption element, then use the subtree of the first such element.
if the table element has a title attribute, then use that attribute.
tr, td, th Use the title attribute.
<a> Use a element subtree.
Use the title attribute.
<area> Use area element's alt attribute.
Use the title attribute.
<iframe> Use the title attribute.
NOTE
The document referenced by the src of the iframe element gets its name from that document's title element, like any other document
Section and Grouping p, hr, pre, blockquote, ol, ul, li, dl, dt, dd, figure, figcaption, div, main and body, article, section, nav, aside, h1, h2, h3, h4, h5, h6, header, footer, address Use the title attribute.
Text-level Elements: abbr, b, bdi, bdo, br, cite, code, dfn, em, i, kbd, mark, q, rp, rt, ruby, s, samp, small, strong, sub and sup, time, u, var, wbr Use the title attribute.

If this step 3 seems a bit ad hoc, the underlying logic is ARIA's categorisation into roles (and therefore elements) that have

  • Name from Author, meaning that you as the content author use a <label> (or legend or caption or figcaption or alt= attribute, or similar as appropriate to the html element) to provide an explicit accessible name
  • Name from Content, meaning that the browser reads the text nodes of the element and uses that as that accessible name.
  • No name. After all, you don't want a name for every paragraph or span or div. That would be really unhelpful.

Note that multiple labels or multiple content sources are valid, and will be concatenated with spaces.

The 3 Cases of the 3rd Step of the Accessible Name Calculation:

If you learn

  • which html elements have name from content,
  • which rely on a label,
  • which should not be named

then you've got 95% of what you need. You can then choose the appropriate way to aria-name your element:

  • Naming with child content.
  • Naming form controls with the label element.
  • Naming fieldsets with the legend element.
  • Naming tables and figures with captions.
  • Naming with a string attribute via aria-label.
  • Naming by referencing content with aria-labelledby.

Overriding the default name and not using the fallback

Steps 1 and 2, using either an aria-label="This is my name" attribute, or an aria-labelledby="id-of-a-labelling-element-with-text-content" is the override mechanism.

The common fallback mechanism — which you can always avoid using— is the title attribute. Prefer using the explicit override rather than letting a title set your name.

Parting comments

  • See https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/ for an understanding of why you are naming things and how to do it well.

  • Returning to my original example, you can see that aria is opiniated. Many aria roles and naming rules are what you'd expect, but ARIA thinking is not identical to html thinking. The input elements get accessible names from their labels, but the form as a whole only gets an accessible name if you explicitly give it one, and it only gets the Aria role of form if it has an accessible name.

  • I have only discussed roles and names. ARIA also has descriptions and states and properties. For instance, you can use aria-busy=true on elements when they are loading or waiting to be populated with data.

  • See testing-library's intro example for more on using testing-library with vanilla html or major frameworks

Learning to do it well

A starting point for learning how to create good accessible sites is https://www.w3.org/WAI/ARIA/apg/