G-7130: Always use parameters or pull in definitions rather than referencing external variables in a local program unit.

Major

Maintainability, Reliability, Testability

Reason

Local procedures and functions offer an excellent way to avoid code redundancy and make your code more readable (and thus more maintainable). Your local program refers, however, an external data structure, i.e., a variable that is declared outside of the local program. Thus, it is acting as a global variable inside the program.

This external dependency is hidden, and may cause problems in the future. You should instead add a parameter to the parameter list of this program and pass the value through the list. This technique makes your program more reusable and avoids scoping problems, i.e. the program unit is less tied to particular variables in the program. In addition, unit encapsulation makes maintenance a lot easier and cheaper.

Example (bad)

CREATE OR REPLACE PACKAGE BODY EMPLOYEE_API IS
   PROCEDURE calc_salary (in_employee_id IN employees.employee_id%TYPE) IS
      r_emp employees%rowtype;

      FUNCTION commission RETURN NUMBER IS
         l_commission employees.salary%TYPE := 0;
      BEGIN
         IF r_emp.commission_pct IS NOT NULL
         THEN
            l_commission := r_emp.salary * r_emp.commission_pct;
         END IF;

         RETURN l_commission;
      END commission;
   BEGIN
      SELECT *
        INTO r_emp
        FROM employees
       WHERE employee_id = in_employee_id;

      SYS.DBMS_OUTPUT.PUT_LINE(r_emp.salary + commission());
   EXCEPTION
      WHEN NO_DATA_FOUND THEN
         NULL;
      WHEN TOO_MANY_ROWS THEN
         NULL;
   END calc_salary;
END employee_api;
/

Example (good)

CREATE OR REPLACE PACKAGE BODY EMPLOYEE_API IS
   PROCEDURE calc_salary (in_employee_id IN employees.employee_id%TYPE) IS
      r_emp employees%rowtype;

      FUNCTION commission (in_salary   IN employees.salary%TYPE
                          ,in_comm_pct IN employees.commission_pct%TYPE)
         RETURN NUMBER IS
         l_commission employees.salary%TYPE := 0;
      BEGIN
         IF in_comm_pct IS NOT NULL THEN
            l_commission := in_salary * in_comm_pct;
         END IF;

         RETURN l_commission;
      END commission;
   BEGIN
      SELECT *
        INTO r_emp
        FROM employees
       WHERE employee_id = in_employee_id;

      SYS.DBMS_OUTPUT.PUT_LINE(
         r_emp.salary + commission(in_salary   => r_emp.salary
                                  ,in_comm_pct => r_emp.commission_pct)
      );
   EXCEPTION
      WHEN NO_DATA_FOUND THEN
         NULL;
      WHEN TOO_MANY_ROWS THEN
         NULL;
   END calc_salary;
END employee_api;
/