Learning from GOOS - work in progress

1,438 views
1,374 views

Published on

I collected thoughts and tips from "Growing OOSW, Guided By Tests" (by Nat Pryce and Steve Freemen) over recent months and find it useful (though far from complete or, well, done) to introduce teams to some important ideas to improve their programming skills.
Feedback, corrections and improvement suggestions very welcome. Will update this as it develops...

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,438
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
21
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • This.lines = lines (aber übergibts OrderLines); deklarierst aber list<orderline> lines\n\nDas passt nicht!\n\npublic OrderBuilder withOrderLines(List<OrderLine> lines) { this.lines = lines; return this; }\n
  • This.lines = lines (aber übergibts OrderLines); deklarierst aber list<orderline> lines\n\nDas passt nicht!\n\npublic OrderBuilder withOrderLines(List<OrderLine> lines) { this.lines = lines; return this; }\n
  • This.lines = lines (aber übergibts OrderLines); deklarierst aber list<orderline> lines\n\nDas passt nicht!\n\npublic OrderBuilder withOrderLines(List<OrderLine> lines) { this.lines = lines; return this; }\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Learning from GOOS - work in progress

    1. 1. TDD
and
OO
Design
Lessons
from
    2. 2.  We
value
code
 
 that
is
easy
to
maintain
 over
code
 
 that
is
easy
to
write
    3. 3. Some
Design
Principles Not
meant
to
be
comprehensive 3
    4. 4.  Coupling
and
Cohesion:  Loose
coupling

easier
maintenance  Cohesion:
unit
of
responsibility Roles,
Responsibilities,
Collaborators:  Role:
set
of
related
responsibilities  Collaborators:
roles
you
interact
with Internals
vs.
Peers
    5. 5.  No
And’s,
Or’s,
or
But’s  Every
object
should
have
a
single,
clearly
defined
 responsibility Three
types
of
Peers:  Dependencies ▪ The
object
can’t
live
without
them  Notifications ▪ Need
to
be
kept
up
to
date,
but
we
“don’t
care”
if
they
 listen  Adjustments
    6. 6.  New
or
new
not.
There
is
no
try.
(Yoda) Dependencies
have
to
be
passed
into
the
 constructor. Notifications
and
Adjustments
can
be
 initialised
to
safe
defaults.
    7. 7.  Identify
Relationships
with
Interfaces Keep
interfaces
narrow Interfaces
are
pulled
into
existence
by
tests No
I<class>
(ICustomer 
Customer) No
InterfaceImpl
(Customer 
CustomerImpl) Name
each
implementation! If
you
don’t
find
a
good
name
for
the
 implementation,
maybe
it’s
not
a
good
 interface…
    8. 8. Important
Design
PrincipleUse
with
Objects,
not
People
:‐)
    9. 9.  Try
to
understand
this
(train
wreck):((EditSaveCustomizer) master.getModelisable() .getDockablePanel() .getCustomizer()) .getSaveItem() .setEnabled(Boolean.FALSE.booleanValue()); Isn’t
this
simpler?master.allowSavingOfCustomisations();
    10. 10.  Or,
now
really
with
a
train:public class Train { private final List<Carriage> carriages […] private int percentReservedBarrier = 70; public void reserveSeats(ReservationRequest request) { for (Carriage carriage : carriages) { if (carriage.getSeats().getPercentReserved() < percentReservedBarrier) { request.reserveSeatsIn(carriage); return; } } request.cannotFindSeats(); }} Why
isn’t
that
good
design?
    11. 11.  Isn’t
this
simpler?public void reserveSeats(ReservationRequest request) { for (Carriage carriage : carriages) { if (carriage.hasSeatsAvailableWithin (percentReservedBarrier)) { request.reserveSeatsIn(carriage); return; } } request.cannotFindSeats() ;}
    12. 12. General
Tips
on
Testing How
to
make
it
simpler... 12
    13. 13. Phrase
Messages
with
Meaning What
went
wrong
here?  Found
<null>
expected
<not
null>  Found
<0>
expected
<17> Use
constants Use
special
types  Found
<Harry>
expected
<Customer.NotFound> 13
    14. 14. Be
Literal Numbers,
Strings,
…
are
hard
to
understand Better:  Constants  Enumerations  Value
Types There
is
no
“too
small”
for
types 14
    15. 15. Beware
“Long”
Strings! Test
format
and
contents
independently “13/09/10
–
Order
8715
signed
by
Manfred
Mayer” Define
a
Value
Type
for
longer
Messages “<decision.date>
‐
Order 
<decision.no>
signed
by
<decision.owner>” Public
class
Decision{  Public
Date
date;  Public
String
no;  Public
String
owner; } 15
    16. 16. Use
Assertions
sensibly Assert
one
expected
behaviour
in
exactly
one
 test  Otherwise
you
have
to
regularly
update
multiple
 tests!  Otherwise
it’s
much
less
clear
what
the
test
does Rather
have
one
more
test
than
an
unclear
 one The
“Single
Responsibility
Principle”
applies
 here,
too! 16
    17. 17. Working
with
Test
Data How
to
make
your
life
easier 17
    18. 18.  Many
attempts
to
communicate
are
nullified
 by
saying
too
much.—
Robert
Greenleaf
    19. 19.  First
Try@Test public void chargesCustomerForTotalCostOfAllOrderedItems() { Order order = new Order( new Customer("Sherlock Holmes", new Address("221b Baker Street", "London", new PostCode("NW1", "3RX")))); order.addLine(new OrderLine("Deerstalker Hat", 1)); order.addLine(new OrderLine("Tweed Cape", 1));[…]}
    20. 20. Test
Data‐by
hand? Complex,
confusing Hard
to
write Error‐prone 20
    21. 21.  Centralise
test
data
creation…
Order order1 = ExampleOrders.newDeerstalkerAndCapeAndSwordstickOrder();Order order2 = ExampleOrders.newDeerstalkerAndBootsOrder();[…]
    22. 22.  Test
are
clearer,
but... Complexity
and
repetition
just
moved
some
 place
else!
    23. 23.  Hide
defaults Show
specialties
    24. 24. public class OrderBuilder { private Customer customer = new CustomerBuilder().build(); private List<OrderLine> lines = new ArrayList<OrderLine>(); private BigDecimal discountRate = BigDecimal.ZERO; public static OrderBuilder anOrder() { return new OrderBuilder(); } public OrderBuilder withCustomer(Customer customer) { this.customer = customer; return this; } public OrderBuilder withOrderLines(List<OrderLine> lines) { this.lines = lines; return this; } public OrderBuilder withDiscount(BigDecimal discountRate) { this.discountRate = discountRate; return this; } public Order build() { Order order = new Order(customer); for (OrderLine line : lines) order.addLine(line); order.setDiscountRate(discountRate); } return order; }}
    25. 25. public class OrderBuilder { private Customer customer = new CustomerBuilder().build(); Default
values private List<OrderLine> lines = new ArrayList<OrderLine>(); private BigDecimal discountRate = BigDecimal.ZERO; public static OrderBuilder anOrder() { return new OrderBuilder(); } public OrderBuilder withCustomer(Customer customer) { this.customer = customer; return this; } public OrderBuilder withOrderLines(List<OrderLine> lines) { this.lines = lines; return this; } public OrderBuilder withDiscount(BigDecimal discountRate) { this.discountRate = discountRate; return this; } public Order build() { Order order = new Order(customer); for (OrderLine line : lines) order.addLine(line); order.setDiscountRate(discountRate); } return order; }}
    26. 26. public class OrderBuilder { private Customer customer = new CustomerBuilder().build(); Default
values private List<OrderLine> lines = new ArrayList<OrderLine>(); private BigDecimal discountRate = BigDecimal.ZERO; public static OrderBuilder anOrder() { return new OrderBuilder(); } public OrderBuilder withCustomer(Customer customer) { this.customer = customer; return this; Return:
Builder } public OrderBuilder withOrderLines(List<OrderLine> lines) { this.lines = lines; return this; } public OrderBuilder withDiscount(BigDecimal discountRate) { this.discountRate = discountRate; return this; } public Order build() { Order order = new Order(customer); for (OrderLine line : lines) order.addLine(line); order.setDiscountRate(discountRate); } return order; }}
    27. 27. public class OrderBuilder { private Customer customer = new CustomerBuilder().build(); Default
values private List<OrderLine> lines = new ArrayList<OrderLine>(); private BigDecimal discountRate = BigDecimal.ZERO; public static OrderBuilder anOrder() { return new OrderBuilder(); } public OrderBuilder withCustomer(Customer customer) { this.customer = customer; return this; Return:
Builder } public OrderBuilder withOrderLines(List<OrderLine> lines) { this.lines = lines; return this; } public OrderBuilder withDiscount(BigDecimal discountRate) { this.discountRate = discountRate; return this; } public Order build() { Order order = new Order(customer); for (OrderLine line : lines) order.addLine(line); order.setDiscountRate(discountRate); } return order; build()
always
returns
a
new
 }} order!
    28. 28.  Default
fits
into
one
row:Order order = new OrderBuilder().build(); Any
deviation
is
obvious:new OrderBuilder() .fromCustomer( new CustomerBuilder() .withAddress(new AddressBuilder().withNoPostcode() .build()) .build()) .build();
    29. 29.  Creation
using
Object
Mother
hides
the
error:TestAddresses.newAddress("221b Baker Street", "London", "NW1 6XE"); With
the
Data
Builder
the
error
is
explicit:new AddressBuilder() .withStreet("221b Baker Street") .withStreet2("London") .withPostCode("NW1 6XE") .build();
    30. 30.  Multiple
objects
lead
to
repetition
(again...):Order orderWithSmallDiscount = new OrderBuilder() .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1) .withDiscount(0.10) .build();Order orderWithLargeDiscount = new OrderBuilder() .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1) .withDiscount(0.25) .build();
    31. 31.  This
is
better
if
objects
differ
in
only
one
field:OrderBuilder hatAndCape = new OrderBuilder() .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1);Order orderWithSmallDiscount = hatAndCape.withDiscount(0.10).build();Order orderWithLargeDiscount = hatAndCape.withDiscount(0.25).build();
    32. 32.  Attention,
possible
error:Order orderWithDiscount = hatAndCape.withDiscount(0.10) .build();Order orderWithGiftVoucher = hatAndCape.withGiftVoucher("abc").build(); The
second
order
has
a
discount
as
well!
    33. 33.  Better: We
add
a
CopyConstructor
to
the
Builder:Order orderWithDiscount = new OrderBuilder(hatAndCape) .withDiscount(0.10) .build();Order orderWithGiftVoucher = new OrderBuilder(hatAndCape) .withGiftVoucher("abc") .build();
    34. 34.  More
elegant: Factory‐Method,
naming
what
it’s
used
for:Order orderWithDiscount = hatAndCape. but().withDiscount( 0.10).build();Order orderWithGiftVoucher = hatAndCape.but().withGiftVoucher("abc").build();
    35. 35.  instead
of:Order orderWithNoPostcode = new OrderBuilder() .fromCustomer(new CustomerBuilder() .withAddress(new AddressBuilder() .withNoPostcode().build()).build()).build(); it’s
more
elegant,
to
pass
the
Builder:Order order = new OrderBuilder() .fromCustomer( new CustomerBuilder() .withAddress(new AddressBuilder().withNoPostcode() ))).build();
    36. 36.  Even
more
elegant:
Factory
methodsOrder order =anOrder().fromCustomer(aCustomer().withAddress(anAddress().withNoPostcode() ) ) .build(); Overloading
makes
it
shorter:Order order = anOrder() .from(aCustomer().with(anAddress().withNoPostcode())).build(); This
is
how
readable
a
test
can
(and
should)
 be.
    37. 37. @Test public void reportsTotalSalesOfOrderedProducts() { Order order1 = anOrder() .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1) .withCustomersReference(1234) .build(); requestSender.send(order1); progressMonitor.waitForCompletion(order1); Order order2 = anOrder() .withLine("Deerstalker Hat", 1) .withCustomersReference(5678) .build(); requestSender.send(order2); progressMonitor.waitForCompletion(order2); TotalSalesReport report = gui.openSalesReport(); report.checkDisplayedTotalSalesFor("Deerstalker Hat", is(equalTo(2) )); report.checkDisplayedTotalSalesFor("Tweed Cape", is(equalTo(1)));}
    38. 38. @Test public void reportsTotalSalesOfOrderedProducts() {submitOrderFor("Deerstalker Hat", "Tweed Cape");submitOrderFor("Deerstalker Hat"); TotalSalesReport report = gui.openSalesReport(); report.checkDisplayedTotalSalesFor("Deerstalker Hat", is(equalTo(2) )); report.checkDisplayedTotalSalesFor("Tweed Cape", is(equalTo(1)));}void submitOrderFor(String ... products) { OrderBuilder orderBuilder = anOrder() .withCustomersReference(nextCustomerReference()); for (String product : products) { orderBuilder = orderBuilder.withLine(product, 1); } Order order = orderBuilder.build(); requestSender.send(order); progressMonitor.waitForCompletion(order);}
    39. 39.  This
won’t
scale
well:void submitOrderFor(String ... products) { […]void submitOrderFor(String product, int count, String otherProduct, int otherCount) { […]void submitOrderFor(String product, double discount) { […]void submitOrderFor(String product, String giftVoucherCode) { […]
    40. 40.  Better,
to
use
the
builder:@Test public void reportsTotalSalesOfOrderedProducts() {sendAndProcess(anOrder() .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1));sendAndProcess(anOrder() .withLine("Deerstalker Hat", 1)); TotalSalesReport report = gui.openSalesReport(); report.checkDisplayedTotalSalesFor("Deerstalker Hat", is(equalTo(2) )); report.checkDisplayedTotalSalesFor("Tweed Cape", is(equalTo(1)));}void sendAndProcess(OrderBuilder orderDetails) { Order order = orderDetails .withDefaultCustomersReference(nextCustomerReference()) .build(); requestSender.send(order); progressMonitor.waitForCompletion(order);}
    41. 41.  Better
names
improve
clarity:@Test public void reportsTotalSalesOfOrderedProducts() {havingReceived(anOrder() .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1));havingReceived(anOrder() .withLine("Deerstalker Hat", 1)); TotalSalesReport report = gui.openSalesReport(); report.displaysTotalSalesFor("Deerstalker Hat", equalTo(2)); report.displaysTotalSalesFor("Tweed Cape", equalTo(1) );}
    42. 42.  This
is
how
readable
a
test
should
be:@Test public void takesAmendmentsIntoAccountWhenCalculatingTotalSales() { Customer theCustomer = aCustomer().build();havingReceived(anOrder().from(theCustomer) .withLine("Deerstalker Hat", 1) .withLine("Tweed Cape", 1));havingReceived(anOrderAmendment().from(theCustomer) .withLine("Deerstalker Hat", 2)); TotalSalesReport report = user.openSalesReport(); report.containsTotalSalesFor("Deerstalker Hat", equalTo(2)); report.containsTotalSalesFor("Tweed Cape", equalTo(1) );}

    ×