Effective Groovy     Hamlet DArcy     Canoo Engineering AG     @HamletDRCwww.canoo.com
Agenda        Effective Java Reconsidered        Effective Groovywww.canoo.com
Groovy & GrailsJavaRich Business ApplicationsUsabilityRefactoring
@HamletDRC   http://hamletdarcy.blogspot.com   http://www.manning.com/koenig2/   Groovy, CodeNarc, JConch Committer   GPar...
Effective Groovywww.canoo.com
Effective Groovywww.canoo.com
Effective Java #2                Consider a builder when faced with                   many constructor parameterswww.canoo...
Person p = new Person(1, "David", "Villa");www.canoo.com
Person p = new Person(1, "David", "Villa");     Person p = new PersonBuilder().               withID(1).               wit...
public class PersonBuilder {         private String firstName         private String lastName         private Integer id  ...
def p = new Person(               id: 1,               firstName: David,               lastName: Villa     )www.canoo.com
def p = new Person().with {         id = 1         firstName = David         lastName = Villa         delegate     }www.ca...
Effective Java #3                Enforce the singleton property                  with a private constructorwww.canoo.com
@Singleton        class Zeus {        }www.canoo.com
Effective Java #5                Avoid creating unnecessary objectswww.canoo.com
assert 12345G    instanceof BigIntegerassert 123.45G   instanceof BigDecimalwww.canoo.com
assert 12345G     instanceof BigIntegerassert 123.45G    instanceof BigDecimalassert     []                 instanceof   A...
assert 12345G     instanceof BigIntegerassert 123.45G    instanceof BigDecimalassert     []                 instanceof   A...
Effective Java #7                Avoid Finalizerswww.canoo.com
Effective Java #7                Avoid Finalizerswww.canoo.com
Effective Java #8                Obey the general contract                when overriding equalswww.canoo.com
@EqualsAndHashCode        class Person {            String firstName            String lastName            int id        }...
Effective Java #9                Always override hashCode                when your override equalswww.canoo.com
Effective Java #9                Always override hashCode                when your override equalswww.canoo.com
Effective Java #10                Always override toStringwww.canoo.com
@ToString        class Person {            String firstName            String lastName            int id        }www.canoo...
Effective Java #12                Consider implementing Comparablewww.canoo.com
def p1 = new Person(     id: 1, firstName: Hamlet, lastName: "DArcy")def p2 = new Person(     id: 2, firstName: Bro, lastN...
Effective Java #15                Minimize Mutabilitywww.canoo.com
@Immutable        class Person {            String firstName            String lastName            int id        }www.cano...
Effective Java #16                Favor composition over inheritancewww.canoo.com
class NoisySet extends HashSet {            @Override            boolean add(i) {                println "adding $i"      ...
class NoisySet implements Set {            @Delegate            Set delegate = new HashSet()                @Override     ...
Effective Java #19                Use interfaces only to define typeswww.canoo.com
Effective Java #19                Use interfaces only to define typeswww.canoo.com
Effective Java #41                Use Overloading Judiciouslywww.canoo.com
def function(Collection c) {            println received collection        }        def function(ArrayList a) {           ...
Effective Java Groovy #41          Prefer default parameters to overloadingwww.canoo.com
@Log class Calculator {            def log(method, parm1, parm2) {                log.info(                    "Method Cal...
@Log class Calculator {            def log(method, parm1 = null, parm2 = null) {                log.info(                 ...
Effective Java #43                Return empty arrays or collections,                             not nullswww.canoo.com
Effective Java #43                Return empty arrays or collections,                             not nullswww.canoo.com
Effective Java #54                Use native methods judiciouslywww.canoo.com
Effective Java #54                Use native methods judiciouslywww.canoo.com
Effective Java #56                Adhere to generally accepted                    naming conventionswww.canoo.com
Effective Java #56                Adhere to generally accepted                    naming conventionswww.canoo.com
Effective Java #57 – #61    Use exceptions only for exceptional conditions    Avoid unnecessary use of checked exceptions ...
Effective Java #57 – #61    Use exceptions only for exceptional conditions    Avoid unnecessary use of checked exceptions ...
Effective Java #65                Dont ignore exceptionswww.canoo.com
Effective Java #65                Dont ignore exceptionswww.canoo.com
def b        new SwingBuilder().frame(                size: [300, 300],                show: true,                defaultC...
Effective Groovy #7          Implement interfaces as maps judiciouslywww.canoo.com
Effective Java #47                Know and use the librarieswww.canoo.com
Effective Groovy #2         Know and use the Collection methodswww.canoo.com
List<Person> people = Person.findAll();        List<Integer> ids = new ArrayList<Integer>();        for (Person p : people...
def people = Person.findAll()        def ids = people.collect { it.id }www.canoo.com
def people = Person.findAll()        def ids = people*.idwww.canoo.com
Effective Groovy #2a                Use collect for List transformationswww.canoo.com
List<Person> people = Person.findAll();        Person joe = null;        for (Person p : people) {                if ("Joe...
def people = Person.findAll()        def joe = people.find { it.firstName == Joe }www.canoo.com
List<Person> people = Person.findAll();        List<Person> bucks = new ArrayList<Person>();        for (Person p : people...
def people = Person.findAll()        def bucks = people.findAll {            it.lastName == Buck        }www.canoo.com
Effective Groovy #2b            Use find and findAll to search listswww.canoo.com
List<Person> people = Person.findAll()        Map<String, List<Person>> frequencies =                           new HashMa...
def people = Person.findAll()        def frequencies = people.inject([:]) { acc, p ->            acc[p.lastName] ?        ...
Effective Groovy #2c                Use inject to accumulate data                 (when collect isnt enough)www.canoo.com
def people = Person.findAll()        def families = people.unique() { it.lastName }www.canoo.com
Effective Groovy #2d                Use unique to filter resultswww.canoo.com
@Field Map cache = new HashMap<Integer, Integer>()        int fib(int seed) {            if (seed == 0) return seed       ...
def fib        fib = { seed   ->            if (seed   == 0) return seed            if (seed   == 1) return seed          ...
Effective Groovy #2d                 Use memoize to cache                idempotent method resultswww.canoo.com
def fact        fact = {int n, BigInteger acc ->            n > 1 ?                 fact.trampoline(n - 1, n * acc) :     ...
Effective Groovy #2e          Use trampoline for recursive functionswww.canoo.com
Effective Groovy #2f         Use join for converting Lists to Stringswww.canoo.com
def people = Person.findAll()        assert people.any { it.firstName == Joe }www.canoo.com
def people = Person.findAll()        def b = people.every { it.firstName == Joe }        assert !bwww.canoo.com
Effective Groovy #2g                Use any and every for logical                    operations on Listswww.canoo.com
def people = Person.findAll()        for (Person p : people) {            println p.firstName        }www.canoo.com
Effective Groovy #2h    Prefer Java for-each to Collections.eachwww.canoo.com
Effective Groovy #3                Know and use the File methodswww.canoo.com
FileReader reader = new FileReader("./01.groovy");        BufferedReader input = new BufferedReader(reader);        String...
def file = new File(./01.groovy)        println file.textwww.canoo.com
def file = new File(./02.groovy)        file.eachLine { println it }www.canoo.com
def file = new File(./03.groovy)        file.text = file.text + n // hello! www.canoo.com
def file = new File(./04.groovy)        file.append n // better hello! www.canoo.com
Effective Java #66 – #74                  Concurrencywww.canoo.com
class MyDataStore {            private final map = [:]                def add(key, value) {                    map.put(key...
class MyDataStore {            private final map = [:]                def synchronized add(key, value) {                  ...
class MyDataStore {                private final map = [:]                private final lock = new Object()               ...
class MyDataStore {            private final map = [:]                @Synchronized                def add(key, value) {  ...
Effective Groovy #4a                Prefer Declarative Synchronizationwww.canoo.com
class MyDataStore {            private final map = [:]            private final lock = new ReentrantReadWriteLock()       ...
class MyDataStore {            private final map = [:]            private final lock = new ReentrantReadWriteLock()       ...
Effective Groovy #8                Prefer ARM blocks to try-finallywww.canoo.com
class MyDataStore {            private final map = [:]                @WithWriteLock                def add(key, value) { ...
Effective Groovy #4b                Prefer Declarative Lockingwww.canoo.com
def count        def t = Thread.start {                count = Person.findAll()?.size()        }        t.join()        pr...
def exe = Executors.newSingleThreadExecutor()        def future = exe.submit({            Person.findAll()?.size()        ...
Effective Java #69                Prefer Concurrency Utilitieswww.canoo.com
Effective Java #68                Prefer executors and tasks to threadswww.canoo.com
@Grab(  group = org.codehaus.gpars,          module = gpars,          version = 0.11)  import static groovyx.gpars.dataflo...
task {            total << personCount.val + personCount.val        }        task {            personCount << Person.findA...
Effective Groovy #5                Prefer Declarative Coordinationwww.canoo.com
Effective Groovy #5½                Learn to Use @Grabwww.canoo.com
More Effective Java    #21: Use function objects to represent strategies    #36: Consistently use @Override    #71: Use La...
More Effective Groovy    #9: Learn to Write a Builder    #10: XMLSlurper/Parser - Do not mix with business logic/layers/et...
More Effective Groovy (with static analysis)www.canoo.com
Thanks!        http://canoo.com/blog        http://hamletdarcy.blogspot.com        @HamletDRC        http://tv.jetbrains.n...
Upcoming SlideShare
Loading in...5
×

GR8Conf 2011: Effective Groovy

1,666

Published on

Published in: Technology, News & Politics

GR8Conf 2011: Effective Groovy

  1. 1. Effective Groovy Hamlet DArcy Canoo Engineering AG @HamletDRCwww.canoo.com
  2. 2. Agenda Effective Java Reconsidered Effective Groovywww.canoo.com
  3. 3. Groovy & GrailsJavaRich Business ApplicationsUsabilityRefactoring
  4. 4. @HamletDRC http://hamletdarcy.blogspot.com http://www.manning.com/koenig2/ Groovy, CodeNarc, JConch Committer GPars, Griffon, Gradle, etc. Contributor GroovyMag, NFJS magazine author JetBrains Academy Memberwww.canoo.com
  5. 5. Effective Groovywww.canoo.com
  6. 6. Effective Groovywww.canoo.com
  7. 7. Effective Java #2 Consider a builder when faced with many constructor parameterswww.canoo.com
  8. 8. Person p = new Person(1, "David", "Villa");www.canoo.com
  9. 9. Person p = new Person(1, "David", "Villa"); Person p = new PersonBuilder(). withID(1). withFirstName("David"). withLastName("Villa"). build();www.canoo.com
  10. 10. public class PersonBuilder { private String firstName private String lastName private Integer id public PersonBuilder withID(int id) { this.id = id; return this; } public PersonBuilder withFirstName(String firstName) { this.firstName = firstName; return this; } public PersonBuilder withLastName(String lastName) { this.lastName = lastName; return this; } public Person build() { return new Person(id, firstName, lastName); } }www.canoo.com
  11. 11. def p = new Person( id: 1, firstName: David, lastName: Villa )www.canoo.com
  12. 12. def p = new Person().with { id = 1 firstName = David lastName = Villa delegate }www.canoo.com
  13. 13. Effective Java #3 Enforce the singleton property with a private constructorwww.canoo.com
  14. 14. @Singleton class Zeus { }www.canoo.com
  15. 15. Effective Java #5 Avoid creating unnecessary objectswww.canoo.com
  16. 16. assert 12345G instanceof BigIntegerassert 123.45G instanceof BigDecimalwww.canoo.com
  17. 17. assert 12345G instanceof BigIntegerassert 123.45G instanceof BigDecimalassert [] instanceof ArrayListassert [:] instanceof LinkedHashMapassert ([] as Set) instanceof HashSetassert ([] as Stack) instanceof Stackwww.canoo.com
  18. 18. assert 12345G instanceof BigIntegerassert 123.45G instanceof BigDecimalassert [] instanceof ArrayListassert [:] instanceof LinkedHashMapassert ([] as Set) instanceof HashSetassert ([] as Stack) instanceof StackCaseInsensitiveList list = [] as CaseInsensitiveListassert list instanceof CaseInsensitiveListwww.canoo.com
  19. 19. Effective Java #7 Avoid Finalizerswww.canoo.com
  20. 20. Effective Java #7 Avoid Finalizerswww.canoo.com
  21. 21. Effective Java #8 Obey the general contract when overriding equalswww.canoo.com
  22. 22. @EqualsAndHashCode class Person { String firstName String lastName int id }www.canoo.com
  23. 23. Effective Java #9 Always override hashCode when your override equalswww.canoo.com
  24. 24. Effective Java #9 Always override hashCode when your override equalswww.canoo.com
  25. 25. Effective Java #10 Always override toStringwww.canoo.com
  26. 26. @ToString class Person { String firstName String lastName int id }www.canoo.com
  27. 27. Effective Java #12 Consider implementing Comparablewww.canoo.com
  28. 28. def p1 = new Person( id: 1, firstName: Hamlet, lastName: "DArcy")def p2 = new Person( id: 2, firstName: Bro, lastName: "DArcy")assert p1 > p2assert p2 < p1assert (p1 <=> p2) == 1www.canoo.com
  29. 29. Effective Java #15 Minimize Mutabilitywww.canoo.com
  30. 30. @Immutable class Person { String firstName String lastName int id }www.canoo.com
  31. 31. Effective Java #16 Favor composition over inheritancewww.canoo.com
  32. 32. class NoisySet extends HashSet { @Override boolean add(i) { println "adding $i" super.add(i) } @Override boolean addAll(Collection i) { for(def x : i) { println "adding $x" } super.addAll(i) } }www.canoo.com
  33. 33. class NoisySet implements Set { @Delegate Set delegate = new HashSet() @Override boolean add(i) { println "adding $i" delegate.add(i) } @Override boolean addAll(Collection i) { for(def x : i) { println "adding $x" } delegate.addAll(i) } }www.canoo.com
  34. 34. Effective Java #19 Use interfaces only to define typeswww.canoo.com
  35. 35. Effective Java #19 Use interfaces only to define typeswww.canoo.com
  36. 36. Effective Java #41 Use Overloading Judiciouslywww.canoo.com
  37. 37. def function(Collection c) { println received collection } def function(ArrayList a) { println received arraylist } function((Collection) [])www.canoo.com
  38. 38. Effective Java Groovy #41 Prefer default parameters to overloadingwww.canoo.com
  39. 39. @Log class Calculator { def log(method, parm1, parm2) { log.info( "Method Called: $method" + (parm1 ? ", p1: $parm1" : ) + (parm2 ? ", p2: $parm2" : ) ) } def log(methodName, parm1) { log(methodName, parm1, null) } def log(methodName) { log(methodName, null, null) } def add(x, y) { log(add, x, y); x+y } def increment(x) { log(increment, x); x++ } }www.canoo.com
  40. 40. @Log class Calculator { def log(method, parm1 = null, parm2 = null) { log.info( "Method Called: $method" + (parm1 ? ", p1: $parm1" : ) + (parm2 ? ", p2: $parm2" : ) ) } def add(x, y) { log(add, x, y); x+y } def increment(x) { log(increment, x); x++ } }www.canoo.com
  41. 41. Effective Java #43 Return empty arrays or collections, not nullswww.canoo.com
  42. 42. Effective Java #43 Return empty arrays or collections, not nullswww.canoo.com
  43. 43. Effective Java #54 Use native methods judiciouslywww.canoo.com
  44. 44. Effective Java #54 Use native methods judiciouslywww.canoo.com
  45. 45. Effective Java #56 Adhere to generally accepted naming conventionswww.canoo.com
  46. 46. Effective Java #56 Adhere to generally accepted naming conventionswww.canoo.com
  47. 47. Effective Java #57 – #61 Use exceptions only for exceptional conditions Avoid unnecessary use of checked exceptions Use checked exceptions for recoverable conditions ...www.canoo.com
  48. 48. Effective Java #57 – #61 Use exceptions only for exceptional conditions Avoid unnecessary use of checked exceptions Use checked exceptions for recoverable conditions … and morewww.canoo.com
  49. 49. Effective Java #65 Dont ignore exceptionswww.canoo.com
  50. 50. Effective Java #65 Dont ignore exceptionswww.canoo.com
  51. 51. def b new SwingBuilder().frame( size: [300, 300], show: true, defaultCloseOperation: EXIT_ON_CLOSE) { b = button(text: Click Me) } b.addMouseListener( [ mouseClicked: { println clicked! } ] as MouseListener)www.canoo.com
  52. 52. Effective Groovy #7 Implement interfaces as maps judiciouslywww.canoo.com
  53. 53. Effective Java #47 Know and use the librarieswww.canoo.com
  54. 54. Effective Groovy #2 Know and use the Collection methodswww.canoo.com
  55. 55. List<Person> people = Person.findAll(); List<Integer> ids = new ArrayList<Integer>(); for (Person p : people) { ids.add(p.getId()); }www.canoo.com
  56. 56. def people = Person.findAll() def ids = people.collect { it.id }www.canoo.com
  57. 57. def people = Person.findAll() def ids = people*.idwww.canoo.com
  58. 58. Effective Groovy #2a Use collect for List transformationswww.canoo.com
  59. 59. List<Person> people = Person.findAll(); Person joe = null; for (Person p : people) { if ("Joe".equals(p.getFirstName())) { joe = p; break; } }www.canoo.com
  60. 60. def people = Person.findAll() def joe = people.find { it.firstName == Joe }www.canoo.com
  61. 61. List<Person> people = Person.findAll(); List<Person> bucks = new ArrayList<Person>(); for (Person p : people) { if ("Buck".equals(p.getLastName())) { bucks.add(p); } }www.canoo.com
  62. 62. def people = Person.findAll() def bucks = people.findAll { it.lastName == Buck }www.canoo.com
  63. 63. Effective Groovy #2b Use find and findAll to search listswww.canoo.com
  64. 64. List<Person> people = Person.findAll() Map<String, List<Person>> frequencies = new HashMap<String, List<Person>>() for (Person p : people) { if (frequencies.containsKey(p.getLastName())) { frequencies.get(p.getLastName()).add(p) } else { frequencies.put(p.getLastName(), [p]) } }www.canoo.com
  65. 65. def people = Person.findAll() def frequencies = people.inject([:]) { acc, p -> acc[p.lastName] ? acc[p.lastName].add(p) : (acc[p.lastName] = [p]) acc }www.canoo.com
  66. 66. Effective Groovy #2c Use inject to accumulate data (when collect isnt enough)www.canoo.com
  67. 67. def people = Person.findAll() def families = people.unique() { it.lastName }www.canoo.com
  68. 68. Effective Groovy #2d Use unique to filter resultswww.canoo.com
  69. 69. @Field Map cache = new HashMap<Integer, Integer>() int fib(int seed) { if (seed == 0) return seed if (seed == 1) return seed int minus2 = cache.get(seed - 2) ?: fib(seed – 2) int minus1 = cache.get(seed - 1) ?: fib(seed – 1) cache.put(seed-2, minus2) cache.put(seed-1, minus1) minus2 + minus1 }www.canoo.com
  70. 70. def fib fib = { seed -> if (seed == 0) return seed if (seed == 1) return seed fib(seed - 2) + fib(seed – 1) }.memoize()www.canoo.com
  71. 71. Effective Groovy #2d Use memoize to cache idempotent method resultswww.canoo.com
  72. 72. def fact fact = {int n, BigInteger acc -> n > 1 ? fact.trampoline(n - 1, n * acc) : acc }.trampoline()www.canoo.com
  73. 73. Effective Groovy #2e Use trampoline for recursive functionswww.canoo.com
  74. 74. Effective Groovy #2f Use join for converting Lists to Stringswww.canoo.com
  75. 75. def people = Person.findAll() assert people.any { it.firstName == Joe }www.canoo.com
  76. 76. def people = Person.findAll() def b = people.every { it.firstName == Joe } assert !bwww.canoo.com
  77. 77. Effective Groovy #2g Use any and every for logical operations on Listswww.canoo.com
  78. 78. def people = Person.findAll() for (Person p : people) { println p.firstName }www.canoo.com
  79. 79. Effective Groovy #2h Prefer Java for-each to Collections.eachwww.canoo.com
  80. 80. Effective Groovy #3 Know and use the File methodswww.canoo.com
  81. 81. FileReader reader = new FileReader("./01.groovy"); BufferedReader input = new BufferedReader(reader); String str; while ((str = input.readLine()) != null) { System.out.println(str); } try {input.close();} catch (IOException ex) {}www.canoo.com
  82. 82. def file = new File(./01.groovy) println file.textwww.canoo.com
  83. 83. def file = new File(./02.groovy) file.eachLine { println it }www.canoo.com
  84. 84. def file = new File(./03.groovy) file.text = file.text + n // hello! www.canoo.com
  85. 85. def file = new File(./04.groovy) file.append n // better hello! www.canoo.com
  86. 86. Effective Java #66 – #74 Concurrencywww.canoo.com
  87. 87. class MyDataStore { private final map = [:] def add(key, value) { map.put(key, value) } def getAt(key) { map[key] } }www.canoo.com
  88. 88. class MyDataStore { private final map = [:] def synchronized add(key, value) { map.put(key, value) } def synchronized getAt(key) { map[key] } }www.canoo.com
  89. 89. class MyDataStore { private final map = [:] private final lock = new Object() def add(key, value) { synchronized(lock) { map.put(key, value) } } def getAt(key) { synchronized(lock) { map[key] } } }www.canoo.com
  90. 90. class MyDataStore { private final map = [:] @Synchronized def add(key, value) { map.put(key, value) } @Synchronized def getAt(key) { map[key] } }www.canoo.com
  91. 91. Effective Groovy #4a Prefer Declarative Synchronizationwww.canoo.com
  92. 92. class MyDataStore { private final map = [:] private final lock = new ReentrantReadWriteLock() def add(key, value) { lock.writeLock().lock() try { map.put(key, value) } finally { lock.writeLock().unlock() } } def getAt(key) { lock.readLock().lock() try { map[key] } finally { lock.readLock().unlock() } } }www.canoo.com
  93. 93. class MyDataStore { private final map = [:] private final lock = new ReentrantReadWriteLock() def add(key, value) { withWriteLock(lock) { map.put(key, value) } } def getAt(key) { withReadLock(lock) { map[key] } } private static withWriteLock(def lock, Closure f) { lock.writeLock().lock() try { f() } finally { lock.writeLock().unlock() } } private static withReadLock(def lock, Closure f) { lock.readLock().lock() try { f() } finally { lock.readLock().unlock() } } }www.canoo.com
  94. 94. Effective Groovy #8 Prefer ARM blocks to try-finallywww.canoo.com
  95. 95. class MyDataStore { private final map = [:] @WithWriteLock def add(key, value) { map.put(key, value) } @WithReadLock def getAt(key) { map[key] } }www.canoo.com
  96. 96. Effective Groovy #4b Prefer Declarative Lockingwww.canoo.com
  97. 97. def count def t = Thread.start { count = Person.findAll()?.size() } t.join() println countwww.canoo.com
  98. 98. def exe = Executors.newSingleThreadExecutor() def future = exe.submit({ Person.findAll()?.size() } as Callable) println future.get() exe.shutdown()www.canoo.com
  99. 99. Effective Java #69 Prefer Concurrency Utilitieswww.canoo.com
  100. 100. Effective Java #68 Prefer executors and tasks to threadswww.canoo.com
  101. 101. @Grab( group = org.codehaus.gpars, module = gpars, version = 0.11) import static groovyx.gpars.dataflow.DataFlow.task import groovyx.gpars.dataflow.DataFlowVariable final count = new DataFlowVariable() task { count << Person.findAll().size() } println count.valwww.canoo.com
  102. 102. task { total << personCount.val + personCount.val } task { personCount << Person.findAll().size() } task { addressCount << Address.findAll().size() } println "Total: $total.val"www.canoo.com
  103. 103. Effective Groovy #5 Prefer Declarative Coordinationwww.canoo.com
  104. 104. Effective Groovy #5½ Learn to Use @Grabwww.canoo.com
  105. 105. More Effective Java #21: Use function objects to represent strategies #36: Consistently use @Override #71: Use Lazy Initialization judiciously – See Groovys @Lazy #47: Know & use the libraries – Read the GDK Docs and Release Notes #63: Include Failure-capture information in detailed messages #11: Override Clone Judiciously – See @AutoClone, @Canonicalwww.canoo.com
  106. 106. More Effective Groovy #9: Learn to Write a Builder #10: XMLSlurper/Parser - Do not mix with business logic/layers/etc #11: Effective Java #21: Use Function Objects to represent strategies #12: Threading: Avoid Busy Wait #13: Threading: Avoid Double Checked Locking #14: Threading: Avoid Inconsistent Property Locking #15: Threading: Avoid Inconsistent Property Synchronization #16: Threading: Avoid Synchronizing On Boxed Primitive #17: Know and use Elvis operator ?: #18: Excessively use the null-safe dereference operator #19: Understand Operator Overloadingwww.canoo.com
  107. 107. More Effective Groovy (with static analysis)www.canoo.com
  108. 108. Thanks! http://canoo.com/blog http://hamletdarcy.blogspot.com @HamletDRC http://tv.jetbrains.net/tags/hamlet http://www.youtube.com/hamletdrc Groovy, Grails, Griffon, and Agile Consulting info@canoo.com orwww.canoo.com hamlet.darcy@canoo.com
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×