Blogs

Where to put your code

By Uniface Test posted 07-23-2015 07:00

  

(Original creator: a4dvo)

As a Uniface developer, I’ve seen a lot of Uniface applications first hand. On more than one occasion I encountered a situation where developers put all their code in the component. This happened for a number of reasons—access to the model or the library was constricted, there wasn’t enough time in the project to do it correctly, or just unfamiliarity with Uniface. I cannot speak on behalf of project managers or architects, but I can tell you how I code my projects. The first rule of Uniface is that you do not copy and paste! (Very obvious movie reference!) If you find yourself in a situation that you think you need to copy code: stop! You are probably better off removing the code from its original source, putting it in either the application model or the library, and then reusing it in both the original component and the component where you wanted to paste it.

Single field implementation

Consider the following:

if (HEIGHT.PERSON < 0) HEIGHT.PERSON = 0 endif

A person can never have a height that is smaller than 0 meters. Maybe there are people with a negative size, but I have never seen one. So if someone enters a negative value we reset the value to 0. If you were to put this in a component, than you need to copy and paste it the next time you need it. Remember the first rule? So where would you put it? The most logical place would be in the trigger of the modeled field HEIGHT in the PERSON entity. Creating an entry on entity level and then calling it from the leave field trigger would score equally well. This way the inheritance in Uniface will provide this piece of code in every component you use the field on.

Multiple field implementation

On record level

But what about two fields in the same entity? The formula for the Body Mass Index of a person would be: 

BMI.PERSON = WEIGHT.PERSON/$sqrt(HEIGHT.PERSON)

The content of BMI is calculated by dividing the WEIGHT by the square root of a person’s HEIGHT. In order to calculate the BMI we need the value of two different fields in the entity PERSON. If you thought about putting it in the modelled entity you’d be correct. I would create an entry that can be reused in (for instance) the value changed triggers of the WEIGHT and HEIGHT field or call it from a collection operation if you wanted to update all the BMI’s in some type of batch.

Between entities

Here is a classic. The total amount of an order is calculated by multiplying the price by the number of items in an order line, and then adding that to the total of the order. : 

forentity "ORDERLINE"  
TOTAL.ORDER += PRIZE.ORDERLINE * NUMBERITEMS.ORDERLINE endfor


The second rule of Uniface (you can actually here the voice of Brad Pitt, can’t you?) is that you never make a reference to another entity from a modelled entity. If you do, you need to include the referenced entity on every component you use the modeled entity on or the compiler will keep wagging its finger at you. So we can’t reference the TOTAL.ORDER field in the trigger of the ORDERLINES entity. The only logical place is to put it in in a component. In this case, I would put it in a service that can be called from other locations as well. I can even activate that service in the modelled trigger of the ORDER entity.

What if it is a non-database entity?

Non-database entities come in two distinct flavors. The modelled ones and the non-modelled ones. An example of a modelled non-database entity is the entity containing a list of buttons containing default behavior that you can reuse when creating components. With these particular non-database entities the same rules apply as for the modelled database entities. Non-modelled entities are created on the fly on a component. In this case there is only one place to put your code. The component level.

And non-database fields?

Non-database fields, have the same distinct flavors. They are either modelled (for instance a button that shows detailed information about a certain record of a modelled entity) or the non-modelled ones. If the non-database field is in the application model, code it there, otherwise code it in the component. When I mention the component, there are actually three levels where to place your code. In the triggers of the component, in the triggers of the non-database entity, or in the triggers of the non-database field. Based on the previous rules you should be able to determine the correct position.

There is no entity or field reference

Once more for good measure:

if ($status < 0)   
return $status endif

This code contains no field references and is of a more technical nature. This is an example of the smallest form of error handling in Uniface. If you intend to use it only once, the component is the best place to put it. If you need it in other places, you should move the code to the library and include it where required.

Can I use A Global Proc, instead of an Include Proc?

I have not used a Global Proc since the introduction of Include Procs. In my mind it is a deprecated feature of Uniface. From a component based development perspective Include Procs are better (but that is for another story). Besides using Global Procs for error handling purposes has one drawback. What happens when your Global Proc fails? Where are you going to catch that?

Let’s Summarize

DescriptionLogical place
Code references exactly one field in one modeled entityTrigger level of the modelled field
Code references more than one field in one modeled entityTrigger level of the modelled entity
Code references more than one modeled entityIn the component, preferably a service.
Code references a non-modeled entityIf a non-modeled entity is used more than once, it should be defined as a modeled non-database entity. If it is a very specific non-modeled entity, it can be only in the component.
Code does not reference a field or an entity.Include proc. Never in a global proc. Component only, when it is really specific.

   

7 comments
6 views

Comments

05-10-2019 08:03

Great to see a discussion on this topic. Even when you disagree, code from people who actually think about where to put their code is much better than code from people who don't.

05-10-2019 08:03

So in order to get a total field to update on the component, both entities must reference a service which compiles the total, but how?
I change the quantity on my order item, which changes the value of that item, and must update the total. The model calls a service (presumably by looping through the entire order items entity and packing the data up (manually into a list, or xmlsave or component to struct, manually is better in this instance, as the other two require knowledge of the components on the entity which you have decided should not be coded in the model)(assuming more than one total must be maintained, you can't just add up the value and pass that to the service), the service loops through the packed/unpacked data and calculates the totals. What does it then do with them? If it returns the order total as an out parameter, then the model for the order items entity (where this code was called to prevent the developer having to remember to include it) needs to know where the order total display field is (in another entity). I just cannot envisage any code which is included in both the order and the items entities, which does not cause compiler warnings if one or other entity is not present. Could you show how an order total value field (your example not mine) would be maintained in an inheritable fashion (Inheritance is of vital importance in these days of flexible and customisable user interfaces) without one entity referencing the existance of another in the U.I.
With reference to the 'failure code', your original examples were "Besides using Global Procs for error handling purposes has one drawback. What happens when your Global Proc fails? Where are you going to catch that?". I was simply showing the three problems inherent in using include procs to code for multiple compiled instances of one function, and pointing out that if the proc is not encapsulated itself (as a local proc (entry) compiled into the component) it requires another include proc to define any variables used, and if it is encapsulated the capture of a failure in the code in the include proc is as difficult as the capture of the failure of a global proc (but the failure of the include proc is more likely, as it is not an object in OO programming, and cannot be defined or encapsulated as such. )

05-10-2019 08:03

There are a few principles I like to go by when I write an article: it needs to fit on a single sheet of paper (about 400 words, in this case I went a little over) and the necessary information needs to be included.
So even though the remark of the super- and subtypes is a valid one I excluded them in this article. It would require me to spend some lines on the concepts on the concepts of super- and subtypes. Something to address in a future article. So stay tuned.
There are situations imaginable where only the parent or the child entity is painted on a component. Compiling these would instantly (if it would contain a reference to the other entity) raise a warning. There should be no warnings in the compilation (another principle I try to live by. More often than not I have to settle for ‘as few warnings as possible, and there is a good explanation for each’).
So I can add the entity to delete the warning even though I do not need it. Or I can just break the inheritance by using a semicolon in the trigger or remove the entry altogether. In the first case I am not being very lean. In the second instance I am violating the cardinal principle that I should avoid breaking inheritance as much as possible. I may even introduce unwanted behavior by excluding that vital piece of code.
So I would create a service (not just any component) that includes the entities (there could even be more than two) and put all the business rules that apply to that (set of) relation(s) in it. I could then reference the service in the model of both entities, informing the developer but not generating the error.
I am not sure that I understand the part of the ‘failure code’ correctly maybe you could supply me with a short example?
For my thoughts on the include procs over global procs please check feel free to check my reply to Rik Lewis’s post.

05-10-2019 08:03

From a component based development point of view I am in favor of Include procs over Global ones. Since the former ensures proper encapsulation of the code in the component, where the Global stuff is in a location outside of it.
From an historic point of view global procs used to be common practice in Uniface versions 6 and down. In these versions it was also common practice to use the $1-$99 global variables for parsing values to these procs. I still remember spending a monstrous amount of time trying to locate where the variable was updated, that messed it all up. To be honest I tend to be more lenient towards Global procs, when you are using params blocks.
I am a firm believer in working smarter, not harder (maybe that is why I joined Uniface). For traceability (it is a word. I have checked), I picked one (Include procs) and stuck with it. I do not want to spend my time looking in two different places for the correct piece of code.
Finally there is progress in innovation. Even though I have access to paper, I write emails every day (can’t remember the last time I have written a letter).

05-10-2019 08:03

A well organized model-template driven development environment delivers power to your development environment. It demands however another type of developer. A developer that follow standard and guidelines. A developer that delivers feedback to improve best practises. A developer that standardizes all its work. Working in that kind of environments is boring. The end result is predictable, no errors, fast, conform planning...no give me unpredictable behaviour, give me pressure and long nights, give me pizza....and make me happy;)

05-10-2019 08:03

I think that global procedures definitely still have their place. We I work we have hundreds of web forms, and we have included procs that are used in our template, and therefore every one of these forms. So if you make a change to the included procedure to fix a bug, you have to re-compile and release hundreds of forms out to customers in order to fix the problem. Not a problem if you have a hosted site, but our application is typically installed on customer sites, so they can maintain their own databases and servers. This makes maintenance and hotfixing of included procedures a nightmare! Whereas a global procedure can easily be exported out and easy to apply as part of a hotfix. Not that I'm admitting to their being any bugs in my code.....

05-10-2019 08:03

There is also the consideration as to whether the code is in the supertype, or in the component subtypes or in the functional subtype, or in the component subtype of the functional subtype.
If the total.order field should ALWAYS be the sum of the (price*qty) of the order lines then not including it in the model requires some form of backup warning message that it has not been done where that field is painted on any form.
Including it in the model is more likely to allow for the programmer spotting that there is an issue with his interface design, put as a local proc on its own in a trigger associated with the field, the contents can be commented out and detached from the model if the total field is not required to be maintained/displayed from that component.
Done this way, the developer has less to remember and is prompted for edge case behaviour.
I would therefore disagree with the blanket statement about across entity operations belonging in the component.
The major advantage of global proc over include proc is similar. Where the 'failure code' requires variables etc, they must either be included in every proc where the include proc is brought in , (so that's two include procs, the variable names MUST be unique so as not to confuse things, and the user must remember to add both) or the include file contains the code for an encapsulated local proc which must then be included manually in every component which requires it (and I don't see how that has any advantage over global proc when it comes to recognising it's own failure... ). It cannot be included in the model, as this would then re-define the local proc at several places within one component.
If the contents of your global error processing proc change to handle environment etc changes, every program using the include proc must be re-compiled and issued to production. As long as the input/output of the global proc does not change, only the global proc need be shipped. Global procs are therefore significantly more useful in a 'patching' environment than include proc, or in any enviroment where the plan is to 'enhance' an existing function (replacing commit for example or $datim) as the extra local proc required does not have to be declared in every program.