CSC/ECE 517 Fall 2012/ch1b 1w20 dm: Difference between revisions
Line 19: | Line 19: | ||
=====Coincidental Cohesion===== | =====Coincidental Cohesion===== | ||
A module has coincidental cohesion if its elements have no meaningful relationship to one another and the module itself can be used to achieve several different types of tasks. The following example illustrates coincidental cohesion [http:// | A module has coincidental cohesion if its elements have no meaningful relationship to one another and the module itself can be used to achieve several different types of tasks. The following example illustrates coincidental cohesion [http://www.kodefuguru.com/post/2009/10/05/Coincidental-Cohesion.aspx]. | ||
public static class BackendService { | public static class BackendService { | ||
Line 38: | Line 38: | ||
In this example, the <code>BackendService</code> provides a one stop shop for many different types of tasks. This type of cohesion is the least desirable and should be avoided. | In this example, the <code>BackendService</code> provides a one stop shop for many different types of tasks. This type of cohesion is the least desirable and should be avoided. | ||
=====Logical Cohesion===== | =====Logical Cohesion===== |
Revision as of 05:31, 23 October 2012
NOTE: This topic relates to merge and update of previous wiki's [1]and [2]
Aim
This article aims at highlighting the new areas been explored in the fields of cohesion and coupling and how using agile methodologies help attain highly cohesive classes and to maintain loose coupling between those classes. Following these approaches we can get code which is more readable and maintainable. We encourage reader to visit the references provided at the end of the article to explore more on the research work done in this field.
Introduction
Cohesion and Coupling are concepts often discussed when referring to object-oriented programming. In fact, one of the primary goals of object-oriented programming is “to build highly cohesive classes and to maintain loose coupling between those classes”[3]. In the first section of this article, we provide the basic definition of cohesion, different types of cohesion, how it is measured, advantages and disadvantages of high cohesion and some examples showing implementation of this concept in real life scenarios. Next, we explain the concept of coupling, different types of cohesion, how it is measured, advantages and disadvantages of high coupling and some examples showing implementation of this concept in real life scenarios. In the third section of this article, we highlight we include a section on how using agile approaches maintain cohesion and coupling, and why this is important, it also discusses the importance of quality metrics, and how agile teams help support the same. In next section we discuss the vast research going on in this field and hence we have included a section on new work been done in this field. Lastly, we provide a conclusion to the topics discussed herein along with some recommended further reading and references.
Cohesion
Cohesion is the "glue" that holds a module together.It can be thought of as the type of association among the component elements of a module. Cohesion is related to the Single Responsibility Principle.Generally, one wants the highest level of cohesion possible. An object with high cohesion is defined for one purpose and it performs only that purpose.High cohesion is desired because it increases the likelihood that a module will be comprehendible, reliable, robust, and reusable [4].Alternatively, a module that exemplifies low cohesion is difficult to comprehend, costly to maintain, and less likely to be reused.An object with low cohesion tends to try to do a lot of different things.
Types of Cohesion [5]
Following are arranged from lowest (least desirable) to highest (most desirable).
Coincidental Cohesion
A module has coincidental cohesion if its elements have no meaningful relationship to one another and the module itself can be used to achieve several different types of tasks. The following example illustrates coincidental cohesion [6].
public static class BackendService { public static float computeNetPay(int orderId) { // implementation } public static float calculateInventoryReorderAmount(int inventoryId) { // implementation } public static boolean generateInvoice(int invoiceId) { // implementation } // ... }
In this example, the BackendService
provides a one stop shop for many different types of tasks. This type of cohesion is the least desirable and should be avoided.
Logical Cohesion
A module has Logical cohesion when parts of a module are grouped together as they are logically categorized to do the same thing, even if they are different by nature.Typically, these modules accept a control flag which indicates which operation to execute. The following example illustrates logical cohesion.
public class DataStore { public void SaveData(int destination, byte[] data) { switch (destination) { default: case 0: SaveToDb(data) break; case 1: SaveToFile(data) break; case 2: SaveToWebService(data) break; } } protected void SaveToDb(byte[] data) { // implementation } protected void SaveToFile(byte[] data) { // implementation } protected void SaveToWebService(byte[] data) { // implementation } }
In this example, although all of the operations are logically related by the fact that they all save data, the truth is they are in fact quite different. Saving to a database likely requires a completely different implementation than saving to a file or web service.
Temporal Cohesion
A temporally cohesive module is one whose elements are functions that are related in time. That is operations that are performed to reflect a specific behavior or state. The following example illustrates temporal cohesion.
public class Startup { public void Initialize() { InitializeLogging(); InitializeUI(); InitializeDb(); } public void InitializeLogging() { // implementation } public void InitializeUI() { // implementation } public void InitializeDb() { // implementation } }
This example highlights a common implementation pattern where tasks are grouped simply by the fact that they need to be executed at around the same time. Aside from needing to be executed at the same time, these operations implement tasks that are indeed quite different.
Procedural Cohesion
Procedural cohesion describes a module whose operations are typically grouped because they are executed within a sequence. Unlike sequential cohesion (discussed below), the operations within a procedural cohesive module can be somewhat unrelated and output from one operation is not necessarily used as input to a subsequently executed operation. The following example illustrates procedural cohesion.
public class GradeBookSystem { public void Login(int teacherId) { // implementation } public ArrayList GetStudents(int teacherId) { // implementation } public void UpdateGrades(ArrayList grades) { // implementation } public void UpdateAttendance(ArrayList dates) { // implementation } public void Logout(int teacherId) { // implementation } }
In this example, the GradeBookSystem
exposes different tasks that allow the teacher to login, track student grades/attendance and logout. These operations are grouped in a procedurally cohesive manner to facilitate the higher level task of upgrading the teacher's grade book.
Communicational Cohesion
A communicational cohesive module is one whose elements perform different functions, but each function references the same input information or output.The following example illustrates communication cohesion.
public class CustomerInformation { public CustomerInformation(int accountNum) { // implementation } public String getName() { // implementation } public float getBalance() { // implementation } // ... }
In this example, the getName
and getBalance
operations facilitate tasks against the common input accountNum
.
Sequential Cohesion
A sequentially cohesive module is one whose functions are related such that output data from one function serves as input data to the next function. The intent is to implement a sequence of operations.
For the same Procedural Cohesion example, the GradeBookSystem
exposes different tasks that allow the teacher to login, track student grades/attendance and logout. These operations when called sequentially lead to proper functioning of the module as a whole.
Information cohesion
Informational cohesion describes modules that perform several tasks against a shared data structure. The following example illustrates information cohesion.
class RectangleTransformer { Rectangle rectangle; public RectangleTransformer(Rectangle rectangle) { this.recentangle = rectangle; } public double getArea() { // implementation } public double getPermiter() { // implementation } public void flip() { // implementation } public void stretch(double width, double height) { // implementation } }
In this example, all of the RectangleTransformer
operations are performing actions against the shared rectangle
member variable.
Functional Cohesion
Functional cohesion describes a module that is designed to perform one and only one task. A functionally cohesive module may contain multiple methods, but all of these methods are designed to help the user achieve a single task. Object-oriented languages tend to support this level of cohesion better than earlier languages do.Studies indicate that the first two types of cohesion are inferior, communicational and sequential cohesion are very good and functional cohesion is superior.
The following example illustrates functional cohesion.
public class Stack { public Stack() { // implementation } public void push(Object obj) { // implementation } public Object pop() { // implementation } public Object peek() { // implementation } public int isEmpty() { // implementation } }
In this example, all of the operations in the Stack
class facilitate the task of supporting a Stack data structure.
Measuring Cohesion
The goal of well-designed systems is to have highly cohesive modules. A high degree of cohesion is attained by:
- Including all the relevant elements of the object together in one class instead of chaining through invisible objects
- Include all the necessary elements in the object, so that the elements presence would model the object accurately.
- Ensure that all the necessary elements are present in the class to achieve completeness.
Below are three metrics that can be used to determine the level of cohesion within a system-
Lack of Cohesion 1 (LCOM1)
LCOM1 = (P > Q) ? (P – Q) : 0 P = Total number of method pairs that do not use a common field of the class. Q = Total number of method pairs that access at least one common field of the class.
Lower LCOM1 values indicate higher cohesion and better overall design.
Lack of Cohesion 2 (LCOM2)
LCOM2 = 1 – sum(mA)/(m*a) m = Total number of methods in the class. a = Total number of attributes in the class. mA = Total number of methods that access attribute a. sum(mA) = Sum of all mA for all attributes of the class.
Lower LCOM2 values indicate higher cohesion and better overall design. If the total number of methods or attributes is zero than the value of LCOM2 is undefined.
Lack of Cohesion 3 (LCOM3)
LCOM3 = (m – sum(mA)/a) / (m – 1) m = Total number of methods in the class. a = Total number of attributes in the class. mA = Total number of methods that access attribute a. sum(mA) = Sum of all mA for all attributes of the class.
LCOM3 values greater than one indicates low cohesion and should be addressed. If the total number of methods is less than two or the number of attributes is zero than the value of LCOM3 is undefined.
Advantages
- Cohesion is the idea that does a single, clearly definable thing. This has the benefit of making your code easier to follow.
- Promotes code reuse, since small atomic blocks are easier to reuse then larger blocks.
- High cohesion makes it easier to replace a module by another one that provides same functionality.
- High cohesion reduces complexity of the system, and hence increase the application's reliability.
- If the module is highly cohesive, the software is more readable and maintainable.
- Communication between developers is easier.
Disadvantages
- Cohesion is the idea that the module does a single task - be it calculating data, checking file etc. The "single task mindedness" drastically reduces code breaking when other modules are changed.
- If a module uses data from multiple other modules - if even one module changes or breaks, this module might need to be changed thus more time wasted.
- It becomes difficult to understand various modules
Coupling
Coupling is a qualitative measure of the degree to which the classes, modules or subsystems are connected to one another. It can be defined as the amount of interaction of one object with another object, or one module with another module. For a good software design, it is always advisable to minimize coupling. Strong coupling means that one object or module is dependent on other object or module to perform an operation or task. It simply means that the object or module is strongly coupled with the implementation details of another object or module. With low coupling, a change in one module will not require a change in the implementation of another module. It is important to understand that low coupling does not mean no coupling, rather the goal is to minimize the coupling not eliminate it. A system with no coupling is, by definition, not a system. Low coupling indicates that each object or module performs independent tasks. In general, low coupling can be well explained by Law of demeter. It states that classes within a module or subsystem should have only limited knowledge of classes in other modules or subsystems. In simple terms, 'Law of Demeter' says "Each unit should only talk to its friends; don't talk to strangers".
Types of Coupling
Content coupling
Content coupling occurs when one or more modules access the internals of another module. The following example illustrates content coupling.
public class Rectangle { public int Top = 0; public int Left = 0; public int Width = 0; public int Height = 0; public Rectangle(int top, int left, int width, int height) { this.Top = top; this.Left = left; this.Width = width; this.Height = Height; } public int getArea() { return this.Width * this.Height; } }
public class FloorPlan { Rectangle rectangle = null; public FloorPlan(int width, int height) { rectangle = new Rectangle(0, 0, 50, 100); } public void modifyDimensions(int width, int height) { rectangle.Width = width; rectangle.Height = height; } public int getArea() { return rectangle.getArea(); } }
In this example, FloorPlan
is able to directly modify the Width
and Height
fields of the Rectangle
object. This coupling creates a dependency from FloorPlan
on the internals of the Rectangle
object that inhibits maintenance of the Rectangle
class. If someone wanted to go back and change the Width
and Height
fields of Rectangle
class to use a different data type they would also have to update the FloorPlan
class.
Common coupling
Common coupling occurs when two or more modules modify the same same global variable. The following example illustrates common coupling.
#include <stdio.h> #include <string.h> #define NUM_FIELDS 3 class EmployeeRecordParser { public: EmployeeRecordParser(char* strRow, int nFields) : m_nCount(nFields), m_aryFields(0) { m_aryFields = new char*[m_nCount]; char* strField = strtok(strRow, ","); for (int ct = 0; ct < m_nCount && strField; ++ct) { m_aryFields[ct] = new char[strlen(strField) + 1]; memcpy(m_aryFields[ct], strField, strlen(strField)); m_aryFields[ct][strlen(strField)] = 0; strField = strtok(NULL, ","); } } ~EmployeeRecordParser() { if (m_aryFields) delete [] m_aryFields; } int GetCount() { return m_nCount; } char* operator[](int nIndex) { return GetField(nIndex); } char* GetField(int nIndex) { return nIndex < m_nCount ? m_aryFields[nIndex] : ""; } private: char** m_aryFields; int m_nCount; };
void ParseRecords(char* strFile) { int nRecords = 0; char* strRow = strtok(strFile, "\n"); while (strRow) { EmployeeRecordParser record(strRow, NUM_FIELDS); printf("\nEmployee Record %d\n------------------------\n", ++nRecords); for (int i = 0; i < record.GetCount(); ++i) { printf("Field %d: %s\n", i, record[i]); } strRow = strtok(NULL, "\n"); } } int main() { char str[] = "Tom,Frank,919-777-2333\nMikel,Dundlin,919-234-5512\nRobert,Skoglund,919-232-2904"; ParseRecords(str); return 0; }
In the C++ example above, both the ParseRecords
method and the EmployeeRecordParser
class make use of the globally accessible strtok function. Internally, strtok
uses a static variable to track the position of the current string being tokenized, which is also used to determine when the whole string has been parsed. In this particular example, the coupling on this common function has a side effect that causes a bug that prevents all the records from being correctly parsed.
Control coupling
Control coupling occurs when one module controls the execution flow of another module. The following example [7] illustrates control coupling.
enum InfoType { id, name, balance } public class CustomerInfo { public Object getCustomerInfo(InfoType type) { Object returnVal = null; switch (infoType) { case InfoType.id: returnVal = getCustomerId(); break; case InfoType.name: returnVal = getCustomerName(); break; case InfoType.balance: returnVal = getCustomerBalance(); break; } return returnVal; } // ... }
public class Client { private customerInfo = new CustomerInfo(); public void execute() { int id = (int)customerInfo.getCustomerInfo(InfoType.id); // ... } }
In this example, the Client
class controls the flow of execution within the CustomerInfo
module. This form of coupling requires modules calling CustomerInfo
to know about the flow of execution within its class.
Stamp coupling
Stamp coupling occurs when two or more modules access or modify the same data of a shared object. The following example illustrates stamp coupling.
public class Customer { private int id = 0; private String name = ""; private float balance = 0.0f; public int getId() { return id; } public void setId(int _id) { id = _id; } public String getName() { return name; } public void setName(String _name) { name = _name; } public float getBalance() { return balance; } public void setBalance(float _balance) { balance = _balance; } }
public class CustomerInfo() { public void save(Customer customer) { int id = customer.getId(); String name = gustomer.getName(); // ... } }
public class Client { private customerInfo = new CustomerInfo(); public void execute() { Customer customer = new Customer(); customer.setId(5); customer.setName("Example"); customer.setBalance(100f); customerInfo.save(customer); } }
In this example, the Client
and CustomerInfo
classes share the common Customer
class. This is a desired form of coupling.
Data coupling
Data coupling occurs when one module passes primitive type or simple data structure to another module as an argument. The following example illustrates data coupling.
public class CustomerInfo { public float getCustomerBalance(int customerId) { // implementation details } } public class Client { private customerInfo = new CustomerInfo(); public void execute(int customerId) { float balance = customerInfo.getCustomerBalance(customerId); // ... } }
In this example, Client
and CustomerInfo
interact using only primitive types of data. This is a desired form of coupling.
Measuring Coupling
While it is impossible to avoid some level of coupling within systems, the goal is to reduce coupling as much as possible. Below are three metrics that can be used to determine the level of coupling within a system.
Coupling Between Objects (CBO)
CBO = sum(t) t = Total number of types that are referenced by a particular class, not including any possible super-classes, primitive types or common framework classes.
Lower CBO values indicate lower coupling.
Data Abstraction Coupling (DAC)
DAC = sum(a) a = Total number of types that are used for attribute declarations, not including primitive types, common framework classes, or types that are inherited from any possible super-classes.
Lower DC values indicate lower coupling.
Method Invocation Coupling (MIC)
MIC = nMIC / (N – 1) N = Total number of classes defined within the project. nMIC = Total number of classes that receive a message from the target class.
Lower MIC values indicate lower coupling.
Demeter's Law
Demeter's Law is a design principle that when applied to object-oriented programming means that object A can reference object B but object A cannot use object B to reference object C. Complying with this principle prevents object A from knowing that object B uses object C thereby reducing coupling. If object A needs to access a function of object C then it is up to object B to expose an operation encapsulating the reference to object C. The following example [8] illustrates how this could be done.
public float calculateTotal(Order order) { return order.getProducts().getTotalCost(); }
In the example object the object which implements calculateTotal()
is calling getTotalCost
on a Products
object which is exposed through order
. An alternative to this approach would be for the order object to expose this functionality as suggested by the following example.
public float calculateTotal(Order order) { return order.getTotalCost() } public class Order { // ... public float getTotalCost() { return products.getTotalCost(); } // ... }
Advantages of high coupling
- Coupling allows interaction between different modules so more complicated tasks can be done.
- Coupling to minimum extent helps system's scope to extend.
Disadvantages of high coupling
- Decreases the flexibility of the application software. Developers / maintenance programmers need to understand potentially the whole system to be able to safely modify a single component.
- Object interaction complexity associated with coupling can lead to increased error generation during development.
- Decreases the scalability of the application software. Changing requirements in one part of software will potentially require wide ranging changes in the entire application.
- Decreases the maintainability of the application software. More thought need to go into choices at the beginning of the lifetime of a software system in order to attempt to predict the long term requirements of the system because changes are more expensive.
- Testability is likely to degrade with a more highly coupled system of objects.
New Metrics for Object-Oriented Systems
Majority of coupling and cohesion metrics rely on structural information. Most of it is based on relations which leads to methods or attributes being used. These can be used when assessing quality, doing impact analysis and indentifying design patterns. However, structural metrics lack the ability to identify conceptual links.
We look at two of the newly developed conceptual metrics for measuring coupling and cohesion in software systems.
- Conceptual Coupling between Object Classes(CCBO) - Based on CBO coupling metric [9]
- Conceptual Lack of Cohesion on Methods(CLCOM) - Based on LCOM cohesion metric [10]
The above metrics differ from old conceptual metrics because they implement different counting mechanisms used in peer structural cohesion and coupling metrics. These metrics can be evaluated against existing structural and conceptual coupling metrics for predicting faults in a large open-source software.Usage of these metrics therefore, can be done to build operational models for predicting fault-proneness of classes.Also implementation can be done with other structural metrics to improve overall accuracy of bug prediction models. This approach of measuring coupling and cohesion relies on the assumption that the methods and classes of OO systems are orthogonally related. These are known as conceptual dependencies to capture conceptual cohesion and coupling of classes.Conceptual coupling and cohesion metrics extract and analyze the information in the software. Developers utilize this information to represent a problem or solution.New metrics should be able to get this information to support other analysis.
Future work in this field can lead to advanced metrics which use conceptual information for coupling and cohesion measurement.These metrics can be refined further by including Inheritance.Also pre-processing techniques can be applied improve the quality of textual information.
Cohesion and Coupling Examples
As the popular proverb says " A picture is worth a thousand words ", we will try to represent cohesion and coupling in terms of visual programming metaphors. Observe Figures 1 and 2.
What can one say about the two diagrams? Which one is easy to understand, remember and modify? Obviously the answer would be Figure 1. This is exactly why it is recommended to have high cohesion and loose coupling in design or implementation of a software application. The two figures gives a clear visualization of the underlying concepts of coupling and cohesion.
.
In General
Let us take a look at some simple real life examples to understand the relation between these two terms.
Example 1. Project team - If we assume that a certain project has 20 people working on it, the possible communication channels would be around 400. The team would be very inefficient with high communication overhead. It can be viewed as an example of tight coupling. Therefore, in any team, certain roles and responsibilities are formally assigned to team member, to enhance productivity of the team with smooth coordination. Well defined roles of a team member can be viewed as high cohesion, and establishing communication channels (like defining hierarchy or reporting structure) can be viewed as low coupling. This is a very practical example of low coupling and high cohesion comes.
Example 2. Car - Let us consider another real life example of a car. It has the engine, tire, steering wheel, gear box, brakes etc. Each component of the car performs a given set of functions. Therefore, if the car breaks down, the problem can be analyzed and only certain part needs to be repaired. This can be considered as high cohesion, as each component focuses on the assigned task. At the same time, in order drive the car, all these parts need to interact with each other. This can be viewed as coupling.
Example 3. Child and Parent relationship - Cohesion and coupling can be well expressed using the analogy of a parent child relationship. The child inherits characteristics of its parents. This can be viewed as coupling. At the same time, there is no coupling in the siblings.
In Object Oriented terms
Polymorphism and encapsulation are two major design principles of object oriented programming. In object oriented terms, we can say that, the main vehicle of coupling is polymorphism [11] and the main vehicle of cohesion is encapsulation. More explanation is provided in cohesion and coupling.
In order to achieve modularity, we need to have low coupling and high cohesion.“Modularity is the property of a system that has been decomposed into a set of cohesive and loosely coupled modules.” Booch 1994
Principle: Modularity = Low Coupling + High Cohesion
It can be simply understood as, breaking something complex into set of manageable pieces. Consider the example of order processing system. If we decide to write entire software in only one program, it will become lengthy, unmanageable, complicated and hard to debug. Instead, we can divide it into modules or subsystems like order entry, order processing, billing and complaints.
Conclusion
In Object Oriented programming, it is important to have a balanced approach when talking about cohesion and coupling.This is because both are interdependent,so low coupling relates to high cohesion in an ideal scenario.By making modules which have high cohesion,programmers can reduce cost while improving readability,reliability and reusability. But after these modules are made a part of code has to be made which is a bit more coupled and less cohesive to give bring the program together.Also at times tighter coupling is desired as it improve performance thereby reducing overall costs.This is when one wants stable process while getting maximum performance.Loose coupling is useful when flexibility has priority over stability.However,year or code design has taught us the achieving the highest degree of cohesion with lowest degree of coupling is not achievable.Hence,programmers should use various metrics to measure the degree of cohesion and coupling in their programs and constantly work towards newer methods so as to achieve one of the primary goals of object oriented programming.