Home Up F/C Conversion Interface Design Offensive Programming Zero Defects? Code Maintainability Encapsulation Comp. Aided Disaster The Silver Bullet Clarion for Dummies DevCon 97 EuroDevcon 97

 

Encapsulation, polymorphism and inheritance. There; we’ve done it. Used all the necessary buzzwords for yet another article espousing the benefits of OOP yet without actually giving people any meat to chew on.

Having done all that is required to retain my credibility, I now want to approach this from a very different angle. We recently had to produce a resize extension template for the Standard Edition product and we set about the problem using traditional, procedural techniques and ended up with an OOP program! I wish to detail some of the steps we took with a minimum of jargon in the hope that we can inspire others to do likewise. This article is necessarily at a language level although it will hopefully provide insight for everyone who has cause to look at the code generated by the TopSpeed template set.

The real interest is not in any one section of code but how each section evolves.

Having been given the task of producing the resize extension I set about my usual approach to code re-use. Sniffed around the office to see if I could beg / borrow / steal the code from someone else. Sure enough, there were a couple of code fragments I could swipe and I did some cutting and pasting and ended up with a procedure that worked as follows (leaving out certain details):-

HoldX SHORT **
HoldY SHORT **
I SIGNED **
CODE
HoldX = TARGET{PROP:Height} **
HoldY = TARGET{PROP:Width} **
ACCEPT
CASE EVENT()
OF EVENT:Sized
LOOP I = FIRSTFIELD() TO LASTFIELD() **
Resize_a_control &c **
Code_goes_on_a_bit **
END **
END
END

The code in ** is the contents of the extension template. The idea is quite simple, remember the old window size, when it changes step through each control and scale it accordingly.

The above code is quite acceptable and worked fine. The problem was the section ‘code goes on a bit’, to do this well you need special cases for buttons, list boxes, tab, list-boxes on tabs etc etc. The code is easy to write and as its’ in a template you only have to write it once but it does mean 30-50 lines of code in every procedure.

Therefore I used a device I have been trying to encourage for some time, the server function. Currently these all loiter in the _sf module of your application. Put simply all the code from the LOOP to the END can sit inside a procedure with just HoldX and HoldY passed in. This instantly wipes ½ K off of every procedure, reduces compile limit problems and speeds compiles.

The code is now structured as follows :-

!**** Global map
ResizeServer(short,short)

! *** Somewhere in program_sf

ResizeServer PROCEDURE(short HoldX, short HoldY)
I SHORT
CODE
LOOP I = FIRSTFIELD() TO LASTFIELD()

Resize_a_control &c
Code_goes_on_a_bit

END

!** Inside the extension template

HoldX SHORT **
HoldY SHORT **
CODE
HoldX = TARGET{PROP:Height} **
HoldY = TARGET{PROP:Width} **
ACCEPT

CASE EVENT()
OF EVENT:Sized

ResizeServer(HoldX,HoldY) **

END

END

The above code was implemented and went for testing. At this point Roy Hawkes (the other member of the template team) came along with a series of ideas for what we could do with the resize template if only we had a queue containing details of every control on the screen. I said he was welcome to try but if the code he produced broke my compiler then I was going to come up with some really good ideas for what he could do in his section of the templates. What we actually discovered was that using the same techniques as above we could radically change the earlier scheme with almost no changes to the template (and thus the stability of the system). Here is Roy’s new code.

!**** In the main program map or data section

ResizeServer(ResizeGroup,ResizeQ)
ResizeInit(ResizeGroup,ResizeQ)
ResizeGroup GROUP,TYPE
HoldX SHORT
etc
END

ResizeQ QUEUE,TYPE
FieldEquate SHORT
At1 SHORT
At2 SHORT
etc
END

! *** Somewhere in program_sf

ResizeServer PROCEDURE(ResizeGroup RG, ResizeQ RQ)
I SIGNED
CODE
LOOP I = FIRSTFIELD() TO LASTFIELD()

Resize_a_control &c
Code_goes_on_a_bit

END

ResizeInit PROCEDURE(ResizeGroup RG, ResizeQ RQ)
I SIGNED
CODE
RG.HoldX = TARGET{PROP:Height}
RG.HoldY = TARGET{PROP:Width}
LOOP I = FIRSTFIELD() TO LASTFIELD()

RQ.FieldEquate = I
RQ.At1 = I{PROP:At,1}
etc
ADD(RQ)

END

!** Inside the extension template

MyResize LIKE(ResizeGroup) **
MyQueue ResizeQ **
CODE
ResizeInit( MyResize , MyQueue ) **
ACCEPT

CASE EVENT()
OF EVENT:Sized

ResizeServer( MyResize, MyQueue ) **

END

END

There are some interesting points and features here.

  1. The definition of ResizeGroup and ResizeQ are global. They mean that the actual procedure contains no information about the contents of either the Group being used or the Queue. This has the advantage that if this code gets ‘frozen’ into handcode it will still remain up-to-date as it will change whenever the definitions do.
  2. Within the implementation of ResizeInit (and server) the fields of the group are accessed using the 2.0 field selection syntax. This allows groups (and queues) to be passed around as one block but individual fields to be accessed simply and efficiently.
  3. The code in the form routine is now very simple, it doesn’t get in the way. Someone debugging it can see that resizing is going on, and when, but their code is not cluttered with the intricacies of Roy’s latest resizing heuristic.

I thought the solution was rather good and said so. Then I dropped the bombshell, "In fact its’ so good it shouldn’t take you more that half an hour to make it into a class."

Some people register excitement by laughing, others by jumping, others by making yipping noises and others by clapping. Roy had the self control to avoid all of these. In fact he managed to look singularly unhappy. "Go on, all you need to do is change the GROUP into a CLASS and move the prototypes and then do a global exchange of RG for SELF". I was right too. Here is Roy’s code in OOPspeak.

!**** In the main program map or data section

ResizeQ QUEUE,TYPE
FieldEquate SHORT
At1 SHORT
At2 SHORT
etc
END

ResizeGroup CLASS,MODULE(‘Rescode.clw’),TYPE
! Method declarations (PROCEDUREs within a CLASS) have an implicit first parameter of type
! ResizeGroup.
Server PROCEDURE(ResizeQ)
Init PROCEDURE(ResizeQ)
HoldX SHORT
etc
END

! *** In Rescode.clw

MEMBER()
MAP.
ResizeGroup.Server PROCEDURE(ResizeQ RQ)
I SIGNED
CODE
LOOP I = FIRSTFIELD() TO LASTFIELD()

Resize_a_control &c
Code_goes_on_a_bit

END

ResizeGroup.Init PROCEDURE(ResizeQ RQ)
I SIGNED
CODE
SELF.HoldX = TARGET{PROP:Height}
SELF.HoldY = TARGET{PROP:Width}
LOOP I = FIRSTFIELD() TO LASTFIELD()

RQ.FieldEquate = I
RQ.At1 = I{PROP:At,1}
etc
ADD(RQ)

END

!** Inside the extension template

MyResize ResizeGroup **
MyQueue ResizeQ **
CODE
Init( MyResize , MyQueue ) **
ACCEPT
CASE EVENT()
OF EVENT:Sized

Server( MyResize, MyQueue ) **

END

END

You will probably look at the above and say, "what’s the big deal?". The OOP code is no better than the procedural, it is exactly the same ideas. And you’re right, OOP is just syntactic sugar on top of good procedural practice. But then again CW is just syntactic sugar on top of a good assembler …

There is one new piece in the above, rather than hijack the _sf module we have put the resize code in its’ own module (signaled in the MODULE statement). We have also used the MEMBER() syntax to unhook the RESCODE source from the program, this prevents it having to be generated for each application.

And finally …. There is one last thing we can do although this is getting more technical. If you look at MyQueue ResizeQ you may notice something slightly strange. It is not used by the procedure that owns it but is used by the methods it calls. This is daft, the procedure (and extension template) should not need to know about the queue, or even if there is one! So the trick is to move the queue down into the resize class. To do this we need the concept of the reference.

In Clarion a variable declaration really consists of two separate parts, the LABEL and the DATA ALLOCATION. In the following statement

MyShort SHORT

you are really saying three things

  1. I want a label called MyShort
  2. I want two bytes of data allocated
  3. I want MyShort to refer to those two bytes of data

A reference is simply a way of defining a label without defining data (a), a reference assignment (&=) is a way of assigning a label to some data c). We also have a way of creating anonymous data on the fly, it is the NEW function (b).

With these three concepts we can now move the queue entirely into the CLASS. I will actually annotate this listing as the final listing to give you a complete idea of exactly what we are doing :-

!**** In the main program map or data section

ResizeQ QUEUE,TYPE
FieldEquate SHORT
At1 SHORT
At2 SHORT
etc
END

Resize CLASS,MODULE(‘Rescode.clw’),TYPE
Server PROCEDURE
!Note we can have simple names, procedural overloading copes with name clashes
Init PROCEDURE
Kill PROCEDURE
!This says HoldQ is a label of a ResizeQ (but don’t allocate any data for it)
HoldQ &ResizeQ
HoldX SHORT
etc
END

! *** In Rescode.clw

MEMBER()
MAP.

Resize.Server PROCEDURE
I SIGNED
CODE
LOOP I = FIRSTFIELD() TO LASTFIELD()

Resize_a_control &c
Code_goes_on_a_bit

END

Resize.Init PROCEDURE
I SIGNED
CODE
!Allocate an anonymous ResizeQ and then attach it to label HoldQ
SELF.HoldQ &= NEW ResizeQ
SELF.HoldX = TARGET{PROP:Height}
SELF.HoldY = TARGET{PROP:Width}
LOOP I = FIRSTFIELD() TO LASTFIELD()

! We can use the period syntax to go into structured references. MyClass.MyReference.MyField
Self.HoldQ.FieldEquate = I
Self.HoldQ.At1 = I{PROP:At,1}
etc
ADD(Self.HoldQ)

END

Resize.Kill PROCEDURE
CODE
! Need to de-allocated ResizeQ when the resize class dies
DISPOSE(SELF.HoldQ)
!** Inside the extension template
MyResize ResizeGroup **
CODE
MyResize.Init ! Init(MyResize) if prefered **
ACCEPT

CASE EVENT()
OF EVENT:Sized

MyResize.Server **

END

END

MyResize.Kill **

And there we have it. A fully OOP resize class. The procedure that uses this template extension has a minimum of overhead (code or data) and a minimum of clutter in the procedure. The resize class has not become a black box (it is still in code you can debug) it has simply been parceled up so that everything to do with resizing is in one file.

Now just look at the code savings; without the use of server functions but with Roy’s clever resizing we would probably having been taking a 2-3K hit on each procedure, this has been reduced to about 100 bytes (4 Lines). A good maxim is an order of magnitude makes a difference. Here we have an order of magnitude. The difference is the functionality provided by the templates can now explode without the complexity of the procedure or the weight of the procedure increasing significantly. In fact I would expect a future set of templates to be able to provide existing levels of functionality whilst radically reducing the size of generated code and improving the readability (by letting the programmer see what is happening rather than how it is happening).

However, there is a still some icing on the cake for template writers and users. You see some of the fields in the CLASS declaration will acquire the PRIVATE attribute, some PROCEDUREs could too although they won’t need to in this case. These attributes make clear to the template user those parts of the system that are up for grabs and those parts which could change from release to release. You can still get at the private items simply by placing access functions in the rescode.clw module (CW has particularly clever privacy rules) but you are safe in the knowledge that code in your main application cannot be accidentally using things that are going to change. So the OOPization is not simply an efficiency gain but also helps the long-term maintainability of the application.

Well, I’ve just about managed a 2,000 word essay on OOP without using a single buzzword (after the first sentence). This is not because I don’t think these concepts are important, they are, but I learnt to use OOP long before it was fashionable to know what OOP was and sometimes I believe that is the best way.

Remember, scientifically speaking a bumble bee cannot fly; fortunately the bumble bee doesn’t know that ….

The Christian Counter

Top Christian Web Sites The Fundamental Top 500