Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Do me a favor, would you? Have a quick look at this code and tell me what you think it does...

...

Do me a favor, would you? Have a quick look at this code and tell me what you think it does...

Code Block
MysteryProcedureUsingCodeBlock  (long StudentID)!,long ! Declare Procedure


FilesOpened     BYTE(0)
Result                                              long
StepNumber  end CourseQ                                       long
MostAvoidedTeacher     Queue CourseID                            cstring(100)
CourseTakenQ                   long Taken                    Queue
CourseID                              BOOL                  long
TeacherID                                  end TeacherQ            long
                               Queue TeacherID                    end
CourseNotTakenQ                          Long ClassesTaught          Queue
CourseID                                Long                long
TeacherID                                    end   x        long
                                          long WasTaken         end
CourseQ                                  bool      CODE     doQueue
OpenFilesCourseID     STU:StudentID = StudentID     get(Student,STU:pkStudentID)     if errorcode()         do CloseFiles         return FALSE     end     set(Course)long
Taken    loop         next(Course)         if errorcode() then break.         clear(CRSI:Record)         CRSI:CourseID = CRS:CourseID      BOOL
  set(CRSI:kCourseIDTeacherID,CRSI:kCourseIDTeacherID)          WasTaken = false         loop              next(CourseInstance)             if errorcode() orend
CRSI:CourseIDTeacherQ <> CRS:CourseID then break.             ENR:StudentID = StudentID             ENR:CourseInstanceID = crsi:CourseInstanceID          Queue
TeacherID  get(Enrollment,ENR:kCourseInstanceUDStudentID)             if not errorcode()                 WasTaken = TRUE           Long
ClassesTaught     break             end         end         if WasTaken      Long
      clear(CourseTakenQ)             CourseTakenQ.CourseID = CRS:CourseID                   CourseTakenQ.TeacherID = CRSI:TeacherID           end
x add(CourseTakenQ)         else             clear(CourseNotTakenQ)             CourseNotTakenQ.CourseID = CRS:CourseID             CourseNotTakenQ.TeacherIDlong
=WasTaken CRSI:TeacherID             add(CourseNotTakenQ)                    end     end     set(Teacher)bool

   loop CODE
    do OpenFiles
 next(Teacher)   STU:StudentID = StudentID
   if errorcodeget(Student,STU:pkStudentID)
then break.   if errorcode()
    TeacherQ.TeacherID = TEA:TeacherID  do CloseFiles
     add(TeacherQ)   return FALSE
end    end
loop x = 1 to recordsset(CourseTakenQCourse)
    loop
   get(CourseTakenQ,x)     next(Course)
   TeacherQ.TeacherID = CourseTakenQ.TeacherID         get(TeacherQ,TeacherQ.TeacherIDif errorcode() then break.
      if not errorcodeclear(CRSI:Record)
! should always find a record   CRSI:CourseID = CRS:CourseID
       TeacherQ.ClassesTaught += 1 set(CRSI:kCourseIDTeacherID,CRSI:kCourseIDTeacherID)
        WasTaken = false
   put(TeacherQ)     loop 
  end     end     sort(TeacherQ,-TeacherQ.ClassesTaughtnext(CourseInstance)
    loop while not errorcode()     if    get(TeacherQ,6errorcode() or CRSI:CourseID <> CRS:CourseID then break.
  delete(TeacherQ)     end     xENR:StudentID = 1StudentID
    loop        ENR:CourseInstanceID  get(CourseNotTakenQ,x)= crsi:CourseInstanceID
           if errorcodeget(Enrollment,ENR:kCourseInstanceUDStudentID)
then break.         TeacherQ.TeacherID = CourseNotTakenQ.TeacherIDif not errorcode()
      get(TeacherQ,TeacherQ.TeacherID)         if notWasTaken errorcode()= TRUE
           x += 1   break
     else       end
     delete(CourseNotTakenQ)   end
     end   if WasTaken
end     TeacherQ.TeacherID = CourseNotTakenQ.TeacherID     get(TeacherQ,TeacherQ.TeacherIDclear(CourseTakenQ)
    get(CourseNotTakenQ,random(1,records(CourseNotTakenQ)))        CourseTakenQ.CourseID = CRS:CourseID
            CourseTakenQ.TeacherID = CRSI:TeacherID
            add(CourseTakenQ)
     do CloseFiles  else
  return false  ! because this code is not yet complete

How are you making out? Do you have a clear idea of what this code does?

If you study the code long enough you should be able to figure out what it does, but you probably still won't know why it's doing what it does.

Now consider this version of the same code (I've omitted the OpenFiles/CloseFiles routines in both cases):

Code Block
MysteryProcedureUsingLocalProcedures  (long StudentID)!,long ! Declare Procedure clear(CourseNotTakenQ)
            CourseNotTakenQ.CourseID = CRS:CourseID
            CourseNotTakenQ.TeacherID = CRSI:TeacherID
            add(CourseNotTakenQ)           
        mapend
    end
    set(Teacher)
    loop
        next(Teacher)
        if errorcode() then break.
        GetStudentRecord(),long
 TeacherQ.TeacherID = TEA:TeacherID
        add(TeacherQ)
    end
    loop x = 1 to records(CourseTakenQ)
        get(CourseTakenQ,x)
         BuildListsOfCoursesTakenAndNotTaken(),long
TeacherQ.TeacherID = CourseTakenQ.TeacherID
        get(TeacherQ,TeacherQ.TeacherID)
        if not errorcode() ! should always find a record
            TeacherQ.ClassesTaught += 1
   CourseWasTaken(),long         put(TeacherQ)
        end
    end
    sort(TeacherQ,-TeacherQ.ClassesTaught)
    loop while not errorcode()
        BuildListOfTeachersget()TeacherQ,long6)
        delete(TeacherQ)
    end
    x = 1
    loop 
                 CountCoursesTaughtToStudent(),longget(CourseNotTakenQ,x)
        if errorcode() then break.
        TeacherQ.TeacherID = CourseNotTakenQ.TeacherID
        get(TeacherQ,TeacherQ.TeacherID)
        if not   SortTeacherListByCountOfCoursesTaughtToStudenterrorcode(),long
            x += 1
        else
            delete(CourseNotTakenQ)
       NarrowTeacherListToFiveMostAvoided(),long end
    end
    TeacherQ.TeacherID = CourseNotTakenQ.TeacherID
    get(TeacherQ,TeacherQ.TeacherID)
                          NarrowCourseNotTakenListToThoseTaughtByFiveMostAvoidedTeachers(),longget(CourseNotTakenQ,random(1,records(CourseNotTakenQ)))
    do CloseFiles
    return false  ! because this code is not                     yet complete

How are you making out? Do you have a clear idea of what this code does?

If you study the code long enough you should be able to figure out what it does, but you probably still won't know why it's doing what it does.

Now consider this version of the same code (I've omitted the OpenFiles/CloseFiles routines in both cases):

Code Block
MysteryProcedureUsingLocalProcedures  (long StudentID)!,long ! Declare Procedure

   CourseIsTaughtByOneOfFiveMostAvoidedTeachers(),long                                     map
       RegisterStudentForRandomlyChosenCourse(),long                                     GetStudentRecord(),long
   end FilesOpened                                         BYTEBuildListsOfCoursesTakenAndNotTaken(0),long
Result                                            CourseWasTaken(),long
 long StepNumber                                          BuildListOfTeachers(),long
MostAvoidedTeacher                                  cstring(100) CourseTakenQ         CountCoursesTaughtToStudent(),long
                              Queue CourseID             SortTeacherListByCountOfCoursesTaughtToStudent(),long
                                  long TeacherID         NarrowTeacherListToFiveMostAvoided(),long
                                     long       NarrowCourseNotTakenListToThoseTaughtByFiveMostAvoidedTeachers(),long
                                            CourseIsTaughtByOneOfFiveMostAvoidedTeachers(),long
end CourseNotTakenQ                                     Queue CourseID     RegisterStudentForRandomlyChosenCourse(),long
                                        end
FilesOpened long TeacherID                                       BYTE(0)
Result       long                                       long
StepNumber             end CourseQ                            long
MostAvoidedTeacher                Queue CourseID                 cstring(100)
CourseTakenQ                              long  Taken        Queue
CourseID                                          BOOL      long
TeacherID                                              end TeacherQlong
                                           Queue TeacherID        end
CourseNotTakenQ                                     Queue
LongCourseID ClassesTaught                                           Long    long
TeacherID                                               long
end      CODE     do OpenFiles     StepNumber = 0     Result = Level:Benign     Loop while Result = Level:Benign         StepNumber += 1      end
CourseQ  execute StepNumber             Result = GetStudentRecord()             Result = BuildListsOfCoursesTakenAndNotTaken()            Queue
CourseID Result = BuildListOfTeachers()             Result = CountCoursesTaughtToStudent()             Result = SortTeacherListByCountOfCoursesTaughtToStudent()             Result = NarrowTeacherListToFiveMostAvoided()long
Taken            Result = NarrowCourseNotTakenListToThoseTaughtByFiveMostAvoidedTeachers()             Result = RegisterStudentForRandomlyChosenCourse()             break         endBOOL
    end     do CloseFiles     Return result      GetStudentRecord                        procedure()!,long     code end
TeacherQ   STU:StudentID = StudentID     get(Student,STU:pkStudentID)     if not errorcode() then return level:benign.     return level:notify      BuildListsOfCoursesTakenAndNotTaken     procedure()!,long     code  Queue
TeacherID  set(Course)     loop         next(Course)         if errorcode() then break.         if CourseWasTaken() = Level:Benign       Long
ClassesTaught     clear(CourseTakenQ)             CourseTakenQ.CourseID = CRS:CourseID             CourseTakenQ.TeacherID = CRSI:TeacherID        Long
    add(CourseTakenQ)         else             clear(CourseNotTakenQ)             CourseNotTakenQ.CourseID = CRS:CourseID           end

CourseNotTakenQ.TeacherID = CRSI:TeacherID  CODE
    do OpenFiles
    add(CourseNotTakenQ)StepNumber = 0
    Result = Level:Benign
    Loop while Result = Level:Benign
 end      end StepNumber += 1
 return Level:Benign      CourseWasTakenexecute StepNumber
            Result = GetStudentRecord()
         procedure   Result = BuildListsOfCoursesTakenAndNotTaken()!,long
WasTaken            Result = BuildListOfTeachers()
            Result =   CountCoursesTaughtToStudent()
    bool     code   Result = clearSortTeacherListByCountOfCoursesTaughtToStudent(CRSI:Record)
     CRSI:CourseID = CRS:CourseID     Result = set(CRSI:kCourseIDTeacherID,CRSI:kCourseIDTeacherIDNarrowTeacherListToFiveMostAvoided()
    loop        Result = nextNarrowCourseNotTakenListToThoseTaughtByFiveMostAvoidedTeachers(CourseInstance)
        if errorcode() or CRSI:CourseID <>Result CRS:CourseID then break.= RegisterStudentForRandomlyChosenCourse()
         ENR:StudentID = StudentID break
       ENR:CourseInstanceID =end
crsi:CourseInstanceID    end
    get(Enrollment,ENR:kCourseInstanceUDStudentID)
do CloseFiles
    Return   ifresult
not errorcode()   
GetStudentRecord         WasTaken = TRUE             breakprocedure()!,long
    code
     endSTU:StudentID = StudentID
    endget(Student,STU:pkStudentID)
    if not WasTakenerrorcode() then return level:benign.
    return level:notify
    
BuildListOfTeachers     BuildListsOfCoursesTakenAndNotTaken                procedure()!,long
    code
    set(TeacherCourse)
    loop
        next(TeacherCourse)
        if errorcode() then break.
        TeacherQ.TeacherIDif CourseWasTaken() = TEALevel:TeacherIDBenign
        add(TeacherQ)    clear(CourseTakenQ)
end     return Level:Benign      CourseTakenQ.CourseID = CRS:CourseID
     CountCoursesTaughtToStudent       CourseTakenQ.TeacherID = CRSI:TeacherID
   procedure()!,long
x         add(CourseTakenQ)
        else
            clear(CourseNotTakenQ)
            CourseNotTakenQ.CourseID = CRS:CourseID
 long     code     loop xCourseNotTakenQ.TeacherID = CRSI:TeacherID
1  to records(CourseTakenQ)         getadd(CourseTakenQ,xCourseNotTakenQ)         TeacherQ.TeacherID = CourseTakenQ.TeacherID
        get(TeacherQ,TeacherQ.TeacherID)end
    end
   if not errorcode() ! should always find a recordreturn Level:Benign
    
CourseWasTaken             TeacherQ.ClassesTaught += 1             putprocedure(TeacherQ)!,long
WasTaken        end     end     return Level:Benign SortTeacherListByCountOfCoursesTaughtToStudent  procedure()!,long     code     sort(TeacherQ,-TeacherQ.ClassesTaught)     returnbool
Level:Benign    code
 NarrowTeacherListToFiveMostAvoided      procedure()!,long
xclear(CRSI:Record)
    CRSI:CourseID = CRS:CourseID
     set(CRSI:kCourseIDTeacherID,CRSI:kCourseIDTeacherID)
    loop 
        next(CourseInstance)
        if errorcode() or CRSI:CourseID <> CRS:CourseID then Longbreak.
    code    ENR:StudentID loop= whileStudentID
not errorcode()       ENR:CourseInstanceID  get(TeacherQ,6)= crsi:CourseInstanceID
        delete(TeacherQget(Enrollment,ENR:kCourseInstanceUDStudentID)
    end    if return Level:Benign
NarrowCourseNotTakenListToThoseTaughtByFiveMostAvoidedTeachers  procedure()!,long
xnot errorcode()
            WasTaken = TRUE
            break
        end
    end
    if WasTaken then return level:benign.
    return level:notify
    
BuildListOfTeachers                     procedure()!,long
    code
    x = 1set(Teacher)
    loop
         get(CourseNotTakenQ,xnext(Teacher)
        if errorcode() then break.
        if CourseIsTaughtByOneOfFiveMostAvoidedTeachers()TeacherQ.TeacherID = LevelTEA:BenignTeacherID
        add(TeacherQ)
   x +=end
1    return Level:Benign
   else         
CountCoursesTaughtToStudent   delete(CourseNotTakenQ)          endprocedure()!,long
x     end     return Level:Benign      CourseIsTaughtByOneOfFiveMostAvoidedTeachers    procedure()!,long     code     TeacherQ.TeacherID = CourseNotTakenQ.TeacherID     get(TeacherQ,TeacherQ.TeacherID)     iflong
not errorcode() then return Level:Benign.code
    returnloop Level:Notifyx = 1 to records(CourseTakenQ)
      RegisterStudentForRandomlyChosenCourse  procedureget(CourseTakenQ,x)!,long
    code     get(CourseNotTakenQ,random(1,records(CourseNotTakenQ)))
 TeacherQ.TeacherID = CourseTakenQ.TeacherID
  return Level:Notify  ! because this code is not yet complete get(TeacherQ,TeacherQ.TeacherID)
        if not errorcode() ! should always 

 

 

 

Code Block
 find a record
  do OpenFiles     StepNumber = 0  TeacherQ.ClassesTaught += 1
Result = Level:Benign     Loop while Result = Level:Benign  put(TeacherQ)
        end
    end
    return Level:Benign
SortTeacherListByCountOfCoursesTaughtToStudent  procedure()!,long
    code
   StepNumber += 1
 sort(TeacherQ,-TeacherQ.ClassesTaught)
    return Level:Benign
    
NarrowTeacherListToFiveMostAvoided  execute StepNumber   procedure()!,long
x         Result = GetStudentRecord()             Result = BuildListsOfCoursesTakenAndNotTaken()             Result = BuildListOfTeachers()
            Result = CountCoursesTaughtToStudent()
            Result = SortTeacherListByCountOfCoursesTaughtToStudent()
            Result = NarrowTeacherListToFiveMostAvoided()
            Result = NarrowCourseNotTakenListToThoseTaughtByFiveMostAvoidedTeachers()
            Result = RegisterStudentForRandomlyChosenCourse()
            break
        end
    end
    do CloseFiles
    Return result    Long
    code
    loop while not errorcode()
        get(TeacherQ,6)
        delete(TeacherQ)
    end
    return Level:Benign
NarrowCourseNotTakenListToThoseTaughtByFiveMostAvoidedTeachers  procedure()!,long
x                                                                   long
    code
    x = 1
    loop 
        get(CourseNotTakenQ,x)
        if errorcode() then break.
        if CourseIsTaughtByOneOfFiveMostAvoidedTeachers() = Level:Benign
            x += 1
        else
            delete(CourseNotTakenQ)
        end
    end
    return Level:Benign
    
CourseIsTaughtByOneOfFiveMostAvoidedTeachers    procedure()!,long
    code
    TeacherQ.TeacherID = CourseNotTakenQ.TeacherID
    get(TeacherQ,TeacherQ.TeacherID)
    if not errorcode() then return Level:Benign.
    return Level:Notify
    
    
RegisterStudentForRandomlyChosenCourse  procedure()!,long
    code
    get(CourseNotTakenQ,random(1,records(CourseNotTakenQ)))
    return Level:Notify  ! because this code is not yet complete
        

I'm pretty confident that you'll find the second version a lot easier to understand, because it breaks up all those blocks of code into procedures with descriptive names. 

But the real beauty of this procedure is a construct Mike Hanson recently showed me, and which I've christened the Hanson Loop:

Code Block
    StepNumber = 0
    Result = Level:Benign
    Loop while Result = Level:Benign
        StepNumber += 1
        execute StepNumber
            Result = GetStudentRecord()
            Result = BuildListsOfCoursesTakenAndNotTaken()
            Result = BuildListOfTeachers()
            Result = CountCoursesTaughtToStudent()
            Result = SortTeacherListByCountOfCoursesTaughtToStudent()
            Result = NarrowTeacherListToFiveMostAvoided()
            Result = NarrowCourseNotTakenListToThoseTaughtByFiveMostAvoidedTeachers()
            Result = RegisterStudentForRandomlyChosenCourse()
            break
        end
    end
    do CloseFiles
    Return result

The Hanson Loop is a while loop - it keeps executing as long as the Result variable is equal to Level:Benign. You could write a siilar loop testing for Result = True, but I think there are some important benefits to using the Clarion return levels, which I'll get to in a moment.

Within the Hanson Loop there's an Execute structure. When the StepNumber variable has a value of 1, the first line in the Execute structure is executed; when the StepNumber variable has a value of 2, the second line executes, and so on. 

The very last statement in the Execute structure is simply a break command, which exits the loop. 

The beauty of the Hanson Loop is it provides a clear overview of the code being executed by the main procedure, while allowing for clean and efficient error handling. 

And this is where the Clarion error levels come in.

The Clarion error levels

I will admit that it has taken me years, almost two decades really, to fully adopt Clarion error levels in my own coding. 

The Clarion help has this to say about error levels, in the context of the ABC ErrorManager object:

Panel

Six Levels of Treatment

By default, the error manager recognizes six different levels of error severity. The default actions for these levels range from no action for benign errors to halting the program for fatal errors. The error manager also supports the intermediate actions of simply notifying the user, or of notifying the user and letting the user decide whether to continue or abort.

Customizable Treatments

These various levels of treatment are implemented with virtual methods so they are easy to customize. The error manager calls a different virtual method for each severity level, so you can override the default error actions with your own application specific error actions. See the various Take methods for examples.

The recognized severity EQUATEs are declared in EQUATES.CLW. These severity levels and their default actions are:

 

Level:Benign (0)

no action, returns Level:Benign

Level:User (1)

displays message, returns Level:Benign or Level:Cancel

Level:Notify (5)

displays message, returns Level:Benign

Level:Fatal (3)

displays message, halts the program

Level:Program (2)

treated as Level:Fatal

Level:Cancel (4)

used to confirm no action taken by User

any other value

treated as Level:Program

 

You may define your own additional severity levels and their associated actions.