SlideShare uma empresa Scribd logo
1 de 83
Baixar para ler offline
I’ve seen Grails code
you wouldn’t believe...
Iván López - @ilopmar
I’ve seen Grails code
you wouldn’t believe...
Iván López - @ilopmar
…Or how to properly write
maintainable Grails applications
Hello!
I am Iván López (@ilopmar)
THIS IS A STORY.
The events depicted in this talk took
place in “some projects” in 2017.
At the request of the survivors, the names
have been changed.
Out of respect for the dead, the rest has
been told exactly as it occurred.
TRUE
Great examples
(Please don’t do this)
1
The larger a class, the better
The larger a class, the better
Controller
15 beans injected
3868 lines!
The larger a method, the better
The larger a method, the better
238 lines
Some stats...
- 41 if / 16 else
- Up to 5 nested ifs
- 19 service calls
- 12 dynamic fnders
- 87 access to “params”
- 0 tests
The larger a setup method in a test, the better
The larger a setup method in a test, the better
Test for controller
Injecting services
in a unit test?
The larger a setup method in a test, the better
Creating shared data
Mocking dependencies the
same way for all the tests!
The larger a setup method in a test, the better
All same data and mocks
shared among 129 tests
● You can’t test it (or too complicated)
● IDE is slow
● Big setup methods because of lots of dependencies
How to detect it?
How do we fx it?
● Small classes (it’s free write more classes)
● Small methods
● Small (or non-existent) shared test code
Why does it matter?
● To write clean and maintainable code
● Be able to test the code
static constraints = {
code unique: true, nullable: false
name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
email email: false, nullable: true, size: 1..255
address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] }
city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
zipCode nullable: true
phone nullable: true
bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] }
vStyle nullable: true, maxSize: 128
origin nullable: true, maxSize: 128
defaultgl nullable: true
currencyCode nullable: true
}
Because copy-paste is always the fastest way...
static constraints = {
code unique: true, nullable: false
name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
email email: false, nullable: true, size: 1..255
address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] }
city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
zipCode nullable: true
phone nullable: true
bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] }
vStyle nullable: true, maxSize: 128
origin nullable: true, maxSize: 128
defaultgl nullable: true
currencyCode nullable: true
}
Because copy-paste is always the fastest way...
static constraints = {
code unique: true, nullable: false
name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
email email: false, nullable: true, size: 1..255
address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] }
city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
zipCode nullable: true
phone nullable: true
bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] }
vStyle nullable: true, maxSize: 128
origin nullable: true, maxSize: 128
defaultgl nullable: true
currencyCode nullable: true
}
Because copy-paste is always the fastest way...
static constraints = {
code unique: true, nullable: false
name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
email email: false, nullable: true, size: 1..255
address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] }
city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
zipCode nullable: true
phone nullable: true
bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] }
vStyle nullable: true, maxSize: 128
origin nullable: true, maxSize: 128
defaultgl nullable: true
currencyCode nullable: true
}
Because copy-paste is always the fastest way...
static constraints = {
code unique: true, nullable: false
name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
email email: false, nullable: true, size: 1..255
address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] }
city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] }
zipCode nullable: true
phone nullable: true
bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] }
vStyle nullable: true, maxSize: 128
origin nullable: true, maxSize: 128
defaultgl nullable: true
currencyCode nullable: true
}
Because copy-paste is always the fastest way...
Because copy-paste is always the fastest way...
class CustomValidations {
protected static final String INVALID_MAX_SIZE_MESSAGE = "default.invalid.max.size.message"
static def validateLength(Integer maxLength) {
return { val, obj ->
if (val?.length() > maxLength) {
return [INVALID_MAX_SIZE_MESSAGE, maxLength]
} else {
true
}
}
}
} static constraints = {
code unique: true, nullable: false
name nullable: false, validator: CustomValidations.validateLength(255)
email email: false, nullable: true, size: 1..255
address nullable: true, validator: CustomValidations.validateLength(500)
city nullable: true, validator: CustomValidations.validateLength(255)
state nullable: true, validator: CustomValidations.validateLength(255)
zipCode nullable: true
phone nullable: true
bU nullable: true, validator: CustomValidations.validateLength(1000)
vStyle nullable: true, maxSize: 128
origin nullable: true, maxSize: 128
defaultgl nullable: true
currencyCode nullable: true
}
Because copy-paste is always the fastest way...
class CustomValidations {
protected static final String INVALID_MAX_SIZE_MESSAGE = "default.invalid.max.size.message"
static def validateLength(Integer maxLength) {
return { val, obj ->
if (val?.length() > maxLength) {
return [INVALID_MAX_SIZE_MESSAGE, maxLength]
} else {
true
}
}
}
} static constraints = {
code unique: true, nullable: false
name nullable: false, validator: CustomValidations.validateLength(255)
email email: false, nullable: true, size: 1..255
address nullable: true, validator: CustomValidations.validateLength(500)
city nullable: true, validator: CustomValidations.validateLength(255)
state nullable: true, validator: CustomValidations.validateLength(255)
zipCode nullable: true
phone nullable: true
bU nullable: true, validator: CustomValidations.validateLength(1000)
vStyle nullable: true, maxSize: 128
origin nullable: true, maxSize: 128
defaultgl nullable: true
currencyCode nullable: true
}
Because copy-paste is always the fastest way...
class CustomValidations {
protected static final String INVALID_MAX_SIZE_MESSAGE = "default.invalid.max.size.message"
static def validateLength(Integer maxLength) {
return { val, obj ->
if (val?.length() > maxLength) {
return [INVALID_MAX_SIZE_MESSAGE, maxLength]
} else {
true
}
}
}
} static constraints = {
code unique: true, nullable: false
name nullable: false, validator: CustomValidations.validateLength(255)
email email: false, nullable: true, size: 1..255
address nullable: true, validator: CustomValidations.validateLength(500)
city nullable: true, validator: CustomValidations.validateLength(255)
state nullable: true, validator: CustomValidations.validateLength(255)
zipCode nullable: true
phone nullable: true
bU nullable: true, validator: CustomValidations.validateLength(1000)
vStyle nullable: true, maxSize: 128
origin nullable: true, maxSize: 128
defaultgl nullable: true
currencyCode nullable: true
}
● Lot of duplicated code
● Don’t repeat yourself (DRY)
● If you copy-paste +2 times extract, refactor & reuse→
How to detect it?
How do we fx it?
Why does it matter?
● Only need to change code in one place
● Reuse the code
def createUserWithActionPermissions(final itemInstance, final currentParams, final currentUser) {
def actionList = null
def actionPermissionError = null
def actionPermissionList = null
def userInstance = null
def errorInstance = null
def exceptionInstance = null
def statusList = null
final customParams = this.buildUserParams((BookType) itemInstance, currentParams, currentUser)
...
...
}
Use def everywhere because #yolo
Use def everywhere because #yolo
def createUserWithActionPermissions(final itemInstance, final currentParams, final currentUser) {
def actionList = null
def actionPermissionError = null
def actionPermissionList = null
def userInstance = null
def errorInstance = null
def exceptionInstance = null
def statusList = null
final customParams = this.buildUserParams((BookType) itemInstance, currentParams, currentUser)
...
...
}
Everything fnal but without type
A bunch of variables
without type
What about the returned type?
● Code-reviews
How to detect it?
How do we fx it?
● Always write your methods signatures as in Java
● If the type is not obvious, write it
Why does it matter?
● Be able to read the code
● Help IDE to help us: auto-completion, refactors, searchs,…
● Help future you (and your team) to read the code
Use String everywhere
Use String everywhere
● Code-reviews
● Codenarc
How to detect it?
How do we fx it?
● Use Enum/Constants
● Hide behavior inside enum (static methods)
Why does it matter?
● Find usages of the constants
● Refactor the code
Use GORM everywhere...
Use GORM everywhere...
Use GORM everywhere...
Use GORM everywhere...
● Code-reviews
How to detect it?
How do we fx it?
● Create new layer RepositoryService, GormService,...
● Only access db from this layer
Why does it matter?
● Decouple from the persistence
● Integration tests vs Unit tests
Life is to short for NullPointerException...
Life is to short for NullPointerException...
Life is to short for NullPointerException...
● Code-reviews
● Codenarc (UnnecessarySafeNavigationOperator Rule)
How to detect it?
How do we fx it?
● Only use ‘?’ when values really can be null
● Think twice before using it
Why does it matter?
● Easy to read/maintain code
Pass params to Services, mutate and return
Pass params to Services, mutate and return
● Code-reviews
How to detect it?
How do we fx it?
● Just don’t do it
● Use types, static inner classes, @Builder
Why does it matter?
● Params belongs to Controllers layer
● Decouple Controllers from Services
Who needs Command Objects? params FTW!
Who needs Command Objects? params FTW!
Who needs Command Objects? params FTW!
Who needs Command Objects? params FTW!
Modifying params
Use COs the wrong way
Pass params and
COs to service
● Code-reviews
How to detect it?
How do we fx it?
● Always use Command Objects
● Never use/modify “params”
Why does it matter?
● Know exactly the expected params
● Test data binding and params transformations
Who needs Services if we have @Transactional Controllers?
208 actions with business
logic that can’t be reused
Who needs Services if we have @Transactional Controllers?
● Code-reviews
● Codenarc
How to detect it?
How do we fx it?
● Put business logic on services
● If you need to add @Transactional in controller Move to→
service
Why does it matter?
● Reuse business logic if it’s in Services
● HTTP Layer ≠ Business Logic
Tests are for people who write bugs...
Tests are for people who write bugs...
● Code-reviews
● Coverage reports
How to detect it?
How do we fx it?
● TDD
● Just don’t push code without tests
Why does it matter?
● Modifcations, refactors, new features
● Sleep well at night
Should I write
tests?
Yes
What if I just
need to fnish
something fast?
Yes
What if I just...
OMG YES!!!
Default UrlMappings because I’m too lazy...
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
}
}
Default UrlMappings because I’m too lazy...
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
}
}
● Code-reviews
How to detect it?
How do we fx it?
● Delete the default mappings
● Create manually a mapping per controller action
Why does it matter?
● Have in one place all urls in application
The master piece
def getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region>
regionList, List<Division> divisionList) {
Sql sql = Sql.newInstance(dataSource) as Sql
def insurancePolicyList = InsurancePolicy.findAllByBookType(bookType);
String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String stmt="""select count(*) as counter from insurance_policy_condition where"""
if(insurancePolicyList?.size()>0){
stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})"""
}
if(companyList?.size()>0){
stmt=stmt+""" and company_id in (${companies})"""
}
if(departmentList?.size()>0){
stmt=stmt+""" and department_id in (${departments})"""
}
if(subdivisionTypesList?.size()>0){
stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})"""
}
if(regionList?.size()>0){
stmt=stmt+""" and region_id in (${regions})"""
}
if(divisionList?.size()>0){
stmt=stmt+""" and division_id in (${divisions})"""
}
stmt=stmt.replaceFirst("and","")
def row
if(stmt!=""){
row = sql.firstRow(stmt)
return row.counter
}else{
return 0
}
}
The master piece
def getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region>
regionList, List<Division> divisionList) {
Sql sql = Sql.newInstance(dataSource) as Sql
def insurancePolicyList = InsurancePolicy.findAllByBookType(bookType);
String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String stmt="""select count(*) as counter from insurance_policy_condition where"""
if(insurancePolicyList?.size()>0){
stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})"""
}
if(companyList?.size()>0){
stmt=stmt+""" and company_id in (${companies})"""
}
if(departmentList?.size()>0){
stmt=stmt+""" and department_id in (${departments})"""
}
if(subdivisionTypesList?.size()>0){
stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})"""
}
if(regionList?.size()>0){
stmt=stmt+""" and region_id in (${regions})"""
}
if(divisionList?.size()>0){
stmt=stmt+""" and division_id in (${divisions})"""
}
stmt=stmt.replaceFirst("and","")
def row
if(stmt!=""){
row = sql.firstRow(stmt)
return row.counter
}else{
return 0
}
}
The master piece
def getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region>
regionList, List<Division> divisionList) {
Sql sql = Sql.newInstance(dataSource) as Sql
def insurancePolicyList = InsurancePolicy.findAllByBookType(bookType);
String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String stmt="""select count(*) as counter from insurance_policy_condition where"""
if(insurancePolicyList?.size()>0){
stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})"""
}
if(companyList?.size()>0){
stmt=stmt+""" and company_id in (${companies})"""
}
if(departmentList?.size()>0){
stmt=stmt+""" and department_id in (${departments})"""
}
if(subdivisionTypesList?.size()>0){
stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})"""
}
if(regionList?.size()>0){
stmt=stmt+""" and region_id in (${regions})"""
}
if(divisionList?.size()>0){
stmt=stmt+""" and division_id in (${divisions})"""
}
stmt=stmt.replaceFirst("and","")
def row
if(stmt!=""){
row = sql.firstRow(stmt)
return row.counter
}else{
return 0
}
}
Let’s pretend we
didn’t see this
signature...
The master piece
def getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region>
regionList, List<Division> divisionList) {
Sql sql = Sql.newInstance(dataSource) as Sql
def insurancePolicyList = InsurancePolicy.findAllByBookType(bookType);
String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String stmt="""select count(*) as counter from insurance_policy_condition where"""
if(insurancePolicyList?.size()>0){
stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})"""
}
if(companyList?.size()>0){
stmt=stmt+""" and company_id in (${companies})"""
}
if(departmentList?.size()>0){
stmt=stmt+""" and department_id in (${departments})"""
}
if(subdivisionTypesList?.size()>0){
stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})"""
}
if(regionList?.size()>0){
stmt=stmt+""" and region_id in (${regions})"""
}
if(divisionList?.size()>0){
stmt=stmt+""" and division_id in (${divisions})"""
}
stmt=stmt.replaceFirst("and","")
def row
if(stmt!=""){
row = sql.firstRow(stmt)
return row.counter
}else{
return 0
}
}
The master piece
String companies =
companyList?.
id?.
toString()?.
replace("[","")?.
replace("]","")?.
replace(" ","")?.
trim()
The result will be a String, at
least is not def
The master piece
String companies =
companyList?.
id?.
toString()?.
replace("[","")?.
replace("]","")?.
replace(" ","")?.
trim()
List<Company> companyList
according method signature
The master piece
String companies =
companyList?.
id?.
toString()?.
replace("[","")?.
replace("]","")?.
replace(" ","")?.
trim()
Collect and extract the id of the
domain objects
[2, 5, 7]
The master piece
String companies =
companyList?.
id?.
toString()?.
replace("[","")?.
replace("]","")?.
replace(" ","")?.
trim()
String representation of the list "[2, 5, 7]"
The master piece
String companies =
companyList?.
id?.
toString()?.
replace("[","")?.
replace("]","")?.
replace(" ","")?.
trim()
Remove [ "2, 5, 7]"
The master piece
String companies =
companyList?.
id?.
toString()?.
replace("[","")?.
replace("]","")?.
replace(" ","")?.
trim()
Remove ] "2, 5, 7"
The master piece
String companies =
companyList?.
id?.
toString()?.
replace("[","")?.
replace("]","")?.
replace(" ","")?.
trim()
Remove “space” "2,5,7"
The master piece
String companies =
companyList?.
id?.
toString()?.
replace("[","")?.
replace("]","")?.
replace(" ","")?.
trim() Trim any space left "2,5,7"
String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
The master piece
String stmt="""select count(*) as counter from insurance_policy_condition where"""
if(insurancePolicyList?.size()>0){
stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})"""
}
if(companyList?.size()>0){
stmt=stmt+""" and company_id in (${companies})"""
}
if(departmentList?.size()>0){
stmt=stmt+""" and department_id in (${departments})"""
}
if(subdivisionTypesList?.size()>0){
stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})"""
}
if(regionList?.size()>0){
stmt=stmt+""" and region_id in (${regions})"""
}
if(divisionList?.size()>0){
stmt=stmt+""" and division_id in (${divisions})"""
}
Multi-line string
because...
The master piece
String stmt="""select count(*) as counter from insurance_policy_condition where"""
if(insurancePolicyList?.size()>0){
stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})"""
}
if(companyList?.size()>0){
stmt=stmt+""" and company_id in (${companies})"""
}
if(departmentList?.size()>0){
stmt=stmt+""" and department_id in (${departments})"""
}
if(subdivisionTypesList?.size()>0){
stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})"""
}
if(regionList?.size()>0){
stmt=stmt+""" and region_id in (${regions})"""
}
if(divisionList?.size()>0){
stmt=stmt+""" and division_id in (${divisions})"""
}
The master piece
String stmt="""select count(*) as counter from insurance_policy_condition where"""
if(insurancePolicyList?.size()>0){
stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})"""
}
if(companyList?.size()>0){
stmt=stmt+""" and company_id in (${companies})"""
}
if(departmentList?.size()>0){
stmt=stmt+""" and department_id in (${departments})"""
}
if(subdivisionTypesList?.size()>0){
stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})"""
}
if(regionList?.size()>0){
stmt=stmt+""" and region_id in (${regions})"""
}
if(divisionList?.size()>0){
stmt=stmt+""" and division_id in (${divisions})"""
}
The string with the
ids: "2,5,7"
Variable on
method signature
Build the sql query
concatenating strings
The master piece
String stmt="""select count(*) as counter from insurance_policy_condition where"""
if(insurancePolicyList?.size()>0){
stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})"""
}
if(companyList?.size()>0){
stmt=stmt+""" and company_id in (${companies})"""
}
if(departmentList?.size()>0){
stmt=stmt+""" and department_id in (${departments})"""
}
if(subdivisionTypesList?.size()>0){
stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})"""
}
if(regionList?.size()>0){
stmt=stmt+""" and region_id in (${regions})"""
}
if(divisionList?.size()>0){
stmt=stmt+""" and division_id in (${divisions})"""
}
stmt=stmt.replaceFirst("and","")
Remove the frst “and”
The master piece
def row
if(stmt!=""){
row = sql.firstRow(stmt)
return row.counter
}else{
return 0
}
“stmt” can’t never be empty
String stmt="""select count(*) as counter from insurance_policy_condition where"""
...
The master piece
def row
if(stmt!=""){
row = sql.firstRow(stmt)
return row.counter
}else{
return 0
}
This is the query executed
and it fails!
String stmt="""select count(*) as counter from insurance_policy_condition where"""
...
What happens if all lists in
method signature are
empty?
The master piece: The solution
Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList,
List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) {
List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType)
return InsurancePolicyCondition.createCriteria().count {
if (insurancePolicyList) {
'in' 'insurancePolicy', insurancePolicyList
}
if (companyList) {
'in' 'company', companyList
}
if (departmentList) {
'in' 'deparment', departmentList
}
if (subdivisionTypesList) {
'in' 'subdivision', subdivisionTypesList
}
if (regionList) {
'in' 'region', regionList
}
if (divisionList) {
'in' 'division', divisionList
}
}
}
The master piece: The solution
Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList,
List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) {
List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType)
return InsurancePolicyCondition.createCriteria().count {
if (insurancePolicyList) {
'in' 'insurancePolicy', insurancePolicyList
}
if (companyList) {
'in' 'company', companyList
}
if (departmentList) {
'in' 'deparment', departmentList
}
if (subdivisionTypesList) {
'in' 'subdivision', subdivisionTypesList
}
if (regionList) {
'in' 'region', regionList
}
if (divisionList) {
'in' 'division', divisionList
}
}
}
The master piece: The solution
Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList,
List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) {
List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType)
return InsurancePolicyCondition.createCriteria().count {
if (insurancePolicyList) {
'in' 'insurancePolicy', insurancePolicyList
}
if (companyList) {
'in' 'company', companyList
}
if (departmentList) {
'in' 'deparment', departmentList
}
if (subdivisionTypesList) {
'in' 'subdivision', subdivisionTypesList
}
if (regionList) {
'in' 'region', regionList
}
if (divisionList) {
'in' 'division', divisionList
}
}
}
The master piece: The solution
Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList,
List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) {
List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType)
return InsurancePolicyCondition.createCriteria().count {
if (insurancePolicyList) {
'in' 'insurancePolicy', insurancePolicyList
}
if (companyList) {
'in' 'company', companyList
}
if (departmentList) {
'in' 'deparment', departmentList
}
if (subdivisionTypesList) {
'in' 'subdivision', subdivisionTypesList
}
if (regionList) {
'in' 'region', regionList
}
if (divisionList) {
'in' 'division', divisionList
}
}
}
The master piece: The solution
Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList,
List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) {
List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType)
return InsurancePolicyCondition.createCriteria().count {
if (insurancePolicyList) {
'in' 'insurancePolicy', insurancePolicyList
}
if (companyList) {
'in' 'company', companyList
}
if (departmentList) {
'in' 'deparment', departmentList
}
if (subdivisionTypesList) {
'in' 'subdivision', subdivisionTypesList
}
if (regionList) {
'in' 'region', regionList
}
if (divisionList) {
'in' 'division', divisionList
}
}
}
Just using GORM the right way!
● Code-reviews
How to detect it?
How do we fx it?
● Teach developers their tools, languages,…
● Mentoring
● Buy books, send them to conferences,...
Why does it matter?
● Write clean and maintainable code
● Help your team grow
“
With great power comes
great responsibility
● http://guides.grails.org/
● http://grails.org/community.html
● https://grails.org/documentation.html
● https://objectcomputing.com/
training/catalog/grails/
Do you want to learn more?
Questions?
http://bit.ly/greach-grails
@ilopmar
lopez.ivan@gmail.com
https://github.com/ilopmar
Iván López
Thank you!

Mais conteúdo relacionado

Semelhante a Greach 2018 - I've seen Grails code you wouldn't believe...

This is a Java Code- you are only creating unit test for Validate and.pdf
This is a Java Code- you are only creating unit test for Validate and.pdfThis is a Java Code- you are only creating unit test for Validate and.pdf
This is a Java Code- you are only creating unit test for Validate and.pdf
aamousnowov
 
Knockout validation
Knockout validationKnockout validation
Knockout validation
LearningTech
 
IT8761-SECURITY LABORATORY-590519304-IT8761 security labmanual.pdf
IT8761-SECURITY LABORATORY-590519304-IT8761 security labmanual.pdfIT8761-SECURITY LABORATORY-590519304-IT8761 security labmanual.pdf
IT8761-SECURITY LABORATORY-590519304-IT8761 security labmanual.pdf
DhanuskarSankar1
 

Semelhante a Greach 2018 - I've seen Grails code you wouldn't believe... (9)

Christopher Latham Portfolio
Christopher Latham PortfolioChristopher Latham Portfolio
Christopher Latham Portfolio
 
This is a Java Code- you are only creating unit test for Validate and.pdf
This is a Java Code- you are only creating unit test for Validate and.pdfThis is a Java Code- you are only creating unit test for Validate and.pdf
This is a Java Code- you are only creating unit test for Validate and.pdf
 
Property Based Testing
Property Based TestingProperty Based Testing
Property Based Testing
 
Knockout validation
Knockout validationKnockout validation
Knockout validation
 
Tajmahal
TajmahalTajmahal
Tajmahal
 
Price of an Error
Price of an ErrorPrice of an Error
Price of an Error
 
The Scalactic Way
The Scalactic WayThe Scalactic Way
The Scalactic Way
 
IT8761-SECURITY LABORATORY-590519304-IT8761 security labmanual.pdf
IT8761-SECURITY LABORATORY-590519304-IT8761 security labmanual.pdfIT8761-SECURITY LABORATORY-590519304-IT8761 security labmanual.pdf
IT8761-SECURITY LABORATORY-590519304-IT8761 security labmanual.pdf
 
Web Forms People Don't Hate
Web Forms People Don't HateWeb Forms People Don't Hate
Web Forms People Don't Hate
 

Mais de Iván López Martín

Mais de Iván López Martín (20)

SalmorejoTech 2024 - Spring Boot <3 Testcontainers
SalmorejoTech 2024 - Spring Boot <3 TestcontainersSalmorejoTech 2024 - Spring Boot <3 Testcontainers
SalmorejoTech 2024 - Spring Boot <3 Testcontainers
 
CommitConf 2024 - Spring Boot <3 Testcontainers
CommitConf 2024 - Spring Boot <3 TestcontainersCommitConf 2024 - Spring Boot <3 Testcontainers
CommitConf 2024 - Spring Boot <3 Testcontainers
 
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdfVoxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
 
VMware - Testcontainers y Spring Boot
VMware - Testcontainers y Spring BootVMware - Testcontainers y Spring Boot
VMware - Testcontainers y Spring Boot
 
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud GatewaySpring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
 
Codemotion Madrid 2023 - Testcontainers y Spring Boot
Codemotion Madrid 2023 - Testcontainers y Spring BootCodemotion Madrid 2023 - Testcontainers y Spring Boot
Codemotion Madrid 2023 - Testcontainers y Spring Boot
 
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
CommitConf 2023 - Spring Framework 6 y Spring Boot 3CommitConf 2023 - Spring Framework 6 y Spring Boot 3
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
 
Construyendo un API REST con Spring Boot y GraalVM
Construyendo un API REST con Spring Boot y GraalVMConstruyendo un API REST con Spring Boot y GraalVM
Construyendo un API REST con Spring Boot y GraalVM
 
jLove 2020 - Micronaut and graalvm: The power of AoT
jLove 2020 - Micronaut and graalvm: The power of AoTjLove 2020 - Micronaut and graalvm: The power of AoT
jLove 2020 - Micronaut and graalvm: The power of AoT
 
Codemotion Madrid 2020 - Serverless con Micronaut
Codemotion Madrid 2020 - Serverless con MicronautCodemotion Madrid 2020 - Serverless con Micronaut
Codemotion Madrid 2020 - Serverless con Micronaut
 
JConf Perú 2020 - ¡Micronaut en acción!
JConf Perú 2020 - ¡Micronaut en acción!JConf Perú 2020 - ¡Micronaut en acción!
JConf Perú 2020 - ¡Micronaut en acción!
 
JConf Perú 2020 - Micronaut + GraalVM = <3
JConf Perú 2020 - Micronaut + GraalVM = <3JConf Perú 2020 - Micronaut + GraalVM = <3
JConf Perú 2020 - Micronaut + GraalVM = <3
 
JConf México 2020 - Micronaut + GraalVM = <3
JConf México 2020 - Micronaut + GraalVM = <3JConf México 2020 - Micronaut + GraalVM = <3
JConf México 2020 - Micronaut + GraalVM = <3
 
Developing Micronaut Applications With IntelliJ IDEA
Developing Micronaut Applications With IntelliJ IDEADeveloping Micronaut Applications With IntelliJ IDEA
Developing Micronaut Applications With IntelliJ IDEA
 
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
CommitConf 2019 - Micronaut y GraalVm: La combinación perfectaCommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
 
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
 
Greach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut ConfigurationsGreach 2019 - Creating Micronaut Configurations
Greach 2019 - Creating Micronaut Configurations
 
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
VoxxedDays Bucharest 2019 - Alexa, nice to meet youVoxxedDays Bucharest 2019 - Alexa, nice to meet you
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
 
JavaDay Lviv 2019 - Micronaut in action!
JavaDay Lviv 2019 - Micronaut in action!JavaDay Lviv 2019 - Micronaut in action!
JavaDay Lviv 2019 - Micronaut in action!
 
CrossDvlup Madrid 2019 - Alexa, encantado de conocerte
CrossDvlup Madrid 2019 - Alexa, encantado de conocerteCrossDvlup Madrid 2019 - Alexa, encantado de conocerte
CrossDvlup Madrid 2019 - Alexa, encantado de conocerte
 

Último

Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 

Último (20)

Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdf
 
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 AmsterdamDEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
DEV meet-up UiPath Document Understanding May 7 2024 Amsterdam
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 

Greach 2018 - I've seen Grails code you wouldn't believe...

  • 1. I’ve seen Grails code you wouldn’t believe... Iván López - @ilopmar
  • 2. I’ve seen Grails code you wouldn’t believe... Iván López - @ilopmar …Or how to properly write maintainable Grails applications
  • 3. Hello! I am Iván López (@ilopmar)
  • 4. THIS IS A STORY. The events depicted in this talk took place in “some projects” in 2017. At the request of the survivors, the names have been changed. Out of respect for the dead, the rest has been told exactly as it occurred. TRUE
  • 6. The larger a class, the better
  • 7. The larger a class, the better Controller 15 beans injected 3868 lines!
  • 8. The larger a method, the better
  • 9. The larger a method, the better 238 lines Some stats... - 41 if / 16 else - Up to 5 nested ifs - 19 service calls - 12 dynamic fnders - 87 access to “params” - 0 tests
  • 10. The larger a setup method in a test, the better
  • 11. The larger a setup method in a test, the better Test for controller Injecting services in a unit test?
  • 12. The larger a setup method in a test, the better Creating shared data Mocking dependencies the same way for all the tests!
  • 13. The larger a setup method in a test, the better All same data and mocks shared among 129 tests
  • 14. ● You can’t test it (or too complicated) ● IDE is slow ● Big setup methods because of lots of dependencies How to detect it? How do we fx it? ● Small classes (it’s free write more classes) ● Small methods ● Small (or non-existent) shared test code Why does it matter? ● To write clean and maintainable code ● Be able to test the code
  • 15. static constraints = { code unique: true, nullable: false name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } email email: false, nullable: true, size: 1..255 address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] } city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } zipCode nullable: true phone nullable: true bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] } vStyle nullable: true, maxSize: 128 origin nullable: true, maxSize: 128 defaultgl nullable: true currencyCode nullable: true } Because copy-paste is always the fastest way...
  • 16. static constraints = { code unique: true, nullable: false name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } email email: false, nullable: true, size: 1..255 address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] } city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } zipCode nullable: true phone nullable: true bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] } vStyle nullable: true, maxSize: 128 origin nullable: true, maxSize: 128 defaultgl nullable: true currencyCode nullable: true } Because copy-paste is always the fastest way...
  • 17. static constraints = { code unique: true, nullable: false name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } email email: false, nullable: true, size: 1..255 address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] } city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } zipCode nullable: true phone nullable: true bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] } vStyle nullable: true, maxSize: 128 origin nullable: true, maxSize: 128 defaultgl nullable: true currencyCode nullable: true } Because copy-paste is always the fastest way...
  • 18. static constraints = { code unique: true, nullable: false name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } email email: false, nullable: true, size: 1..255 address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] } city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } zipCode nullable: true phone nullable: true bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] } vStyle nullable: true, maxSize: 128 origin nullable: true, maxSize: 128 defaultgl nullable: true currencyCode nullable: true } Because copy-paste is always the fastest way...
  • 19. static constraints = { code unique: true, nullable: false name nullable: false, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } email email: false, nullable: true, size: 1..255 address nullable: true, validator: { val -> if (val?.length() > 500) ["default.invalid.max.size.message", 500] } city nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } state nullable: true, validator: { val -> if (val?.length() > 255) ["default.invalid.max.size.message", 255] } zipCode nullable: true phone nullable: true bU nullable: true, validator: { val -> if (val?.length() > 1000) ["default.invalid.max.size.message", 1000] } vStyle nullable: true, maxSize: 128 origin nullable: true, maxSize: 128 defaultgl nullable: true currencyCode nullable: true } Because copy-paste is always the fastest way...
  • 20. Because copy-paste is always the fastest way... class CustomValidations { protected static final String INVALID_MAX_SIZE_MESSAGE = "default.invalid.max.size.message" static def validateLength(Integer maxLength) { return { val, obj -> if (val?.length() > maxLength) { return [INVALID_MAX_SIZE_MESSAGE, maxLength] } else { true } } } } static constraints = { code unique: true, nullable: false name nullable: false, validator: CustomValidations.validateLength(255) email email: false, nullable: true, size: 1..255 address nullable: true, validator: CustomValidations.validateLength(500) city nullable: true, validator: CustomValidations.validateLength(255) state nullable: true, validator: CustomValidations.validateLength(255) zipCode nullable: true phone nullable: true bU nullable: true, validator: CustomValidations.validateLength(1000) vStyle nullable: true, maxSize: 128 origin nullable: true, maxSize: 128 defaultgl nullable: true currencyCode nullable: true }
  • 21. Because copy-paste is always the fastest way... class CustomValidations { protected static final String INVALID_MAX_SIZE_MESSAGE = "default.invalid.max.size.message" static def validateLength(Integer maxLength) { return { val, obj -> if (val?.length() > maxLength) { return [INVALID_MAX_SIZE_MESSAGE, maxLength] } else { true } } } } static constraints = { code unique: true, nullable: false name nullable: false, validator: CustomValidations.validateLength(255) email email: false, nullable: true, size: 1..255 address nullable: true, validator: CustomValidations.validateLength(500) city nullable: true, validator: CustomValidations.validateLength(255) state nullable: true, validator: CustomValidations.validateLength(255) zipCode nullable: true phone nullable: true bU nullable: true, validator: CustomValidations.validateLength(1000) vStyle nullable: true, maxSize: 128 origin nullable: true, maxSize: 128 defaultgl nullable: true currencyCode nullable: true }
  • 22. Because copy-paste is always the fastest way... class CustomValidations { protected static final String INVALID_MAX_SIZE_MESSAGE = "default.invalid.max.size.message" static def validateLength(Integer maxLength) { return { val, obj -> if (val?.length() > maxLength) { return [INVALID_MAX_SIZE_MESSAGE, maxLength] } else { true } } } } static constraints = { code unique: true, nullable: false name nullable: false, validator: CustomValidations.validateLength(255) email email: false, nullable: true, size: 1..255 address nullable: true, validator: CustomValidations.validateLength(500) city nullable: true, validator: CustomValidations.validateLength(255) state nullable: true, validator: CustomValidations.validateLength(255) zipCode nullable: true phone nullable: true bU nullable: true, validator: CustomValidations.validateLength(1000) vStyle nullable: true, maxSize: 128 origin nullable: true, maxSize: 128 defaultgl nullable: true currencyCode nullable: true }
  • 23. ● Lot of duplicated code ● Don’t repeat yourself (DRY) ● If you copy-paste +2 times extract, refactor & reuse→ How to detect it? How do we fx it? Why does it matter? ● Only need to change code in one place ● Reuse the code
  • 24. def createUserWithActionPermissions(final itemInstance, final currentParams, final currentUser) { def actionList = null def actionPermissionError = null def actionPermissionList = null def userInstance = null def errorInstance = null def exceptionInstance = null def statusList = null final customParams = this.buildUserParams((BookType) itemInstance, currentParams, currentUser) ... ... } Use def everywhere because #yolo
  • 25. Use def everywhere because #yolo def createUserWithActionPermissions(final itemInstance, final currentParams, final currentUser) { def actionList = null def actionPermissionError = null def actionPermissionList = null def userInstance = null def errorInstance = null def exceptionInstance = null def statusList = null final customParams = this.buildUserParams((BookType) itemInstance, currentParams, currentUser) ... ... } Everything fnal but without type A bunch of variables without type What about the returned type?
  • 26. ● Code-reviews How to detect it? How do we fx it? ● Always write your methods signatures as in Java ● If the type is not obvious, write it Why does it matter? ● Be able to read the code ● Help IDE to help us: auto-completion, refactors, searchs,… ● Help future you (and your team) to read the code
  • 29. ● Code-reviews ● Codenarc How to detect it? How do we fx it? ● Use Enum/Constants ● Hide behavior inside enum (static methods) Why does it matter? ● Find usages of the constants ● Refactor the code
  • 34. ● Code-reviews How to detect it? How do we fx it? ● Create new layer RepositoryService, GormService,... ● Only access db from this layer Why does it matter? ● Decouple from the persistence ● Integration tests vs Unit tests
  • 35. Life is to short for NullPointerException...
  • 36. Life is to short for NullPointerException...
  • 37. Life is to short for NullPointerException...
  • 38. ● Code-reviews ● Codenarc (UnnecessarySafeNavigationOperator Rule) How to detect it? How do we fx it? ● Only use ‘?’ when values really can be null ● Think twice before using it Why does it matter? ● Easy to read/maintain code
  • 39. Pass params to Services, mutate and return
  • 40. Pass params to Services, mutate and return
  • 41. ● Code-reviews How to detect it? How do we fx it? ● Just don’t do it ● Use types, static inner classes, @Builder Why does it matter? ● Params belongs to Controllers layer ● Decouple Controllers from Services
  • 42. Who needs Command Objects? params FTW!
  • 43. Who needs Command Objects? params FTW!
  • 44. Who needs Command Objects? params FTW!
  • 45. Who needs Command Objects? params FTW! Modifying params Use COs the wrong way Pass params and COs to service
  • 46. ● Code-reviews How to detect it? How do we fx it? ● Always use Command Objects ● Never use/modify “params” Why does it matter? ● Know exactly the expected params ● Test data binding and params transformations
  • 47. Who needs Services if we have @Transactional Controllers?
  • 48. 208 actions with business logic that can’t be reused Who needs Services if we have @Transactional Controllers?
  • 49. ● Code-reviews ● Codenarc How to detect it? How do we fx it? ● Put business logic on services ● If you need to add @Transactional in controller Move to→ service Why does it matter? ● Reuse business logic if it’s in Services ● HTTP Layer ≠ Business Logic
  • 50. Tests are for people who write bugs...
  • 51. Tests are for people who write bugs...
  • 52. ● Code-reviews ● Coverage reports How to detect it? How do we fx it? ● TDD ● Just don’t push code without tests Why does it matter? ● Modifcations, refactors, new features ● Sleep well at night
  • 53. Should I write tests? Yes What if I just need to fnish something fast? Yes What if I just... OMG YES!!!
  • 54. Default UrlMappings because I’m too lazy... class UrlMappings { static mappings = { "/$controller/$action?/$id?(.$format)?"{ constraints { // apply constraints here } } "/"(view:"/index") "500"(view:'/error') "404"(view:'/notFound') } }
  • 55. Default UrlMappings because I’m too lazy... class UrlMappings { static mappings = { "/$controller/$action?/$id?(.$format)?"{ constraints { // apply constraints here } } "/"(view:"/index") "500"(view:'/error') "404"(view:'/notFound') } }
  • 56. ● Code-reviews How to detect it? How do we fx it? ● Delete the default mappings ● Create manually a mapping per controller action Why does it matter? ● Have in one place all urls in application
  • 57. The master piece def getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) { Sql sql = Sql.newInstance(dataSource) as Sql def insurancePolicyList = InsurancePolicy.findAllByBookType(bookType); String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String stmt="""select count(*) as counter from insurance_policy_condition where""" if(insurancePolicyList?.size()>0){ stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})""" } if(companyList?.size()>0){ stmt=stmt+""" and company_id in (${companies})""" } if(departmentList?.size()>0){ stmt=stmt+""" and department_id in (${departments})""" } if(subdivisionTypesList?.size()>0){ stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})""" } if(regionList?.size()>0){ stmt=stmt+""" and region_id in (${regions})""" } if(divisionList?.size()>0){ stmt=stmt+""" and division_id in (${divisions})""" } stmt=stmt.replaceFirst("and","") def row if(stmt!=""){ row = sql.firstRow(stmt) return row.counter }else{ return 0 } }
  • 58. The master piece def getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) { Sql sql = Sql.newInstance(dataSource) as Sql def insurancePolicyList = InsurancePolicy.findAllByBookType(bookType); String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String stmt="""select count(*) as counter from insurance_policy_condition where""" if(insurancePolicyList?.size()>0){ stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})""" } if(companyList?.size()>0){ stmt=stmt+""" and company_id in (${companies})""" } if(departmentList?.size()>0){ stmt=stmt+""" and department_id in (${departments})""" } if(subdivisionTypesList?.size()>0){ stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})""" } if(regionList?.size()>0){ stmt=stmt+""" and region_id in (${regions})""" } if(divisionList?.size()>0){ stmt=stmt+""" and division_id in (${divisions})""" } stmt=stmt.replaceFirst("and","") def row if(stmt!=""){ row = sql.firstRow(stmt) return row.counter }else{ return 0 } }
  • 59. The master piece def getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) { Sql sql = Sql.newInstance(dataSource) as Sql def insurancePolicyList = InsurancePolicy.findAllByBookType(bookType); String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String stmt="""select count(*) as counter from insurance_policy_condition where""" if(insurancePolicyList?.size()>0){ stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})""" } if(companyList?.size()>0){ stmt=stmt+""" and company_id in (${companies})""" } if(departmentList?.size()>0){ stmt=stmt+""" and department_id in (${departments})""" } if(subdivisionTypesList?.size()>0){ stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})""" } if(regionList?.size()>0){ stmt=stmt+""" and region_id in (${regions})""" } if(divisionList?.size()>0){ stmt=stmt+""" and division_id in (${divisions})""" } stmt=stmt.replaceFirst("and","") def row if(stmt!=""){ row = sql.firstRow(stmt) return row.counter }else{ return 0 } } Let’s pretend we didn’t see this signature...
  • 60. The master piece def getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) { Sql sql = Sql.newInstance(dataSource) as Sql def insurancePolicyList = InsurancePolicy.findAllByBookType(bookType); String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String stmt="""select count(*) as counter from insurance_policy_condition where""" if(insurancePolicyList?.size()>0){ stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})""" } if(companyList?.size()>0){ stmt=stmt+""" and company_id in (${companies})""" } if(departmentList?.size()>0){ stmt=stmt+""" and department_id in (${departments})""" } if(subdivisionTypesList?.size()>0){ stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})""" } if(regionList?.size()>0){ stmt=stmt+""" and region_id in (${regions})""" } if(divisionList?.size()>0){ stmt=stmt+""" and division_id in (${divisions})""" } stmt=stmt.replaceFirst("and","") def row if(stmt!=""){ row = sql.firstRow(stmt) return row.counter }else{ return 0 } }
  • 61. The master piece String companies = companyList?. id?. toString()?. replace("[","")?. replace("]","")?. replace(" ","")?. trim() The result will be a String, at least is not def
  • 62. The master piece String companies = companyList?. id?. toString()?. replace("[","")?. replace("]","")?. replace(" ","")?. trim() List<Company> companyList according method signature
  • 63. The master piece String companies = companyList?. id?. toString()?. replace("[","")?. replace("]","")?. replace(" ","")?. trim() Collect and extract the id of the domain objects [2, 5, 7]
  • 64. The master piece String companies = companyList?. id?. toString()?. replace("[","")?. replace("]","")?. replace(" ","")?. trim() String representation of the list "[2, 5, 7]"
  • 65. The master piece String companies = companyList?. id?. toString()?. replace("[","")?. replace("]","")?. replace(" ","")?. trim() Remove [ "2, 5, 7]"
  • 66. The master piece String companies = companyList?. id?. toString()?. replace("[","")?. replace("]","")?. replace(" ","")?. trim() Remove ] "2, 5, 7"
  • 67. The master piece String companies = companyList?. id?. toString()?. replace("[","")?. replace("]","")?. replace(" ","")?. trim() Remove “space” "2,5,7"
  • 68. The master piece String companies = companyList?. id?. toString()?. replace("[","")?. replace("]","")?. replace(" ","")?. trim() Trim any space left "2,5,7" String insurancePolicies=insurancePolicyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String companies=companyList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String departments=departmentList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String subdivisionTypes=subdivisionTypesList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String regions=regionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim() String divisions=divisionList?.id?.toString()?.replace("[","")?.replace("]","")?.replace(" ","")?.trim()
  • 69. The master piece String stmt="""select count(*) as counter from insurance_policy_condition where""" if(insurancePolicyList?.size()>0){ stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})""" } if(companyList?.size()>0){ stmt=stmt+""" and company_id in (${companies})""" } if(departmentList?.size()>0){ stmt=stmt+""" and department_id in (${departments})""" } if(subdivisionTypesList?.size()>0){ stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})""" } if(regionList?.size()>0){ stmt=stmt+""" and region_id in (${regions})""" } if(divisionList?.size()>0){ stmt=stmt+""" and division_id in (${divisions})""" } Multi-line string because...
  • 70. The master piece String stmt="""select count(*) as counter from insurance_policy_condition where""" if(insurancePolicyList?.size()>0){ stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})""" } if(companyList?.size()>0){ stmt=stmt+""" and company_id in (${companies})""" } if(departmentList?.size()>0){ stmt=stmt+""" and department_id in (${departments})""" } if(subdivisionTypesList?.size()>0){ stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})""" } if(regionList?.size()>0){ stmt=stmt+""" and region_id in (${regions})""" } if(divisionList?.size()>0){ stmt=stmt+""" and division_id in (${divisions})""" }
  • 71. The master piece String stmt="""select count(*) as counter from insurance_policy_condition where""" if(insurancePolicyList?.size()>0){ stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})""" } if(companyList?.size()>0){ stmt=stmt+""" and company_id in (${companies})""" } if(departmentList?.size()>0){ stmt=stmt+""" and department_id in (${departments})""" } if(subdivisionTypesList?.size()>0){ stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})""" } if(regionList?.size()>0){ stmt=stmt+""" and region_id in (${regions})""" } if(divisionList?.size()>0){ stmt=stmt+""" and division_id in (${divisions})""" } The string with the ids: "2,5,7" Variable on method signature Build the sql query concatenating strings
  • 72. The master piece String stmt="""select count(*) as counter from insurance_policy_condition where""" if(insurancePolicyList?.size()>0){ stmt=stmt+""" and insurance_policy_id in (${insurancePolicies})""" } if(companyList?.size()>0){ stmt=stmt+""" and company_id in (${companies})""" } if(departmentList?.size()>0){ stmt=stmt+""" and department_id in (${departments})""" } if(subdivisionTypesList?.size()>0){ stmt=stmt+""" and subdivision_type_id in (${subdivisionTypes})""" } if(regionList?.size()>0){ stmt=stmt+""" and region_id in (${regions})""" } if(divisionList?.size()>0){ stmt=stmt+""" and division_id in (${divisions})""" } stmt=stmt.replaceFirst("and","") Remove the frst “and”
  • 73. The master piece def row if(stmt!=""){ row = sql.firstRow(stmt) return row.counter }else{ return 0 } “stmt” can’t never be empty String stmt="""select count(*) as counter from insurance_policy_condition where""" ...
  • 74. The master piece def row if(stmt!=""){ row = sql.firstRow(stmt) return row.counter }else{ return 0 } This is the query executed and it fails! String stmt="""select count(*) as counter from insurance_policy_condition where""" ... What happens if all lists in method signature are empty?
  • 75. The master piece: The solution Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) { List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType) return InsurancePolicyCondition.createCriteria().count { if (insurancePolicyList) { 'in' 'insurancePolicy', insurancePolicyList } if (companyList) { 'in' 'company', companyList } if (departmentList) { 'in' 'deparment', departmentList } if (subdivisionTypesList) { 'in' 'subdivision', subdivisionTypesList } if (regionList) { 'in' 'region', regionList } if (divisionList) { 'in' 'division', divisionList } } }
  • 76. The master piece: The solution Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) { List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType) return InsurancePolicyCondition.createCriteria().count { if (insurancePolicyList) { 'in' 'insurancePolicy', insurancePolicyList } if (companyList) { 'in' 'company', companyList } if (departmentList) { 'in' 'deparment', departmentList } if (subdivisionTypesList) { 'in' 'subdivision', subdivisionTypesList } if (regionList) { 'in' 'region', regionList } if (divisionList) { 'in' 'division', divisionList } } }
  • 77. The master piece: The solution Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) { List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType) return InsurancePolicyCondition.createCriteria().count { if (insurancePolicyList) { 'in' 'insurancePolicy', insurancePolicyList } if (companyList) { 'in' 'company', companyList } if (departmentList) { 'in' 'deparment', departmentList } if (subdivisionTypesList) { 'in' 'subdivision', subdivisionTypesList } if (regionList) { 'in' 'region', regionList } if (divisionList) { 'in' 'division', divisionList } } }
  • 78. The master piece: The solution Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) { List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType) return InsurancePolicyCondition.createCriteria().count { if (insurancePolicyList) { 'in' 'insurancePolicy', insurancePolicyList } if (companyList) { 'in' 'company', companyList } if (departmentList) { 'in' 'deparment', departmentList } if (subdivisionTypesList) { 'in' 'subdivision', subdivisionTypesList } if (regionList) { 'in' 'region', regionList } if (divisionList) { 'in' 'division', divisionList } } }
  • 79. The master piece: The solution Integer getInsurancePolicyConditions(String bookType, List<Company> companyList, List<Department> departmentList, List<SubdivisionType> subdivisionTypesList, List<Region> regionList, List<Division> divisionList) { List<InsurancePolicy> insurancePolicyList = InsurancePolicy.findAllByBookType(bookType) return InsurancePolicyCondition.createCriteria().count { if (insurancePolicyList) { 'in' 'insurancePolicy', insurancePolicyList } if (companyList) { 'in' 'company', companyList } if (departmentList) { 'in' 'deparment', departmentList } if (subdivisionTypesList) { 'in' 'subdivision', subdivisionTypesList } if (regionList) { 'in' 'region', regionList } if (divisionList) { 'in' 'division', divisionList } } } Just using GORM the right way!
  • 80. ● Code-reviews How to detect it? How do we fx it? ● Teach developers their tools, languages,… ● Mentoring ● Buy books, send them to conferences,... Why does it matter? ● Write clean and maintainable code ● Help your team grow
  • 81. “ With great power comes great responsibility
  • 82. ● http://guides.grails.org/ ● http://grails.org/community.html ● https://grails.org/documentation.html ● https://objectcomputing.com/ training/catalog/grails/ Do you want to learn more?