78
Use Your Type System; Write Less Code 17 th April 2015 @SamirTalwar

Use your type system; write less code

Embed Size (px)

Citation preview

Page 1: Use your type system; write less code

Use Your Type System;Write Less Code

17th April 2015@SamirTalwar

Page 2: Use your type system; write less code

So I’ve got this website.

Page 3: Use your type system; write less code
Page 4: Use your type system; write less code

It’s got a lot of code.

But a little while ago, I started to think it’s not as good as I thought when we were first getting started.

So I got to work.

Here’s what I found.

Page 5: Use your type system; write less code

Readability

Page 6: Use your type system; write less code

Readability

Tell me what’s more readable.

Page 7: Use your type system; write less code

public Stream<Integer> searchForProperties( boolean renting, int monthlyBudget, double latitude, double longitude, double distanceFromCentre) { ... }

public Stream<PropertyId> abc( PurchasingType def, Budget ghi, Coordinate jkl, Radius mno) { ... }

Readability

Page 8: Use your type system; write less code

Readability readable /ˈriːdəb(ə)l/ adjective

1. able to be read or deciphered; legible. “a code which is readable by a computer” synonyms: legible, easy to read, decipherable, easily deciphered, clear, intelligible, understandable, comprehensible, easy to understand “the inscription is still perfectly readable” antonyms: illegible, indecipherable 2. easy or enjoyable to read. “a marvellously readable book”

Page 9: Use your type system; write less code

Readability readable /ˈriːdəb(ə)l/ adjective

1. able to be read or deciphered; legible. “a code which is readable by a computer” synonyms: legible, easy to read, decipherable, easily deciphered, clear, intelligible, understandable, comprehensible, easy to understand “the inscription is still perfectly readable” antonyms: illegible, indecipherable 2. easy or enjoyable to read. “a marvellously readable book”

Page 10: Use your type system; write less code

first second

return type Stream<Integer> Stream<PropertyId>

name searchForProperties

buying or renting? boolean renting PurchasingType

monthly budget int monthlyBudget Budget

centre coordinates

double latitude,double longitude Coordinate

maximum distance

double distanceFromCentre Radius

Readability

Page 11: Use your type system; write less code

Stream<Integer> searchResults = searchForProperties( true, 500, 51.525094, -0.127305, 2);

Stream<PropertyId> searchResults = searchForProperties( PropertyType.RENTAL, MonthlyBudget.of(500, GBP), Coordinate.of(51.525094, -0.127305), Radius.of(2, MILES));

Readability

Page 12: Use your type system; write less code

Stream<PropertyId> searchResults = searchForProperties( PropertyType.RENTAL, MonthlyBudget.of(500, GBP), Coordinate.of(51.525094, -0.127305), Radius.of(2, MILES));

Stream<PropertyId> searchResults = searchForProperties( PropertyType.RENTAL, MonthlyBudget.of(500, GBP), CircularArea.around(Coordinate.of( 51.525094, -0.127305)) .with(Radius.of(2, MILES)));

Readability

Page 13: Use your type system; write less code

Stream<PropertyId> searchForProperties( PurchasingType purchasingType, Budget budget, Area area) { ... }

SearchQuery<PropertyId> searchForProperties( PurchasingType purchasingType, Budget budget, Area area) { ... }

Readability

Page 14: Use your type system; write less code

public interface SearchQuery<T> { public Stream<T> fetch(); public T fetchFirst();}

Readability

Page 15: Use your type system; write less code

Readability

OK, the inputs make sense. But the output?

Why bother with a PropertyId?

Page 16: Use your type system; write less code

public final class PropertyId { private final int value; public PropertyId(int value) { this.value = value; } public SearchQuery<Property> query( DatabaseConnection connection) { ... } public void renderTo(Somewhere else) { ... } // equals, hashCode and toString}

Readability

Page 17: Use your type system; write less code

ReadabilityWhat does PropertyId do?

SearchQuery<Property> query (DatabaseConnection connection)

void renderTo(Somewhere else)

new PropertyId(int value)

String toString()

boolean equals(Object other) int hashCode()

Page 18: Use your type system; write less code

Readabilityaddition

multiplication

subtraction

division

modulus

negation

bit manipulation operations such as &, |, ^ and ~

further bit manipulation functionality from java.lang.Integer (I count 9 separate methods)

equality

hashing

comparison with other integers

treatment as an unsigned integer

treatment as a sign (the Integer.signum function returns a value representing negative, positive or zero)

conversion to and from other number types (such as double)

conversion to and from strings in decimal, hexadecimal, octal and binary

all of the other methods on java.lang.Integer

What does int do?

Page 19: Use your type system; write less code

Flexibility

Page 20: Use your type system; write less code

public SearchQuery<PropertyId> searchForProperties( PurchasingType purchasingType, Budget budget, Area area) { Area rectangle = area.asRectangle(); return connection .query(query -> query .select() .from(PROPERTY) .where(PROPERTY.PURCHASING_TYPE.equal( purchasingType.name())) .and(PROPERTY.BUDGET.lessOrEqual(budget.inPounds())) .and(PROPERTY.LONGITUDE .between(rectangle.minX()).and(rectangle.maxX())) .and(PROPERTY.LATITUDE .between(rectangle.minY()).and(rectangle.maxY()))) .filter(row -> area.contains(row.getValue(PROPERTY.LATITUDE), row.getValue(PROPERTY.LONGITUDE))) .map(row -> new PropertyId(row.getValue(PROPERTY.ID)));}

Flexibility

Page 21: Use your type system; write less code

public final class PropertyId { private final int value; ...}

Flexibility

Page 22: Use your type system; write less code

Readability

But our website was slow, so we decided to switch to Cassandra.

Page 23: Use your type system; write less code

public final class PropertyId { private final UUID value; private final int humanRepresentation; ...}

Flexibility

Page 24: Use your type system; write less code

public SearchQuery<PropertyId> searchForProperties( PurchasingType purchasingType, Budget budget, Area area) { Area rectangle = area.asRectangle(); return connection .query(query -> query .select() .from(PROPERTY) .where(PROPERTY.PURCHASING_TYPE.equal( purchasingType.name())) .and(PROPERTY.BUDGET.lessOrEqual(budget.inPounds())) .and(PROPERTY.LONGITUDE .between(rectangle.minX()).and(rectangle.maxX())) .and(PROPERTY.LATITUDE .between(rectangle.minY()).and(rectangle.maxY()))) .filter(row -> area.contains(row.getValue(PROPERTY.LATITUDE), row.getValue(PROPERTY.LONGITUDE))) .map(row -> new PropertyId( row.getValue(PROPERTY.ID), row.getValue(PROPERTY.HUMAN_REPRESENTATION)));}

Flexibility

Page 25: Use your type system; write less code

Correctness

Page 26: Use your type system; write less code

Correctness

In his book, Understanding the Four Rules of Simple Design, Corey Haines talks about a really important concept in software design.

He calls things that embody this concept, behaviour attractors.

Page 27: Use your type system; write less code

public Stream<Integer> searchForProperties( boolean renting, int monthlyBudget, double latitude, double longitude, double distanceFromCentre) { check(distanceFromCentre, is(greaterThan(0))); ...}

Correctness

Page 28: Use your type system; write less code

public final class Radius { public static Radius of( @NotNull double magnitude, @NotNull DistanceUnit unit) { check(magnitude, is(greaterThan(0))); return new Radius(magnitude, unit); } private Radius( @NotNull double magnitude, @NotNull DistanceUnit unit) { ... } ...}

Correctness

Page 29: Use your type system; write less code

Correctness

I haven’t been totally honest with you.

There’s more code than you thought.

Except it’s not really code.

Page 30: Use your type system; write less code

/** * Searches for properties in the database * matching the specified parameters. * * @param renting True if renting, false if buying. * ... * @return A stream of property IDs. * @throws DatabaseQueryException if there is a connection error. */public Stream<Integer> searchForProperties( boolean renting, int monthlyBudget, double latitude, double longitude, double distanceFromCentre) { check(distanceFromCentre, is(greaterThan(0))); ...}

Correctness

Page 31: Use your type system; write less code

/** * Searches for properties in the database * matching the specified parameters. * * @throws DatabaseQueryException * if there is a connection error. */Stream<PropertyId> searchForProperties( PurchasingType purchasingType, Budget budget, Area area) { ... }

Correctness

Page 32: Use your type system; write less code

public interface SearchQuery<T> { /** * @throws DatabaseQueryException * if there is a connection error. */ public Stream<T> fetch(); /** * @throws DatabaseQueryException * if there is a connection error. */ public T fetchFirst();}

Correctness

Page 33: Use your type system; write less code

public interface SearchQuery<T> { public Stream<T> fetch() throws DatabaseQueryException; public T fetchOne() throws DatabaseQueryException;}

Correctness

Page 34: Use your type system; write less code

@Path("/properties")public final class PropertiesResource { private final Template PropertyTemplate = Template.inClassPath("/com/buymoarflats/website/property-details.html"); @GET @Path("/{propertyId}") public Response propertyDetails(@PathParam("propertyId") int id) { try { return propertyResponse(new PropertyId(id)); } catch (DatabaseQueryException e) { return Response.serverError().entity(e).build(); } } private Response propertyResponse(PropertyId id) throws DatabaseQueryException { Output output = formattedProperty(id); if (output == null) { return Response.notFound().entity(id).build(); } return Response.ok(output).build(); } private Output formattedProperty(PropertyId id) throws DatabaseQueryException { Property property = retrieveProperty(id); if (property == null) { return null; } return PropertyTemplate.format(property); } private Property retrieveProperty(PropertyId id) throws DatabaseQueryException { return id.query(connection).fetchOne(); }}

Correctness

Page 35: Use your type system; write less code

public Response propertyDetails(@PathParam("propertyId") int id) { try { return propertyResponse(new PropertyId(id)); } catch (DatabaseQueryException e) { return Response.serverError().entity(e).build(); }}private Response propertyResponse(PropertyId id) throws DatabaseQueryException { Output output = formattedProperty(id); ... }private Output formattedProperty(PropertyId id) throws DatabaseQueryException { Property property = retrieveProperty(id); ... }private Property retrieveProperty(PropertyId id) throws DatabaseQueryException { return id.query(connection).fetchOne();}

Correctness

Page 36: Use your type system; write less code

Correctness

To paraphrase Nat Pryce and Steve Freeman:

Your types are trying to tell you something.

You should listen to them.

Page 37: Use your type system; write less code

public Response propertyDetails(@PathParam("propertyId") PropertyId id) { try { return propertyResponse( id, formattedProperty(retrieveProperty(id))); } catch (DatabaseQueryException e) { return Response.serverError().entity(e).build(); }}private Response propertyResponse(PropertyId id, Output output) { ...}private Output formattedProperty(Property property) { ...}private Property retrieveProperty(PropertyId id) throws DatabaseQueryException { return id.query(connection).fetchOne();}

Correctness

Page 38: Use your type system; write less code

Correctness

Now, what about all that duplication?

Page 39: Use your type system; write less code

Output process(Input thing) { if (thing == null) { return null; } // process thing and return}

Correctness

Page 40: Use your type system; write less code

Output process(Input thing) { if (thing == null) { return null; } else { // process thing and return }}

Correctness

Page 41: Use your type system; write less code

Output process(Input thing) { Function<Input, Output> processor = value -> { // process value and return }; if (thing == null) { return null; } else { return processor.apply(thing); }}

Correctness

Page 42: Use your type system; write less code

Output processOptional( Function<Input, Output> processor, Input thing) { if (thing == null) { return null; } else { return processor.apply(thing); }}

Correctness

Page 43: Use your type system; write less code

Optional<Output> process( Optional<Input> thing) { return thing.map(value -> { // process the thing and return });}

Correctness

Page 44: Use your type system; write less code

public Response propertyDetails(@PathParam("propertyId") PropertyId id) { try { return propertyResponse( id, retrieveProperty(id).map(this::formattedProperty)); } catch (DatabaseQueryException e) { return Response.serverError().entity(e).build(); }}private Response propertyResponse(PropertyId id, Optional<Output> maybeOutput) { return maybeOutput .map(output -> Response.ok(output)) .orElse(Response.notFound().entity(id)) .build(); }private Output formattedProperty(Property property) { return PropertyTemplate.format(property); }

private Optional<Property> retrieveProperty(PropertyId id) throws DatabaseQueryException { return id.query(connection).fetchOne();}

Correctness

Page 45: Use your type system; write less code

@GET@Path("/{propertyId}")public Response propertyDetails( @PathParam("propertyId") PropertyId id) { try { return id.query(connection).fetchOne() .map(PropertyTemplate::format) .map(Response::ok) .orElse(Response.notFound().entity(id)) .build(); } catch (DatabaseQueryException e) { return Response.serverError() .entity(e).build(); }}

Correctness

Page 46: Use your type system; write less code

Correctness

That works with my DatabaseQueryException.

But what if my templating library can throw a TemplateFormattingException too?

Page 47: Use your type system; write less code

public final class Optional<T> { ... public <U> Optional<U> map( Function<T, U> mapper) { ... }}

@FunctionalInterfacepublic interface Function<T, R> { R apply(T t); // does not throw ...}

Correctness

Page 48: Use your type system; write less code

@GET@Path("/{propertyId}")public Response propertyDetails( @PathParam("propertyId") PropertyId id) { try { return id.query(connection).fetchOne() .map(PropertyTemplate::format) .map(Response::ok) .orElse(Response.notFound().entity(id)) .build(); } catch (DatabaseQueryException e) { return Response.serverError() .entity(e).build(); }}

Correctness

Page 49: Use your type system; write less code

enum RequestFailure { ResourceNotFound, DatabaseQueryFailure, TemplateFormattingFailure}

Correctness

Page 50: Use your type system; write less code

abstract class RequestException extends Exception { ... }final class ResourceNotFoundException extends RequestException { ... }final class DatabaseQueryException extends RequestException { ... }final class TemplateFormattingException extends RequestException { ... }

Correctness

Page 51: Use your type system; write less code

Correctness

OK, I’ve got a type that can handle all of my failures.

But what about success?

Well, we can either have success or failure.

And if we fail, we don’t want to go any further.

Page 52: Use your type system; write less code

public interface Either<F, S> { public <T> Either<F, T> then( Function<S, Either<F, T>> function);}

public class Failure<F, S> implements Either<F, S> { public <T> Either<F, T> then(Func<> function) { return new Failure<>(value); }}

public class Success<F, S> implements Either<F, S> { public <T> Either<F, T> then(Func<> function) { return function.apply(value); }}

Correctness

Page 53: Use your type system; write less code

@Path("/properties")public final class PropertiesResource { @GET @Path("/{propertyId}") public Response propertyDetails( @PathParam("propertyId") PropertyId id) { Either<RequestException, Property> property = id.query(connection).fetchOne(); Either<RequestException, Output> output = property.then(PropertyTemplate::format); output .map(Response::ok) .on(ResourceNotFoundException.class, e -> Response.notFound().entity(id)) .failWith(e -> Response.serverError().entity(e)) .build(); }}

Correctness

Page 54: Use your type system; write less code

@Path("/properties")public final class PropertiesResource { @GET @Path("/{propertyId}") public Response propertyDetails( @PathParam("propertyId") PropertyId id) { id.query(connection).fetchOne() .then(PropertyTemplate::format) .map(Response::ok) .on(ResourceNotFoundException.class, e -> notFound().entity(id)) .failWith(e -> serverError().entity(e)) .build(); }}

Correctness

Page 55: Use your type system; write less code

Performance

Page 56: Use your type system; write less code

Performance

On BuyMoarFlats.com, we let you short list properties.

Page 57: Use your type system; write less code

Set<ShortListedProperty> shortList = connection .query(query -> query .select() .from(SHORT_LIST) .join(PROPERTY) .on(SHORT_LIST.PROPERTY_ID .eq(PROPERTY.ID)) .where(SHORT_LIST.USER_ID .eq(user.id()))) .map(row -> propertyFrom(row)) .fetch() .collect(toSet());

Performance

Page 58: Use your type system; write less code

List<ShortListedProperty> sortedShortList = shortList.stream() .sorted(comparing(dateTimeAdded)) .collect(toList());

Performance

Page 59: Use your type system; write less code

Map<City, List<ShortListedProperty>> shortListsByCity = sortedShortList.stream() .collect(groupingBy(city));

Performance

Page 60: Use your type system; write less code

Set<City> cities = shortListByCity.keySet();

Performance

Page 61: Use your type system; write less code

List<ShortListedProperty> upForAuctionSoon =shortListsByCity.values().stream() .flatMap(Collection::stream) .filter(property -> property.isUpForAuctionInLessThan( 1, WEEK)) .collect(toList());

Performance

Page 62: Use your type system; write less code

Stream<Property> randomPromotedAuction = connection .query(query -> query .select() .from(PROPERTY) .where(PROPERTY.SALE_TYPE .eq(PropertySaleType.AUCTION)) .and(PROPERTY.PROMOTED.eq(true)) .limit(1)) .fetch();List<Property> highlighted = Stream.concat(randomPromotedAuction, upForAuctionSoon.stream()) .collect(toList());

Performance

Page 63: Use your type system; write less code

Performance

And we’ve got more features coming every week!

Page 64: Use your type system; write less code

public final class ShortList { Set<ShortListedProperty> shortList; List<ShortListedProperty> sortedShortList; Map<City, List<ShortListedProperty>> byCity; Set<City> cities; List<ShortListedProperty> upForAuctionSoon; Optional<Property> randomPromotedAuction; List<Property> highlighted; ...}

Performance

Page 65: Use your type system; write less code

public final class ShortList { Set<ShortListedProperty> shortList; List<ShortListedProperty> sortedShortList; Map<City, List<ShortListedProperty>> byCity; Set<City> cities; List<ShortListedProperty> upForAuctionSoon; Optional<Property> randomPromotedAuction; List<Property> highlighted; ...}

Performance

Page 66: Use your type system; write less code

Performance

But we process the list five times.

Page 67: Use your type system; write less code

public final class ShortList { Set<ShortListedProperty> shortList; List<ShortListedProperty> sortedShortList; Map<City, List<ShortListedProperty>> byCity; Set<City> cities; List<ShortListedProperty> upForAuctionSoon; Optional<Property> randomPromotedAuction; List<Property> highlighted; ...}

Performance

Page 68: Use your type system; write less code

Performance

Here’s the magic bullet.

We don’t need to optimise our algorithms.

The database author already did that.

We just need to write code that does less.

Page 69: Use your type system; write less code

public final class ShortList { Map<City, List<ShortListedProperty>> byCity; Set<City> cities; List<Property> highlighted; ...}

Performance

Page 70: Use your type system; write less code

In Conclusion

Page 71: Use your type system; write less code

We tackled four discrete problems, but the solution was always the same.

Make a new type.

Page 72: Use your type system; write less code

public class CircularArea implements Area { ...}

Page 73: Use your type system; write less code

public final class Optional<T> { ...}

public final class Either<F, S> { ...}

Page 74: Use your type system; write less code

Quoting @jbrains (who was paraphrasing Kent Beck),

“I define simple design this way.

A design is simple to the extent that it:

1. Passes its tests

2. Minimizes duplication

3. Maximizes clarity

4. Has fewer elements”

http://www.jbrains.ca/permalink/the-four-elements-of-simple-design

Page 75: Use your type system; write less code

1. Passes its tests

2. Minimizes duplication

3. Maximizes clarity

4. Has fewer elements

Page 76: Use your type system; write less code

Jackpot.

Go wrap some data in types.

Page 77: Use your type system; write less code
Page 78: Use your type system; write less code

talks.samirtalwar.com

Heckling starts now.

17th April 2015@SamirTalwar