Upload
phungdiep
View
224
Download
1
Embed Size (px)
Citation preview
Saubere Arbeit !
Fehlervermeidung und Debugging in APEX
WHEN OTHERS Beratung | Programmierung | Coaching rund um Oracle Application Express [email protected] http://when-others.com
Andreas Wismann
Einstieg
• Es gibt wenig "APEX Design Patterns" • Einheitliche Entwurfsmuster für die Praxis • wartbare APEX-Anwendungen • pragmatischer Ansatz
Credo
• Never Add Functionality Early • Know Your Tools • Test Your Code • RTFM
HTML & Co. beherrschen
• HTML • CSS • jQuery • jQuery mobile
SQL und PL/SQL auffrischen
• Pivot • Model Clause • Pipelined Functions • RESULT_CACHE • XML DB • …
Zwei-Schichten-Architektur
DB
APEX
Ordnung schaffen!
• Validierungen • Prozesse • Hidden Items • Berechnungen • Templates • Conditions • Selects • … (puh!)
Seiten "sperren"
• Vor jeder Bearbeitung auf das Schloss klicken • Schutz ist nicht 100% wasserdicht… • Admin kann Sperren aufheben
Drei ///
• Markieren Sie Baustellen mit /// • ist in jedem Zeichensatz lesbar • verträgt sich mit SQL*Plus • wird von jeder Volltextsuche gefunden
v_info := '/// mit Fachbereich klären';
Testbaren Code schreiben
• keine Mega-SQL-Statements in APEX • Views, Trigger verwenden • Pipelined Table Functions • „Kurzer Prozess“ • V('…') erschwert (automatisches) Testen • V('…') bedeutet Vermeiden
V('…') vermeiden
FUNCTION get_rollen RETURN apex_application_global.vc_arr2 IS v_rollen apex_application_global.vc_arr2; BEGIN select rollenname bulk collect into v_rollen from user_rollen where user_name = v('APP_USER'); END get_rollen;
V('…') vermeiden
FUNCTION get_rollen RETURN apex_application_global.vc_arr2 IS v_rollen apex_application_global.vc_arr2; v_app_user varchar2 := v('APP_USER'); BEGIN select rollenname bulk collect into v_rollen from user_rollen where user_name = v_app_user; END get_rollen;
V('…') vermeiden
FUNCTION get_rollen (i_app_user IN VARCHAR2) RETURN apex_application_global.vc_arr2 IS v_rollen apex_application_global.vc_arr2; BEGIN select rollenname bulk collect into v_rollen from user_rollen where user_name = i_app_user; END get_rollen;
PL/SQL gehört in die Datenbank
• in APEX Performance-Nachteile • im APEX-Fenster kein Debugging möglich • kein Komfort durch Code Completion • Invalider Code kann überleben
warum?
Ein Package pro Seite
Page 1000
Page 1100
Page 1200
APEX
P1000
P1100
P1200
Package
Einheitliche Namen
• Button: SPEICHERN • Request: SPEICHERN • Prozess: SPEICHERN • Branch: SPEICHERN
P100.SPEICHERN ( i_datum => :P100_DATUM );
Verteilte Geschäftslogik • Validierung in APEX :P2_VERTRAGSBEGINN >= trunc(SYSDATE) and extract (DAY FROM to_date(:P2_VERTRAGSBEGINN, 'DD.MM.YYYY')) = 1
"Der Vertragsbeginn kann nur an einem Monatsersten und nicht rückwirkend beginnen."
• Fehlermeldung
• Gleich mehrere Probleme…
Zentrale Geschäftslogik • APEX-Validierung ruft Datenbankfunktion auf
Zentrale Geschäftslogik • APEX-Validierung ruft Datenbankfunktion auf
Zentrale Geschäftslogik • APEX-Validierung ruft Datenbankfunktion auf FUNCTION check_vertragsbeginn (i_datum in date) RETURN VARCHAR2 IS BEGIN IF i_datum < trunc(SYSDATE) OR extract(DAY FROM i_datum) != 1 THEN RETURN 'Der Vertrag kann nur an einem Monatsersten' || ' und nicht rückwirkend beginnen'; END IF; RETURN NULL; -- fehlerfrei END check_vertragsbeginn;
Page 0
• PL/SQL-Region namens TEST_OUTPUT • Einfaches Platzhalter-Template verwenden
<div id="#REGION_STATIC_ID#"> #BODY# </div>
• Der Region eine Static ID mitgeben • Build Option TEST
Hidden Items
• Page 0: HTML-Region namens HIDDEN_ITEMS für sämtliche Hidden Items der Anwendung
• Wieder das Platzhalter-Template verwenden • Werte „hervorzaubern“ mit jQuery
Hidden-Items-Region auf Page 0
$(function() { var table = '<table>'; // Region Static ID ist "hiddenItemsPage0" $('#hiddenItemsPage0 input[id]').each(function() { table += '<tr><td>' + $(this).attr('id') + ':</td><td>' + $(this).val() +'</td></tr>'; }); table += '</table>'; // Region Static ID ist "testOutput" $('#testOutput').prepend($(table)); });
Demo: Hidden Items
Subscription ("Abonnieren")
• Templates (u.a.) zentral verwalten • Mit wenigen Klicks sämtliche Anwendungen
im Workspace aktualisieren • Ebenso: LOVs, Shortcuts, Plugins, Navigation
Bar Entries, …
Demo: Subscription ///
Conditions zentralisieren
• Formular hat mehrere logische Bereiche? • Regions-Template namens PARENT_REGION
<div class="parent_region"> #BODY# </div>
• Bereiche als Parent-Regionen definieren, alle abhängigen Regionen unterordnen
• Formulieren Sie die Conditions nur ein einziges Mal auf den Parent-Regionen
Shortcuts verwenden
• Nicht-Wiederverwendbarkeit von Items und Regionen abmildern
• PL/SQL-Shortcuts sehen &VARIABLEN. IF &P0_BEDINGUNG. THEN &P0_PROZEDURNAME. ; END IF;
Build Options verwenden
• Alles, was zum Entwickeln und Testen dient, erhält die Build Option TEST ("Include")
• Später wird TEST wieder auf "Exclude" umgestellt
Plug-ins verwenden
• Funktionalität und Code werden gekapselt • Entwickler brauchen nur "auszuwählen" • Neue Versionen einfach verteilbar
Session State
Komisch, jetzt funktioniert's
plötzlich!
Session State
• In-Memory Session State vs. Persisted Session State
• Merkwürdig? Ausloggen und Einloggen!
Demo: Tabs
Items können nicht rechnen
:P1_A := 3000; :P1_B := 5; :P1_X := :P1_A + :P1_B; -- 3005 IF :P1_A > :P1_B THEN -- wird niemals ausgeführt!
Demo: Rechnen
Items sind VARCHAR's!
:P100_A := to_number('1000'); :P100_B := to_number('5'); IF :P100_A > :P100_B THEN -- nie im Leben!
Items sind VARCHAR's!
:P100_A := 1000; :P100_B := 5; IF to_number(:P100_A) > to_number(:P100_B) THEN -- dann klappt's auch mit der Number!
Gnädige Browser
• Es existieren semantische JavaScript-Fehler • id="xyz" mehrfach vergeben • Zirkelbezüge in Dynamic Actions • "submit" sowohl im Objekt als auch in Dynamic Action
• Skripte funktionieren zunächst • Irgendwann sind zu viele "Spielregeln" verletzt
APEX_DEBUG_MESSAGE
• Einfaches Logging-Werkzeug • Schreibt in das Debug-Log der Anwendung • selbst, wenn Debugging ausgeschaltet ist
APEX_DEBUG_MESSAGE.LOG_MESSAGE ( p_message => '/// Code dringend prüfen in ' || $$PLSQL_UNIT || ', Zeile: ' || $$PLSQL_LINE ,p_enabled => TRUE ,p_level => 1 );
DBMS_APPLICATION_INFO
• Sagt Ihrem Admin, welches "Programm" läuft • Erleichtert Identifizierung und Beenden
hängender Sessions DBMS_APPLICATION_INFO.set_module ( module_name => 'meine_procedure' ,action_name => 'Plausi-Prüfung' );
DBMS_APPLICATION_INFO
PROCEDURE log_module ( i_module_name in VARCHAR2 ) IS BEGIN DBMS_APPLICATION_INFO.set_module ( module_name => i_module_name ); APEX_DEBUG.ENTER ( p_routine_name => i_module_name ); END;
DBMS_APPLICATION_INFO PROCEDURE log_module ( i_module_name in VARCHAR2 ) IS BEGIN NULL; $IF $$DEBUG_MODE $THEN
DBMS_APPLICATION_INFO.set_module ( module_name => i_module_name ); APEX_DEBUG.ENTER ( p_routine_name => i_module_name );
$END END;
ALTER PROCEDURE log_me COMPILE PLSQL_CCFLAGS = 'DEBUG_MODE:TRUE' REUSE SETTINGS;
Code-Generatoren verwenden PROCEDURE meine_prozedur ( i_tag IN NATURAL ,i_monat IN NATURAL ,i_jahr IN NATURAL ) IS BEGIN …
APEX_DEBUG.ENTER ( p_routine_name => 'meine_prozedur' ,p_name01 => 'i_tag' ,p_value01 => i_tag ,p_name02 => 'i_monat' ,p_value02 => i_monat ,p_name03 => 'i_jahr' ,p_value03 => i_jahr );
Leuchtspur-Munition
• "Warum macht er das denn nicht???" BEGIN :P1_X := UMSATZ ( p_monat => 1 ); END;
Leuchtspur-Munition
• Lassen Sie es krachen! BEGIN :P1_X := UMSATZ ( p_monat => 1/0 ); END;
EXCEPTION HANDLING
• Entweder überhaupt keins • oder: konsistent und überall
• Fachliche Fehler abfangen
(z.B. WHEN NO_DATA_FOUND THEN RETURN NULL;)
• Technische Fehler hochgeben (z.B. WHEN OTHERS THEN RAISE;)
Application Errors
• Home > Administration > Monitor Activity > Application Errors
Eigene Error-Function
Eigene Error-Function
APEX Advisor
• Findet :TIPPEFHLER • Erkennt invalide Codeblöcke
return VALIDIERUNG.check_vertragsbeginn ( i_datum => :P2_VERTRASGBEGINN )
APEX Advisor
APEX Advisor
APEX-Views
SELECT * FROM APEX_APPLICATION_PAGE_ITEMS
APEX-Views
Funktioniert trotzdem nicht???
• wegwerfen und neu bauen ist oft keine schlechte Idee…
Team Development
• Angaben zur Anwendung und Seite nicht vergessen
Anwender-Feedback
• Feedback-Seite in die Anwendung einbauen
Literaturempfehlung
• Expert Oracle Application Express John Scott, Dietmar Aust et. al. (2011) ISBN 978-1430235125
• zu Version 4.0 • diverse Fallbeispiele
Literaturempfehlung
• Pro Oracle SQL Karen Morton (2010) ISBN 978-1430232285
• zu Version 11g • kein SQL-Einsteigerbuch
Literaturempfehlung
• Der Pragmatische Programmierer Andrew Hurt, David Thomas (2003) ISBN 978-3446223097
• ausführliche Betrachtung verbreiteter Fehler
• professionelle Entwurfsmuster
• trotzdem kurzweilig
Viel Spaß beim Schrauben…
WHEN OTHERS Beratung | Programmierung | Coaching rund um Oracle Application Express
Andreas Wismann
[email protected] http://when-others.com
Wenn noch Zeit ist…
• Auf NULL als Returnwert verzichten
• Browser exakt spezifizieren
Einheitliche Bedienung
1 2 3 4 5 6 7 8 9
1 4 7 2 5 8 3 6 9
ID einmalig vergeben
// falsch: <input id="datumseingabe" name="datum1" type="text" value=""> <input id="datumseingabe" name="datum2" type="text" value=""> <input id="datumseingabe" name="datum3" type="text" value=""> <input id="datumseingabe" name="datum4" type="text" value=""> <input id="datumseingabe" name="datum5" type="text" value="">
// richtig: <input class="datumseingabe" name="datum1" type="text" value=""> <input class="datumseingabe" name="datum2" type="text" value=""> <input class="datumseingabe" name="datum3" type="text" value=""> <input class="datumseingabe" name="datum4" type="text" value=""> <input class="datumseingabe" name="datum5" type="text" value="">
HTML-Tags schließen
<!-- falsch --> <table> <tr> <td>Zelle 1 <td>Zelle 2 </table>
<!-- richtig --> <table> <tr> <td>Zelle 1 </td> <td>Zelle 2 </td> </tr> </table>
Pipelined Function FUNCTION komplexer_report (i_info IN VARCHAR2) RETURN report_table_t PIPELINED IS v_report report_t; BEGIN for c in ( -- kompliziertes Select select feld1 , case when feld2 = 'XYZ' then '123' else NULL END as feld2 from … -- noch komplizierter! where … -- höchst kompliziert !!! ) loop v_report.feld1 = geschaeftslogik1 ( c.feld1, i_info ); v_report.feld2 = geschaeftslogik2 ( c.feld2, i_info ); PIPE ROW; end loop; RETURN; END;
Pipelined Function
select * from table ( komplexer_report (:P500_XY) )
• Return-Typen (für Row und Table) müssen in der Datenbank registriert sein