|
What is a Control?
For computers, control is simple. The SAS system compiles the code into
a stream of CPU instructions. The stream is executed sequentially starting
with the first instruction, ending with the last instruction. Speed of
execution is the only significant difference between batch of compiled
instructions and the next.
For SAS programmers, control is simple to code and easy to understand.
- 00% is the definition of a logical step.
- 50% is executing each logical step in sequence
- 50% is evaluating positive and negative result messages created within
each logical step.
Three software structures implement the Control Relationship.
(1) Application Driver
- Base/SAS is interpreted as a single program from top to the bottom.
- SAS Object applications must have a main driver that indicates which
program is first, second, third, last.
(2) Component Driver
- Base/SAS is interpreted as a single program from top to the bottom.
- SAS Objects may evaluate the decision to execute as a precondition,
may evaluate the decision to continue after each logical step, and
may roll back the results following a post-result evaluation.
- SAS Objects may self-enable or self-disable in response to signals
from decision-logic and business-rule logic located in other objects.
(3) Driver Status Monitor: application driver and every instance driver
are tied to the same monitor.
- Feature not available in Base/SAS.
- Any instance (program) may evaluate positive and negative result
messages, errors, and exceptions from any other instance (program).
Standard Naming Convention
Apparently, the physical name that a programmer assigns
to a block of logic in a SAS script has exactly ZERO effect on the computational
speed of the application when executing in memory. This means exactly
two things for SAS programers.
- The name of each Source code should indicate the function, meaning,
step sequence, or feature implementation.
- When multiple independent functions are in a single source code,
it's time to split that program into smaller pieces. Create one program
for each independent function.
The following structure defines four physical steps within one logical
step. For presentation context, each SAS object represents on logical
step. This code shows that four steps MUST execute, without error, for
this logical step to be considered "complete". Any error indicates
complete failure for the program.
public list interface / (initialValue={'interface1',
'interface2',
'interface3',
'interface4'});
A simple system utility coded in SCL can populate this type of property
at runtime before execution.
SAS programmers need only define the property.
Application Main Driver
By default, SAS programs never have a main driver. The SAS System is
designed to read scripts, compile, and execute. There is no need for
driver logic because SAS knows which instruction to execute first, second,
and last.
Object programs are different because the code is already compiled
when submitted for execution. The main difference is that The SAS System
does not execute the program starting with the first line of code, the
programmer has to indicate the starting point. The first major problem
is that every programmer adopts a different naming convention for the
main driver.
The simple solution is to adopt a universal naming convention. The
code example below shows how to execute a SAS object program where every
program in the application has one method named runInterface.
Also, take note of the complete lack of decision logic. No program is
specifically named and executed like you see in Base/SAS.
Every program is executed unconditionally.
A main driver works on the same principal as your car engine.
The decision to (A) continue or (B) stop is NOT made inside the engine.
public list component / (sendEvent='N');
public list gMessage / (sendEvent='N');
runInterface: method; dcl num xObject; dcl object thisObject; do xObject=1 to listlen(component) while (listlen(gMessage)=0); thisObject=getitemo(component, xObject); put 'Executing Program.....' thisObject.description; thisObject.runInterface(); end; endmethod;
Most object programs are simple enough to execute until one of the
components issue a message that indicates an error. That message will
be detected by the while statement, causing the application to terminate.
The decision to stop can also come from a SAS/Frame GUI or another program
The code below demonstrates a second global vector that is evaluated
for content. Click the "STOP" button on a SAS/Frame GUI and
the command to stop is inserted into the global data vector named command.
That command is detected inside the main driver through the local property
named gCommand and the application will terminate normally.
public list component / (sendEvent='N');
public list gMessage / (sendEvent='N');
public list gCommand / (sendEvent='N');
runInterface: method; dcl num xObject; dcl object thisObject; do xObject=1 to listlen(component) while (listlen(gMessage)=0 or nameditem(gCommand, 'stop')); thisObject=getitemo(component, xObject); put 'Executing Program.....' thisObject.description; thisObject.runInterface(); end; endmethod;
Component Main Driver
Base/SAS programmers may consider every data step and SAS procedure
to be an independent logical step.
proc sql;
create table retail as select * from sashelp.retail; quit;
SAS object programmers have the same view, at first.
runInterface: method; dcl num xMethod; do xMethod=1 to listlen(interface) while (listlen(gMessage)=0); call send(_self_, getitemc(interface, xMethod)); end; endmethod; interface1: method / (description='Report Data'); submit continue sql; create table retail as select * from sashelp.retail; quit; endsubmit; endmethod;
SAS object programming offers more options when it comes to controlling
application response to errors and unexpected conditions in the data.
For example, a large program may have a large number of logical steps.
SQL generates two automatic variables that are easily collected with
a SAS object.
SQLRC - indicates a syntax error
SQLOBS - indicates the number of rows in the SAS dataset.
Unlike Base/SAS, syntax errors inside a submit block will not cause
the entire object application to bomb. Syntax errors must be detected
and handled within the application. In this case, if there is any SQLRC
value above zero a message will be inserted into the global message
vector and the application main driver will stop.
Zero observations need to be handled on a case-by-case basis.
In the code below an absence of data is an error.
interface1: method / (description='Report Data'); submit continue sql; create table retail as select * from sashelp.retail; quit; endsubmit; if symgetn('sqlrc') then insertc(gMessage, description||' '||_method_, -1, 'SQL Error');
if symgetn('sqlobs') then insertc(gMessage, description||' '||_method_, -1, 'Zero Observations'); endmethod;
Not all errors that occur inside a submit block are syntax errors.
Object programmers have the option of adding additional steps to detect
every possible problem. For example, add the following code to the application
and when this error is triggered it will instantly pinpoint the problem.
No need to hunt through the SAS Log looking for ERROR and WARNING messages.
Bulletproofing software may seem useful for everyday SAS programs; however,
this becomes absolutely mandatory for any application with GUI.
interface0: method / (description='Verify the dataset is present');
if exist('sashelp.retail')=0 then insertc(gMessage, description||' '||_method_, -1, 'Absent dataset'); endmethod;
SAS objects allow the programmer to easily detect an ABSENCE. The following
code throws a message that indicates a SAS dataset does not have an
expected column - without saying which column is absent or if multiple
columns are absent. Code that indicates which columns are present and
absent belongs in a separate program. The purpose here is to detect
specific errors and absences, without elaborating
interface3: method / (description='Validate dataset content'); submit continue sql; create table columns as select name from sashelp.vcolumn
where libname='SASHELP' and
memname='RETAIL' and
name in('SALES','YEAR','MONTH','DATE'); quit; endsubmit;
if symgetn('sqlobs') NE 4 then insertc(gMessage, 'Dataset SASHELP.RETAIL: a required column was not found'); endmethod;
Base/SAS and SAS Object perform exactly the same function. The core
code is the same. Even with the limited examples on this page, the difference
should be obvious and clear. Object programs can be loaded to the hilt
with automatic validations that detect and handle every possible
syntax error, data omission, and unexpected data value.
SAS Object were specifically structured for additional automatic validations.
Object programming will translate into better software design, with
fewer years of programming experience.
Automation + better design = lower IT budget.
Driver Status Monitor
The "main driver" in Relational Objects show four differences
compared to traditional software design.
- A status monitor is strictly a receiver of information.
- Any number of instances (programs) can connect and contribute
to the same status monitor.
- Any number of physically separate applications can connect and
contribute to the same status monitor.
- The number of instances (programs) and applications involved in
the status relationship may change each session.
The primary job of the status monitor in the Application Driver is
to determine when to terminate the application. The implementation can
be as simple as the WHILE clause with a single criteria.
public list gMessage / (sendEvent='N');
runInterface: method / (description='Application Driver'); dcl num xObject; dcl object thisObject; do xObject=1 to listlen(component) while (listlen(gMessage)=0); thisObject=getitemo(component, xObject); put 'Executing Program.....' thisObject.description; thisObject.runInterface(); end; endmethod;
The primary job of the status monitor in the Component Driver is to
determine when to stop executing logical steps. The implementation can
be as simple as the WHILE clause with a single criteria.
public list gMessage / (sendEvent='N');
runInterface: method / (description='Component Driver'); dcl num xMethod; do xMethod=1 to listlen(interface) while (listlen(gMessage)=0); call send(_self_, getitemc(interface, xMethod)); end; endmethod;
|