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:
You may define your own additional severity levels and their associated actions. |