Coding a batch process with numerous steps that can succeed or fail, while keeping control in calling program whether the whole process has succeeded and, if not, knowing the reason why, can end up with an unreadable program because of the multiple if ... endif or do case ... case ... endcase indented blocks.

Using if m.success... endif ends up with unreadable nested indentations and makes it difficult to change the tasks order:

procedure do_task as Boolean

local success as Boolean

... some code ...
if m.success

  ... some code ...
  if m.success

    ... some code ...
    if m.success

    endif
  endif
endif

assert m.success
return m.success
endproc

Using if !m.success... endif repeats code and adds multiple returns, but makes task order changes easier:

procedure do_task as Boolean

local success as Boolean

... some code ...
if !m.success
  assert .F.
  return .F.
endif

... some code ...
if !m.success
  assert .F.
  return .F.
endif

... some code ...
if !m.success
  assert .F.
  return .F.
endif

endproc

Using do case ... case ... endcase requires sub-procedures and is still uselessly verbose and somewhat difficult to understand as it relies on negations:

procedure do_task as Boolean

local success as Boolean

do case
case !do_task_step1()
case !do_task_step2()
case !do_task_step3()
otherwise
  success = .T.
endcase

assert m.success
return m.success
endproc

We found a convenient way to make program more readable by:

  • moving all concrete work to sub-procedures using a standard scheme for naming, parameters and returned value
  • chaining the calls to sub-procedures into a single logical expression

Let’s say main procedure is do_task with step1, step2, etc.

First create one procedure per step:

procedure do_task_step1 as Boolean
lparameters result && @ expected by reference -- always 1rst parameter 

local success as Boolean; && for step 1
, cResult as String && local result

... some code ...

cResult = iif(m.success, '', "reason for failure")
result = m.result + iif(empty(m.result), '', CRLF) + m.cResult && add to global result
assert m.success message m.cResult && stop right where error has occurred!
return m.success
endproc

Then create the main procedure:

procedure do_task as Boolean
lparameters result && @ expected by reference

result = evl(m.result, '') && makes sure m.result is a string

return .T.; && .T. and anything is anything
 and do_task_step1(@m.result); && pass m.result by reference
 and do_task_step2(@m.result);
 ...;
 and do_task_stepN(@m.result);
 and .T.

endproc

Very convenient is the ability to change the tasks’ order by just swapping 2 lines.

Of course, do_task_stepX() could be further broken into do_task_stepX_phaseY, etc.