2. Coding Humanely
● What if our code had feelings ?
● What if we treated people like we treat code ?
– e.g., Sir, you will need a foot transplant to be able to
use our latest range of trainers.
3. Desirable Code Qualities
● Simplicity
– Ease of understanding.
● Extensibility
– Ease of extending functionality.
● Flexibility
– Ease of adapting to the operating environment.
● Testability
– Ease of testing from units of code to the entire
systems
4. Challenges: Size and Complexity
● Problems
– Most real world problems requiring automation are
complex.
● Requirements
– Increasing demand for comprehensive automation.
● Solutions
– Resulting solutions are large and sophisticated.
5.
6. Modularisation
● Decomposing large systems into smaller and
simpler parts.
– Functions and procedures.
– Classes and modules.
● Building the parts.
● Assembling the parts to complete the system.
● To achieve desirable code qualities parts should
be interchangeable.
7. Keys to Interchangeability
● Coupling
– Interdependence between different software modules.
– Low coupling is preferred as it allows flexibility and
extensibility.
– Low coupling via simple and stable interfaces.
● Cohesion
– Degree to which elements of a module functionally
belong together.
– Higher cohesion reduces complexity of components.
● Higher cohesion and low coupling enables
interchangeability.
8. Spot Issues ?
package app;
import calc.Calculator;
public class App {
public static void main(String[] args) {
String line = System.console().readLine();
String[] parts = line.split(" ");
Calculator.operand1 = Double.parseDouble(parts[0]);
Calculator.operator = parts[1].charAt(0);
Calculator.operand2 = Double.parseDouble(parts[2]);
Calculator.calculate();
}
}
9. Spot Issues ?
package app;
import calc.Calculator;
public class App {
public static void main(String[] args) {
String line = System.console().readLine();
String[] parts = line.split(" ");
Calculator.operand1 = Double.parseDouble(parts[0]);
Calculator.operator = parts[1].charAt(0);
Calculator.operand2 = Double.parseDouble(parts[2]);
Calculator.calculate();
}
}
High Coupling
10. Spot Issues ?
package calc;
public class Calculator {
public static double operand1;
public static double operand2;
public static char operator;
public static void calculate() {
double result = Double.NaN;
switch(operator) {
case '+':
result = operand1 + operand2;
break;
case '-':
result = operand1 - operand2;
break;
case '*':
result = operand1 * operand2;
break;
case '/':
result = operand1 / operand2;
break;
default:
System.out.printf("Unrecognised operator %cn", operator);
}
System.out.printf("%f %c %f = %fn",
operand1, operator, operand2, result);
}
}
11. Spot Issues
package calc;
public class Calculator {
public static double operand1;
public static double operand2;
public static char operator;
public static void calculate() {
double result = Double.NaN;
switch(operator) {
case '+':
result = operand1 + operand2;
break;
case '-':
result = operand1 - operand2;
break;
case '*':
result = operand1 * operand2;
break;
case '/':
result = operand1 / operand2;
break;
default:
System.out.printf("Unrecognised operator %cn", operator);
}
System.out.printf("%f %c %f = %fn",
operand1, operator, operand2, result);
}
}
High Coupling
Low Cohesion
12. Simplifying with SOLID
● Types of coupling
– Content coupling, Common coupling, Stamp coupling,
Control coupling, Data coupling.
● Types of cohesion
– Co-incidental cohesion, Logical cohesion, Temporal
cohesion, Procedural cohesion, Communicational
cohesion, Sequential cohesion, Functional cohesion.
● SOLID
– Five principles of object oriented programming.
– Aims to deliver extensible and maintainable software.
13. SOLID
● Single Responsibility Principle (SRP)
– A class should have a single responsibility.
● Open Close Principle (OCP)
– Open for extension but closed for modification.
● Liskov Substitution Principle (LSP)
– Substitution of objects by instances of sub-classes.
● Interface Segregation Principle (ISP)
– Many client specific interfaces instead of one big one.
● Dependency Inversion Principle (DIP)
– Depend on abstractions instead of implementations.
15. Single Responsibility Principle
● A class should have one and only one reason
to change.
– A class should have only one responsibility.
● Promotes high cohesion and low coupling.
– High cohesion because of focused classes.
– Low coupling because of dependency on other
cohesive classes.
16. Example
public class MeanCalculator {
public double calculate(final String data) {
double sum = 0.0;
final String[] parsedData = data.split(",");
for (String item : parsedData) {
sum += Double.parseDouble(item);
}
return sum/parsedData.length;
}
}
17. Example
public class MeanCalculator {
public double calculate(final String data) {
double sum = 0.0;
final String[] parsedData = data.split(",");
for (String item : parsedData) {
sum += Double.parseDouble(item);
}
return sum/parsedData.length;
}
}
18. Example
public class MeanCalculator {
public double calculate(final String data) {
double sum = 0.0;
final String[] parsedData = data.split(",");
for (String item : parsedData) {
sum += Double.parseDouble(item);
}
return sum/parsedData.length;
}
}
Two reasons to change
1. Parsing
2. Computation
19. Example
Functional (Java 8) Implementation
public class MeanCalculator {
public double calculate(final List<Double> data) {
double sum = 0.0;
for (Double item : data) {
sum += item;
}
return sum/data.size();
}
}
public class MeanCalculator {
public double calculate(final List<Double> data) {
return data.
stream().
collect(Collectors.
averagingDouble(item -> item));
}
}
21. Open/Close Principle
● Software entities (Classes, methods, modules
etc.) should be open for extension but closed
for modification.
– Changing functionality need not require modifying
existing code.
● Preferred approaches,
– Inheritance,
– Composition of abstractions (plugins).
22. Example
public class Parser {
List<Double> parse(final String data) {
final String[] parsedCsv = data.split(",");
return toDoubleList(parsedCsv);
}
private List<Double> toDoubleList(final String[] data) {
final List<Double> list = new ArrayList<>();
for(String item: data) {
list.add(Double.parseDouble(item));
}
return list;
}
}
23. Example:Extendinga
ClosedParser
public class Parser {
public List<Double> parse(final String data, final Format format) {
String[] parsedData = null;
switch(format){
case CSV:
parsedData = data.split(",");
break;
case XML:
try {
parsedData = parseXml(data);
} catch (Exception exp) {
throw new IllegalArgumentException(exp);
}
break;
default:
throw new IllegalArgumentException(String.format("Unknown format %s", format));
}
return toDoubleList(parsedData);
}
/**
* Format: <DataList><Item>3</Item><Item>4.5</Item><Item>7.5</Item></DataList>
*/
private String[] parseXml(final String xmlString) throws Exception {
final Document xmlDoc = DocumentBuilderFactory.
newInstance().
newDocumentBuilder().
parse(new ByteArrayInputStream(xmlString.getBytes()));
final NodeList nodes = xmlDoc.getElementsByTagName("Item");
final String[] textList = new String[nodes.getLength()];
for (int i = 0; i < nodes.getLength(); i++) {
textList[i] = nodes.item(i).getTextContent().trim();
}
return textList;
}
private List<Double> toDoubleList(final String[] data) {
final List<Double> list = new ArrayList<>();
for(String item: data) {
list.add(Double.parseDouble(item));
}
return list;
}
}
24. public List<Double> parse(final String data,
final Format format) {
String[] parsedData = null;
switch(format){
case CSV:
parsedData = data.split(",");
break;
case XML:
try {
parsedData = parseXml(data);
} catch (Exception exp) {
throw new IllegalArgumentException(exp);
}
break;
default:
throw new IllegalArgumentException(
String.format("Unknown format %s", format));
}
25. Example: Opening With Template
Method
public abstract class Parser {
public List<Double> parse(final String data) {
final String[] parsedData = split(data);
return toDoubleList(parsedData);
}
protected abstract String[] split(final String data);
private List<Double> toDoubleList(final String[] data) {
final List<Double> list = new ArrayList<>();
for(String item: data) {
list.add(Double.parseDouble(item));
}
return list;
}
}
26. Example: Parser Using Streams
public abstract class Parser {
public List<Double> parse(final String data) {
final String[] parsedData = split(data);
return Arrays.asList(parsedData).stream().
map(item -> Double.parseDouble(item)).
collect(Collectors.toList());
}
protected abstract String[] split(final String data);
}
27. Example: Opening With Template
Method
public class CsvParser extends Parser {
@Override
protected String[] split(String data) {
return data.split(",");
}
}
28. public class XmlParser extends Parser {
/**
* Format:
* <DataList><Item>3</Item><Item>4.5</Item><Item>7.5</Item></DataList>
*/
@Override
protected String[] split(String data) {
String[] textList = null;
try {
final Document xmlDoc = DocumentBuilderFactory.
newInstance().
newDocumentBuilder().
parse(new ByteArrayInputStream(data.getBytes()));
final NodeList nodes = xmlDoc.getElementsByTagName("Item");
textList = new String[nodes.getLength()];
for (int i = 0; i < nodes.getLength(); i++) {
textList[i] = nodes.item(i).getTextContent().trim();
}
} catch (Exception exp) {
throw new IllegalArgumentException(exp);
}
return textList;
}
}
30. Liskov Substitution Principle
● Named after MIT professor Barbara Liskov.
● Functions that use references to base classes
must be able to use objects of the derived class
without knowing it.
– Derived classes must be substitutable for base class.
● Caution,
– Should not alter the behaviour of the application.
– Inheritance hierarchies should not violate domain
concepts.
● E.g., a square is a rectangle instead of both are shapes.
31. public class AvergerApp {
public static void main(String[] args) {
final List<String> params = Arrays.asList(args);
if ((params.size() != 2) ||
(params.stream().filter(i -> i.contains("=")).count() != 2)) {
System.err.println("Parameters: f=<CSV/XML> m=<message>");
} else {
final String format = params.stream().
filter(str -> str.contains("f")).findFirst().get().split("=")[1];
final String message = params.stream().
filter(str -> str.contains("m")).findFirst().get().split("=")[1];
final Parser parser = getParser(format);
final List<Double> data = parser.parse(message);
final double avg = new MeanCalculator().calculate(data);
System.out.printf("Average(%s) = %f", data, avg);
}
}
private static Parser getParser(final String format) {
Parser parser = null;
switch (format) {
case "CSV":
parser = new CsvParser();
break;
case "XML":
parser = new XmlParser();
break;
default:
throw new IllegalArgumentException("Unknown format " + format));
}
return parser;
}
}
32. public class AvergerApp {
public static void main(String[] args) {
final List<String> params = Arrays.asList(args);
if ((params.size() != 2) ||
(params.stream().filter(i -> i.contains("=")).count() != 2)) {
System.err.println("Parameters: f=<CSV/XML> m=<message>");
} else {
final String format = params.stream().
filter(str -> str.contains("f")).findFirst().get().split("=")[1];
final String message = params.stream().
filter(str -> str.contains("m")).findFirst().get().split("=")[1];
final Parser parser = getParser(format);
final List<Double> data = parser.parse(message);
final double avg = new MeanCalculator().calculate(data);
System.out.printf("Average(%s) = %f", data, avg);
}
}
private static Parser getParser(final String format) {
Parser parser = null;
switch (format) {
case "CSV":
parser = new CsvParser();
break;
case "XML":
parser = new XmlParser();
break;
default:
throw new IllegalArgumentException("Unknown format " + format));
}
return parser;
}
}
34. Interface Segragation Principle
● Multiple fine grained client or domain specific
interfaces are better than few coarse grained
interfaces.
● Consequences
– Classes only implement interfaces that are
requirement specific.
– Client classes are exposed only to the functionality
that they need.
40. Dependency Inversion Principle
● High-level modules should not depend on low-
level modules.
– Both should depend on abstractions.
● Abstractions should not depend on details.
– Details should depend on abstractions.
● Dependencies are a risk,
– Details bind dependants to concrete implementations.
● This limits flexibility and extensibility.
– Abstractions provides independence to dependants.
● Dependants are able to select most suitable
implementations.
41. Example: Averaging Calculator
● A class that uses
– An averaging algorithm to process input.
– A parser to parse text input to a collection of doubles.
– Input and output classes.
● Input formats can change.
– Binding the calculator to a concrete parser and IO
makes it inflexible.
– Averaging calculator references a parser, an input and
an output interface.
– The application specifies the parser and IO needed.
42. Example: Parser
public interface IParser {
List<Double> parse(final String data);
}
public abstract class Parser implements IParser {
public List<Double> parse(final String data) {
final String[] parsedData = split(data);
return Arrays.asList(parsedData).stream().
map(item -> Double.parseDouble(item)).
collect(Collectors.toList());
}
protected abstract String[] split(final String data);
}
43. Example: Input
public interface IInput {
String read();
}
public class FixedInput implements IInput {
private final String data;
public FixedInput(final String data) {
this.data = data;
}
@Override
public String read() {
return data;
}
}
44. Example: Output
public interface IOutput {
void write(final List<Double> data,
final double average);
}
public class ConsoleOutput implements IOutput {
@Override
public void write(final List<Double> data,
final double average) {
System.out.printf("Average%s = %fn",
data, average);
}
}
45. Example: Mean Processor
public interface IProcessor {
double process(List<Double> data);
}
public class MeanProcessor implements IProcessor {
public double process(final List<Double> data) {
return data.stream().
collect(Collectors.
averagingDouble(item -> item));
}
}
46. Example: Averaging Calculator
public class AveragingCalculator {
final private IInput input;
final private IOutput output;
final private IProcessor algo;
final private IParser parser;
public AveragingCalculator(final IProcessor algo,
final IParser parser,
final IInput input,
final IOutput output) {
this.algo = algo;
this.parser = parser;
this.input = input;
this.output = output;
}
public void run() {
final String inputData = input.read();
final List<Double> data = parser.parse(inputData);
final double average = algo.process(data);
output.write(data, average);
}
}
47. Example: CSV Averaging App
public class CsvAveragingApp {
public static void main(String[] args) {
final IInput input = new FixedInput(args[0]);
final IOutput output = new ConsoleOutput();
final IParser parser = new CsvParser();
final IProcessor algo = new MeanProcessor();
final AveragingCalculator calc =
new AveragingCalculator(algo,
parser,
input,
Output);
calc.run();
}
}
48. Example: XML Averaging App
public class XmlAveragingApp {
public static void main(String[] args) {
final IInput input = new FixedInput(args[0]);
final IOutput output = new ConsoleOutput();
final IParser parser = new XmlParser();
final IProcessor algo = new MeanProcessor();
final AveragingCalculator calc =
new AveragingCalculator(algo,
parser,
input,
output);
calc.run();
}
}
49.
50. Opposite of SOLID
Single Responsibility Principle
Open Close Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
51. Opposite of SOLID
Single Responsibility Principle
Open Close Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
S
T
U
P
I
D
52. Opposite of SOLID
Single Responsibility Principle
Open Close Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Singletons
Tight Coupling
Untestability
Premature Optimisation
Indescriptive Naming
Duplication