Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface...

16
Object-oriented metrics Design decisions: Class Cohesion • Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution Law of Demeter Reused Abstractions

Transcript of Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface...

Page 1: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Object-oriented metrics

Design decisions:

• Class Cohesion• Open-Closed• Single Responsibility• Interface Segregation• Dependency Inversion• Liskov Substitution• Law of Demeter• Reused Abstractions

Page 2: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Class cohesion

• Class design should reduce the need to edit multiple classes when making changes to application logic.

• A fundamental goal of OO design is to place the – behaviour (methods) as close to the – data they operate on (attributes) as possible, so

that changes are less likely to propagate across multiple classes

Page 3: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Class cohesion metrics

Lack of Cohesion of Methods (LCoM)

M #methods

A attribute accessed by R(A) methods:

LCoM = ((∑ R(A)-A) – M ) / (1- M)

Page 4: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Open-closed

• Once a class is tested and working, modifying its code can introduce new bugs. We avoid this by extending the class, leaving its code unchanged, to add new behaviour.

• Classes should be open to extension, but closed to modification

Page 5: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Open-closed metric

• Per successful check-in– classes extended and not modified / classes

extended and/or modified

Page 6: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Single responsibility

• Changing code in a tested class can introduce new bugs. We seek to minimise the reasons why a class might need to change.

• The more different things a class does, the more reasons it might have to change.

Page 7: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Single responsibility metric

• Responsibility / class

• Responsibility := ?

Page 8: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Interface segregation

• If different clients depend on different methods of the same class, then a change to one method might require a recompile and redeployment of other clients who use different methods.

• Creating several client-specific interfaces, one for each type of client, with the methods that type of client requires, reduces this problem significantly.

Page 9: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Interface segregation

If type T exposes N methods, and client C uses n of them, then T’s interface is n/N

specific with respect to C.

• Average n/N for all clients of T

Page 10: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Dependency inversion

• Much of the duplication in code comes from client objects knowing about all sorts of specialised suppliers, that from the client’s perspective do similar things but in different ways.

• Polymorphism is a powerful mechanism that underpins OO design. It allows us to bind to an abstraction, and then we don’t need to know what concrete classes we are collaborating with. This makes it much easier to plug in new components with no need to change the client code.

Page 11: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Dependency inversion

• dependencies on abstractions / total dependencies

Page 12: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Liskov substitution principle

Dynamic polymorphism is a pow erful mechanism that allows us to invert dependencies, reducing duplication and making change much easier. All OO design principles depend upon polymorphism, but we must ensure that any type can be substituted for any of its subtypes at run-time without having any adverse effect on the client.

Subtypes must obey all of the rules that apply to their super-types, pre-conditions for calling methods, post-conditions of methods called, and invariants that always apply between method calls.

Page 13: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

Frequently used OO metrics

• McCabe• Efferent coupling• Lack of Cohesion of Methods

– Chidamber – Kemerer– Henderson-Sellers

• LOC• # of attributes• Inheritence level• Nesting level

Page 14: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

(weighted) McCabe

Problem

Without being given any additional information, how many execution paths could there be in the following code?

String EvaluateSalaryAndReturnName( Employee e ) { if( e.Title() == "CEO" || e.Salary() > 100000 ) { cout << e.First() << " " << e.Last() << " is overpaid" << endl; } return e.First() + " " + e.Last(); }Answer: 23 (in just four lines of code!)

Page 15: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

• For the non-exceptional execution paths, the trick was to know C/C++'s short-circuit evaluation rule:

• 1. If e.Title() == "CEO" then the second part of the condition doesn't need to be be evaluated (e.g., e.Salary() will never get called), but the cout will be performed.[2]

• 2. If e.Title() != "CEO" but e.Salary() > 100000, both parts of the condition will be evaluated and the cout will be performed.

• 3. If e.Title() != "CEO" and e.Salary() <= 100000, the cout will not be performed.

• This leaves the exceptional execution paths:• String EvaluateSalaryAndReturnName( Employee e )• ^*^ ^4^

• 4. The argument is passed by value, which invokes the Employee copy constructor. This copy operation might throw.

• *. String's copy constructor might throw while copying the temporary return value into the caller's area. We'll ignore this one, however, since it happens outside this function (and it turns out that we have enough execution paths of our own to keep us busy anyway!).

• if( e.Title() == "CEO" || e.Salary() > 100000 )• ^5^ ^7^ ^6^ ^11^ ^8^ ^10^ ^9^

Page 16: Object-oriented metrics Design decisions: Class Cohesion Open-Closed Single Responsibility Interface Segregation Dependency Inversion Liskov Substitution.

• 5. The Title() member function might itself throw, or it might return an object of class type by value, and that copy operation might throw.

• 6. To match a valid operator==, the string literal may need to be converted to a temporary object of class type (probably the same as e.Title()'s return type), and that construction of the temporary might throw.

• 7. If operator== is a programmer-supplied function, it might throw.

• 8. Similarly to #5, Salary() might itself throw, or it might return a temporary object and this construction operation might throw.

• 9. Similarly to #6, a temporary object may need to be constructed and this construction might throw.

• 10. Similarly to #7, this might be a programmer-provided function and therefore might throw.

• 11. Similarly to #7 and #10, this might be a programmer-provided function and therefore might throw (see note [2] again).

• cout << e.First() << " " << e.Last() << " is overpaid" << endl;

• 12-16. As documented in the draft standard, any of the five calls to operator<< might throw.

• 17-18. Similarly to #5, First() and/or Last() might throw, or each might return a temporary object and those construction operations might throw.

• return e.First() + " " + e.Last();

• 19-20. Similarly to #5, First() and/or Last() might throw, or each might return a temporary object and those construction operations might throw.

• 21. Similarly to #6, a temporary object may need to be constructed and this construction might throw.

• 22-23. Similarly to #7, this might be a programmer-provided function and therefore might throw.