Why You Need Coding
Standard?
The best applications are coded properly. This sounds like
an obvious statement, but by 'properly', it means that the code not only does
its job well, but is also easy to add, to maintain and debug.
This "maintainable code" is a popular talking point among those
involved in standardization of coding in all languages. There's nothing worse
than inheriting an application or needing to make changes to code that requires
a lot of energy to decipher – you end up trawling through lines and lines of
code that doesn't make its purpose or intentions clear. Looking through
unfamiliar code is much easier if it is laid out well and everything is neatly
commented with details that explain any complicated constructs and the
reasoning behind them.
The Problem
- Can you actually read the code? Is it spaced out clearly?
- If you come back to the
code in a few weeks or months, will you be able to work out what’s
happening without needing to look at every line?
- How are you commenting the work?
- Have you used complex language functions/constructs that are quicker
to write but affect readability?
When we learn a new language,
we usually begin to code in a specific style. In most cases, we'll write in a
style that we want, not one that has been suggested to us. But once we start to
code using a particular style, like a spoken language dialect, it will become
second nature -- we'll use that style in everything we create. Such a style
might include the conventions we use to name variables and functions (v_userName,
v_username or v_user_name for example), and how we comment our work. Any style
should ensure that we can read our code easily.
However, what happens when we
start to code bigger projects and introduce additional people to help create a
large application? Conflicts in the way you write your code will most
definitely appear.
The Solution: a Coding Standards Document
A coding standards document tells developers how
they must write their code. Instead of each developer coding in their own
preferred style, they will write all code to the standards outlined in the
document. This makes sure that a large project is coded in a consistent style
-- parts are not written differently by different programmers. Not only does
this solution make the code easier to understand, it also ensures that any
developer who looks at the code will know what to expect throughout the entire
application. Developers will likely adopt the style gradually, just as an
accent develops over time.
Naming
conventions
General Naming
Standards
Use meaningful names!
Avoid using short or arbitrary variable names such as "x" or
"var". These names convey
nothing about what is stored within the variable. Object names in your code should cause your
code to be self-documenting. In other
words, if you name your items meaningfully, it should usually be quite obvious
what your code does.
If you are naming a variable or a parameter that references
a value stored in a database column, use the column name for your variable
name. For example, a local variable used
to store the value of the HOME_PHONE column in a table would be called
l_home_phone.
Never use "a", "b", "c",
etc. as table aliases in your SQL statements.
A query joining several tables quickly becomes difficult to read and
debug when you have WHERE clause conditions like:
SELECT a.order_date
FROM orders a,
order_lines b,
line_prices c
WHERE a.order_id =
b.order_id
AND b.order_line_id
= c.order_line_id;
Use
meaningful table aliases instead.
Consider the difference:
SELECT ord.order_date
FROM orders ord,
order_lines line,
line_prices prc
WHERE ord.order_id =
line.order_id
AND line.order_line_id
= prc.order_line_id;
A third option that requires less typing but still
provides more meaning than (than single-letter aliases) is to use the initial
letters of the words in the table name to provide the aliases:
SELECT o.order_date
FROM orders o,
order_lines ol,
line_prices lp
WHERE o.order_id =
ol.order_id
AND ol.order_line_id
= lp.order_line_id;
The second and third examples above are acceptable, but
the first example is not. The third
option also provides one benefit that the others do not: a fairly consistent
rule for naming table aliases. You may
have to bend the rule slightly when joining tables with the same initials, but
it works fine in most cases.
Use
underscores to separate words or identifiers in all names, e.g.
“use_names_like_this”. Do not used mixed
case for this purpose, e.g. “DontUseNamesLikeThis” (see the section on
Capitalization).
When declaring datatypes of variables or parameters,
use anchoring if your variable or parameter refers to the value in a database
column. Anchoring allows the developer
to “anchor” the variable datatype to the datatype of the underlying column in
the table. Anchoring is done using the
syntax “table_name.column_name%TYPE” in place of an actual datatype.
Variables
Variable scope is determined by a prefix of “l” for
local variables or “g” for global variables.
Constant variables include the letter “c” in the prefix. Scalar variables are additionally identified
by suffix.
Local Variables
Prefix local variable names with the letter “l” and an
underscore, e.g. “l_variable_name”. This
includes all variables that are local in scope to the current procedure or
function.
Global Variables
Prefix global variable names with “g_”, e.g.
“g_variable_name”. This includes
package-level variables that are global in scope to all procedures and
functions within the package.
Cursors
Suffix cursor names with “_cur”, e.g.
“l_cursor_name_cur”. Note that cursors
must be identified as local or global just like any other variable name.
Records
Suffix variables that store scalar record data with
“_rec”, e.g. “l_record_name_rec”. This
applies to programmer-defined records as well as cursor or table-based record
variables.
Record Types
Record type names should include the suffix “_rec_type”
after the record name, e.g. “l_record_name_rec_type”.
Constants
Constant variables should include the letter “c”
immediately after the prefix letter indicating scope, e.g. “lc_variable_name”
or “gc_variable_name”.
PL/SQL Tables
Table names should include the suffix “_tab” after the
table name, e.g. “l_table_name_tab”.
PL/SQL Table Types
Table type names should include the suffix “_tab_type”
after the table name, e.g. “l_table_type_name_tab_type”.
Parameters
Procedure and Function Parameters
IN
parameters should be prefixed with “i_”, e.g. i_parameter_name.
OUT
parameters should be prefixed with “o_”, e.g. “o_parameter_name”.
IN
OUT parameters should be prefixed by “io_”, e.g. “io_parameter_name”.
Cursor Parameter
Cursor
parameters should be prefixed with “p_”, e.g. “p_cursor_parameter”.
Procedures &
Functions
When naming procedures, use a verb-noun combination
that describes what the procedure does: for example,
validate_customer_phone. When naming
functions, think about how the function will be called from code and name it
something that makes sense in that context.
For example, customer_no_is_valid is a function that returns a BOOLEAN
value. In code you would call this
function like this:
IF customer_no_is_valid(l_customer_no) THEN
do_something;
END IF;
do_something;
END IF;
Using customer_no_is_valid for the function name makes
the code more readable than something like this does:
IF validate_customer_no(l_customer_no) THEN
do_something;
END IF;
do_something;
END IF;
Packages
Package names should be suffixed by “_pack”, e.g.
“package_name_pack”. Name packages based
on the function of the package. For
example, if a package contains procedures and functions written to read and
write data to and from the order_statuses table, name the package
“order_statuses_pack”.
Code Formatting
Standards
Capitalization
All SQL keywords should be in all caps. Any Oracle keywords or built-ins should also
be in all caps. Everything else should
be in all lower case. Do not use mixed
case for anything.
This method makes it easy to differentiate between a
built-in procedure call and a call to a custom procedure.
Indentation
Three spaces should be used (instead of the TAB key)
whenever an indent is required. DO NOT
use embedded TAB characters. Use indentation:
Using three spaces instead of embedded tabs may seem
harsh for those of us who prefer to use tabs to indent code, but there are
several good reasons for this:
·
Tabs are not portable across editors
Different
programmers use different editors to work on code, and some editors use a
different spacing than others. Many editors,
including Notepad, do not allow tab spacing to be modified. If another programmer needs to look at or
modify your code, and she uses a different editor than you did, she will likely
have a mess on her hands.
·
Tabs do not store code properly in the
database
Database
source code can be viewed using the data dictionary, either through the
USER_SOURCE, ALL_SOURCE, or DBA_SOURCE views.
These views do not properly display code that has been formatted with
embedded tabs. This makes it difficult
to look at stored code when it may be unnecessary to check out the source code
file.
·
Tabs are usually too long
Notepad
and many other editors use 8 spaces to display a tab character. Nested structures quickly run off the side of
the page when displaying this code.
Three spaces is deep enough to offset the structure of your code, but
not so deep that nested code runs off the right margin. If your code is nested too deeply to get it
on the page using three space indents, then you may want to consider ways to
modularize your code more effectively.
If you are accustomed to using tabs instead of spaces
to indent code, consider using a third-party editor to manage your code. There are many freeware and public domain
editors designed for programmers that will automatically insert a user-defined
number of spaces when you hit the tab key.
One such editor is PL/SQL Developer.
Information about this particular freeware editor is available at the
following URL: http://www.allroundautomations.com/plsqldev.html
Alignment
Align code consistently:
·
Left-align DECLARE, BEGIN, EXCEPTION, and
END statements.
·
Left-align SQL statement keywords with each
other.
·
Left-align application-specific components
(don’t worry about making this work with GROUP BY or ORDER BY clauses, or with
other clauses that are longer than the SELECT keyword, such as DELETE FROM or
INSERT INTO).
·
When you have more than one conditional or
assignment statement, align the elements of each condition with similar
elements from other conditions. For
example, if you have a long WHERE clause with several conditions, align all the
operators (i.e. “=”, “>”, etc.) with each other and align all the right-hand
side of the conditions with each other.
The example statements below use this alignment style. Note that this standard may sometimes be
ignored if one conditional statement is substantially longer than the
others. In this case, only align the
conditions where it makes sense.
·
In procedure and function declarations,
left-align parameter names with each other, left-align parameter directions
with each other, and left-align parameter data types with each other (see
example below).
Example SQL statements:
SELECT t.tag_no,
t.weight,
t.piece_count
FROM cs_tags t,
location_identifiers li,
cs_location_statuses ls
WHERE t.location_token = li.location_token
AND li.site_no = i_site_no
AND li.location_status
= ls.location_status
AND ls.sell_flag = 'Y'
AND li.location_type = 'F'
AND t.afs_flag = 'Y'
AND t.part_no = i_part_no
AND t.contract = i_contract
AND t.piece_count <> i_piece_count
AND t.tag_no
NOT IN (SELECT tag_no
FROM cs_tag_orders
WHERE release_status = 'ACTIVE');
UPDATE cs_tag_orders
SET release_status = 'ACTIVE',
shippable_flag = 'Y'
WHERE tag_no
= l_tag_no(l_loop_index)
AND order_no
= i_order_no
AND way_longer_name_than_the_others =
i_way_longer_name_than_the_others
AND order_code = i_order_code
AND line_no
= i_line_no;
INSERT INTO cs_tag_orders
(order_no
,order_code
,line_no
,rel_no)
VALUES
('123456'
,'O'
,1
,0);
Note
- either of the following two syntaxes is legal - developers should use
whichever method they prefer.
DELETE FROM cs_tag_orders
WHERE order_no
= i_order_no
AND order_code = i_order_code
AND line_no
= i_line_no;
DELETE cs_tag_orders
WHERE order_no
= i_order_no
AND order_code = i_order_code
AND line_no
= i_line_no;
Example program alignment:
CREATE OR REPLACE PROCEDURE create_release (
i_mill_order_no IN oeorder_detail.cs_mill_order_no%TYPE,
io_dummy_param IN
OUT NUMBER)
IS
CURSOR l_order_cur
IS
SELECT order_no, order_code, line_no
FROM
oeorder_detail
WHERE cs_mill_order_no = i_mill_order_no;
l_order_rec
l_order_cur%ROWTYPE;
BEGIN
OPEN
l_order_cur;
FETCH l_order_cur INTO l_order_rec;
CLOSE l_order_cur;
FOR
l_counter IN 1 .. 10
LOOP
INSERT INTO oeorder_release (order_no,
order_code,
line_no,
rel_no)
VALUES (order_rec.order_no,
order_rec.order_code,
order_rec.line_no,
l_counter);
END
LOOP;
EXCEPTION
WHEN
OTHERS
THEN
raise_an_error;
END create_release;
Example
of bad statement alignment:
l_long_string := 'Tag ' || i_tag || ' has not passed the ' || i_inspection_test || ‘ that was performed on ‘ || TO_CHAR(l_date_created, 'MM/DD/YYYY');
Better
statement alignment:
l_long_string
:= 'Tag number ' ||
i_tag || ' ' ||
'has not passed ' || i_inspection_test || ' ' ||
'that was performed on ' || TO_CHAR(l_date_created, MM/DD/YYYY');
Each will store ‘Tag 00001 has not passed the chemical test that was performed on 05/08/2007’. If you need to change the message to read, ‘Tag 00001 has not passed the chemical test that was performed by QA tester Fuqan Ahmed on 05/08/2007’, the second one is much easier to maintain. Here’s another example from an Oracle Forms program unit:
Poor
alignment makes it hard to see the individual settings for each item:
SET_ITEM_PROPERTY(‘block1.item1’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block100.item100’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block10.item2’, ENABLE, PROPERTY_FALSE);
SET_ITEM_PROPERTY(‘block10000.item3’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block1.item2’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block100.item101’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block10.item3’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block10000.item4’, ENABLE, PROPERTY_TRUE);
Same code with parameters aligned.
SET_ITEM_PROPERTY(‘block1.item1’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block100.item100’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block10.item2’, ENABLE, PROPERTY_FALSE);
SET_ITEM_PROPERTY(‘block10000.item3’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block1.item2’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block100.item101’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block10.item3’, ENABLE, PROPERTY_TRUE);
SET_ITEM_PROPERTY(‘block10000.item4’, ENABLE, PROPERTY_TRUE);
With the code aligned, it is much easier to see which item is set differently.
Using Commas to Align and Separate Elements
As
you can see from some of the examples above, it is usually easier to read code
that places each logical element on a separate line (e.g. the column names and
values in the INSERT statement).
When
defining or calling procedures with multiple parameters, put only one parameter
on each line.
PROCEDURE get_order
(order_no IN
oeorder_detail.order_no%TYPE
,order_code
IN oeorder_detail.order_code%TYPE
,line_no IN
oeorder_detail.line_no%TYPE)
get_order
(l_order_no
,l_order_code
,l_line_no);
Commas
should be placed at the beginning of the line when used to separate arguments,
parameters, or other parenthetical lists.
This results in parameters that are lined up evenly, since the comma on
the second and subsequent lines aligns with the opening parenthesis on the
first line.
Commas
should be placed at the end of the line when used to separate elements not
contained within parenthesis, as in the list of columns in a SELECT statement. See the example SELECT and INSERT statements
above for examples.
Comments
Include
a standard header comment at the top of every procedure and function. The header should be updated any time
modifications are made to the code. Note
that the header is not intended to take the place of in-line comments – you
should still comment your code internally.
The header should be considered a summary of the program unit.
/*
----------------------------------------------------------
Author :
XXXXXXXXXX
Purpose :
XXXXXXXXXXXXXXXXXXX
Logic
Structure:
<use this area to document the basic
flow of the program at a high level. You
should also make notes about anything else
that might help someone reading your
code.>
Modification
History:
Date Name Revision Summary
---------- ----------
-----------------------------------
----------------------------------------------------------
*/
Keep
your audience in mind when commenting code.
If you are commenting a private package procedure, realize that the
person who will need your comments is the person who is considering making
changes to the procedure. On the other
hand, if you are commenting a package specification, the person reading your
comments is the developer who will be using the procedures or functions within
the package. Tailor your comments
accordingly: In package specifications, include comments that instruct the
reader how to use the programs within the package. In package bodies, include comments
describing how the code works.
Use
comments to explain complex logic in your code.
In general, if you follow good naming conventions and write modular
code, your code will be largely self-documenting. Don't include comments that repeat what the
code tells the reader. Example:
--if the status code is null, then raise an
error
IF status_code IS NULL THEN
IF status_code IS NULL THEN
RAISE FORM_TRIGGER_FAILURE;
END IF;
This
is a useless comment. Anyone reading your
code would realize what the code does.
The comment simply re-states the code.
Here
is an example of some code that is enhanced by a comment:
/*
this calculation added 26/05/2006 to correct rounding errors caused by the fact that we store lengths rounded to 3 decimal places. This code basically increases the number of significant digits we can use to figure out a weight. For example, suppose the length was 26.417, or 26 feet, 5 inches. The actual length is 26.416666666..repeating. If we take .417 and multiply by 96, we get 40.032. Round this and you get 40. Now divide by 96 and you get .41666666..repeating. We can add this back to our even length(26 feet) to get a real length_no to use for calculations. 96 was used because the smallest length we use is 1/8 inch, and there are 96 1/8 inch units in a foot.
*/
this calculation added 26/05/2006 to correct rounding errors caused by the fact that we store lengths rounded to 3 decimal places. This code basically increases the number of significant digits we can use to figure out a weight. For example, suppose the length was 26.417, or 26 feet, 5 inches. The actual length is 26.416666666..repeating. If we take .417 and multiply by 96, we get 40.032. Round this and you get 40. Now divide by 96 and you get .41666666..repeating. We can add this back to our even length(26 feet) to get a real length_no to use for calculations. 96 was used because the smallest length we use is 1/8 inch, and there are 96 1/8 inch units in a foot.
*/
SELECT (((ROUND(96 * (length_no - FLOOR(length_no)))/96) + FLOOR(length_no))
* cs.lbs_ft
* i_qty) exact_length
FROM cs_sizes cs,
part_description pd
WHERE pd.part_no =
i_part_no
AND pd.contract =
i_contract
AND pd.size_code
= cs.size_code;
Obviously,
the complicated calculation involved in this select statement needs some
explanation, since it is not obvious what the calculation is doing just from
looking at the code. If you are unsure
if a comment is necessary in a particular situation, go ahead and put it
in. As anyone who has done code
maintenance will tell you, it is better to over-comment than under-comment.
To
comment the body of your code, you may choose to use single-line (preceded by
two dashes) or multi-line (begins with /* and ends with */) depending on the
comment. Align comments with the code
they describe. Avoid using several two
dash single-line comments when your comment is multiple lines long.
PL/SQL Elements
Parameters
Do
not use OUT or IN OUT parameters in a function.
A function should only be used when a single return value is needed (the
function return). If more return values
are needed, use a procedure instead.
Likewise,
if your procedure has only a single OUT parameter, use a function instead.
Always identify parameter direction (IN, OUT,
or IN OUT). IN is the default direction
if not specified, but code is more readable if every parameter direction is
explicitly stated. Cursor parameters
must be IN parameters, so it is not necessary to state parameter direction for
cursor parameters.
Use
default values for parameters where appropriate, remembering not to hard code
any literal values. A good example of
when to use default values is with Boolean parameters. Boolean parameters can
be used to give the calling programs more control over how your program
works. For example, a program that
creates a new customer might have a Boolean parameter called
i_preferred_customer that will normally be FALSE. If it is TRUE, the program performs some
additional tasks specific to preferred customers. It is a good idea to place Boolean parameters
at the end of your parameter list and give them default values of TRUE or
FALSE. This way, other developers can
call your procedure and leave some or all of the Boolean parameters blank,
thereby accepting the default behavior of these “flags”.
Any
parameters that have default values should be placed at the end of the
parameter list, where practical. This allows
calling programs to simply omit all arguments that have default values, rather
that having to use named notation.
Loops
When
using loops, try to keep the beginning and end of the loop close together. If you can’t get them on the same page when
browsing your program, then you need to perform additional modularization. This is especially true if you are using
nested loops.
Loops
should have only one way in and one way out.
Try to avoid using multiple exit points.
This can lead to some very confusing code.
Use
loop labels when dealing with nested loops.
Also use loop labels when you cannot avoid placing more than about one
page of code between the beginning and the end of the loop. Place the beginning loop label on a line
directly above and left-aligned with the LOOP statement. Place the ending loop label on the same line
as the END LOOP statement. See the
examples below:
Nested loops without loop labels:
LOOP
Some
statements
EXIT
WHEN termination_condition1;
LOOP
Some
more statements that cause page breaks
EXIT
WHEN termination_condition2;
END
LOOP;
END LOOP;
Same
code using loop labels:
<<customers_loop>>
LOOP
Some
statements that cause page breaks
EXIT
customers_loop WHEN termination_condition1;
<<orders_loop>>
LOOP
Some
more statements that cause page breaks
EXIT
orders_loop WHEN termination_condition2;
END
LOOP orders_loop;
END LOOP customers_loop;
For Loops
For
loops have this syntax:
FOR l_iterations IN 1..l_max_iterations
LOOP
…
END LOOP;
…
END LOOP;
A
for loop should only be used when the loop needs to execute a specific number
of times no matter what. Don’t exit the
loop until it has been executed the specified number of times. Don’t change the value of the loop index to
get out of the loop early.
While Loops
While
loops have this syntax:
WHILE l_boolean_condition LOOP
…
END LOOP;
…
END LOOP;
While
loops should only be used when the loop needs to execute until a specific
condition (specified in the WHILE clause) is met. Don’t exit the loop until this condition is
met.
While
loops are a better choice than For loops if you need to loop until something
happens. You can simply set the Boolean condition to TRUE when you are ready to
exit the loop. Don’t use While loops
instead of For loops when you will always want to execute the loop a certain
number of times:
l_value := 1;
WHILE l_value <= 10 LOOP
…
END LOOP;
WHILE l_value <= 10 LOOP
…
END LOOP;
If
your While loop looks like this, you should use a For loop instead.
Simple Loops
Despite
the name, Simple loops are best used for more complicated conditional
exits. The EXIT and EXIT WHEN statements
should only be used within simple loops.
Use a simple loop if you need to exit the loop somewhere other than
right at the beginning. If you only need
to exit the loop at the beginning, consider a While loop.
Cursors
Do
not use implicit cursors. Always use
explicit cursors instead.
An
explicit cursor is one that is defined in the declaration section of a
block. An implicit cursor is what Oracle
uses to perform a fetch when your code performs a SELECT … INTO … operation. Always use explicit cursors instead of
implicit. Explicit cursors are more
efficient because the developer manually controls the number of fetches. An implicit fetch will attempt one more fetch
than is actually needed to make sure no more rows are found that match the
criteria.
Implicit
fetch example:
PROCEDURE get_customer_name (
i_customer_no IN oecustomer.customer_no%TYPE,
o_customer_fname OUT
oecustomer.first_name%TYPE,
o_customer_lname OUT
oecustomer.last_name%TYPE)
IS
BEGIN
SELECT first_name, last_name
INTO
o_customer_fname, o_customer_lname
FROM
oecustomer
WHERE customer_no = i_customer_no;
END;
Same code converted TO
explicit FETCH:
PROCEDURE get_customer_name (
i_customer_no IN oecustomer.customer_no%TYPE,
o_customer_fname OUT
oecustomer.first_name%TYPE,
o_customer_lname OUT
oecustomer.last_name%TYPE)
IS
CURSOR l_name_cur
IS
SELECT first_name, last_name
FROM
oecustomer
WHERE customer_no = i_customer_no;
l_name_rec
l_name_cur%ROWTYPE;
BEGIN
OPEN
l_name_cur;
FETCH l_name_cur INTO l_name_rec;
CLOSE l_name_cur;
o_customer_fname := l_name_rec.first_name;
o_customer_lname := l_name_rec.last_name;
END;
The
explicit fetch requires slightly more code and is not quite as easy to read at
first glance, but it is more efficient and affords the developer more
control. Exception handling is easier: a
TOO_MANY_ROWS exception will not be raised when manually fetching from an
explicit cursor. This allows you to
define what will happen in these cases in your code, instead of relying on an
exception handler to trap the exceptions.
This gives the developer more flexibility in code structure.
When
fetching from cursors, you should normally fetch values into a Record
variable. This makes it easier to change
the columns in the SELECT list of the cursor later. It is not necessary to fetch into a Record
variable if you are performing a “SELECT 1” operation to avoid a COUNT query or
a similar operation.
IF Statements
Align
IF statements with the corresponding ELSIF, ELSE, and END IF statements. Indent code inside this structure.
Using
IF…ELSIF…ELSE…END IF is PL/SQL’s version of a CASE statement. If you use an ELSIF clause in your IF
statement, you should always include an ELSE clause. This will trap any other case that your logic
might have missed. Even if you think it
isn’t possible for another condition to exist, it is good coding practice to
always catch every condition using a ELSE clause.
An
example of this is an IF statement that examines a Boolean variable:
IF l_boolean THEN
do_something;
ELSIF NOT l_boolean THEN
do_something_else;
END IF;
This
code works for both TRUE and FALSE values, but what about a NULL value? This is better (assuming that NULL values
should not be treated the same as either TRUE of FALSE):
IF l_boolean THEN
do_something;
ELSIF NOT l_boolean THEN
do_something_else;
ELSE
raise_an_error;
END IF;
Exception Handlers
Exception
handlers should be used only for capturing errors. Keep in mind that once control branches to an
exception handler, control will not return to the block that generated the
exception.
Left-align
the EXCEPTION keyword with the BEGIN and END of the block containing the
exception handler. Indent all other
lines within the handler. Example
exception block:
BEGIN
(various DML statements)
EXCEPTION
WHEN
e_user_defined_problem THEN
ROLLBACK;
write_to_error_log;
WHEN
OTHERS THEN
ROLLBACK;
RAISE;
END;
Be
very careful of transaction management when using exception handlers. Consider what DML might need to be rolled
back in case of a problem. You should
always include an exception handler with transaction management when your
program involves DML statements.
Every
program needs a top-level exception handler to make sure that unexpected errors
are trapped and handled in some way.
Consider how your program will be used to determine how to structure
your exception handler.
Do
not use a WHEN OTHERS exception handler by itself unless that is what is really
needed. Normally, your program will need
to take specific actions depending on what type of problem was
encountered. The WHEN OTHERS trap is
designed to handle exceptions that were not trapped by other traps.
Exception
handlers in functions should always return a value if program execution should
continue after the exception.
In
general, do not use exception handlers to trap events that are expected to
occur. Use conditional logic
instead. (there are certain cases where
it makes more sense to use an exception handler for performance reasons, etc.)
Performance and
Reusability Standards
Client-Side SQL
Avoid
the use of SQL statements stored in local program units or triggers on the
client side. Client-side SQL reduces
performance and code reusability.
Exceptions to this rule include:
·
Record group queries.
·
Implicit SQL performed by Oracle Forms in
conjunction with base table blocks.
·
Situations requiring the form to process
records fetched from a cursor individually at the form level (for example,
manual block or record group population).
·
Fetching the next value from a database
sequence (or other fetches from the DUAL table);
From
Developer/2000 Release 2 and onwards developers can take advantage of new
features in the tool to avoid using client-side SQL even in many of the cases
mentioned in the exceptions above.
Always
place server-side stored functions and procedures in a package. This allows maximum code reuse and allows the
DBA the flexibility to "pin" your package in a shared memory area on
the database server to allow maximum performance.
The
program administrator should be responsible for managing shared packages. If you need a procedure or function, make
sure it does not already exist in a package somewhere before you take the time
to write it.
Modularity
The
best way to achieve a minimum of code duplication is to promote modularity by
limiting procedures and functions to a single task. If your function or procedure does more than
one thing, consider breaking it up into multiple program units so that other
code may be able to use it. A general
rule of thumb is that if a procedure or function is more than 3 or 4 pages in
length, it should be reviewed for ways to modularize the code.
If
you have more that a page of code between an IF…END IF block, your code will be
difficult to interpret. Nobody wants to
manage code with giant IF statement blocks.
Avoid IF and LOOP structures with more that a page of code in between. If you need to do more than this, simply
place all of your logic in a separate procedure or function and call it from
within the IF or LOOP structure.
If
you keep your program units small and name your procedures and functions using
meaningful names, it should be fairly simple for someone unfamiliar with your
code to read your procedure and describe (at a high level) what you are doing.
Of
course, it is possible to get too much of a good thing. Over-modular programs are simply harder to
figure out. Avoid modularizing program
units that will only be called from one place, unless they make a significant difference
in the readability of your program. In
general, if you are not sure if it makes sense to make a procedure modular, go
ahead and do it. It’s better to err on
the side of caution. You may decide at
some point in the future to call this procedure from another place, in which
case you will be glad you made the choice to go modular.
Hard-coding
Never
reference a literal value in your code.
There are many alternatives to literal values in code, including
constants, public or private package variables, procedure or function
parameters, and table-based “constants”.
If you think you have no choice but to reference a literal value, think
again.
Note
that it is perfectly acceptable to reference a literal value as long as you do
it indirectly. For example, say you want
to set a column value equal to the word “CLOSED”. Set up a constants table that has two
columns: constant code and constant value.
Set the code equal to the value “CLOSED_STATUS” and set the value equal
to “CLOSED”. Then instead of referencing
the value directly, look it up in the constants table by looking up the
corresponding value to the constant code “CLOSED_STATUS”. In this way, if the name of the closed status
changes from “CLOSED” to “COMPLETE”, you will more likely be able to adjust to
this change without any changes to your code.
Constants
tables are useful in many situations like this.
A typical constants table includes a const_code column and one column
each for number, string, and date values, in addition to a comments column. You can write a generic package that contains
overloaded functions to retrieve constant values of all three types given the
constant code. The package should use
PL/SQL tables to buffer previously looked up values to improve performance, and
should of course include a “reset” feature that allows the buffers to be
flushed for those cases where you absolutely, positively have to have the most
current value. By preparing this package
and table in advance, you can encourage everyone on the development team to
avoid hard-coding by making the right choice more convenient for them.
Hard
coded items are sometimes more subtle than literal values. For example, if you have a formula that
appears in your code in more than one place, this is a subtle form of hard-coding. For example, if you determine the weight of a
widget by multiplying the weight per foot times the length, this logic should
be encapsulated in a function in a package.
Every place in the application that requires this calculation should
then reference this function. In this
way, if the logical formula should ever change, the change only needs to be
made in one place.
Unnecessary Group
Functions
Group
functions are costly when a large number of rows are involved. Performance problems can be created when
group functions are used in situations that don't really require the data to be
grouped.
The
most obvious example of this problem is the COUNT function. There is a simple rule that can be remembered
to avoid performance problems related to this: Don't use COUNT unless you need
to know the exact number of rows that match your criteria. Never use COUNT if you only need to know if a
single row exists, or even if you need to know if multiple rows exist. Use an explicit cursor and fetch the number
of records from it that satisfies your requirement.
Here
is an example of an unnecessary COUNT query.
Assume that we only need to determine if zero, one, or many rows exist
so that we know whether to raise an error (zero), use the data found (one), or
display a list to allow the user to select the correct record (many):
DECLARE
l_name
oecustomer.name%TYPE;
l_count
NUMBER := 0;
BEGIN
l_name :=
'FRANK';
SELECT NVL (COUNT (1), 0)
INTO
l_count
FROM
oecustomer
WHERE name = l_name;
IF
l_count = 0
THEN
show_error;
ELSIF l_count = 1
THEN
use_data;
ELSE
display_list;
END
IF;
END;
--This
PROCEDURE could be better written AS FOLLOWS:
DECLARE
CURSOR l_name_cur
IS
SELECT 1
FROM
oecustomer
WHERE name = l_name;
l_name
oecustomer.name%TYPE;
l_found
NUMBER := 0;
BEGIN
l_name :=
‘FRANK’;
OPEN
l_name_cur;
LOOP
FETCH l_name_cur INTO l_found;
EXIT
WHEN l_name_cur%NOTFOUND OR l_name_cur%ROWCOUNT > 1;
END
LOOP;
IF
l_name_cur%ROWCOUNT = 0
THEN
CLOSE l_name_cur;
show_error;
ELSIF l_name_cur%ROWCOUNT = 1
THEN
CLOSE l_name_cur;
use_data;
ELSE
CLOSE l_name_cur;
display_list;
END
IF;
END;
Procedure and
Function Design
Transaction
Handling
Be
sure to consider the impact of an incomplete transaction in your code. For example, if your procedure does an insert
into the customer table and then an insert into the customer addresses table,
what should happen if the second insert fails?
Should the first insert stand, or should it be rolled back? When considering this, think about how the
user will recover in case of failure.
Will they simply try it again?
Will they assume it worked? How
will they know?
Group
your DML statements into logical transactions, using savepoints, rollbacks, and
commits to control when DML statements get done and when they don’t. Keep in mind that exceptions can be raised
due to events far outside the control of the developer. For example, a data file could go offline or
a rollback segment can be full or locked.
Your exception handlers can perform many clean-up duties in case of
failure.
Local Private
Procedures
You
can create a procedure or function inside another procedure or function. The inner(private) procedure can only be used
by the outer procedure. This technique
can be useful to make your outer procedure more readable. Take care not to overdo it, however. Too many (or too complicated) inner
procedures can make your code even harder to figure out.
Variable
Declarations
Use
comments to describe the purpose of variables when it is not obvious from the
variable name. Anchor the datatypes of
variables whenever possible. Avoid the
use of the CHAR datatype; use VARCHAR2 instead.
CHAR datatypes are blank-padded to the specified length; this is rarely
beneficial to the efficiency of your code.
Use
variables with a Boolean datatype when you need a flag. This results in code that is more efficient
and easier to read than code that uses a number or character field for this
purpose.
Parameter
Validation
If
your code only works if the parameters passed are the length or format your
code expects, you need to perform some parameter validation. Common parameter validations such as
verifying the length of character parameters and making sure a number is a
positive integer will go a long way toward making your code bulletproof.
Test
your procedures by passing in bizarre and unexpected parameters. Your code should either work as expected or
error out gracefully no matter what you pass in as parameters.
No comments:
Post a Comment