Microsoft Visual Studio 2010: Improving Class Quality with Cohesion

Exclusive offer: get 50% off this eBook here
Refactoring with Microsoft Visual Studio 2010

Refactoring with Microsoft Visual Studio 2010 — Save 50%

Refactor with Microsoft Visual Studio 2010 and evolve your software system to support new and ever-changing requirements by updating your C# code base with patterns and principles with this book and eBook

$35.99    $18.00
by Peter Ritchie | July 2010 | Enterprise Articles Microsoft

This two-part article series by Peter Ritchie, author of Refactoring with Microsoft Visual Studio 2010, introduces code quality metrics like cohesion and coupling. Principles related to cohesion and coupling are introduced and refactorings that increase cohesion and decrease coupling are covered in this article series. In this first part we will cover how cohesion can be applied to increase code quality.

(For more resources on Microsoft products, see here.)

Larry Constantine is attributed with the creation of systematic measurement of software quality. In the mid-to-late seventies, Larry Constantine (and Ed Yourdon) attributed several things to the quality of software code. Under the umbrella of structured design, among those attributes of quality software code were cohesion and coupling. At the time they associated quality with generality, flexibility, and reliability. In this article series, we're going to concentrate on generality and flexibility and how cohesion and coupling can be applied to increase code quality.

Larry Constantine:http://en.wikipedia.org/wiki/Larry_Constantine

Cohesion applies to many different disciplines. Cohesion in physics relates to the force that keeps molecules integrated and united and that makes the molecule what it is. A highly cohesive molecule is one that tends to remain autonomous and not adhere to or blend with other molecules. In geology, cohesion is the strength of substances to resist being broken apart. In linguistics, it's the degree to which text is related. Text whose content relates to the same subject is cohesive.

Cohesion in software is very similar to cohesion elsewhere. A cohesive block of code is a block of code that relates well together and has the ability to remain together as a unit. Object-oriented programming brings distinct cohesive abilities. All programming languages have certain cohesive abilities, such as their ability to group code in modules, source files, functions, and so on. A programming language's ability to define physical boundaries enables cohesiveness. A module, for example, defines a specific boundary for which some content is retained and other content is repelled. Code from one module can use code from another module, but only in specific and defined ways—usually independent of language syntax. All code within a module has innate cohesion: their relation amongst themselves as being contained within the module.

Any rule, principle, guideline, or practice needs to be implemented thoughtfully. This text isn't a manual on how you must perform your refactoring; it's a description of several types of refactorings and their impetus. By the same token, this text doesn't set out to prove the benefits of any particular rule, principle, guideline, or practice. "Mileage may vary" and incorrect usage will often negate most, if not all, benefits. I'll leave it as an exercise for the reader to find the research that "proves" the benefits of any particular rule, principle, guideline, or practice. This text assumes the generally accepted benefits of various principles and practices, including cohesion, as an indicator of quality. If you decide that the benefits aren't outweighing the costs, it's up to you to decide not to implement that principle.

Class cohesion

Object-orientation brings extra cohesive abilities to the programmer. The programmer has the ability to relate code together within a class. Other code can use the code within a class, but only through its defined boundaries (the class's methods and properties).

In object-oriented design, cohesion is generally much more than simply code contained within a class. Object-oriented cohesiveness goes beyond the physical relation of code within a class and deals with the relation of meaning of the code within a class.

Object-oriented language syntax allows the programmer to freely relate code to other code through a class definition, but this doesn't mean that code is cohesive. For example, let's revisit our Invoice class so far.

/// <summary>
/// Invoice class to encapsulate invoice line items
/// and drawing
/// </summary>
public class Invoice
{
private IInvoiceGrandTotalStrategy
invoiceGrandTotalStrategy;
public Invoice(IEnumerable<InvoiceLineItem>
invoiceLineItems,
IInvoiceGrandTotalStrategy invoiceGrandTotalStrategy)
{
InvoiceLineItems = new
List<InvoiceLineItem>(invoiceLineItems);
this.invoiceGrandTotalStrategy =
invoiceGrandTotalStrategy;
}
private List<InvoiceLineItem> InvoiceLineItems
{
get;
set;
}
public void GenerateReadableInvoice(Graphics graphics)
{
graphics.DrawString(HeaderText,
HeaderFont,
HeaderBrush,
HeaderLocation);

float invoiceSubTotal = 0;
PointF currentLineItemLocation = LineItemLocation;
foreach (InvoiceLineItem invoiceLineItem in
InvoiceLineItems)
{
float lineItemSubTotal =
CalculateLineItemSubTotal(invoiceLineItem);
graphics.DrawString(invoiceLineItem.Description,
InvoiceBodyFont,
InvoiceBodyBrush,
currentLineItemLocation);

currentLineItemLocation.Y +=
InvoiceBodyFont.GetHeight(graphics);
invoiceSubTotal += lineItemSubTotal;
}
float invoiceTotalTax =
CalculateInvoiceTotalTax(invoiceSubTotal);
float invoiceGrandTotal =
invoiceGrandTotalStrategy.CalculateGrandTotal(
invoiceSubTotal,
invoiceTotalTax);
CalculateInvoiceGrandTotal(invoiceSubTotal,
invoiceTotalTax);
graphics.DrawString(String.Format(
"Invoice SubTotal: {0}",
invoiceGrandTotal - invoiceTotalTax),
InvoiceBodyFont, InvoiceBodyBrush,
InvoiceSubTotalLocation);
graphics.DrawString(String.Format("Total Tax: {0}",
invoiceTotalTax), InvoiceBodyFont,
InvoiceBodyBrush, InvoiceTaxLocation);
graphics.DrawString(String.Format(
"Invoice Grand Total: {0}",
invoiceGrandTotal), InvoiceBodyFont,
InvoiceBodyBrush, InvoiceGrandTotalLocation);
graphics.DrawString(FooterText,
FooterFont,
FooterBrush,
FooterLocation);
}

public static float CalculateInvoiceGrandTotal(
float invoiceSubTotal, float invoiceTotalTax)
{
float invoiceGrandTotal = invoiceTotalTax +
invoiceSubTotal;
return invoiceGrandTotal;
}

public float CalculateInvoiceTotalTax(
float invoiceSubTotal)
{
float invoiceTotalTax =
(float)((Decimal)invoiceSubTotal *
(Decimal)TaxRate);
return invoiceTotalTax;
}
public static float
CalculateLineItemSubTotal(
InvoiceLineItem invoiceLineItem)
{
float lineItemSubTotal =
(float)((decimal)(invoiceLineItem.Price
- invoiceLineItem.Discount)
* (decimal)invoiceLineItem.Quantity);
return lineItemSubTotal;
}
public string HeaderText { get; set; }
public Font HeaderFont { get; set; }
public Brush HeaderBrush { get; set; }
public RectangleF HeaderLocation { get; set; }
public string FooterText { get; set; }
public Font FooterFont { get; set; }
public Brush FooterBrush { get; set; }
public RectangleF FooterLocation { get; set; }
public float TaxRate { get; set; }
public Font InvoiceBodyFont { get; set; }
public Brush InvoiceBodyBrush { get; set; }
public Point LineItemLocation { get; set; }
public RectangleF InvoiceSubTotalLocation { get; set; }
public RectangleF InvoiceTaxLocation { get; set; }
public RectangleF InvoiceGrandTotalLocation { get; set; }
}

We have an operational Invoice class. It does some things, and they work. But, our Invoice class isn't very cohesive. The Invoice class has distinct groups of fields. Some are for the state of an invoice and some are for generating a readable invoice. Methods that deal with the behavior and attributes of an invoice don't use the fields that deal with generating a readable invoice.

Our Invoice class is implementing two distinct tasks: managing invoice state and generating a readable invoice. The data required to generate a readable invoice (over and above the data shown on an invoice) isn't used by Invoice when not generating a readable invoice.

Our Invoice class can be said to have multiple responsibilities: the responsibility of managing state and the responsibility of generating a readable invoice. What makes an invoice an invoice may be fairly stable; we may occasionally need to add, remove, or change fields that store data contained in an invoice. But, the act of displaying a readable invoice may be less stable: it may change quite frequently. Worse still, the act of displaying a readable invoice may depend on the platform it is running on.

The Single Responsibility Principle

In terms of focusing refactoring efforts towards cohesiveness of a class, the Single Responsibility Principle (SRP) gives us guidance on what a particular class should or should not do. The Single Responsibility Principle states that "there should never be more than one reason for a class to change". In the case of our invoice class there's a couple of reasons why we'd need to change the class:

  • The way an invoice is displayed needs to change.
  • The data that is contained in an invoice needs to change.

The stability of each responsibility shouldn't need to depend on the other. That is, any change to the Invoice class affects stability of the whole class. If I often need to change the way an invoice renders a displayable invoice, all of Invoice is instable—including its responsibility to the data an invoice contains.

The Single Responsibility Principle's focus is fairly narrow: class responsibility. A novice may simply accept that scope and use it only to focus cohesion efforts at the class level. The fact is that the Single Responsibility Principle is applicable at almost all levels of software design, including method, namespace, module/assembly, and process; that is, a method could implement too much responsibility, the types within an assembly or namespace could have unrelated responsibilities, and the responsibilities of a given process might not be focused effectively.

Simply saying "single responsibility" or detailing that something has too many responsibilities is simple. But the actual act of defining what a responsibility is can be very subjective and subtle. Refactoring towards Single Responsibility can take some time and some work to get right. Let's see how we can improve quality through better cohesion and the principle of single responsibility.

Refactoring classes with low-cohesion

Clearly single responsibility (and the heading of this section) suggests that we should refactor Invoiceinto multiple classes. But, how do we do that given our current scenario? The designer of this class has obviously thought that the functionality of Invoice included rendering a readable invoice, so how do we make a clean separation? Fortunately, the lack of cohesiveness gives us our separation points. What makes an invoice an invoice doesn't include those fields that deal solely with rendering a readable invoice. Fields like HeaderFont, HeaderBrush, HeaderText, HeaderLocation, FooterFont, FooterBrush, FooterText, FooterLocation, InvoiceBodyFont, InvoiceBodyBrush, LineItemLocation, InvoiceBustTotalLocation, InvoiceTaxLocation, and InvoiceGrandTotalLocation all deal with just the rendering responsibility.

The Invoice class is modeling a real-world invoice. When you hold an invoice in your hand or view it on the screen, it's already rendered. In the real-world we'd never think that a responsibility of rendering an invoice would be a responsibility of the invoice itself.

We know we want to retain our original Invoice class and we want to move the rendering responsibility to a new class. This new class will encapsulate the responsibility of rendering an invoice. Since this new class will take an Invoice object and help another class produce something useful, we can consider this an invoice rendering service.

In order to refactor our existing Invoice class to a new Invoice rendering service, we start with a new InvoiceRenderingService class and move the HeaderFont, HeaderBrush, HeaderText, HeaderLocation, FooterFont, FooterBrush, FooterText, FooterLocation, InvoiceBodyFont, InvoiceBodyBrush, LineItemLocation, InvoiceBustTotalLocation, InvoiceTaxLocation, and InvoiceGrandTotalLocation fields to the InvoiceRenderingService. Next, we move the GenerateReadableInvoice method to the InvoiceRenderingService. At this point, we basically have a functional class, but since the InvoiceRenderingService method was on the Invoice classes, the other properties that the GenerateReadableInvoice uses need an Invoice object reference—effectively changing it from "this" to a parameter to the GenerateReadableInvoice method. Since the original Invoice class was never expected to be used externally like this, we need to add a CalculateGrandTotal method that delegates to the invoiceGrandTotalStrategy object. The result is something like the following:

/// <summary>
/// Encapsulates a service to render an invoice
/// to a Graphics device.
/// </summary>
public class InvoiceRenderingService
{
public void GenerateReadableInvoice(Invoice invoice,
Graphics graphics)
{
graphics.DrawString(HeaderText,
HeaderFont,
HeaderBrush,
HeaderLocation);
float invoiceSubTotal = 0;
PointF currentLineItemLocation = LineItemLocation;
foreach (InvoiceLineItem invoiceLineItem in
invoice.InvoiceLineItems)
{

float lineItemSubTotal =
Invoice.CalculateLineItemSubTotal(
invoiceLineItem);
graphics.DrawString(invoiceLineItem.Description,
InvoiceBodyFont,
InvoiceBodyBrush,
currentLineItemLocation);

currentLineItemLocation.Y +=
InvoiceBodyFont.GetHeight(graphics);
invoiceSubTotal += lineItemSubTotal;
}
float invoiceTotalTax =
invoice.CalculateInvoiceTotalTax(
invoiceSubTotal);
float invoiceGrandTotal =
invoice.CalculateGrandTotal(
invoiceSubTotal,
invoiceTotalTax);
Invoice.CalculateInvoiceGrandTotal(invoiceSubTotal,
invoiceTotalTax);

graphics.DrawString(String.Format(
"Invoice SubTotal: {0}",
invoiceGrandTotal - invoiceTotalTax),
InvoiceBodyFont, InvoiceBodyBrush,
InvoiceSubTotalLocation);
graphics.DrawString(String.Format("Total Tax: {0}",
invoiceTotalTax), InvoiceBodyFont,
InvoiceBodyBrush, InvoiceTaxLocation);
graphics.DrawString(String.Format(
"Invoice Grand Total: {0}",
invoiceGrandTotal), InvoiceBodyFont,
InvoiceBodyBrush, InvoiceGrandTotalLocation);
graphics.DrawString(FooterText,
FooterFont,
FooterBrush,
FooterLocation);
}
public string HeaderText { get; set; }
public Font HeaderFont { get; set; }
public Brush HeaderBrush { get; set; }
public RectangleF HeaderLocation { get; set; }
public string FooterText { get; set; }
public Font FooterFont { get; set; }
public Brush FooterBrush { get; set; }
public RectangleF FooterLocation { get; set; }
public Font InvoiceBodyFont { get; set; }
public Brush InvoiceBodyBrush { get; set; }
public Point LineItemLocation { get; set; }
public RectangleF InvoiceSubTotalLocation { get; set; }
public RectangleF InvoiceTaxLocation { get; set; }
public RectangleF InvoiceGrandTotalLocation { get; set; }
}

Alternatively, the whole use of invoiceGrandTotalStrategy can be moved into the InvoiceRenderingService—which is a better design decision.

Detecting classes with low-cohesion

So, we've seen a fairly simple example of making a not-so-cohesive class into two more-cohesive classes; but, one of the tricky parts of refactoring away classes with low cohesion is finding them. How do we find classes with low-cohesion?

Fortunately, many people have put time and effort over the years into defining what it means for a class to be cohesive. There have been various metrics researched and created over the years to define cohesion in classes. The most popular metric is Lack of Cohesion of Methods (LCOM) . Lack of Cohesion of Methods measures the degree to which all methods use all fields. The more segregated field usage is amongst methods of a class, the higher the Lack of Cohesion of Methods metric of the class will be. Lack of Cohesion of Methods is a measure of the entire class, so it won't point out where the class is not cohesive or indicate where the responsibilities can be separated.

Lack of Cohesion of Methods is a measurement of the degree to which fields are used by all methods of a class. Perfection as defined by Lack of Cohesion of Methods is that every method uses every field in the class. Clearly not every class will do this (and arguably this will hardly ever happen); so, Lack of Cohesion of Methods is a metric, as most metrics are, that requires analysis and thought before attempting to act upon its value. LCOM is a value between 0 and 1, inclusive. A measure of 0 means every method uses every field. The higher the value, the less cohesive the class; the lower the value, the more cohesive the class. A typical acceptable range is 0 to 0.8. But, there's no hard-and-fast definition of specific value that represents cohesive; just because, for example, a class has an LCOM value of 0.9, that doesn't mean it can or should be broken up into multiple classes. Lack of Cohesion of Methods values should be used as a method of prioritizing cohesion refactoring work by focusing on classes with higher LCOM values before other classes (with lower LCOM values).

In the case of our Invoice class, it's apparent that its LCOM value does mean it can be split into multiple classes as we detailed in the previous section.

Refactoring with Microsoft Visual Studio 2010 Refactor with Microsoft Visual Studio 2010 and evolve your software system to support new and ever-changing requirements by updating your C# code base with patterns and principles with this book and eBook
Published: July 2010
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:

(For more resources on Microsoft products, see here.)

Method cohesion

Methods on their own can also suffer from low-cohesion. One symptom of a method with low-cohesion is size. Methods with low-cohesion are often large. A large method is generally doing more than it needs to.Refactoring is as simple as breaking the method up into multiple methods. Each method should take on a single responsibility.

Another symptom of a class that suffers from low-cohesion is one that has many parameters. A method that takes many parameters is probably doing too many things. Unfortunately, how to refactor depends on what is trying to be accomplished. One example of too many arguments is often constructors. For example:

/// <summary>
/// Example of a class with low-cohesion
/// </summary>
public class Invoice
{
public string GivenName { get; private set; }
public string SurName { get; private set; }
public string Street { get; private set; }
public string City { get; private set; }
public string Province { get; private set; }
public string Country { get; private set; }
public string PostalCode { get; private set; }

public Invoice(IEnumerable<InvoiceLineItem>
invoiceLineItems, string givenName,
string surName, string street,
string city, string province,
string country, string postalCode,
Func<float, float, float> calculateGrandTotalCallback)
{
GivenName = givenName;
SurName = surName;
Street = street;
City = city;
Province = province;
Country = country;
PostalCode = postalCode;
}
//...
}

The Invoice class has taken on the extra responsibility of managing customer information (given name, surname, street, and so on) and thus one of its constructors has many parameters.

Refactoring methods with low-cohesion

Invoice could be refactored to make the constructor (and the class, for that matter) more cohesive by encapsulating customer information into a Customer class, encapsulating address information into an Address class and the Invoice class, and accepting a Customer parameter to the constructor that initializes a Customer property. The resulting refactoring would look like the following:

/// <summary>
/// Customer shape to encapsulate
/// name and address
/// </summary>
public class Customer
{
public String FirstName { get; private set; }
public String LastName { get; private set; }
public Address Address { get; private set; }

public Customer(string firstName, string lastName,
Address address)
{
FirstName = firstName;
LastName = lastName;
Address = address;
}
}
/// <summary>
/// Address shape to encapsulate
/// western-style addresses
/// </summary>
public class Address
{
public string Street { get; private set; }
public string City { get; private set; }
public string Province { get; private set; }
public string Country { get; private set; }
public string PostalCode { get; private set; }
public Address(string street, string city, string province,
string country, string postalCode)
{
Street = street;
City = city;
Province = province;
Country = country;
PostalCode = postalCode;
}
}
/// <summary>
/// Invoice class that makes use
/// of <seealso cref="Customer"/>
/// </summary>
public class Invoice
{
public Customer Customer { get; private set; }
public Invoice(IEnumerable<InvoiceLineItem>
invoiceLineItems, Customer customer,
Func<float, float, float> calculateGrandTotalCallback)
{
Customer = customer;
InvoiceLineItems = new
List<InvoiceLineItem>(invoiceLineItems);
this.calculateGrandTotalCallback =
calculateGrandTotalCallback;
}
//...
}

We now have an Invoice class that makes use of the Customer class, and the Customer class manages the customer (including address) responsibility.

This provides a much more cohesive constructor and encapsulates customer and address information so that code to initialize that type of information does not have to be repeated. We can use the code that used to be contained in Invoice anywhere else with Customer without having to repeat that code within the other classes that need it.

In other cases, a method with too many parameters is simply trying to do too many things.

/// <summary>
/// Invoice that uses callback for
/// grand total calculation
/// </summary>
public class Invoice
{
private Func<float, float, float>
calculateGrandTotalCallback;
public Invoice(IEnumerable<InvoiceLineItem>
invoiceLineItems,
Func<float, float, float> calculateGrandTotalCallback)
{
InvoiceLineItems = new
List<InvoiceLineItem>(invoiceLineItems);
this.calculateGrandTotalCallback =
calculateGrandTotalCallback;
}
public List<InvoiceLineItem> InvoiceLineItems { get; set; }
public float TaxRate { get; set; }
public bool CalculateTotals(out float invoiceSubTotal,
out float invoiceTotalTax, out float invoiceGrandTotal)
{
invoiceSubTotal = 0;
foreach (InvoiceLineItem invoiceLineItem in
InvoiceLineItems)
{
invoiceSubTotal +=
(float)((decimal)(invoiceLineItem.Price
- invoiceLineItem.Discount)
* (decimal)invoiceLineItem.Quantity);
}

invoiceTotalTax = (float)((Decimal)invoiceSubTotal *
(Decimal)TaxRate);
invoiceGrandTotal =
calculateGrandTotalCallback(invoiceTotalTax,
invoiceTotalTax);
return true;
}
//...
}

CalculateTotals is a straightforward example that effectively returns three floating-point values from a method: calculating three values and assigning them to output parameters. What is common with methods with "multiple return values" is to return a Boolean result. This Boolean result is always true—and an indication that there's a design issue. In this example, the designer thought it would be useful to be able to query all the total values at the same time. The problem with this is that if only one value is needed, then the caller has to create a couple of dummy variables to needlessly store the unwanted values.

In order to refactor this, we simply need to perform Extract Method refactorings to split up the method into three methods: CalculateInvoiceGrandTotal, CalculateInvoiceTotalTax, and CalculateLineItemSubtotal.

/// <summary>
/// Example of method with too many parameters
/// </summary>
public class Invoice
{
private Func<float, float, float>
calculateGrandTotalCallback;
public Invoice(IEnumerable<InvoiceLineItem>
invoiceLineItems,
Func<float, float, float> calculateGrandTotalCallback)
{
InvoiceLineItems = new
List<InvoiceLineItem>(invoiceLineItems);
this.calculateGrandTotalCallback =
calculateGrandTotalCallback;
}
public List<InvoiceLineItem> InvoiceLineItems { get; set; }
public float TaxRate { get; set; }
public bool CalculateTotals(out float invoiceSubTotal,
out float invoiceTotalTax, out float invoiceGrandTotal)
{
invoiceSubTotal = 0;
foreach (InvoiceLineItem invoiceLineItem in
InvoiceLineItems)
{
invoiceSubTotal +=
(float)((decimal)(invoiceLineItem.Price
- invoiceLineItem.Discount)
* (decimal)invoiceLineItem.Quantity);
}
invoiceTotalTax = (float)((Decimal)invoiceSubTotal *
(Decimal)TaxRate);
invoiceGrandTotal =
calculateGrandTotalCallback(invoiceTotalTax,
invoiceTotalTax);
return true;
}
//...
}

We've now introduced three methods. CalculateInvoiceGrandTotal extracts the portion of code that calculates the invoice grand total and calculates the grand total based on an invoice subtotal and invoice total tax. CalculateInvoiceTotalTax extracts the portion of code that calculates the total tax based on an invoice subtotal. CalculateInvoiceSubTotal extracts the portion of code that sums the line item subtotals. Each of these methods takes on a single responsibility from the original CalculateTotals —which had taken on multiple responsibilities.

Another example is method, which is both a query and a modifier. Sometimes, this is easy to detect, as the word And is usually in the method name: ChangeTaxAndCalculateGrandTotal. Sometimes, it's harder to detect and requires a bit of analysis of the method body. For example:

/// <summary>
/// Example Command AND Query method
/// </summary>
/// <param name="taxRate"></param>
/// <returns></returns>
public float CalculateGrandTotal(float taxRate)
{
TaxRate = taxRate;

float invoiceSubTotal = 0;

foreach (InvoiceLineItem invoiceLineItem in
InvoiceLineItems)
{
invoiceSubTotal +=
(float)((decimal)(invoiceLineItem.Price
- invoiceLineItem.Discount)
* (decimal)invoiceLineItem.Quantity);
}
float invoiceTotalTax = (float)((Decimal)invoiceSubTotal *
(Decimal)TaxRate);
return calculateGrandTotalCallback(invoiceTotalTax,
invoiceTotalTax);
}

This method first sets the TaxRate property , then proceeds to calculate the grand total based on the TaxRate property. In the case of this method, it's not clear from its signature (unlike ChangeTaxAndCalculateGrandTotal) that it is both a Command and a Query.

Command: A method that performs an action on an object; generally modifying state.
Query: A method or property that returns data to the caller.

In some circles, a Command is also known as a Modifier.

This can be refactored by performing the Separate Query from Modifier refactoring.

In our case, this is just a matter of removing the taxRate parameter from the method and removing the assignment to the TaxRate property, similar to the following code:

/// <summary>
/// Refactored to Query and not Command
/// </summary>
/// <returns></returns>
public float CalculateGrandTotal()
{
float invoiceSubTotal = 0;

foreach (InvoiceLineItem invoiceLineItem in
InvoiceLineItems)
{
invoiceSubTotal +=
(float)((decimal)(invoiceLineItem.Price
- invoiceLineItem.Discount)
* (decimal)invoiceLineItem.Quantity);
}
float invoiceTotalTax = (float)((Decimal)invoiceSubTotal
(Decimal)TaxRate);
return calculateGrandTotalCallback(invoiceTotalTax,
invoiceTotalTax);
}

Code that needs to calculate the grand total with a different tax rate should simply use the TaxRate property before calling CalculateGrandTotal. For example, instead of this:

float grandTotal = invoice.CalculateGrandTotal(.12f);

We'd refactor to this:

TaxRate = .12f;
float grandTotal = invoice.CalculateGrandTotal();

In a more complex Separate Query From Modifier refactoring, a new method or property would have to be created to separate the Query, and the method performing the modification would remove the code that modifies state and is renamed (for example, ChangeTaxRateAndCalculateGrandTotal to CalculateGrandTotal) to be clear that it isn't modifying state. Calls to the method would have to be changed to the new query method, and a call to the modifier added before the call to the query.

Refactoring with Microsoft Visual Studio 2010 Refactor with Microsoft Visual Studio 2010 and evolve your software system to support new and ever-changing requirements by updating your C# code base with patterns and principles with this book and eBook
Published: July 2010
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:

(For more resources on Microsoft products, see here.)

Namespace cohesion

As with any logical grouping of code, what is contained within the grouping may or may not be related. Syntactically, a namespace can contain any number of classes with any number of purposes related to any number of things. Grouping in a namespace is for the programmer; if it doesn't add any value, there's not much point to using it. Classes within a namespace should be related to one another in a particular way that adds value.

Refactoring namespaces with low-cohesion

Unfortunately, there isn't a built-in way to move a class from one namespace to another. You can rename a namespace, but if there is more than one class within the namespace, you "move" all the classes to a new or existing namespace.

Let's say we have a class in the Invoicing namespace, and we want to move it to the Invoicing.Domain namespace because this class represents a fundamental domain entity and locating it in the Domain namespace will mean it will be cohesive with the other members of the Domain namespace.

namespace Invoicing
{
/// <summary>
/// Address shape to encapsulate
/// western-style addresses
/// </summary>
public class Address
{
public string Street { get; private set; }
public string City { get; private set; }
public string Province { get; private set; }
public string Country { get; private set; }
public string PostalCode { get; private set; }
public Address(string street, string city,
string province, string country, string postalCode)
{
Street = street;
City = city;
Province = province;
Country = country;
PostalCode = postalCode;
}
}
}

In order to perform a Move to Another Namespace refactoring , right-click the namespace name Invoicing, and select Refactor\Rename... then enter "Invoicing. Domain". This effectively "moves" the Address class to a new namespace, the Invoicing.Domain namespace . This results in the following:

namespace Invoicing.Domain
{
/// <summary>
/// Address shape to encapsulate
/// western-style addresses
/// </summary>
public class Address
{
public string Street { get; private set; }
public string City { get; private set; }
public string Province { get; private set; }
public string Country { get; private set; }
public string PostalCode { get; private set; }
public Address(string street, string city,
string province, string country, string postalCode)
{
Street = street;
City = city;
Province = province;
Country = country;
PostalCode = postalCode;
}
}
}

The only "heavy lifting" at this point you'll have to do is move the file this class lives in from one directory to another (if you're synchronizing namespace names with directory names). This can be accomplished by dragging and dropping the file in the Solution Explorer.

  1. If your namespace has many classes in it and you don't want all the classes to be moved, you'll have to manually perform the move:
  2. Use Find All References to find all references to Address.
  3. Change the namespace from Invoicing to Invoicing.Domain in Address.cs.
  4. For each entry in the Find Symbol Results, double-click.
  5. Add using directive for Invoicing.Domain.
  6. Optionally move Address.cs to another folder with drag/drop in Solution Explorer.

Assembly cohesion

Assembly cohesion can be a bit of a red herring—it diverts attention away from assemblies' inherent features as a deployment strategy. Breaking up a solution into multiple assemblies simply to give related types a cohesive place to live is not the intention here. Assemblies are a deployment strategy; assemblies may need to exist for specific reasons unrelated to cohesion. For example, if two groups of different code need to be executed in separate processes or on separate computers, they need to live in different assemblies regardless of whether their packaging together would be more cohesive.

If you're restructuring a system at the assembly level, you've got some pretty specific needs for what is contained in what assembly. That's not to say you won't have multiple assemblies per deployment platform. You may want to have assemblies that are shared amongst multiple applications, and they may need to be highly cohesive. .NET is a good example of this. .NET 2.0 deployed a certain set of assemblies. .NET 3.0 added types and features, but they're primarily added to assemblies new to .NET 3.0 to avoid changing existing binaries. For example, types in the System. Data.Linq namespace could have been included the System.Data.dllassembly and that assembly would have still been cohesive. But, because of the deployment issues (whereSystem.Data.dll shouldn't be modified for anything other than show-stopper bugs) added, LINQ types and features were added to a new assembly: System.Data.Linq.dll.

Refactoring assemblies

When approaching refactoring assemblies by moving classes from one to another, the best starting point is to have all the projects associated with those assemblies in one Visual Studio® solution. Often, systems already have this type of organization. Larger projects may have to avoid this specific organization for performance and usability reasons within Visual Studio®. A temporary solution that contains these projects, in these cases, is the recommended place to start. When dealing with many project files, this may cause a bit of grief creating and loading the solution, but it will give a huge payoff if you want to move more than a couple of classes.

When approaching performing the Move Type to new Namespace from a single solution, performing the refactoring within Visual Studio® becomes very simple. Moving a class from one project to another becomes a simple process of selecting it in the Solution Explorer and dragging it to another project folder and dropping it while holding the Shiftkey down. Holding the Shift key down while dropping causes a move to occur instead of a copy. Once copied, the new file should be edited to change the original namespace to the destination namespace.

Summary

We've reviewed what it is to be highly cohesive. We've seen what it means to be non-cohesive and how to detect certain non-cohesiveness through metrics. With some simple refactoring we can make something cohesive where it was previously not. In the next part we will discuss principles related to coupling and refactorings that decrease coupling.


Further resources on this subject:


About the Author :


Peter Ritchie

Peter Ritchie is a software development consultant. Peter is president of Peter Ritchie Inc. Software Consulting Co., a software consulting company in Canada's National Capital Region specializing in Windows-based software development management, process, and implementation consulting. Peter has worked with such clients as Mitel, Nortel, Passport Canada, and Innvapost from mentoring to architecture to implementation. Peter has considerable experience building software development teams and working with startups towards agile software development.

Peter's range of experience ranges from designing and implementing simple standalone applications to architecting distributed n-tier applications spanning dozens of computers; from C++ to C#. Peter is active in the software development community attending and speaking at various events as well as authoring various works including Refactoring with Microsoft Visual Studio 2010.

Books From Packt


ASP.NET 3.5 Application Architecture and Design
ASP.NET 3.5 Application Architecture and Design

Microsoft Silverlight 4 Data and Services Cookbook
Microsoft Silverlight 4 Data and Services Cookbook

Microsoft Silverlight 4 and SharePoint 2010 Integration
Microsoft Silverlight 4 and SharePoint 2010 Integration

WCF 4.0 Multi-tier Services Development with LINQ to Entities
WCF 4.0 Multi-tier Services Development with LINQ to Entities

Microsoft Windows Workflow Foundation 4.0 Cookbook
Microsoft Windows Workflow Foundation 4.0 Cookbook

.NET Compact Framework 3.5 Data Driven Applications
.NET Compact Framework 3.5 Data Driven Applications

Applied Architecture Patterns on the Microsoft Platform
Applied Architecture Patterns on the Microsoft Platform

Microsoft Visio 2010 Business Process Diagramming and Validation
Microsoft Visio 2010 Business Process Diagramming and Validation


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software