Hard coding w PL/SQL - dlaczego należy unikać sztywnego kodowania.
Hard-coding w wolnym tłumaczeniu – sztywne kodowanie, pojawia się gdy my – programiści – piszemy kod mało elastyczny lub uzupełniony o “magiczne” literały. Efektem takich działań jest sztywny, trudny i kosztowny w utrzymaniu kod. Warto unikać takich błędów, a wystarczy stosować się do prostych zasad.
Nie twórz sztywnego kodu
Język PL/SQL jest intuicyjny, łatwy do nauczenia i pozwala na programowanie systemów na poziomie bazy danych Oracle, a co za tym idzie – umożliwia pracę z danymi za pomocą SQL. W tym wszystkim tkwi haczyk, pułapka na którą wpada każdy początkujący jak i doświadczony programista PL/SQL. Idąc na skróty, łatwo o kod mało elastyczny, niedostosowany do zmian wymagań na poziomie logiki systemu. Poniżej przykładowy kod zawierający zaledwie 27 linii i aż 10 błędów.
CREATE OR REPLACE PROCEDURE p_process_salary(pi_how_much NUMBER, pi_department_id NUMBER)
IS
v_employee_id NUMBER;
v_salary NUMBER;
v_empname VARCHAR2(100);
CURSOR emp_salary_cur
IS
SELECT employee_id, salary, first_name || ' ' || last_name empname
FROM employees
WHERE department_id = pi_department_id;
BEGIN
OPEN emp_salary_cur;
LOOP
FETCH emp_salary_cur INTO v_employee_id, v_salary, v_empname;
IF v_salary < 10000 THEN
pi_increase_salary(v_employee_id, pi_how_much);
END IF;
EXIT WHEN emp_salary_cur%NOTFOUND;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20017, 'Something went wrong!');
END;
Zacznijmy od nagłówka procedury, linia pierwsza:
CREATE OR REPLACE PROCEDURE p_process_salary(pi_how_much NUMBER, pi_department_id NUMBER)
Dwa argumenty wejściowe do procedury, pierwszy hardcode. W drugim argumencie programista założył, że pi_department_id
zawsze będzie liczbą. Tak może być przez kilka miesięcy lub lat od wdrożenia projektu, jednak jest to proszenie się o kłopoty. Jeżeli argument wskazuje na ID departamentu w tabeli EMPLOYEES, to programista powinien uwzględnić zmianę typu tej kolumny na inny.
Linia 3-5:
v_employee_id NUMBER;
v_salary NUMBER;
v_empname VARCHAR2(100);
Podobnie jak w poprzednim przykładzie, założono sztywne typy zmiennych dla v_employee_id oraz v_salary. Taki kod może spowodować problem, gdy ktoś postanowi zmienić typy kolumn w tabeli EMPLOYEES. W przypadku v_empname, założono, że zmienna nigdy nie będzie większa niż 100 bajtów. Jeżeli taki wymóg uwzględniono w specyfikacji projektu, to jak najbardziej kod jest poprawny i zgłoszone błędy możemy zrzucić na analityka Jednakże lepiej zachować się pragmatycznie i uwzględnić większy rozmiar, gdyby w przyszłości doszło do sytuacji, że jednak imię i nazwisko pracownika jest dłuższe niż 100 bajtów. W języku PL/SQL można zadeklarować maksymalnie 32767 bajtów dla zmiennej typu VARCHAR2. W SQL ten rozmiar wynosi już 4000 bajtów.
Teraz linia 8:
SELECT employee_id, salary, first_name || ' ' || last_name empname
Początkowo kod może wyglądać poprawnie. Niestety to złudzenie. Uwzględniono biały znak między imieniem i nazwiskiem pracownika. Spacja, a może tab? Lepiej będzie zastąpić to na przykład wcześniej zdefiniowaną stałą, lub funkcją chr(32)
zwracającą odstęp – SPACE z kodu ASCII – #32.
Linia 15:
FETCH emp_salary_cur INTO v_employee_id, v_salary, v_empname;
Taki zapis można często spotkać w kodzie początkującego programisty PL/SQL. Zapis jest poprawny składniowo, ale w praktyce jest trudny w analizie i źle się go czyta. W praktyce lepiej będzie zwrócić z kursora wszystkie kolumny jakie zostały w nim zadeklarowane wprost do obiektu typu RECORD o typie
%ROWTYPE
na ten kursor. Z drugiej strony można przebudować pętlę LOOP EXIT WHEN na czytelniejszy FOR rec IN <kursor> LOOP
. Więcej w poprawionym kodzie na końcu tego artykułu.
Linia 16:
IF rec.salary < 10000 THEN
To typowy przykład hard-coding. Programista zapisał w kodzie wartość równą 10000. Nie mniej, nie więcej. Gdyby ten kod miał kilka tysięcy lini, znalezienie takiego warunku mogłoby być kłopotliwe. Lepiej potraktować ten literał jako wartość stałą w deklaracji procedury (lub specyfikacji pakietu) albo jako wartość zwracaną z funkcji.
Linia 22:
COMMIT;
Tak, commit to hard-code. W tym miejscu programista świadomie zapisuje zmiany. Jednak od takiej sytuacji może już nie być powrotu gdy zajdzie błąd. Ponadto taki zapis utrudnia testowanie. Testerzy tracą elastyczność w swoich programach testowych. Taki zapis, choć wydaje się prawidłowy, może powodować nieporozumienie w zespołach projektowych.
Ostatni hard-code, linia 26:
raise_application_error(-20017, 'Something went wrong!');
Procedura raise_application_error pozwala stosować zdefiniowane przez programistę wyjątki. W razie niepowodzeń, procedura zwróci błąd do aplikacji (lub kodu wywołującego daną procedurę). Pierwszym argumentem tej procedury (z dwóch wymaganych) to liczba całkowita w zakresie -20000 do -20999. Na drugim miejscu jest łańcuch znaków składający się na opis błędu. Błędy jakie popełniono w tej linii to: zapisany na sztywno kod błędu i jego opis. Kod błędu jaki będzie zwrócony przez procedurę w razie niepowodzeń, powinien być ustalony z zespołem projektowym i/lub powinien być widoczny w deklaracji procedury (lub w specyfikacji pakietu). W przyszłości istnieje szansa, że inny programista będzie potrzebował wykorzystać taką procedurę w swoim kodzie, więc powinien posiadać wiedzę co taki numer błędu dla niego oznacza.
Poprawnie zapisany kod może wyglądać następująco. W przykładowym kodzie zamiennie możemy zastąpić wartości stałe wynikiem funkcji wbudowanej lub składowej pakietu, nad którym pracujemy.
CREATE OR REPLACE PROCEDURE p_process_salary(pi_how_much NUMBER, pi_department_id EMPLOYEES.DEPARTMENT_ID%TYPE)
IS
c_ex_numb CONSTANT NUMBER := -20017;
c_ex_text CONSTANT VARCHAR2(100) := 'Something gone wrong!';
c_space CONSTANT VARCHAR2(1char) := chr(32);
c_max_sal CONSTANT EMPLOYEES.SALARY%TYPE := 10000;
CURSOR emp_salary_cur
IS
SELECT employee_id, salary, first_name || c_space || last_name empname
FROM employees
WHERE department_id = pi_department_id;
v_emp emp_salary_cur%ROWTYPE;
BEGIN
OPEN emp_salary_cur;
LOOP
FETCH emp_salary_cur INTO v_emp;
IF v_emp.salary < c_max_sal THEN
pi_increase_salary(v_emp.employee_id, pi_how_much);
END IF;
EXIT WHEN emp_salary_cur%NOTFOUND;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(c_ex_numb, c_ex_text);
END;
Kod można zapisać również inaczej – przepiszemy pętlę na czytelniejsza postać. Znika jedna zmienna – v_emp
.
CREATE OR REPLACE PROCEDURE p_process_salary(pi_how_much NUMBER, pi_department_id EMPLOYEES.DEPARTMENT_ID%TYPE)
IS
c_ex_numb CONSTANT NUMBER := -20017;
c_ex_text CONSTANT VARCHAR2(100) := 'Something gone wrong!';
c_space CONSTANT VARCHAR2(1char) := chr(32);
c_max_sal CONSTANT EMPLOYEES.SALARY%TYPE := 10000;
CURSOR emp_salary_cur
IS
SELECT employee_id, salary, first_name || c_space || last_name empname
FROM employees
WHERE department_id = pi_department_id;
BEGIN
FOR emp IN emp_salary_cur LOOP
IF emp.salary < c_max_sal THEN
pi_increase_salary(emp.employee_id, pi_how_much);
END IF;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(c_ex_numb, c_ex_text);
END;
Nigdy się nie powtarzaj
DRY (Don’t Repeat Yourself) to reguła stosowana podczas wytwarzania oprogramowania, zalecająca unikanie wszelkich powtórzeń kodu w swoich aplikacjach (termin wprowadzony przez autorów książki The Pragmatic Programmer). W języku PL/SQL, tak jak w każdym innym języku programowania, łatwo i wygodnie jest się powtarzać. Niestety może prowadzić to do błędów i nawet do konfliktów w zespołach projektowych. To też jest hard-coding. Dobry programista zawsze stosuje zasadę DRY! Jeżeli trzeba pewien fragment kodu powtórzyć, lepiej zastosować funkcję lub procedurę. Ta cecha ma się również do sposobu pracy. Jeżeli coś można zautomatyzować, lepiej to zrobić.
Podsumowanie
Hardcoding to praktyka programistyczna, która potrafi zepsuć czytelność kodu lub w najgorszym przypadku wymusza na programiście przegląd i naprawę całej aplikacji. Z moich obserwacji wynika, że język PL/SQL może być bardziej podatny na hardcoding niż inne języki programowania jak na przykład Java. Programowanie w PL/SQL jest zależne od struktur danych na bazie Oracle. Jakiekolwiek zmiany w tych strukturach mogą mieć szkodliwy wpływ na działanie programów napisanych w tym języku.
Temat hardcoding nie został wyczerpany przeze mnie całkowicie. Istnieje jeszcze wiele innych powodów jak negatywny wpływ na aplikacje ma sztywne kodowanie. Po więcej informacji na ten temat zachęcam do zapoznania się z serią filmów udostępnionych przez guru PL/SQL – Stevena Feuersteina. Mam nadzieję, że omówione przykłady pozwolą zobrazować problem i zachęcą programistów do dbania o kod w swoich aplikacjach, tak aby był pozbawiony błędów, których w prosty sposób można unikać.