This is neither Hubitat nor Groovy specific, but a friend turned me onto the so-called "never nest" aka "no indent" programming method. Really, more of a philosophy than a methodology. In short, never nest is to avoid if-else statements. After doing it for awhile, it just seems to intuitively better than the norm, tho it does take a bit of practice to get the full benefits. First, it's just easier to read, plain and simple. Second, it forces breaking out tricky code into their own function. Third, it pushes data validation to be "just in time". And fourth, it actually runs faster. Video: https://youtu.be/CFRhGnuXG-4 (not affiliated).
As an example, here's code of mine that takes a string for 'on', 'off', or 'toggle', and sets an atomicState variable for the device. It checks if the inputs are valid, if it's a change from the current setting, then if the atomicState exists, sets 'on' and 'off', then 'toggle'. Granted, it's not necessarily good code, even using traditional nesting, but that's actually the point. I have found using "never nest" forces my at best mediocre coding skills to be... better, maybe even good. It doesn't let me do spaghetti code.
def updateStateSingle(singleDevice,action){
if(singleDevice && action){
time = new Date().time
if(atomicState?."deviceState${singleDevice.id}" && atomicState."deviceState${singleDevice.id}".'state'){
if(!atomicState."deviceState${singleDevice.id}".'state' == action){
if(action != 'toggle'){
atomicState."deviceState${singleDevice.id}" = ['state':action,'time':time]
} else if(action == 'toggle'){
if(atomicState."deviceState${singleDevice.id}".'state' == 'on') {
atomicState."deviceState${singleDevice.id}" = ['state':'off','time':time]
} else if(atomicState."deviceState${singleDevice.id}".'state' == 'off') {
atomicState."deviceState${singleDevice.id}" = ['state':'on','time':time]
}
}
}
} else {
if(action!= 'toggle'){
atomicState."deviceState${singleDevice.id}" = ['state':action,'time':time]
} else {
atomicState."deviceState${singleDevice.id}" = ['state':'on','time':time]
}
}
} else {
log.debug 'ERROR'
}
return
}
Now, same code using "never indent":
if(!updateStateSingle(singleDevice,action) log.debug 'ERROR'
def updateStateSingle(singleDevice,action){
if(!singleDevice) return
if(!action) return
time = new Date().time
if(!atomicState?."deviceState${singleDevice.id}") atomicState."deviceState${singleDevice.id}" = [:]
if(action == 'toggle'){
if(!atomicState?."deviceState${singleDevice.id}"?.'state') action == 'on'
if(atomicState."deviceState${singleDevice.id}".'state' == 'on') action == 'off'
if(atomicState."deviceState${singleDevice.id}".'state' == 'off') action == 'on'
}
if(atomicState."deviceState${singleDevice.id}".'state' == action) return // No change required
atomicState."deviceState${singleDevice.id}" = ['state':action,'time':time]
return true
}
It does virtually the same thing, but a lot fewer lines (only difference being the original did NOT check if the map key exists). I'm actually embarrassed for the first code. It's terrible. But as bad as my coding skills might be to produce that, the same level of skill did the second. Aside from being cleaner, easier to debug, etc., it self-prompts me to create smaller functions. In this example, the nest (for setting up 'toggle') suggests maybe I should break it out.
if(!updateStateSingle(singleDevice,action) log.debug 'ERROR'
def updateStateSingle(singleDevice,action){
if(!singleDevice) return
if(!action) return
time = new Date().time
if(!atomicState?."deviceState${singleDevice.id}") atomicState."deviceState${singleDevice.id}" = [:]
action = getUpdateStateSingleToggle(singleDevice,action)
if(atomicState."deviceState${singleDevice.id}".'state' == action) return // No change required
atomicState."deviceState${singleDevice.id}" = ['state':action,'time':time]
}
def getUpdateStateSingleToggle(singleDevice,action){
if(action != 'toggle') return action // If original value is already 'on' or 'off', preserves it
if(!atomicState?."deviceState${singleDevice.id}"?.'state') return 'on'
if(atomicState."deviceState${singleDevice.id}".'state' == 'on') return 'off'
if(atomicState."deviceState${singleDevice.id}".'state' == 'off') return 'on'
}
edit: Disclaimer on the example.... The error handling isn't great in either version. It does not enforce 'on', 'off' or 'toggle'. On the first, could do 'if(action != on || off || toggle)" at the top. With nesting, could just check "on || off" after doing the 'toggle' state, because without nests, it doesn't have to be in the right branch(es) of a if-then-else.