Saubere Arbeit! Fehlervermeidung und Debugging in APEX Andreas Wismann WHEN OTHERS Beratung Programmierung Coaching rund um Oracle Application Express wismann@when-others.com http://when-others.com
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 APEX DB
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 /// v_info := '/// mit Fachbereich klären'; Markieren Sie Baustellen mit /// ist in jedem Zeichensatz lesbar verträgt sich mit SQL*Plus wird von jeder Volltextsuche gefunden
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 warum? in APEX Performance-Nachteile im APEX-Fenster kein Debugging möglich kein Komfort durch Code Completion Invalider Code kann überleben
Ein Package pro Seite APEX Package Page 1000 P1000 Page 1100 P1100 Page 1200 P1200
Einheitliche Namen Button: Request: Prozess: Branch: SPEICHERN SPEICHERN SPEICHERN 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 Fehlermeldung "Der Vertragsbeginn kann nur an einem Monatsersten und nicht rückwirkend beginnen." 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 END; :P1_X := UMSATZ ( p_monat => 1 );
Leuchtspur-Munition Lassen Sie es krachen! BEGIN END; :P1_X := UMSATZ ( p_monat => 1/0 );
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
Andreas Wismann WHEN OTHERS Beratung Programmierung Coaching rund um Oracle Application Express wismann@when-others.com 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