little did he know

49
Little Did He Know ... Burt Beckwith

Upload: burt-beckwith

Post on 17-Jul-2015

796 views

Category:

Software


4 download

TRANSCRIPT

"Little did he know that this simple, seemingly innocuous act would result in his imminent death."

Did you say 'Little did he know ...'?

Dear god, I've written papers on 'Little did he know …'

I used to teach a class based on 'Little did he know ...'

I mean, I once gave an entire seminar on 'Little did he know ...'

The Groovy “Map” Constructor

The Groovy “Map” Constructor

● Not a real constructor (i.e. it's not in the bytecode)

The Groovy “Map” Constructor

● Not a real constructor (i.e. it's not in the bytecode)

● Implemented in

MetaClassImpl.invokeConstructor() or

o.c.g.runtime.callsite.ConstructorSite.

callConstructor()

The Groovy “Map” Constructor

● Not a real constructor (i.e. it's not in the bytecode)

● Implemented in

MetaClassImpl.invokeConstructor() or

o.c.g.runtime.callsite.ConstructorSite.

callConstructor()

● Calls the default constructor, then calls

MetaClassImpl.setProperty() for each map

key/value pair to invoke setters

The Groovy “Map” Constructor

● Depends on an existing default (no-arg) constructor,

which is always created by javac/groovyc if

there are no constructors in the code

The Groovy “Map” Constructor

● Depends on an existing default (no-arg) constructor,

which is always created by javac/groovyc if

there are no constructors in the code

● It's a bit more complicated in Grails domain classes

The Groovy “Map” Constructor

● Depends on an existing default (no-arg) constructor,

which is always created by javac/groovyc if

there are no constructors in the code

● It's a bit more complicated in Grails domain classes

● Grails uses an AST transformation to add a default

constructor to support dependency injection

The Groovy “Map” Constructor

● So what if I want to add a parameterized

constructor in a domain class?

package little

class ConstructedDomain { String name}

The Groovy “Map” Constructor

● So what if I want to add a parameterized

constructor in a domain class?

package little

class ConstructedDomain { String name

ConstructedDomain(String name) { this.name = name }}

The Groovy “Map” Constructor

● So what if I want to add a parameterized

constructor in a domain class?

package little

class ConstructedDomain { String name

ConstructedDomain(String name) { this.name = name }

ConstructedDomain() { // default }}

Need to explicitlyadd a no-arg

constructor forthe Map

constructor, right?

The Groovy “Map” Constructor

● So what if I want to add a parameterized

constructor in a domain class?

package little

class ConstructedDomain { String name

ConstructedDomain(String name) { this() this.name = name }}

Not in domainclasses - the AST

adds a no-argconstructor!

So call this() toretain DI

new Date().getTime()

● Please stop using new Date().getTime() to get the

current time in milliseconds, e.g.

long start = new Date().getTime()// do some timed stufflong end = new Date().getTime()long timeDelta = end - start

new Date().getTime()

● This isn't particularly expensive - creating and discarding

a Date instance is lightweight, but it's best to use

System.currentTimeMillis() (which is where Date

gets its value btw):

long start = System.currentTimeMillis()// do some timed stufflong end = System.currentTimeMillis()long timeDelta = end - start

fooId

● If you have a many-to-one in a domain class, e.g.

there is an authorId dynamic property that can be

used to access the id of the Author instance without

retrieving the instance

class Book { String title Date published Author author}

fooId● Run this with SQL logging enabled:

new Author(name: 'a1').save()new Author(name: 'a2').save()new Book(author: Author.load(2), published: new Date(), title: 'b1').save()

Book.withSession {it.flush()it.clear()

}

def b = Book.get(1)println b.titleprintln b.publishedprintln b.authorIdprintln "calling b.author.id"println b.author.id

Don't do this:

if (foo.validate() && foo.save()) { ...}

Primitives in domain classes

● Think before using primitive types (boolean, int,

long, etc.)

● They are initialized to 0 for numbers and false for

boolean

● This means that it can be difficult to know if a

user chose that value, or if they didn't make a

choice at all

Primitives in domain classes

● If the database column is nullable, expect

NullPointerExceptions

● Your business rules may consider null and 0 (or

false) to be equivalent, this is not a general

default – null means no value and 0 and false

are values

Primitives in domain classes

● Hibernate proactively tries to keep you from

shooting yourself in the foot and always forces

database columns for primitives to be not-null

● This is great, but you end up with an inconsistency

between your constraints and the database

● So use the wrapper classes (Integer, Long,

Boolean, etc.) to detect that no value was chosen

or to properly support nullable columns

findById vs get

● Both seem equivalent, but they are not

● get is a special query method to retrieve a single

instance by id, but findById is a dynamic finder

● They are cached very differently, and query caching

can be quite brittle – see

Hibernate query cache considered harmful?

● There doesn't appear to be any reason to ever use

findById over get

● But findByIdAndSomeOtherProperty can be

useful

License weirdness

● Very interesting Twitter thread about iText and Craig

Burke's Groovy Document Builder here

● It turns out that you cannot use MPL/LGPL libraries

in an ASL2-licenced project, which sounds crazy, but

it's very true:

● https://www.apache.org/legal/resolved.html#cate

gory-x

License weirdness

● “We avoid GPLv3 software because merely linking to

it is considered by the GPLv3 authors to create a

derivative work” -

https://www.apache.org/licenses/GPL-compatibility

.html● Bruno Lowagie (iText author): It's not even ok to use

pre-AGPL versions:

https://stackoverflow.com/questions/25696851/can-

itext-2-1-7-or-earlier-can-be-used-commercially

Log4j vs Slf4j

● Slf4j is an excellent logger API wrapper library

● It's a good idea especially in plugins to use Slf4j in

case the containing app switched to Logback/Log4j

2/etc.

● Unfortunately the type of the message in Slf4j

methods is String, whereas it's Object in Log4j

Log4j vs Slf4j

● Why is that a problem? GStrings:

● log.debug(“The huge collection

contains ${things}”)

● In Log4j, if the logger level is higher than DEBUG,

the message isn't logged, and there's ~0 cost

● But in Slf4j, the GString is coerced to a String

(expensive for large embedded expressions) and

then discarded

Log4j vs Slf4j

● The solution is to use the Slf4j placeholder support:

● log.debug(“The huge collection

contains {}”, things)

Use “public” for constants

● The public keyword is usually unnecessary noise in

Groovy classes – it's the default scope

● But weirdness happens when you omit it for static

properties, e.g. constants

● static final String FOO = 'FOO'

● public static final String BAR = 'BAR'

Use “public” for constants

● Groovy auto-creates getters and setters for

properties, both instance and static (although no

setters for final properties)

● This means that you'll have a getFOO method:

private static final String FOO = "FOO";public static final String BAR = "BAR";

public static final String getFOO() {return FOO;

}

Hat tip to Ken Kousenfor originally pointing

this out

Domain class transient methods

● Is the transients property needed in this domain?

class Thing { String name

int getClickCount() { … }

static transients = ['clickCount']}

Domain class transient methods

● How about now?

class Thing { String name

void setClickCount(int count) { … }

static transients = ['clickCount']}

Domain class transient methods

● And now?

class Thing { String name

int getClickCount() { … } void setClickCount(int count) { … }

static transients = ['clickCount']}

Domain class transient methods

● It's only needed when there is a matched

getter/setter pair

● This is because the pair creates a property

● Recall that Groovy generates a getter and setter for

unscoped properties and converts the declared

property to a private field

Domain class transient methods

● So for this domain class (or any POGO)

If you decompile the .class file you'll see code like

class Thing { String name}

class Thing { private String name public String() getName() { this.name } public void setName(String s) { name = s}}

Domain class transient methods

● Hibernate knows nothing about Groovy, and Grails

doesn't auto-register declared properties as persistent –

Hibernate sees the getter and setter and considers it a

property

● So whether groovyc creates the getter/setter pair or you

do, either way it's a property

→ only add the property name to transients for the

third example

Custom application templates

● We all know that it's easy to customize artifact

templates by running grails install-

templates and editing the files under

src/templates

● But what about application templates

(BuildConfig.groovy, DataSource.groovy,

etc.)?

Custom application templates

● It's possible for Grails 2.3/2.4/2.5 – see

this StackOverflow answer for details

● Basically it involves extracting the jars from

dist/grails-resources-x.x.x.jar and

extracting the templates from those jars, editing,

then repackaging and storing in the correct

$HOME/.grails subfolder

Exceptions are very expensive

● Don't be careless about exceptions

● We've all seen Groovy stacktraces as tall as Mount

Everest – there's a nontrivial cost to fill in the stack

● Use them for exceptional cases

● When a user makes a mistake and there's a

validation problem, that's not exceptional – it's

entirely expected

Exceptions are very expensive

● This implies that save(failOnError:true) is a

really really bad idea except for rare cases

● This includes test data and creating instances in

BootStrap.groovy; in both cases you're hard-

coding values that you expect to be correct – use

failOnError only here, as a fail-safe

● In general do not use failOnError in app code

Exceptions are very expensive

● Consider failOnError vs. checking hasErrors():

def thing = new Thing(foo: 'bar')thing.save()if (thing.hasErrors()) { // handle errors}else { // handle success}

def thing = new Thing(foo: 'bar')try { thing.save(failOnError: true) // handle success}catch (ValidationException e) { // handle errors}

Pretty similar –can someone tellme the value of

failOnError?Munchausen by

Proxy Syndrome?

Exceptions are very expensive

● Let's stop sabotaging the performance of our

Groovy-based applications by throwing or triggering

dumb exceptions, ok?

● The Spring Security plugin has a no-stack Exception:

package grails.plugin.springsecurity.userdetails;

public class NoStackUsernameNotFoundException ... { ... @Override public synchronized Throwable fillInStackTrace() { // do nothing return this; }}

Exceptions are very expensive

● As of Java 7 you can also call this subclass

constructor from yours:

protected Exception(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace);}

Exceptions are very expensive

● This trick was in the comments of this very informative

blog post:

The hidden performance costs of instantiating Throwables

● Also see “The Exceptional Performance of Lil' Exception”

¡Gracias!

http://cuteoverload.files.wordpress.com/2014/03/cute-smiling-animals-251.jpg