Vous êtes sur la page 1sur 69

SOLID, DRY, SLAP

Design principles
presenter: Sergey Karpushin

Agenda

Justification
DRY
SLAP
Single Responsibility
Open/Closed
Liskov substitution
Interface segregation
Dependency inversion
2

Justification 1/4
Fixing tons of
bugs

Im scared! This
will break
something
I need to expand
consciousness to have
a clue

Where you spend


time?
Implementing new
functionality
How do you feel
about new change
requests?
Its ok, just need to make
right design decision
How complicated
current codebase
is?
Its large, but easy to
understand and safe to
change

Justification 2/4
Fixing tons of
bugs

Im scared! This
will break
something
I need to expand
consciousness to have
a clue

Where you spend


time?
Implementing new
functionality
How do you feel
about new change
requests?
Its ok, just need to make
right design decision
How complicated
current codebase
is?
Its large, but easy to
understand and safe to
change

Signal words: urgent, stress, overtime, chaos,


more than 100 open bugs, who wrote this crap?
4

Justification 3/4
Fixing tons of
bugs

Im scared! This
will break
something
I need to expand
consciousness to have
a clue

Where you spend


time?
Implementing new
functionality
How do you feel
about new change
requests?
Its ok, just need to make
right design decision
How complicated
current codebase
is?
Its large, but easy to
understand and safe to
change

Signal words: ahead of schedule, only 3 open bugs,


thank you
5

Justification 4/4
OOD & other related principles
Takes time to understand and feel for
the first time
Relatively easy to apply later

Disclaimer:

NAMING CONVENTION

Naming convention
Design principles are common for all Object-Oriented
languages
Java naming conventions used within this
presentation
Name example for interfaces, DTOs and simple
classes:
UserService
Name example for classes which implements
interfaces
UserServiceImpl
All variable and parameter names example
camelCaseName
8

DRY + SLAP +
SOLID
DONT REPEAT YOURSELF

Dont Repeat Yourself

DRY 1/15
Do not Repeat Yourself
Reuse your code, do not duplicate it
Assign clear names
Choose right location

Clear names and right location allows


to identify duplicate candidates

10

Dont Repeat Yourself

DRY 2/15 reuse


Maintain single source of truth
Change once
Test once

Do not confuse with abstractions, its


different

11

Dont Repeat Yourself

DRY 3/15 reuse


Same code in multiple places

stm t.execute("SELECT nam e, em ailFRO M users W H ERE id = ?",id);

12

Dont Repeat Yourself

DRY 4/15 reuse


A change required?
i.e. database field renamed

stm t.execute("SELECT nam e, em ailFRO M users W H ERE id = ?",id);

13

Dont Repeat Yourself

DRY 5/15 reuse


A multiple changes required!

stm t.execute("SELEC T last_nam e, em ailFRO M users W H ERE id = ?", id);

14

Dont Repeat Yourself

DRY 6/15 reuse


What if code is in one place?
getU serById(id);

stm t.execute(
"SELECT nam e, em ailFRO M users W H ERE id = ?",
id);

15

Dont Repeat Yourself

DRY 7/15 reuse


Exactly! 1 change required.
getU serById(id);

stm t.execute(
"SELECT last_nam e,em ailFRO M users W H ERE id = ?",
id);

16

Dont Repeat Yourself

DRY 8/15 confusing w/


abstractions

Suppose we have Users and Articles


getU serN am eById(id);
stm t.execute(
"S ELEC T n am e FR O M u sers W H ER E id = ?",
id);

getArticleN am eById(id);
stm t.execute(
"S ELEC T n am e FR O M articles W H ER E id = ?",
id);

17

Dont Repeat Yourself

DRY 9/15 confusing w/


abstractions

Hey! It was a duplicate! We got rid of


it!
getO bjectN am eById(table, id);
stm t.execute(
"SELECT nam e FRO M ? W H ERE id = ?",
table, id);

18

Dont Repeat Yourself

DRY 10/15 confusing w/


abstractions

Incoming change request: Article


name must be displayed along with
author and date

19

Dont Repeat Yourself

DRY 11/15 confusing w/


abstractions

Complete mess, this is what you end


up with
?!?!?!?

?!?!?!?
getO bjectN am eById(table, id);

?!?!?!?

?!?!?!?
stm t.execute(
"SELECT nam e FRO M ? W H ERE id = ?",
id);

?!?!?!?

20

Dont Repeat Yourself

DRY 12/15 confusing w/


abstractions

For abstractions recommended


approach is
Write
Copy & Paste
Only then extract abstraction

21

Dont Repeat Yourself

DRY 13/15 clear names


Q: How can I determine if name is clear?
A: If javadoc will look redundant
Q: How to pick clear name?
A: Write java doc, get essence of it, use as name
Q: It sometimes so complicated to pick short but
still clear name!
A: Be honest with yourself, it happens rarely. In
such case write descriptive javadoc
22

Dont Repeat Yourself

DRY 14/15 clear names


An example
You can say almost nothing about it:
String[] l1 = getArr(str1);

But this one is pretty descriptive:


String[] fi
eldValues = parseCsvRow (csvRow );

23

Dont Repeat Yourself

DRY 15/15 right location


No one will guess that it might be
here!
package ru.it.projects.um m s.dom .users;
public class ArrayU tils {
}

24

DRY + SLAP +
SOLID
SINGLE LAYER OF
ABSTRACTION PRINCIPLE

ngle Layer of Abstraction Principle

SLAP 1/3
Single Layer of Abstraction Principle
for methods
Have common things with DRY, SRP, ISP
Basic idea is:
Do not write endless methods which logic is
hard to follow.
All instructions for each method should
operate on same level of abstraction and
related to one task
26

ngle Layer of Abstraction Principle

SLAP 2/3 (violated)

p rivate U serInfo retrieveU serInfo(String userToken) {


String userTokenParts[] = userToken.split(":");
lon g userId;
if (userTokenParts.length = = 1) {
userId = Long.parseLong(userToken);
} else {
if ("legacy".equals(userTokenParts[0])) {
userId = Long.parseLong(userTokenParts[1]);
} else {
userId = Long.parseLong(userTokenParts[0] + userTokenParts[1]);
}
}
String url= userServiceBasePath;
if ("legacy".equals(userTokenParts[0])) {
url+ = "/v1/getU serD ata?userId= " + userId;
} else {
url+ = "/v2/users/" + userId;
}
try {
H ttpResponse getResponse = client.execute(url);
fi
n al in t statusCode = getResponse.getStatusLine().getStatusCode();
if (statusCode != H ttpStatus.SC _O K) {
Log.w (getC lass().getSim pleN am e(), "Error " + statusCode + " for U RL " + url);
retu rn n u ll;
}

27

ngle Layer of Abstraction Principle

SLAP 3/3 (following)

p rivate U serInfo retrieveU serInfo(String userToken) {


lon g userId = extractU serIdFrom Token(userToken);
if (isLegacyU ser(userToken)) {
retu rn retreiveLegacyU serInfo(userId);
} else {
retu rn retreiveU serInfo(userId, extractU serD om ainIdFrom Token(userToken));
}
}

The only concern: youll have more


methods.
Yes, but each of them is clear and
damn simple to follow (thus
maintain)

28

SOLID

OPEN-CLOSED PRINCIPLE

Open-Closed Principle

OCP 1/11 general idea


Open-Closed Principle
Class should be
Open for extension
Closed for modification

Effects
Increased stability existing code
(almost) never changes
Increased modularity, but many small
classes
30

Open-Closed Principle

OCP 2/11 i.e.


Customer brings new user story:
As the user I want to see results of
past games. Lets keep up with NHL
& NBA games

31

Open-Closed Principle

OCP 3/11 i.e.


public class S p ortIn foParser {
public S p ortIn fo parseSportInfo(String[] sportInfoArray) {
if ("nba".equals(sportInfoArray[0])) {
N baSportInfo nbaSportInfo = n ew N b aS p ortIn fo();
M ap< Long, Integer> scores = new H ashM ap< Long, Integer> ();
scores.put(Long.parseLong(sportInfoArray[12]), Integer.parseInt(sportInfoArray[13]));
N baSportInfo.setScores(scores);
return nbaSportInfo;
} else if ("nhl".equals(sportInfoArray[0])) {
N hlSportInfo nhlSportInfo = n ew N h lS p ortIn fo();
nhlSportInfo.setSlapShotCount(1);
return nhlSportInfo;
}
}
}

32

Open-Closed Principle

OCP 4/11 i.e.


Customer feedback:
Thank you, it works well!
And brings another user story:
As the user I want to see results of
MLB games

33

Open-Closed Principle

OCP 5/11 i.e.


public class SportInfoParser {
public SportInfo parseSportInfo(String[] sportInfoArray) {
if ("nba".equals(sportInfoArray[0])) {
N baSportInfo nbaSportInfo = new N baSportInfo();
M ap< Long, Integer> scores = new H ashM ap< Long, Integer> ();
scores.put(Long.parseLong(sportInfoArray[12]), Integer.parseInt(sportInfoArray[13]));
N baSportInfo.setScores(scores);
return nbaSportInfo;
} else if ("nhl".equals(sportInfoArray[0])) {
N hlSportInfo nhlSportInfo = new N hlSportInfo();
nhlSportInfo.setSlapShotCount(1);
return nhlSportInfo;
} else if (sp ortIn foA rray[0].eq u alsIg n oreC ase("m lb ")) {
M lbSportInfo m lbSportInfo = n ew M lb S p ortIn fo();
m lbSportInfo.setH its(Integer.parseInt(sportInfoArray[1]));
m lbSportInfo.setRuns(Integer.parseInt(sportInfoArray[2]));
return m lbSportInfo;
}
}
}

34

Open-Closed Principle

OCP 6/11 i.e.


Customer feedback:
Why users see errors? I pay you for
working app, not for errors! Make it
work right till evening!
After debugging you found silly NPE
mistake

35

Open-Closed Principle

OCP 7/11 i.e.


public class SportInfoParser {
public SportInfo parseSportInfo(String[] sportInfoArray) {
if ("nba".equals(sportInfoArray[0])) {
N baSportInfo nbaSportInfo = new N baSportInfo();
M ap< Long, Integer> scores = new H ashM ap< Long, Integer> ();
scores.put(Long.parseLong(sportInfoArray[12]), Integer.parseInt(sportInfoArray[13]));
N baSportInfo.setScores(scores);
return nbaSportInfo;
} else if ("nhl".equals(sportInfoArray[0])) {
N hlSportInfo nhlSportInfo = new N hlSportInfo();
nhlSportInfo.setSlapShotCount(1);
return nhlSportInfo;
} else if (sp ortIn foA rray[0].eq u alsIg n oreC ase("m lb")) { // N P E occu red
M lbSportInfo m lbSportInfo = new M lbSportInfo();
m lbSportInfo.setH its(Integer.parseInt(sportInfoArray[1]));
m lbSportInfo.setRuns(Integer.parseInt(sportInfoArray[2]));
return m lbSportInfo;
}
}
}

36

Open-Closed Principle

OCP 8/11 i.e.


You did small change, but whole class
became unstable
OCP is violated - we modified existing
code
How to make code open for
extension?
Use inheritance
Use delegation/composition
37

Open-Closed Principle

OCP 9/11 i.e.


public class SportInfoParserD elegatingIm plim plem ents S p ortIn foParser {
private M ap< String, S p ortIn foParser> parsers;
p u b lic SportInfoParserD elegatingIm pl (M ap < S trin g , S p ortIn foParser> p arsers) {
this.parsers = parsers;
}
public SportInfo parseSportInfo(String[] sportInfoArray) {
SportInfoParser parser = p arsers.g et(sp ortIn foA rray[0]);
if (parser = = null) {
return null;
}
retu rn p arser.p arseS p ortIn fo(sp ortIn foA rray);
}
}
public class SportInfoParserM baIm plim plem ents SportInfoParser { ... }
public class SportInfoParserN hlIm plim plem ents SportInfoParser { ... }
public class SportInfoParserM lbIm plim plem ents SportInfoParser { ... }

38

Open-Closed Principle

OCP 10/11 i.e.


You fixed bug and did refactoring to
follow OCP
Customer feedback:
Great, now it works well! Lets add
support for WNBA!

39

Open-Closed Principle

OCP 11/11 i.e.


public class SportInfoParserD elegatingIm plim plem ents SportInfoParser {// N o ch an g es req u ired !
private M ap< String, SportInfoParser> parsers;
public SportInfoParserD elegatingIm pl (M ap< String, SportInfoParser> parsers) {
this.parsers = parsers;
}
public SportInfo parseSportInfo(String[] sportInfoArray) {
SportInfoParser parser = parsers.get(sportInfoArray[0]);
if (parser = = null) {
return null;
}
return parser.parseSportInfo(sportInfoArray);
}
}
public class SportInfoParserM baIm plim plem ents SportInfoParser { ... }
public class SportInfoParserN hlIm plim plem ents SportInfoParser { ... }
public class SportInfoParserM lbIm plim plem ents SportInfoParser { ... }
p u b lic class S p ortIn foParserW n b aIm p l im p lem en ts S p ortIn foParser { ... }

40

SOLID

SINGLE RESPONSIBILITY
PRINCIPLE

ngle Responsibility Principle

SRP 1/8 - idea


Single class should have a single
responsibility
Class should have only one reason to
change
Friendly principles:
DRY less responsibility more descriptive
class and method names, right location
OCP less responsibility less reason for
modification
42

ngle Responsibility Principle

SRP 2/8 ex. 1


public class C u rren cyC on verter {
public BigD ecim alconvert(Currency from ,C urrency to, BigD ecim alam ount) {
// 1. asks som e online service to convert currency
// 2. parses the answ er and returns results
}
public BigD ecim alg etIn f l
a tion In d ex(Currency currency, D ate from , D ate to) { // 3
// 4. ask som e online service to get data about currency infl
ation
// 5. parses the answ er and returns results
}
}

1, 2, 4, 5: What if online service changes?


3: Why getInfl
ationIndex located in
CurrencyConverter?!
Principles violated: SRP, DRY
43

ngle Responsibility Principle

SRP 3/8 ex. 1


public class C u rren cyC on verter {
public BigD ecim alconvert(Currency from ,C urrency to, BigD ecim alam ount) {
// asks som e online service to convert currency
// parses the answ er and returns results
}
}
public class In f l
a tion In d exC ou n ter {
public BigD ecim alg etIn f l
a tion In d ex(Currency currency, D ate from , D ate to) {
// ask som e online service to get data about currency infl
ation
// parses the answ er and returns results
}
}

Now each class have only single responsibility


Will change only CurrencyConverter if currency conversion
logic changes
Methods placed at right location
Names are clear
44

ngle Responsibility Principle

SRP 4/8 ex. 2


public class U serAuthenticator {
public boolean authenticate(String usernam e,String passw ord) {
U ser user = getU ser(usernam e);
return user.getPassw ord().equals(passw ord);
}
private U ser g etU ser(String usernam e) { // 1
// ...
st.execu teQ u ery("select u ser.n am e, u ser.p assw ord from u ser w h ere id = ?"); // 2
// ...
return user;
}
}

1: Why getUser located in UserAuthenticator?


2: What if jdbc will be changed to ORM or user
might come from different sources?
Principles violation: SRP, DRY, OCP
45

ngle Responsibility Principle

SRP 5/8 ex. 2


public class U serAuthenticator {
private U serD etailsService userD etailsService;
public U serAuthenticator(U serD etailsService service) {
userD etailsService = service;
}
public boolean authenticate(String usernam e,String passw ord) {
U ser user = u serD etailsS ervice.g etU ser(u sern am e);
return user.getPassw ord().equals(passw ord);
}
}

Now UserAuthenticator has single


responsibility
UserAuthenticator doesnt depend on specific
UserDetailsService implementation
46

ngle Responsibility Principle

SRP 6/8 ex. 3


public class D ocum entServiceIm pl{
D ocum ent getCachedD ocum entU uid(String docum entU uid) { ...};
SignedD ocum ent signD ocum ent(D ocum ent docum ent, Signature signature) { ... };
boolean isD ocum entSignatureValid(SignedD ocum ent signedD ocum ent) { ...};
}

Task 1: need new implementation of


DocumentService which uses
ehcache as caching mechanism
47

ngle Responsibility Principle

SRP 7/8 ex. 3


public class D ocum entServiceEh C ach eIm plextends D ocu m en tS erviceIm p l {
@ O verrid e
public D ocum ent getCachedD ocum entU uid(String docum entU uid) {
// som e im pl
}
}

Task 2: now we need new impl which


the same as base but uses different
way of verifying document signature

48

ngle Responsibility Principle

SRP 8/8 ex. 3


public class D ocum entServiceG ost1024 SignIm plextends D ocu m en tS erviceIm p l {
@ O verrid e
public SignedD ocum ent signD ocum ent(D ocum ent docum ent, Signature signature) {
// som e im pl
}
@ O verrid e
public boolean isD ocum entSignatureValid(SignedD ocum ent signedD ocum ent) {
// som e im pl
}
}

Task 3: ok, now we need impl which


uses ehcache as caching mechanism
and new signature verification
mechanism
49

SOLID

INTERFACE SEGREGATION
PRINCIPLE

terface Segregation Principle

ISP 1/4 - idea


Client code shouldnt be obligated to depend on
interfaces it doesnt use
Its better if class implements many small
interfaces rather than one big/fat/polluted
interface
Initially hard to distinguish with SRP, but there is
a difference
Friendly principles
DRY if ISP is violated, it might lead to code/logic
duplicate
51

terface Segregation Principle

ISP 2/4 i.e.


public interface Spam D etector {
boolean isLooksLikeSpam (Q u ickM essag e quickM essage); // 2
}
public interface Q uickM essage {
Author getAuthor();
String getM essageText();
}

2: SpamDetector depends on
QuickMessage interface, but there is no
need to access author, why we need it
here?!
Q: What will happen if you need to detect
spam in file or, lets say, email?
52

terface Segregation Principle

ISP 3/4 i.e.


public interface Spam D etector {
boolean isLooksLikeSpam (Q uickM essage quickM essage);
}
public interface Q uickM essage {
Author getAuthor();
String getM essageText();
}
// ???
public interface TextFileContent {
S trin g g etTextC on ten t();
}
// ???
public interface Em ailM essage { // i.e. Leg acy D TO you can t ch an g e
Author getAuthor();
S trin g g etS u b ject();
S trin g g etB od y();
}

53

terface Segregation Principle

ISP 4/4 i.e.


public interface Spam D etector { // Its very g en eric n ow !
boolean isLooksLikeSpam (H asText hasText);
}
p u b lic in terface Q u ickM essag e exten d s H asText { ... }
p u b lic in terface TextFileC on ten t exten d s H asText { ... }
public class Leg acyEm ailM essag eH asTextA d ap ter im plem ents H asText {
public LegacyEm ailM essageH asTextAdapter(Em ailM essage em ailM essage) {
this.em ailM essage = em ailM essage;
}
@ O verride
public String g etText() {
return em ailM essage.getSubject() + em ailM essage.getBody();
}
}

54

SOLID

DEPENDENCY INVERSION
PRINCIPLE

ependency Inversion principle

DI 1/4 - idea
Dependency Inversion
Code to abstraction, not to implementation
Objects that use other objects, shouldnt
create latter ones

Profits
Unit testing is possible
Easy change of concrete implementation
Each module depends on minimum and
well-defined outer abstrations
56

ependency Inversion principle

DI 2/4 i.e.
public interface H tm lParser {
H tm lD ocum ent parseU rl(String url);
}
public class D om BasedH tm lParser im plem ents H tm lParser {
public H tm lD ocum ent parseU rl(String url) {
// do the best
}
}
public class Craw ler {
public void saveH tm lD ocum ent() {
D om B ased H tm lParser p arser = n ew D om B ased H tm lP arser();
H tm lD ocum ent docum ent = parser.parseU rl("http://.....");
// do craw l
}
}

Unit testing of Crawler is not possible, you cant mock parser


Crawler is not flexible, you cant just use other implementattion
57

ependency Inversion principle

DI 3/4 i.e.
public class Craw ler {
p rivate D om B ased H tm lP arser d om B ased H tm lP arser;
p u b lic void C raw ler(D om B ased H tm lP arser d om B ased H tm lP arser) {
this.dom BasedH tm lParser = dom BasedH tm lParser;
}
public void saveH tm lD ocum ent() {
H tm lD ocu m en t d ocu m en t = d om B ased H tm lP arser.p arseU rl("h ttp ://.....");
// do craw l
}
}

Ok, now we can inject it and do unit


testing
But still locked on specific implementation
58

ependency Inversion principle

DI 4/4 i.e.
public class Craw ler {
p rivate H tm lP arser h tm lParser;
p u b lic void C raw ler(H tm lParser h tm lParser) {
this.h tm lP arser = h tm lParser;
}
public void saveH tm lD ocum ent() {
H tm lD ocu m en t d ocu m en t = h tm lParser.p arseU rl("h ttp ://.....");
// do craw l
}
}

Great! Now Crawler dont depend on


outer world specific and its not really
interested in implementation details
59

SOLID

LISKOV SUBSTITUTION
PRINCIPLE

skov Substitution Principle

LSP 1/8 - idea


Liskov Substitution Principle
Says: derived types must be completely
substitutable for their base types
Helps to use inheritance correctly
Helps to abstract from specific
implementation

Friendly principles
DI
SRP, ISP
61

skov Substitution Principle

LSP 2/8 - idea


Major examples of LSP violation
Sub-class implements only some methods,
other look redundant and weird
Some methods behavior violates contract
equals() method symmetry requirement is
violated
Subclass throws exception which are not
declared by parent class/interface (java
prevents from introducing checked
exceptions)
62

skov Substitution Principle

LSP 3/8 redundant methods


class Bird extends Anim al{
@ O verride
public void w alk() { ...}
@ O verride
public void m akeO ff
spring() { ... };
public void f l
y () {...} // w illlook w eird for Em u
}
class Em u extends Bird {
public void m akeO ff
spring() {...}
}

63

skov Substitution Principle

LSP 4/8 redundant methods


class B ird extends Anim al{
@ O verride
public void w alk() { ...}
@ O verride
public void m akeO ff
spring() { ... };
}
class Flyin g B ird extends Bird {
public void f l
y () {...}
}
class Em u extends B ird {
public void m akeO ff
spring() {...}
}

64

skov Substitution Principle

LSP 5/8 contract violation


interface ArraySorter {
O bject[] sort(O bject[] args);
}
class D efaultArraySorter im plem ents ArraySorter {
public O bject[] sort(O bject[] array) {
O bject[] result = array.clone();
// ...
}
}
class Q uickArraySorter im plem ents ArraySorter {
public O bject[] sort(O bject[] array){
O b ject[] resu lt = array;
// orig in al array ch an g ed ! Error! N eg ative sid e-ef f
e ct!
}
}

65

skov Substitution Principle

LSP 6/8 buggy equals()


public class P oin t {
private int x;
private int y;
@ O verride
public boolean eq u als(O bject o) {
if (this = = o) return true;
if (!(o instanceof Point)) return false;
Point point = (Point) o;
if (x != point.x) return false;
if (y != point.y) return false;
return true;
}
}

66

skov Substitution Principle

LSP 7/8 buggy equals()


public class C olored P oin t exten d s P oin t {
private int color;
@ O verrid e
public boolean eq u als(O bject o) {
if (this = = o) return true;
if (!(o in stan ceof C olored P oin t)) return false;
if (!super.equals(o)) return false;
C oloredPoint that = (C oloredPoint) o;
if (color != that.color) return false;
return true;
}
}
Point point = new Point(1, 1);
C oloredPoint coloredPoint = new ColoredPoint(1, 1, 1);
point.equals(coloredPoint)) = = tru e
coloredPoint.equals(point)) = = false

67

skov Substitution Principle

LSP 8/8 buggy equals()


public class ColoredPoint {
private Point point; // U se d eleg ation in stead of in h eritan ce!
private int color;
}
Point point = new Point(1, 1);
C oloredPoint coloredPoint = new ColoredPoint(1, 1, 1);
point.equals(coloredPoint) = = false
coloredPoint.equals(point) = = false

68

THATS IT, THANK YOU!

p.s. Please,
dont write BS
which makes my
eyes bleeding

Vous aimerez peut-être aussi