I posted a query last week requesting feedback on the use of attributes in an API and their effect on the usability of that API (thanks for all the responses!). My query was driven by a study that I was running and that is now complete. I promised that I would describe the results that we obtained from the study with respect to attributes.

The API that we studied makes extensive use of attributes (I don't want to go into the details of the specific API so I'll just use finctional examples here to demonstrate the different points). To achieve any of the functionality of the API, developers must decorate their code with specific attributes. Nothing useful can be achieved in the API without doing so. For example, the API is used to create instances of Foo which can then be plugged in to some executable framework which calls methods on Foo. The API exposes a Foo base class that must be derived from, but also a Foo attribute that must be used to decorate the derived class with, like so:

[Foo(“Some property“,“Some other property“)]
class MyFoo : Foo

Many of the participants thought that the Foo attribute was overkill and was not required - they felt that deriving from Foo should be enough.

Deriving a class from Foo and decorating it with the Foo attribute is still not enough to achieve any useful functionality. You need to create some public members (properties or fields) and then decorate them with the Bar attribute. Only then will the execution context know which members of your class it should access. On top of this, you need to override one of three methods that the Foo class defines. In the example below, we've overridden Method1:

[Foo(“Some property“,“Some other property“)]
class MyFoo : Foo
{
    [Bar(optionalparam1=false, optionalparam2=0)]
    public string Message;

    public void override Method1()
    {...}
...
}

Note those optional parameters in the Bar attribute. These were pretty critical to the success of participants in the study. In order to implement some particular behavior, participants needed to alter the values of those optional parameters, and to set some other parameters. In many cases though, participants would not consider the attributes when they were thinking about what they needed to do in order to accomplish a given task. Instead, they would concentrate on their implementation of Method1 and think about what they needed to change there. They would end up spending a lot of time writing code that wouldn't help them accomplish their goal. In many cases, participants needed to be prompted to consider the attributes as a means to accomplish the goal.

The problem was made worse by more advanced tasks that required participants to decorate members with two attributes, and to set some optional parameters to specific values:

    [Blah(“some string“)]
    [Bar(optionalparam1=true, optionalparam2=0)]
    public string Message;

Note how optionalparam1 is now true. Without being set to true, the attribute Blah has no effect. Blah on it's own also has no effect (if the user just used Blah and did not also decorate Message with the Bar attribute, nothing would happen). This caused some significant issues for participants in the study. In particular, the requirement to change the value of the optional parameter was difficult to see. The interaction between the two attributes just wasn't clear, since they both look as if they operate in isolation from one another.

These problems were not insurmountable. After a couple of hours, most participants started to grow accustomed to the way that the API worked. But it was clear that they struggled for a while to get to that level, making their initial experience with the API an unpleasant one. Furthermore, even after growing accustomed to the API, many participants commented that they felt that the attributes took a lot of control away from them and hid a lot of the details of how the API works. This made it much more difficult for them to form a conceptual model of how the API works. Thus resolving problems and bugs in their code was much more difficult for them than if they had been able to form an accurate conceptual model of the API.

The key take aways from the study with respect to the use of attributes are:

  • Many developers will not expect core functionality of an API to be exposed through attributes only.
  • Many developers will be uncomfortable with an API that exposes most, if not all, of it's functionality through attributes due to the perceived lack of control afforded by attributes.
  • Combinatorial effects between different attributes should be avoided. If they cannot be avoided, the interaction between different attributes should be expressed through good naming.
  • Many developers will be unlikely to consider modifying attributes in code to achieve particular functionality and will instead concentrate on their own (imperative) code.