2Clean Code – Robert C. Martin ●Most of this lecture’s content comes from the Clean Code book–Robert C. Martin / “Uncle Bob”●Link is on eClass
3The importance of good code●Writing code is one thing●Maintaining code is much more difficult●It is much harder to maintain code of low quality (‘bad code’)●Adding new functionality to bad code often leads to more bad code
4So how do we end up with bad code?●Time crunch/rushing●Tired programmers●Overloaded programmers●...●Can replace ‘programmer’ with student :)
5“We will fix it later...”●Unfortunately, ‘later equals never’–LeBlanc’s law●Even though it is not always our fault, bad code is always considered our fault–Don’t be afraid to tell a manager the truth about timelines
6Symptoms of bad code●Difficult to change (rigidity)–Leads to fear of making non-critical changes●Breaks in many places every time it is changed (fragility)●Inability to reuse code (immobility)●When ‘good’ changes are harder to make than hacks (viscosity)
7The SOLID Principles●Acronym for five design principles to improve software designs–Subset of the principles discussed by Uncle Bob●These principles are not rules or laws–They have been observed to work in many cases, but there is no proof that they always work●The principles are actually quite simple–Which makes them so powerful
10Single responsibility principle (SRP)●“A class should have only one reason to change.”●This means a class should have only one job/responsibility●We want our systems to be composed of many small classes, not a few large ones
11What is wrong with this design?Studentvoid study()int markExam(Exam e)boolean admit(Program p)
12What is wrong with this design?●We probably should not let students mark their own exams, or decide about admissionStudentvoid study()int markExam(Exam e)boolean admit(Program p)
13This would be a better design●Indeed, the responsibilities should be moved into separate classes●Then the student can focus on their responsibilityStudentvoid study()Markerint markExam(Exam e, Student s)AdmissionOfficerboolean admit(Program p, Student s)
14Why the single responsibility principle matters●Improves cohesion in classes–Cohesion = how well things belong together●Makes classes smaller–Easier to maintain or test●Makes classes easier to reuse–Smaller building blocks are more versatile
17Open-closed principle (OCP)●“A module should be open for extension but closed for modification”●We should be able to change what a module does without changing its code●Polymorphism is essential to achieve this–Dynamic/runtime polymorphism (e.g., inheritance)–Static/compile-time polymorphism (e.g., generics)
18An example of why OCP matters●What if we want to add a class Circle?publicclassRectangle {publicdoublelength;publicdoublewidth;}publicclassAreaCalculator {publicstaticdoublecalculateArea(Rectangle r) {returnr.length*r.width;}}
19This is a non-OCP way to do itpublicclassCircle {publicdoubleradius;}publicclassAreaCalculator {publicstaticdoublecalculateArea(Rectangle r) {returnr.length*r.width;}publicstaticdoublecalculateArea(Circle c) {returnMath.PI*c.radius*c.radius;}}
20Why is this not a good approach?publicclassCircle {publicdoubleradius;}publicclassAreaCalculator {publicstaticdoublecalculateArea(Rectangle r) {returnr.length*r.width;}publicstaticdoublecalculateArea(Circle c) {returnMath.PI*c.radius*c.radius;}}
21Why is this not a good approach?●For every type of shape we add, we need to change the AreaCalculator class●So this design is not closed for modification●We can use polymorphism to fix this
22OCP in actionpublicinterfaceShape {publicabstractdoublecalculateArea();}publicclassRectangle implementsShape {privatedoublelength;privatedoublewidth;@OverridepublicdoublecalculateArea() {returnlength*width;}}
23OCP in actionpublicclassAreaCalculator {publicstaticdoublecalculateArea(Shape s) {returns.calculateArea();}}
24This implementation follows theopen-closed principle●It is open for extension–We can add any type of new Shape we want by adding a class that implements Shape●AreaCalculator or Shape do not have to be modified when we add a new shape
27Liskov substitution principle (LSP)●“Subclasses should be substitutable for their base classes.”●A user of a base class should continue to function properly if a subclass of that class is passed to it●Named after Prof. Barbara Liskov (MIT)–Won a Turing award in 2008 for her contributions to computer programming languages
28LSP in practiceCarFormulaOneCarRaceTrackhas-a●If RaceTrack takes a Car object...●It should be legal to use an instance of FormulaOneCar in RaceTrack as well
29But, what about this?●A MiniVan should not be on the race track!●This is a violation of the LSP and indicates a design flawCarFormulaOneCarRaceTrackhas-aMiniVan
30But, what about this?●How can we fix the design flaw while still allowing different types of cars on the RaceTrack?CarFormulaOneCarRaceTrackhas-aMiniVan
31We need to add an interface RaceCar to distinguish cars that can and cannot raceRaceCarFormulaOneCarRaceTrackhas-aMiniVanCar
32We need to add an interface RaceCar to distinguish cars that can and cannot raceRaceCarFormulaOneCarRaceTrackhas-aMiniVanCarNow, we cannot add a Car to RaceTrack anymore! (If you are unsure: follow the direction of the arrows)
33Alternative solution: Make RaceCar a subclass of CarRaceCarFormulaOneCarRaceTrackhas-aMiniVanCar
34How do we fix LSP-related issues?●LSP applies to a contract between classes–e.g., a RaceTrack has a contract with the cars on it that they can race●Factoring out common features into a separate class can help create an LSP-proof hierarchy–e.g., by adding a RaceCar interface that all cars that can race must implement
37Interface segregation principle (ISP)●“Many client specific interfaces are better than one general purpose interface.”●Can also be read as “No client should be forced to depend on methods it does not use.”●If you have a service that is used by several classes, create specific interfaces for each class
38A non ISP-proof exampleService// methods for A..// methods for B..// methods for C..ClassCClassBClassA
39Why is this problematic?●The classes can call methods that were not intended for them●Even worse, if Service is an interface, classes must implement irrelevant methods–This would result in lots of UnsupportedOperationExceptions–Which in turn would violate the Liskov Substitution Principle
40An ISP-proof exampleClassCClassBClassA<<interface>>ServiceA// methods for A..<<interface>>ServiceB// methods for B..<<interface>>ServiceC// methods for C..Service// methods for A..// methods for B..// methods for C..
41How do we fix ISP-related issues?●We ‘segregate’ the functionality into smaller interfaces by extracting only the relevant methods●Note that this also fixes the LSP violation
44Dependency inversion principle (DIP)●“Depend upon abstractions. Do not depend upon concretions.”●Higher-level classes should depend on abstractions, and not on the details of lower-level classes directly
45A real-life example of DIP
46A real-life example of DIP ●In this example, the Lamp is a higher-level class that depends on a lower-level power source●But, we do not really care where the power is coming from●So the Lamp should depend on an abstract power source, rather than on the low-level details (= soldering directly into the outlet)
47The Lamp example in OOP●Clearly, this leads to a very restrictive type of Lamp–What if we move the lamp? We could no longer use it●NB in a procedural design, this dependency on lower-level classes is actually very commonLampParticularOutletInCPsHouse
48The Lamp example in OOP, DIP-proof●The lamp now depends on an abstraction of the power source, instead of on a concrete class●We ‘inverted’ the dependency from low-level to high-levelLamp<<interface>>PowerSourceOutlet
49Dependency inversion allows us to reduce coupling between classes●Coupling indicates how closely two classes are tied together●High coupling means that making a change in one class is likely to require a change in the other class ●By depending on abstractions, we reduce coupling, since they take away details about the implementation●Note that you gain a lot in terms of flexibility – our program can now use any type of PowerSource
50Another application of DIP in practice●This principle is also the reason that we declare objects as ‘the most general/abstract’ type●It leads to more flexibility later onRaceCar rc = newFormulaOneCar();
51S.O.L.I.D. summarized●Keep in mind: these principles are not laws●You should aim to follow them as much as possible... but maintain good judgment●Make sure understand the principles and opportunities for applying them will arise–These should become very intuitive●We will consider these principles as parts of the code quality/cleanliness points on assignments from here on