Introduction
Wandering through codebases, I've encountered some examples of code where ReSharper issues the above-mentioned warning. Although the fix seems straightforward, the warning itself may hide a more subtle issue connected to the 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 {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}";
- }
- }
Indeed, ReSharper 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".
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:
- 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 the information expert principle should be used together with a high cohesion principle.
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 our unit testing. That's why static should be handled with care. RASP patterns, in combination with SOLID, allows us to examine such cases in order to reduce the support cost of our codebases.