Introduction
Wandering through codebases, I've encountered some examples of code where Microsoft Code Analysis issues the above-mentioned warning. Although the fix seems straightforward, the warning itself may hide a more subtle issue connected to object responsibility assignment.
Toy Example
Let's take a look at the following code:
public class EmailConstructor
{
private const string Signature = "Regards";
public string Construct(User recipient, string body)
{
var builder = new StringBuilder();
builder.Append($"Hello {GetNiceUserName(recipient)}");
builder.Append(Environment.NewLine);
builder.Append(body);
builder.Append(Environment.NewLine);
builder.Append(Signature);
return builder.ToString();
}
private string GetNiceUserName(User user)
{
return $"{user.Name} {user.Surname}";
}
}
public class User
{
public string Name { get; set; }
public string Surname { get; set; }
}
Indeed code analysis issues the warning.
Feature Envy
Although things may seem obvious at first glance, actually the code above is the example of Feature envy. As the rule of thumb which would help you to spot such cases without using tools such as ReSharper, you may use one of the principles postulated by Craig Larman in his book "Applying UML and patterns".
Quote:
Information expert will lead to placing the responsibility on the class with the most information required to fulfill it.
According to this principle, you can fix the warning my moving method inside the User
class as follows as the User
is the one who has more information to represent his name in one or another way:
public class EmailConstructor
{
private const string Signature = "Regards";
public string Construct(User recipient, string body)
{
var builder = new StringBuilder();
builder.Append($"Hello {recipient.GetNiceUserName()}");
builder.Append(Environment.NewLine);
builder.Append(body);
builder.Append(Environment.NewLine);
builder.Append(Signature);
return builder.ToString();
}
}
public class User
{
public string Name { get; set; }
public string Surname { get; set; }
public string GetNiceUserName()
{
return $"{Name} {Surname}";
}
}
High Cohesion
Naturally, the question arises from the example above: "Won't this lead to bloating User
class with lots of responsibilities such as, for example, working with persistent storage?". The answer is that Information expert principle should be used together with high cohesion principle.
Quote:
High cohesion is generally used in support of low coupling. High cohesion means that the responsibilities of a given element are strongly related and highly focused. Breaking programs into classes and subsystems is an example of activities that increase the cohesive properties of a system. Alternatively, low cohesion is a situation in which a given element has too many unrelated responsibilities. Elements with low cohesion often suffer from being hard to comprehend, hard to reuse, hard to maintain and averse to change.
Thus if we, for example, would like to extend our toy code with persistent storage interaction, we would likely extract highly cohesive functions dedicated to that into some sort of Unit of Work pattern.
Conclusion
Static
members have some disadvantages such as complicating unit testing. That's why static
should be considered with care. Craig Larman's GRASP patterns in combination with SOLID allow us to examine such cases in order to reduce the support cost of our codebases.
History
- 11th February, 2018: Initial version
- 16th August, 2022: Replaced ReSharper with Microsoft Code Analysis