oo inheritance - not always evil - refactoring to open-closed with inheritance

113
Refactoring an unmaintainable mess of nested condi3onals to the Open/Closed principle using OO inheritance an example Inheritance is not always evil! © 2014 Philip Schwarz hHps://twiHer.com/philip_schwarz [email protected] This work is licensed under a Crea3ve Commons AHribu3on NonCommercial NoDerivs 3.0 Unported License .

Upload: philip-schwarz

Post on 17-Jul-2015

154 views

Category:

Software


0 download

TRANSCRIPT

Refactoring  an  unmaintainable  mess  of  nested  condi3onals  to  the  Open/Closed  principle    

 -­‐  using  OO  inheritance  -­‐  

 -­‐  an  example  -­‐  

Inheritance  is  not  always  evil!  

©  2014  Philip  Schwarz  hHps://twiHer.com/philip_schwarz  [email protected]  

     

This  work  is  licensed  under  a  Crea3ve  Commons  AHribu3on-­‐NonCommercial-­‐NoDerivs  3.0  Unported  License.  

This  talk  complements  and  extends  some  aspects  of  my  OCP  talk:  

Downloadable  at  hHps://github.com/philipschwarz/presenta3ons  

Shortly  aVer  giving  the  OCP  talk  I  watched  the  following:  

Sandi’s  talk  complements  and  extends  parts  of  mine  so  well      

hHp://www.poodr.com/  

that  I  just  HAVE  TO  show  you  the  video!  

So  this  is  not  your  regular  talk.    

I’ll  wrap  my  slides  around  her  talk  •  slides  at  the  beginning:  

•  to  give  you  addi3onal  background  that  helps  you  follow  the  video  

•  to  link  back  to  the  topics  of  my  other  presenta3on  

•  slides  at  the  end:  to  recap  and  elaborate  some  of  Sandi’s  key  points  

What  I’ll  actually  be  doing  is  showing  you  the  video  of  Sandi’s  talk  (A  talk  within  a  talk!).  

The  Open-­‐Closed  Principle  

Modules  should  be  both  open  and  closed  

Bertrand  Meyer  Object  Oriented  SoVware  Construc3on  

1988  

We  want  modules  to  be  both                                  for  extension  and                              for  modifica3on  

the  two  goals  of    openness  and  closedness  

 

with  tradiEonal  techniques  

are  incompaEble  

With  non-­‐OO  methods,    there  seem  to  be  only  2  soluEons,    equally  unsaEsfactory  

If  non-­‐OO  methods  are  all  we  have,  then  Meyer  says  we  face  a  change  or  copy  dilemma  

CHANGE   COPY  

A  B   C  

D  

E  

A  module  and  its  clients  

A’  F  

G   H   I  

New  clients  which  need  A’,  an  adapted  or  extended  version  of  A  

Typical  situa3on  where  the  needs  for  Open  and  Closed  modules  are  hard  to  reconcile  

=    client  of  

Analysability   Stability   Reliability  

soluEon  

-­‐ -­‐ -­‐

Analysability   Stability   Reliability  

soluEon  

-­‐   -­‐   -­‐  

With  non-­‐OO  methods,  there  are  only  only  2  soluEons  available  to  us,    BOTH  UNSATISFACTORY  

mulEple  maintenance  problem  

Change  by    Modifica3on  

CHANGE  

COPY  

A  B   C  

D  

E  

A’  F  

G   H   I  

So  how  can  we  have  modules  that  are  both                                and                                    ?  

How  can  we  keep  A  and  everything  in  the  top  part  of  the  figure  unchanged,  …  

…while  providing  A’  to  the  boHom  clients,  and  avoiding  duplicaEon  of  soTware?    

A  B   C  

D  

E  

A’  F  

G   H   I  

With  the  OO  concept  of  inheritance  

Inheritance  allows  us  to  get  out  of  the  CHANGE  OR  COPY  dilemma…  

…because  inheritance  allows  us  to  define  a  new  module  A'  in  terms  of  an  exisEng  module  A,  …by  staEng  only  the  differences  between  the  two    

A’  defines  new  features,  and  redefines  (i.e.  modifies)  one  or  more  of  A’s  features  

inherits  from  

Change  by    Addi3on  

Hacking = Slipshod approach to building and modifying code

Slipshod = Done poorly or too quickly; careless.

The  Hacker    may  seem    bad  

but  oVen  his    heart  is  pure.  

He  sees  a  useful  piece  of  soVware,  which  is  almost  able  to  address  the  needs  of  the    moment,  more  general  than  the  soVware’s  original  purpose.    

Hacker    

Spurred  by  a  laudable  desire  not  to  redo  what  can  be  reused,  our  hacker  starts    modifying  the  original  to  add  provisions  for  new  cases  

soluEon  

The  impulse  is  good  but  the  effect  is  oVen  to  pollute  the  soVware  with  many  clauses  of  the  form    if  that_special_case  then…  

if  (<special  case  D>)  then …  

if  (<special  case  C>)  then …  

if  (<special  case  B>)  then …  

if  (<special  case  A>)  then …  

switch!

Open-Closed Principle =  

One  way  to  describe  the  OCP  and  the  consequent  OO  techniques  is  to  think  of  them  as  organised  hacking  

Hacking  

The  organised  form  of  hacking  will  enable  us  to  cater  to  the  variants    without  affecEng  the  consistency  of  the  original  version.  

Inheritance  

Change  by    Modifica3on  

Change  by    Addi3on  

extends  is  evil!!!!!  

But,  using  inheritance  is  no  longer  the  main  approach    to  sa3sfying  the  OCP  

Allen  Holub   2004  

2003  

Using  inheritance  is  s3ll  one  of  the  ways  of  saEsfying  the  OCP,    and  was  considered  THE  approach  for  a  long  while  

Why  extends  is  evil  

1988  -­‐  1st  ed.     1997  –  2nd  ed.    

1995    

That  started  changing  with  the    emergence  of  the    design  techniques  presented  in  Design  Pa\erns  

2003  

mulEple  maintenance  problem  

Change  by    Modifica3on  

CHANGE  soluEon  

COPY    soluEon  

Hacker    

Change  by    Addi3on  

OCP  soluEon  

Chooses  

Chooses  switch!

extends    is  evil!!!!!  

But…  

Sandi  deals  with  a  perfect  example  of  the  unmaintainable  mess  that  you  end  up  with  when  you  keep  extending  code  by  adding  condi3onals  

switch!

The  CondiEonal  Sandi    will  be  dealing  with  

switch!

REFACTOR  

Sandi  deals  with  the  43-­‐line  condi3onal  by  refactoring  it    so  it  sa3sfies  the  Open  Closed  Principle  

switch!

Inheritance  

       Sandi  uses  the  original  approach  to  sa3sfying  the  OCP:    using  OO  inheritance                So  we  get  a  nice  example  of  the  technique  

inheritance  is  not  evil,    and  I  can  tell  you  exactly  when  it  is  safe  to  use  it  

switch!

Sandi’s  talk  also…    

looks  a  liHle  bit  at  why    switch  creep  takes  root  

acts  as  an  example  of  refactoring  from  procedural  code  to  OO  code  

contains  other  bits  of    her  design  wisdom  

Refactoring  

Sandi’s  talk  is  centered  around  the  Gilded  Rose  Kata  

A  code  kata  is  an  exercise  in  programming  which  helps  a  programmer  hone  their  skills  through  prac3ce  and  repe33on  

A  kata  is  an  exercise  in  karate  where  you  repeat  a  form  many,  many  3mes,  making  liHle  improvements  in  each  

The  intent  behind  a  code  kata  is  similar  

The  Gilded  Rose  Inn  

An  Inn  is  where  travellers  can  seek  lodging  and,  usually,  food  and  drink  

The  Gilded  Rose  Kata  centers  around  a  fic3onal  Inn  in  a  game  called  World  of  WarcraT      

Hi  and  welcome  to  team  Gilded  Rose.  As  you  know,  we  are  a  small  inn  with  a  prime  loca3on  in  a  prominent  city  ran  by  a  friendly  innkeeper  named  Allison.      

hHp://iamnotmyself.com/2011/02/13/refactor-­‐this-­‐the-­‐gilded-­‐rose-­‐kata/  

The  Original  Gilded  Rose  Kata  

Aged  Brie  

Sulfuras,  Hand  of  Ragnaros  

Backstage  Pass  to  a    TAFKAL80ETC  concert  +5  Dexterity  Vest  

Elixir  of  the    Mongoose  

 We  also  buy  and  sell  only  the  finest  goods.  Unfortunately,  our  goods  are  constantly  degrading  in  quality  as  they  approach  their  sell  by  date.  We  have  a  system  in  place  that  updates  our  inventory  for  us.  It  was  developed  by  a  no-­‐nonsense  type  named  Leeroy,  who  has  moved  on  to  new  adventures.  

Your  task  is  to  add  the  new  feature  to  our  system  so    that  we  can  begin  selling  a  new  category  of  items.      

First  an  introduc3on  to  our  system…  

‘sellIn’  denotes  the  number  of  days  we  have  to  sell  the  Item  

int sellIn = 4;!

All  items  have  a    sellIn  value    

‘quality’  denotes  how  valuable  the  item  is  

int quality = 7;!

All  items  have  a    quality  value    

At  the  end  of  each  day  the  system  lowers  both  values  for  every  item  

for (Item item : items) !{!!!!!!}!

sellIn -= X! quality -= Y!

PreHy  simple,  right?    

!!!!!!

quality -= Y!

Once  the  sell  by  date  has  passed…  

…quality  degrades  twice  as  fast  

x  2  

•  Once  the  sell  by  date  has  passed,  Quality  degrades  twice  as  fast  •  The  Quality  of  an  item  is  never  nega3ve  •  “Aged  Brie”  actually  increases  in  Quality  the  older  it  gets  •  The  Quality  of  an  item  is  never  more  than  50  •  “Sulfuras”,  being  a  legendary  item,  never  has  to  be  sold  or  decreases  in  Quality  •  “Backstage  passes”,  like  aged  brie,  increases  in  Quality  as  it’s  SellIn  value    

approaches;      Quality  increases  by  2  when  there  are  10  days  or  less  and  by  3  when  there  are      5  days  or  less  but  Quality  drops  to  0  aVer  the  concert  

That’s  just  the  first  of  several  addi3onal  ‘rules’  affec3ng  items:  

It  is  not  hard  to  imagine  developers  implemen3ng  those  requirements  one  at  a  3me,  each  3me  adding  one  or  more  branches  to  the  condi3onal.  

switch!

We  have  recently  signed  a  supplier  of  conjured  items.      

Feel  free  to  make  any  changes  to  the  UpdateQuality  method  and  add  any  new  code  as  long  as  everything  s3ll  works  correctly.  

However,  do  not  alter  the  Item  class  or  Items  property  as  those  belong  to  the  goblin  in  the  corner  who  will  insta-­‐rage  and  one-­‐shot  you  as  he  doesn’t  believe  in  shared  code  ownership  

Conjured  Item  

Conjured    Mana  Cake  

X  

This  requires  an  update  to  our  system:    “Conjured”  items  degrade  in  Quality  twice  as  fast  as  normal  items  

That  was  the  original  kata  (in  C#)  

Emily  Bache  

This  is  designed  as  a  refactoring  kata,  where  you  take  this  less  than  clean  code,  and  transform  it  via  small  steps  into  something  that  can  be  maintained  and  extended.  

Conjured  Item  

When  you  have  the  code  under  control,  it  should  be  easy  to  add  the  new  feature  for  “Conjured”  items.  

Two  approaches  to  this  Kata  •  In  the  original  version  of  this  Kata,  there  were  no  tests  provided,  

only  a  textual  descrip3on  of  the  requirements.    •  In  a  later  addiEon,  I  added  Text-­‐Based  (aka  Approval)  tests.      

Emily  Bache  

So  you  can  do  this  kata  in  two  ways,    •  either  wriEng  your  own  tests,  and  prac3ce  wri3ng  really  good  ones    •  or  just  jump  straight  to  the  refactoring  part,  leaning  on  the  text-­‐based  tests.  

Sandi  takes  neither  of  these  approaches:    

https://github.com/jimweirich/gilded_rose_kata

The  original  had  no  tests.  Since  this  is  a  refactoring  kata,  I  feel  the  tests  are  important  and  provide  a  fairly  complete  test  suite.  Just  delete  the  tests  if  you  wish  to  "go  it  alone".  

she  uses  a  Ruby  version  of  the  kata  that  already  has  tests  

Sandi  also  ignores  the  restric3on  we  saw  earlier:    ‘do  not  alter  the  Item  class  or  Items  property’        

In  fact  she  goes  further:  she  doesn’t  even  look  at  the  explana3on  of  the  problem.  

She  just  takes  the  code  and  tries  to    add  the  required  new  func3onality  

X  

“But  I  didn’t  do  that  [look  at  the  problem  explana3on].  I  wanted  to  treat  this  problem  as  if  it  was  a  real  produc3on  problem,  and  that  my  only  source  of  informa3on  was  the  tests  and  the  code”  

Watch  ‘All  the  LiHle  Things’  (just  under  40  minutes  long)  

hHps://www.youtube.com/watch?v=8bZh5LMaSmE    hHp://www.confreaks.com/videos/3358-­‐railsconf-­‐all-­‐the-­‐liHle-­‐things  

Let’s Recap and Elaborate Some of Sandi’s

Take Home Points

RailsConf  2014  

Ruby  on  Ales  2014  

There  are  two  versions  of  Sandi’s  Talk:  

In  recapping,  I’ll  sample  from  both  versions.  

Your  task  is  to  add  the  new  feature  to  our  system  so    that  we  can  begin  selling  a  new  category  of  items.      

I  went  and  I  tried.    

I  tried  really  hard,  but  I  failed  miserably.  I  could  not  do  it.    

I  spent  hours  trying…    I  found  it  impossible  

if … !then … !else …!

IMPOSSIBLE  

                     

ALL  

If  it  is  so  hard,  if  it  is  impossible  to  change  that  if  statement,    

and  I  am  supposed  to  be  all  OOP,      

then  you  have  to  wonder  why  I  even  tried.    What  made  me  choose  as  my  strategy,  changing  that  if  statement?  

Well  it  is  because  I  felt    I  was  supposed  to  do  it.  

And  here  is  what  happens,  right?  

you  write  some  code  

someone  asks  for  a  change    

What  do  we  do?  You  go  looking  around  the  codebase  for  code  that  is  the  closest  thing  to  the    thing  that  you  are  trying  to  do.  You  put  the  new  code  there  

if … !then … !else …!

if … !then … !else …!

Maybe  the  first  person  that  wrote  it  put  an  if  statement  in.    

And  if  statements  exert  gravita3onal  pull.  

 and  if  that  thing  already  has  an  if  statement,  well  they  just  put  in  another  branch  on  it,  right?  that’s  how  it  works.  

Novices  especially,  they  are  afraid  to  make  new  objects  so  the  just  go  put  more  code  in    where  they  can  find  the  thing  they  are  trying  to  add    

So,  the  natural  tendency  of  code  is  to  grow  bigger  and  bigger  and  bigger,  and  there  comes  a  point  when  it  gets  big  enough  that  it  3ps  

DIS  

and  it  feels  like  even  if  you  are  the  kind  of  person  who  would  normally  make  a  new  class,    

       

   that  you  would  be  doing  the  past  and  the  future  a  disservice  by  pu|ng  the  code  anywhere  else.  

at  that  point  it  is    so  big  that  you  cannot  imagine  pu|ng  code  anywhere  else.  

if … !then … !else …!

if … !then … !else …!

when  you  have  a  5000  line  in  an  ac3ve  record,  you  do  not  make  a  20  line  service  object  that  goes  with  it  when  you  have  a  new  requirement,  you  feel  like  you  have  to  put  the  code  where  it  is  

Service  X  

Let’s  take  a  2  minute  detour  to  see  a  real-­‐world  example  of  a  condi3onal  that  has  grown  well  beyond  the  3pping  point.      

switch!

Detour  over.  Now  back  to  Sandi’s  talk  è  è  è  

If  the  paHern  is  a  good  one  then  the  code  gets  beHer,  and  if  the  paHern  is  a  bad  one,  we  exacerbate  the  problem.  

We  have  a  bargain  to  follow  the  paHern.      

And  so  the    paHern    failed  me.    

That  if  statement,  it  felt  like  it  had  evolved,    evolu3onarily,        

if … !then … !else …!

if … !then … !else …!

and  that  any  change  I  made  I  was  supposed  to  make  there  

But  fortunately  I  couldn’t  do  it.    

And  so  I  decided  I  would  make  a  new  paHern:    

I  am  not  going  to  try  to  add  Conjured,  I  am  going  to  refactor  the  code  so  that  it  is  simpler  and  so  that  I  can  then  add  Conjured.    

Conjured  Item  

inheritance  

But,  but…extends  is  evil!!!!  

Sandi  sa3sfied  the  OCP  using  inheritance  

Despite  the  fact  that  people  tell  you  ‘never  ever  ever  use  inheritance’…  

inheritance    is  not  evil!!!!!  

X  

1.  The  hierarchy  is  shallow  and  narrow,  it  is  not  deep  and  wide  

If  those  condi3ons  are  all  true  for  the  thing  that  you  are  doing  

I  will  give  you  dispensa3on  to  use  it  in  a  very  specific  case  

 2.  The  subclasses  use  every  bit  of  code  that  is  in  the  superclass    3.  If  you  draw  a  mental  image  of  your  object  graph,  this  li\le  hierarchical  cluster  is  at                  the  edge  of  the  graph  (it  is  on  a  leaf  node  of  your  object  hierarchy)  

then  it  is  hard  to  find  another  more  intenEon-­‐revealing  way  to  write  this  code.  

1.  The  hierarchy  is  shallow  and  narrow,  it  is  not  deep  and  wide  2.  The  subclasses  use  every  bit  of  code  that  is  in  the  superclass  

Let’s now look at how Sandi elaborates on her first two conditions

In her book: POODR

1.  The  hierarchy  is  shallow  and  narrow,  it  is  not  deep  and  wide  

A  hierarchy’s  shape  is  defined  by  its  overall  breadth  and  depth  

It  is  this  shape  that  determines  ease  of  use,  maintenance,  and  extension.  

Here  are  a  few  of  the  possible  variaEons  of  shape:  

Easy  to  understand   Slightly  more  complicated.  

A  bit  more  challenging  

Have  a  tendency  to  get  wider  

Difficult  to  understand    and  costly  to  maintain  

SHOULD  BE  AVOIDED  

The  problems  with  deep  hierarchies  

They  have  a  very  long  search    path  for  message  resolu3on  

They  provide  numerous  opportuni3es  for  objects  in  that  path  to  add  behavior  as  the  message  passes  by.  

Objects  depend  on  everything  above  them,  so  a  deep  hierarchy  has    a  large  set  of  built-­‐in  dependencies,  each  of  which  might  someday  change.  

Programmers  tend  to  be  familiar  with  just  the  classes  at  their  tops  and  boHoms;  

*  They  tend  to  understand  only  the  behavior  implemented  at  the  boundaries          of  the  search  path  

*  The  classes  in  the  middle  get  short  shriT.  

*  Changes  to  these  vaguely  understood  middle  classes  stand  a  greater  chance  of                  introducing  errors.  

1.  The  hierarchy  is  shallow  and  narrow,  it  is  not  deep  and  wide  

2.  “The  subclasses  use  every  bit  of  code  that  is  in  the  superclass”  

What  does  she  mean?  Let’s  look  at  how  she  elaborates  this  in  POODR    

Subclasses  are  specializaEons  of  their  superclasses.    

A  MountainBike  should  be  everything  a  Bicycle  is,  plus  more.  

Any  object  that  expects  a  Bicycle  should  be  able  to  interact  with  a  MountainBike  in  blissful  ignorance  of  its  actual  class.  

These  are  the  rules  of  inheritance;  break  them  at  your  peril.    

For  inheritance  to  work…the  objects  that  you  are  modeling  must  truly  have  a  generalizaEon–specializaEon  rela3onship.    

[When]  subclasses…are  not  truly  specializaEons  of  their    superclasses,  the  hierarchy    becomes  untrustworthy.  

IS-­‐A  __  

trust  

Untrustworthy  hierarchies  force  objects  that  interact  with  them  to  know  their  quirks  

trust  

Inexperienced  programmers    do  not  understand  and  cannot  fix  a  faulty  hierarchy  

Knowledge  of  the  structure    of  the  hierarchy  leaks  into    the  rest  of  the  applica3on,    creaEng  dependencies  that    raise  the  cost  of  change.    

if (bicycle instanceof MountainBike) {

// do XYZ }

if (bicycle instanceof MountainBike) {

// do XYZ }

if (bicycle instanceof MountainBike) {

// code that knows about }

oTen  by  explicitly    checking  the  classes  of  objects.    

when  asked  to  use    one  they  will  embed    knowledge  of  its    quirks  into  their  own    code,  

All  of  the  code  in  an  abstract  superclass    should  apply  to  every    class  that  inherits  it  

Superclasses  should  not  contain  code  that  applies    to  some,  but  not  all,  subclasses.  

=======  =======  =======  

=======    =======  

 =======  =======  

=======  =======  =======  

=======  =======  =======  

=======  =======  =======  

 When  interac3ng  with  these  awkward  objects,  programmers  are  forced  to  know  their  quirks  and  into  dependencies  that  are  beHer  avoided.  

Faulty  abstracEons  cause  inheri3ng  objects  to  contain  incorrect  behavior;  aHempts  to  work  around  this  erroneous  behavior  will  cause  your  code  to  decay.    

subclasses  agree  to  a  contract    

they  promise  to  be    subsEtutable    for  their  superclasses.  

Subclasses  that  fail  to  honor    their  contract  are  difficult  to    use.  They’re  “special”and    cannot  be  freely  subsEtuted    for  their  superclasses.        These  subclasses  are  declaring  that    they  are  not  really  a  kind-­‐of  their    superclass  and  cast  doubt  on  the    correctness  of  the  en3re  hierarchy.  

They  are  not  permi\ed    to  do  anything  that    forces  others  to  check    their  type  in  order  to    know  how  to  treat  them    or  what  to  expect  of  them.    

if (bicycle instanceof MountainBike) {

// code that knows }

trust  

IS-­‐A  __  

Let’s  look  in  more  detail  at  the  LSP  to    beHer  understand  what  Sandi  means    by  contract,  and  subsEtutability  

When  you  honor  the  contract,  you  are  following  the  Liskov  SubsEtuEon  Principle,  which  is  named  for  its  creator,  Barbara  Liskov,  and  supplies  the  “L”  in  the  SOLID  design  principles  

Barbara  Liskov  

The  Liskov  SubsEtuEon  Principle  (LSP)  -­‐  1988  

Let  q(x)  be  a  property  provable  about  objects  x  of  type  T.      Then  q(y)  should  be  provable  for  objects  y  of  type  S  where  S  is  a  subtype  of  T.  

[in  a  Type  hierarchy]  the  supertype’s  behavior  must  be  supported  by  the  subtypes:  subtype  objects  can  be  subsEtuted  for  supertype  objects  without  affecEng  the  behavior  of  the  using  code.    

Behaviour  of  -­‐-­‐-­‐-­‐>  

supported    by  -­‐-­‐-­‐-­‐>  

2000  

[the  LSP]  allows  using  code  to  be  wri\en  in  terms  of  the  supertype    specificaEon,  yet  work  correctly  when  using  objects  of  the  subtype.    

For  example,  code  can  be  wriHen  in  terms  of  the  Reader  type,  yet  work  correctly  when  using  a  BufferedFileReader.  

private void foo(BufferedReader bufferedReader) throws IOException { … bar(bufferedReader); … } private void bar(Reader reader) throws IOException { … System.out.println( reader.read() ); … }

the  subsEtuEon  principle  requires  that  the  subtype  specifica3on  support  reasoning  based  on  the  supertype  specifica3on.      Three  proper3es  must  be  supported:    1.  Signature  Rule    2.  Methods  Rule    3.  ProperEes  Rule    

The  subtype  objects  must  have  all  the  methods  of  the  supertype,  and  the  signatures  of  the  subtype  methods  must  be  compaEble  with  the  signatures  of  the  corresponding  supertype  methods.  

This  rule  is  enforced  by  the  compiler,  so  we  are  all  familiar  with  it.  

#1  The  Signature  Rule    

1.  Signature  Rule    2.  Methods  Rule    3.  ProperEes  Rule    

Before  we  can  cover  the  other  two  rules  we    must  briefly  look  at  Design  by  Contract  

The  other  two  rules  cannot  be  checked  by  a    compiler  because  they  require  reasoning  about    the  meaning  of  specifica3ons  

Viewing  the  rela3onship  between  a  class  and  its  clients  as  a  formal  agreement  ,  expressing  each  party’s  rights  and  obligaEons  

DbC  uses  precondiEons,  postcondiEons  and  class  invariants    to  document  (or  beHer,  programma3cally  assert)    the  contract  between  classes,  methods  and  their  callers.  

1986  

Bertrand  Meyer  

Design  by  Contract  (DbC)    

DbC  

Condi3ons  that  must  always  be  true  of  a  class.  They  are  implicitly  added  to  the  precondiEons  and  postcondiEons  of  every  method.  

DbC   Object  Oriented  SoTware  ConstrucEon  

Condi3ons  that  must  be  true  before  a  method  can  execute.    if  the  condi3ons  are  not  met,  it  is  a  bug  in  the  client.  

Condi3ons  that  must  be  true  when  a  method  is  finished  execuEng.    if  the  condi3ons  are  not  met,  it  is  a  bug  in  the  method.  

hHp://www.cs.olemiss.edu/~hcc/soVArch/notes/iContract/OW_Berlin99_web.pdf  

https://code.google.com/p/cofoja/wiki/AddContracts

Open-­‐source  library  released  by  Google  to  bring  DbC  to  Java  

#2  The  Methods  Rule    Calls  of  these  subtype  methods  must  “behave  like”  calls  to  the  corresponding  supertype  methods.  

The  subtype  objects  must  have  all  the  methods  of  the  supertype,  and  the  signatures  of  the  subtype  methods  must  be  compaEble  with  the  signatures  of  the  corresponding  supertype  methods.  

#1  The  Signature  Rule    

What  does  Liskov  mean  by  “behave  like”?  

Let’s  look  at  Meillir-­‐Jones’  explana3on,  in  terms  of    precondi3ons  and  postcondi3ons    

•  Every  opera3on’s  precondiEon  is  no  stronger  than  the  corresponding  opera3on  in  the  superclass      

•  Every  opera3on’s  postcondiEon  is  at  least  as  strong  as  the  corresponding  opera3on  in  the  superclass        

a  subtype  method  can  •  expect  the  same  or  less    •  promise  the  same  or  more  

Explana3on  of  “methods  must  ‘behave  like’  calls  to  the  corresponding  supertype  methods”  

Meillir-­‐Jones  

A  subtype  method  can  weaken  the  precondiEon  and  can  strengthen  the  postcondiEon  

1999  

#2  The  Methods  Rule    Calls  of  these  subtype  methods  must  “behave  like”  calls  to  the  corresponding  supertype  methods.  

The  subtype  objects  must  have  all  the  methods  of  the  supertype,  and  the  signatures  of  the  subtype  methods  must  be  compaEble  with  the  signatures  of  the  corresponding  supertype  methods.  

#1  The  Signature  Rule    

The  subtype  must  preserve  all  properEes  that  can  be  proved  about  supertype  objects.  

#3  The  ProperEes  Rule    

The  class  invariant  of  a  subclass  must  be  equal  to  or  stronger  than  that  of  its  superclass  

An  Example  of  a  subclass  viola3ng  the  contract  of  its  superclass  

Robert  MarEn  (aka  Uncle  Bob)   Agile  SoTware  Development  Principles,  Pa\erns  and  PracEces  

2002  

public class Rectangle {

private Point topLeft;

private double width; private double height;

public void setWidth(double width) {

this.width = width;

} public double getWidth() {

return width; }

public void setHeight(double height)

{

this.height = height; }

public double getHeight() {

return height; }

}

Imagine  that  this  applica3on  works  well  and  is  installed  in  many  sites.  

One  day,  the  users  demand  the  ability  to  manipulate  squares  in  addi3on  to  rectangles.  

It  is  oVen  said  that  inheritance  is  the  IS-­‐A  relaEonship.    

In  other  words,  if  a  new  kind  of  object  can  be  said  to  fulfill  the  IS-­‐A  relaEonship  with  an  old  kind  of  object,  the  class  of  the  new  object  should  be  derived  from  the  class  of  the  old  object.  

public class Rectangle!{!

!…!

!!!public void setWidth(double width)!!{ !! !this.width = width; !!} !!!…!

! public void setHeight(double height) {

this.height = height; }

…!

}!For  all  normal  intents  and  purposes,  a  square  is  a  rectangle.  

So  it  is  logical  to  view  the  Square  class  as    being  derived  from  the  Rectangle  class.   IS-­‐A  

!public class Square extends Rectangle!{ !!}!

public class Rectangle!{!

!…!

@Ensures({“getWidth() = width”})!!public void setWidth(double width)!!{ !! !this.width = width; !!} !!!…!

!@Ensures({“getHeight() = height”})! public void setHeight(double height) {

this.height = height; }

…!

}!

@Invariant(“getWidth() = getHeight()”)!public class Square extends Rectangle!{ !!}!

A  square  has  the  invariant  that  its  width  and  height  are  idenEcal,    but  Square  inherits  Rectangle’s  seHers,  which  do  not  preserve  the  invariant.  

public void f(Square square) {

square.setWidth(5); assert square.getHeight() == 5;

}

There  is  a  problem  though  

The  asserEon  fails  

To  see  why,  let’s  make  the  contract  explicit  

public class Rectangle!{!

!…!

@Ensures({“getWidth() = width”})!!public void setWidth(double width)!!{ !! !this.width = width; !!} !!!…!

!@Ensures({“getHeight() = height”})! public void setHeight(double height) {

this.height = height; }

…!

}! We  can  sidestep  the  problem  by  overriding  the  seHers.  

@Invariant(“getWidth() = getHeight()”)!public class Square extends Rectangle!{! @Ensures({“getWidth() = width”})!

!public void setWidth(double width)!!{ !! !this.width = width; !! !this.height = width; !!} !!

! @Ensures({“getHeight() = height”})!

public void setHeight(double height) { this.height = height; ! !this.width = height; } !!

}!

Now  Square  and  Rectangle  appear  to  work.  No  maHer  what  you  do  to  a  Square  object,  it  will  remain  consistent  with  a  mathema3cal  square,  and  regardless  of  what  you  do  to  a  Rectangle  object,  it  will  remain  a  mathema3cal  rectangle.  

public class Rectangle!{!

!…!

@Ensures({“getWidth() = width”})!!public void setWidth(double width)!!{ !! !this.width = width; !!} !!!…!

!@Ensures({“getHeight() = height”})! public void setHeight(double height) {

this.height = height; }

…!

}!

@Invariant(“getWidth() = getHeight()”)!public class Square extends Rectangle!{! @Ensures({“getWidth() = width”})!

!public void setWidth(double width)!!{ !! !this.width = width; !! !this.height = width; !!} !!

! @Ensures({“getHeight() = height”})!

public void setHeight(double height) { this.height = height; ! !this.width = height; } !!

}!

The  asserEon  now  passes.  The  invariant  of  the  Square  is  now  sa3sfied.  

public void f(Square square) {

square.setWidth(5); assert square.getHeight() == 5;

}

Moreover,  you  can  pass  a  Square  into  a  func3on  that  accepts  a  Rectangle,  and  the  Square  will  s3ll  act  like  a  square  and  will  remain  consistent.  

public class Rectangle!{!

!…!

@Ensures({“getWidth() = width”})!!public void setWidth(double width)!!{ !! !this.width = width; !!} !!!…!

!@Ensures({“getHeight() = height”})! public void setHeight(double height) {

this.height = height; }

…!

}!

So  we  might  conclude  that  the  design  is  now  self-­‐consistent  and  correct.    

@Invariant(“getWidth() = getHeight()”)!public class Square extends Rectangle!{! @Ensures({“getWidth() = width”})!

!public void setWidth(double width)!!{ !! !this.width = width; !! !this.height = width; !!} !!

! @Ensures({“getHeight() = height”})!

public void setHeight(double height) { this.height = height; ! !this.width = height; } !!

}!

However,  this  conclusion  would  be  amiss.  A  design  that  is  self-­‐consistent  is  not  necessarily  consistent  with  all  its  users!    

public class Rectangle!{!

!…!

@Ensures({“getWidth() = width”})!!public void setWidth(double width)!!{ !! !this.width = width; !!} !!!…!

!@Ensures({“getHeight() = height”})! public void setHeight(double height) {

this.height = height; }

…!

}!

public void g(Rectangle rectangle) {

rectangle.setWidth(5); rectangle.setHeight(4);

assert rectangle.getArea() == 20;

}

Consider  a  client’s  func3on  g:  

@Invariant(“getWidth() = getHeight()”)!public class Square extends Rectangle!{! @Ensures({“getWidth() = width”})!

!public void setWidth(double width)!!{ !! !this.width = width; !! !this.height = width; !!} !!

! @Ensures({“getHeight() = height”})!

public void setHeight(double height) { this.height = height; ! !this.width = height; } !!

}!

public class Rectangle!{!

!…!

@Ensures({“getWidth() = width”})!!public void setWidth(double width)!!{ !! !this.width = width; !!} !!!…!

!@Ensures({“getHeight() = height”})! public void setHeight(double height) {

this.height = height; }

…!

}!

public void g(Rectangle rectangle) {

rectangle.setWidth(5); rectangle.setHeight(4);

assert rectangle.getArea() == 20;

}

This  func3on  sets  the  width  and  height  of    what  it  believes  to  be  a  Rectangle.    

@Invariant(“getWidth() = getHeight()”)!public class Square extends Rectangle!{! @Ensures({“getWidth() = width”})!

!public void setWidth(double width)!!{ !! !this.width = width; !! !this.height = width; !!} !!

! @Ensures({“getHeight() = height”})!

public void setHeight(double height) { this.height = height; ! !this.width = height; } !!

}!

 The  func3on  works  fine  for  a  Rectangle  but  fails  if  passed  a  Square.      Problem:  The  author  of  g  assumed  that  changing  the  width  of  a  Rectangle  leaves  its  height  unchanged.  

@Invariant(“getWidth() = getHeight()”)!public class Square extends Rectangle!{! @Ensures({“getWidth() = width”})!

!public void setWidth(double width)!!{ !! !this.width = width; !! !this.height = width; !!} !!

! @Ensures({“getHeight() = height”})!

public void setHeight(double height) { this.height = height; ! !this.width = height; } !!

}!

public class Rectangle!{!

!…!

@Ensures({“getWidth() = width”})!!public void setWidth(double width)!!{ !! !this.width = width; !!} !!!…!

!@Ensures({“getHeight() = height”})! public void setHeight(double height) {

this.height = height; }

…!

}!

Clearly,  it  is  reasonable  to  assume  that  changing  the  width  of  a  rectangle  does  not  affect  its  height!    In  fact  it  is  so  obviously  right  that  we  didn’t  even  bother  explicitly  adding  it  as  a  postcondi3on  of  Rectangle’s  seHers,  we  leV  it  implicit!    

Let’s  make  it  explicit.  

public class Rectangle!{!

!…!

@Ensures({! “getWidth() = width! getHeight() = old(getHeight())”})!!public void setWidth(double width)!!{ !! !this.width = width; !

!} !!!…!!@Ensures({!

“getHeight() = height! getWidth() = old(getWidth())”})!

public void setHeight(double height)

{

this.height = height; }

!}!

Clearly,  it  is  reasonable  to  assume  that  changing  the  width  of  a  rectangle  does  not  affect  its  height!  

@Invariant(“getWidth() = getHeight()”)!public class Square extends Rectangle!{! @Ensures({“getWidth() = width”})!

!public void setWidth(double width)!!{ !! !this.width = width; !! !this.height = width; !!} !!

! @Ensures({“getHeight() = height”})!

public void setHeight(double height) { this.height = height; ! !this.width = height; } !!

}!

 In  fact  it  is  so  obviously  right  that  we  didn’t  even  bother  explicitly  adding  it  as  a  postcondi3on  of  Rectangle’s  seHers,  we  leV  it  implicit!    

Let’s  make  it  explicit.  

public class Rectangle!{!

!…!

@Ensures({! “getWidth() = width”! getHeight() = old(getHeight())”})!!public void setWidth(double width)!!{ !! !this.width = width; !

!} !!!…!!@Ensures({!

“getHeight() = height”! getWidth() = old(getWidth())”})!

public void setHeight(double height)

{

this.height = height; }

!}!

@Invariant(“getWidth() = getHeight()”)!public class Square extends Rectangle!{! @Ensures({“getWidth() = width”})!

!public void setWidth(double width)!!{ !! !this.width = width; !! !this.height = width; !!} !!

! @Ensures({“getHeight() = height”})!

public void setHeight(double height) { this.height = height; ! !this.width = height; } !!

}!

Clearly,  the  postcondi3on  of  Square’s  setWidth  is  weaker  than  the  postcondi3on  of    Rectangle’s  setWidth,  since  it  does  not  enforce  the  constraint  (getHeight() = old(getHeight()).  Similarly  for  Square’s  setHeight.  

Square’s  setWidth  and  setHeight  methods  violate  the  contract  of  the  Rectangle  base  class.  

•  Every  opera3on’s  precondiEon  is  no  stronger  than  the  corresponding  opera3on  in  the  superclass      

•  Every  opera3on’s  postcondiEon  is  at  least  as  strong  as  the  corresponding  opera3on  in  the  superclass        

“methods  must  ‘behave  like’  calls  to  the  corresponding  supertype  methods”  

#2  The  Methods  Rule    Square’s  se\ers  don’t    behave  like  Rectangle’s  Se\ers!  

The  postcondiEons  of  Square’s  se\ers    are  weaker  than  those  of  Rectangle’s  se\ers!  

1.   Signature  Rule    2.   Methods  Rule    3.   ProperEes  Rule    

Square  Violates  Rectangle’s  contract  

It  is  oVen  said  that  inheritance  is  the  IS-­‐A  rela3onship.      

e.g.  a  square  is  a  rectangle,  therefore  Square  should  be  derived  from  the  Rectangle.  

ISA  

If  a  new  kind  of  object  can  be  said  to  fulfill  the  IS-­‐A  rela3onship  with  an  old  kind  of  object,  the  class  of  the  new  object  should  be  derived  from  the  class  of  the  old  object.  

Flawed  reasoning  –  recap  

But  IS-­‐A  is  about  behaviour.  

In  OOD,  a  square  IS-­‐A  rectangle  only  if  it  behaves  like  a  rectangle,  if  it  honours  Rectangle’s  contract,  if  it  sa3sfies  the  LSP.    

The  contract  may  be  implicit  (not  so  good).    

A  square  is  a  rectangle,  but  only  in  a  geometrical  sense,  not  in  a  behavioural  sense.    

In  OOD,  Square  violates  Rectangle’s  contract,  it  violates  the  LSP,  therefore  it  does  not  behave  like  a  Rectangle:  it  is  not  true  that  a  Square  IS-­‐A  Rectangle  and  therefore    Square  should  not  inherit  from  Rectangle.    

IS-­‐A  __  

X  X  

It  may  be  expressed  in  comments  (beHer).    It  may  be  expressed  as  automated  unit  tests  (much  beHer).    Or  it  may  be  expressed  using  a  DbC  framework  (rare?).    

             

To  adhere  to  LSP  in  Java,  we  must  make  sure  that  developers  define  precondi3ons  and  postcondi3ons  for  each  of  the  methods  on  an  abstract  class.      

X  In  order  to  take  advantage  of  LSP,  we  must  adhere  to  OCP  because  violaEons  of  LSP  also  are  violaEons  of  OCP,  but  not  vice  versa.  

X  X  When  defining  our  subclasses,  we  must  adhere  to  these  precondi3ons  and  postcondi3ons.      If  we  do  not  define  precondiEons  and  postcondiEons  for  our  methods,  it  becomes  virtually  impossible  to  find  violaEons  of  LSP.  

Kirk    Knoernschild  

The  Liskov  SubsEtuEon  Principle  is  one  of  the  prime  enablers  of  OCP.  

We  can  think  of  the  Liskov  Subs3tu3on  Principle  (LSP)  as  an  extension  to  OCP.    

2002  

When  interac3ng  with  these  awkward  objects,  programmers  are  forced  to  know  their  quirks  and  into  dependencies  that  are  beHer  avoided.  

when  asked  to  use  one  they  will  embed  knowledge  of  its  quirks  into  their  own  code  

if (bicycle instanceof MountainBike) {

// do XYZ }

if (bicycle instanceof MountainBike) {

// do XYZ }

if (bicycle instanceof MountainBike) {

// code that knows about }

oVen  by  explicitly    checking  the  classes  of  objects.    

Recap:  Sandi  on  consequences  of    untrustworthy  Hierarchies  

every  viola3on  of  the  LSP  is  a  latent  viola3on  of  the  OCP     X   X  

because  in  order  to  repair  the  damage  …  we  are  going  to  have  to  add  if  statements  and  hang  dependencies  upon  subtypes  

if (bicycle instanceof MountainBike) {

// do XYZ }

if (bicycle instanceof MountainBike) {

// do XYZ }

if (bicycle instanceof MountainBike) {

// code that knows about }

Uncle  Bob  on  violaEons  of  LSP  

Next  Eme  you  are  faced  with  adding  logic  to  a  non-­‐trivial  condiEonal,  don’t  just  follow  the  pa\ern  and  add  another  branch.  

See  if  the  condi,onal  is  one  of  those  that  can  be  refactored  to  the  OCP,    using  inheritance  if  necessary.    

Next  Eme  you  consider  introducing  an  inheritance  hierarchy,  ask  yourself  if  it  meets  Sandi’s  criteria  for  using  inheritance.    

If  not,  consider  the  consequences  of  going  ahead  (if  you  decide  to  do  so).  

Next  Eme  you  create  a  base  class,  spend  some  Eme  thinking  about  how  its  contract    needs  to  be  expressed.    

Is  it  really  acceptable  to  leave  it  implicit?  Is  it  so  obvious?    

Is  it  workable  and  effecEve  to  express  the  contract  using  unit  tests?      If  not,  maybe  explore  the  possibility  of  using  a  DbC  framework.  

 If  not,  express  the  contract  using  unit  tests.  

Next  Eme  you  create  a  derived  class,  verify  that  it  saEsfies  the  contract  of  its  superclass.  

Next  Eme  you  come  across  instanceof  usages,  see  if  they  are  the  smell  of    an  untrustworthy  hierarchy.    

If  it  doesn’t,  take  remedial  ac,on.  

If  so,  fix  the  hierarchy.  

References    All  images  sourced  from  hHp://www.google.co.uk/advanced_image_search,  so  see  there  for  details  of  which  are    subject  to  copyright    PracEcal  Object-­‐Oriented  Design  in  Ruby:  An  Agile  Primer  –  by  Sandi  Metz  |  Publica3on  Date:  September  15,  2012  |  ISBN-­‐10:  0321721330    |  ISBN-­‐13:  978-­‐0321721334    Agile  SoTware  Development,  Principles,  Pa\erns,  and  PracEces  –  by  Robert  C.  MarEn  |  Publica3on  Date:    October  25,  2002  |  ISBN-­‐10:  0135974445  |  ISBN-­‐13:  978-­‐0135974445    Object-­‐Oriented  SoTware  ConstrucEon  –  by  Bertrand  Meyer  |  Publica3on  Date:  3  April  1997  |  ISBN-­‐10:  0136291554  |  ISBN-­‐13:  978-­‐0136291558  |  Edi3on:  2    Program  Development  in  Java  –  AbstracEon,  SpecificaEon  and  OO  Design  –  by  Barbara  Liskov,  with  John  Gu\ag  |  Publica3on  Date:  6  Jun  2000  |  ISBN-­‐10:  0201657686  |  ISBN-­‐13:  978-­‐0201657685          

 Java  Design:  Objects,  UML,  and  Process  –  by  Kirk  Knoernschild  |  Publica3on  Date:  December  18,  2001  |    ISBN-­‐10:  0201750449  |  ISBN-­‐13:  978-­‐0201750447    The  Coding  Dojo  Handbook  -­‐  a  pracEcal  guide  to  creaEng  a  space  where  good  programmers  can  become  great  programmers  –  by  Emily  Bache  |  Publica3on  Date:  29  October  2013  |  hHps://leanpub.com/codingdojohandbook    Fundamentals  of  Object  Oriented  Design  in  UML  –  by  Meilir  Page-­‐Jones  |  Publica3on  Date:  November  13,  1999  |  ISBN-­‐13:  978-­‐0201699463    ISBN-­‐10:  020169946X      The  AnE-­‐IF  Campaign  -­‐  hHp://an3ifcampaign.com/    The  Gilded  Rose  Kata  -­‐  hHp://iamnotmyself.com/2011/02/13/refactor-­‐this-­‐the-­‐gilded-­‐rose-­‐kata/  

       

References  (con3nued)