What kind of Groovy developer you are?
Groovy Hacker
class Whatever {
def doSomeStuff(a, b, c) {
def iDontCare = c
if (iDontCare < a) {
iDontCare = "I don't care"
} else if (iDontCare > b) {
iDontCare = "I don't care anyway"
}
return "$iDontCare about other developers".bytes
}
}
Groovy Hacker
@Unroll
void 'given a=#a and b=#b we should get result'() {
expect:
new String(new Whatever().doSomeStuff(a, b, c)) == result
where:
a | b | c | result
0 | 0 | 0 | "0 about other developers"
1 | 2 | 0 | "I don't care about other developers"
-1 | -2 | 0 | "I don't care anyway about other developers"
}
Groovy Craftsman
@CompileStatic
class Ranges {
private Ranges() { }
static boolean inRange(int min, int max, int number) {
if (min > max) {
throw new IllegalArgumentException(
"Min must be lower or equal to max! min=$min, max=$max"
)
}
if (number < min || number > max) {
return false
}
return true
}
}
Groovy Craftsman
@Unroll
void '#number #expected in range (#min, #max)'() {
expect:
Ranges.inRange(min, max, number) == result
where:
min | max | number | result
0 | 0 | 0 | true
1 | 2 | 0 | false
-2 | -2 | 0 | false
expected = result ? 'is' : 'is not'
}
Groovy Developer Manifesto
1. I will write code in Java unless writing in Groovy would add some value; removing semicolon does not add value
Enums
enum SessionType {
REGULAR_TALK,
LIGHTNING_TALK,
WORKSHOP
}
public enum SessionType {
REGULAR_TALK,
LIGHTNING_TALK,
WORKSHOP;
}
Interfaces
interface Attendee {
void voteForSession(String sessionName, int points)
void voteForSession(String sessionName, int points, String remarks)
}
public interface Attendee {
default void voteForSession(String sessionName, int points) {
voteForSession(sessionName, points, null);
};
void voteForSession(String sessionName, int points, String remarks);
}
Functional Programming
Iterable<Session> filterSession(Iterable<Session> all, SessionType type) {
return all.findAll { Session session ->
session.type == type
}
}
Iterable<Session> filterSession(Iterable<Session> all, SessionType type) {
return StreamSupport.stream(all.spliterator(), true)
.filter(s -> type.equals(s.getType()))
.collect(Collectors.toList());
}
Data Classes
@Canonical
@CompileStatic
class Session {
final String name
final SessionType type
Session(String name, SessionType type) {
this.name = name
this.type = type
}
}
Data Classes
public class Session {
private final String name;
private final SessionType type;
public Session(String name, SessionType type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public SessionType getType() {
return type;
}
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
@Override
public String toString() { ... }
}
2. I will follow Effective Java rules even when I am writing Groovy code
Prefer Immutability
@ToString
@EqualsAndHashCode
class Session {
final String name
final String description
Sesssion(String name, String description) {
this.name = name
this.description = description
}
// ...
}
@CompileStatic
class Conference implements Event {
private static final Map<String, Conference> CONFERENCES = [:]
static Conference create(String name) {
CONFERENCES.computeIfAbsent(name) {
new Conference(name)
}
}
private final String name;
private final List<Session> sessions = []
private Conference(String name) {
this.name = name
}
@Override
void addSession(Session session) {
sessions.add(session)
}
@Override
Iterable<Session> getSessions() {
return sessions.asImmutable()
}
@Override
Iterator<Session> iterator() {
return getSessions().iterator()
}
}
3. I will use @CompileStatic annotation by default unless I really need dynamic language features
Static Compilation
@CompileStatic
Iterable<Session> filterSession(Iterable<Session> all, SessionType type) {
return all.findAll { Session session ->
session.type == type
}
}
Dynamic Structures
@CompileStatic
class StaticVsDynamic {
String readSomething(Map<String, Object> map) {
map.something
}
@CompileDynamic
String readSomethingElse(Node node) {
node.something.else
}
}
Domain Specific Languages
@CompileStatic
class DomainSpecificLanguages {
void buildSpreadsheet(SpreadsheetBuilder builder) {
builder.build {
sheet('Sample') {
row {
cell 'A'
}
row {
cell 1
}
}
}
}
}
4. I will never ever use def keyword
Groovy Hacker
class Whatever {
def doSomeStuff(a, b, c) {
def iDontCare = c
if (iDontCare < a) {
iDontCare = "I don't care"
} else if (iDontCare > b) {
iDontCare = "I don't care anyway"
}
return "$iDontCare about other developers".bytes
}
}
No Guarantee
import groovy.transform.CompileStatic
@CompileStatic
static String aMethod() {
def r = 'a string'
System.out.println "result is now ${r.getClass()}"
// imagine plenty of code
r = [trash: 'bin']
// imagine plenty of code
System.out.println "result is now ${r.getClass()}"
return r
}
println "r: ${aMethod()}"
5. I will never use Map as an excuse not to create meaningful class
6. I will use @NamedParam, @NamedDelegate and @NamedVariant instead of raw Map method parameters
Map instead of Object
Iterable<Session> filterSession(
Iterable<Session> all,
SessionType type,
Map options
) {
Collection<Session> result = all.findAll { Session session ->
session.type == type
}
if (options.offset) {
result = result.drop(options.offset as Integer)
}
if (options.limit) {
result = result.take(options.limit as Integer)
}
return result
}
repository.filterSession(sessions, type, [offset: 10])
Object instead of Map
Iterable<Session> filterSession(
Iterable<Session> all,
SessionType type,
PaginationOptions options
) {
Collection result = all.findAll { Session session ->
session.type == type
}
return result.drop(options.offset).take(options.limit)
}
repository.filterSession(sessions, type, withOffset(10))
Named Variant
@NamedVariant
Iterable<Session> filterSession2(
Iterable<Session> all,
SessionType type,
@NamedDelegate PaginationOptions options
) {
Collection result = all.findAll { Session session ->
session.type == type
}
return result.drop(options.offset).take(options.limit)
}
repository.filterSession2(sessions, type, offset: 10)
7. I will never use raw Closure without a type parameter
8. I will use @ClosureParams for every Closure method parameter
9. I will use @DelegatesTo for every Closure method parameter with altered delegate
Closure instead of Map/Object
repository.filterSession(sessions, type) {
offset 50
limit 25
}
Closure instead of Map/Object
Iterable<Session> filterSession(
Iterable<Session> all,
SessionType type,
@DelegatesTo(
value = PaginationOptions.Builder,
strategy = Closure.DELEGATE_FIRST
)
@ClosureParams(
value = SimpleType,
options = 'manifesto.groovy.PaginationOptions.Builder'
)
Closure<PaginationOptions.Builder> paginationOptions
) {
Collection result = all.findAll { Session session ->
session.type == type
}
PaginationOptions.Builder builder = new PaginationOptions.Builder()
builder.with paginationOptions
PaginationOptions options = builder.build()
return result.drop(options.offset).take(options.limit)
}
10. CodeNarc is my best friend forever
Thank You
Groovy Developer Manifesto Explained
By musketyr
Groovy Developer Manifesto Explained
- 2,005