Slide 1

Slide 1 text

Exceptional Feedback in Clojure Howard Lewis Ship - [email protected] - @hlship

Slide 2

Slide 2 text

S.C.E.F.

Slide 3

Slide 3 text

Simplicity

Slide 4

Slide 4 text

Consistency

Slide 5

Slide 5 text

Efficiency

Slide 6

Slide 6 text

Feedback

Slide 7

Slide 7 text

REPL Workflow Write Code Try / Test Interpret Exception Analyze

Slide 8

Slide 8 text

org.postgresql.util.PSQLException: ERROR: column ak.password_hasp does not exist Position: 109 at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2161) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1890) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:560) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:417) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:302) ~[postgresql-9.3-1100-jdbc41.jar:na] at com.jolbox.bonecp.PreparedStatementHandle.executeQuery(PreparedStatementHandle.java:174) ~[bonecp-0.8.0.RELEASE.jar:na] at clojure.java.jdbc$db_query_with_resultset$run_query_with_params__2406.invoke(jdbc.clj:786) ~[na:na] at clojure.java.jdbc$db_query_with_resultset.invoke(jdbc.clj:793) ~[na:na] at clojure.java.jdbc$query.doInvoke(jdbc.clj:828) ~[na:na] at clojure.lang.RestFn.invoke(RestFn.java:425) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.AFn.applyToHelper(AFn.java:156) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.RestFn.applyTo(RestFn.java:132) [clojure-1.7.0-alpha5.jar:na] at clojure.core$apply.invoke(core.clj:630) [clojure-1.7.0-alpha5.jar:na] at fan.resources.common$db_query$fn__17537.invoke(common.clj:306) ~[na:na] at fan.resources.common$wrap_jdbc.invoke(common.clj:285) ~[na:na] at fan.resources.common$db_query.doInvoke(common.clj:306) [na:na] at clojure.lang.RestFn.invoke(RestFn.java:425) [clojure-1.7.0-alpha5.jar:na] at fan.auth.resources.api_keys$create_token$fn__23631.invoke(api_keys.clj:144) [na:na] at clojure.java.jdbc$db_transaction_STAR_.doInvoke(jdbc.clj:580) [na:na] at clojure.lang.RestFn.invoke(RestFn.java:521) [clojure-1.7.0-alpha5.jar:na] at clojure.java.jdbc$db_transaction_STAR_.doInvoke(jdbc.clj:596) [na:na] at clojure.lang.RestFn.invoke(RestFn.java:425) [clojure-1.7.0-alpha5.jar:na] at fan.auth.resources.api_keys$create_token.invoke(api_keys.clj:141) [na:na] at clojure.lang.AFn.applyToHelper(AFn.java:165) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.AFn.applyTo(AFn.java:144) [clojure-1.7.0-alpha5.jar:na] at clojure.core$apply.invoke(core.clj:626) [clojure-1.7.0-alpha5.jar:na] at io.aviso.rook.dispatcher$construct_namespace_table_entry$fn__11402.invoke(dispatcher.clj:437) [na:na] at fan.endpoint_tracking$operation_tracking_sync_wrapper$fn__17773$fn__17774.invoke(endpoint_tracking.clj:53) [na:na] at clojure.core.async$thread_call$fn__9309.invoke(async.clj:405) [na:na] at clojure.lang.AFn.run(AFn.java:22) [clojure-1.7.0-alpha5.jar:na] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_31] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_31] at java.lang.Thread.run(Thread.java:745) [na:1.8.0_31] fan.auth.resources.api_keys$create_token$fn__23631.invoke api_keys.clj:144

Slide 9

Slide 9 text

Chronological Order

Slide 10

Slide 10 text

at java.lang.Thread.run(Thread.java:745) [na:1.8.0_31] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_31] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_31] at clojure.lang.AFn.run(AFn.java:22) [clojure-1.7.0-alpha5.jar:na] at clojure.core.async$thread_call$fn__9309.invoke(async.clj:405) [na:na] at fan.endpoint_tracking$operation_tracking_sync_wrapper$fn__17773$fn__17774.invoke(endpoint_tracking.clj:53) [na:na] at io.aviso.rook.dispatcher$construct_namespace_table_entry$fn__11402.invoke(dispatcher.clj:437) [na:na] at clojure.core$apply.invoke(core.clj:626) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.AFn.applyTo(AFn.java:144) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.AFn.applyToHelper(AFn.java:165) [clojure-1.7.0-alpha5.jar:na] at fan.auth.resources.api_keys$create_token.invoke(api_keys.clj:141) [na:na] at clojure.lang.RestFn.invoke(RestFn.java:425) [clojure-1.7.0-alpha5.jar:na] at clojure.java.jdbc$db_transaction_STAR_.doInvoke(jdbc.clj:596) [na:na] at clojure.lang.RestFn.invoke(RestFn.java:521) [clojure-1.7.0-alpha5.jar:na] at clojure.java.jdbc$db_transaction_STAR_.doInvoke(jdbc.clj:580) [na:na] at fan.auth.resources.api_keys$create_token$fn__23631.invoke(api_keys.clj:144) [na:na] at clojure.lang.RestFn.invoke(RestFn.java:425) [clojure-1.7.0-alpha5.jar:na] at fan.resources.common$db_query.doInvoke(common.clj:306) [na:na] at fan.resources.common$wrap_jdbc.invoke(common.clj:285) ~[na:na] at fan.resources.common$db_query$fn__17537.invoke(common.clj:306) ~[na:na] at clojure.core$apply.invoke(core.clj:630) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.RestFn.applyTo(RestFn.java:132) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.AFn.applyToHelper(AFn.java:156) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.RestFn.invoke(RestFn.java:425) [clojure-1.7.0-alpha5.jar:na] at clojure.java.jdbc$query.doInvoke(jdbc.clj:828) ~[na:na] at clojure.java.jdbc$db_query_with_resultset.invoke(jdbc.clj:793) ~[na:na] at clojure.java.jdbc$db_query_with_resultset$run_query_with_params__2406.invoke(jdbc.clj:786) ~[na:na] at com.jolbox.bonecp.PreparedStatementHandle.executeQuery(PreparedStatementHandle.java:174) ~[bonecp-0.8.0.RELEASE.jar:na] at org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:302) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:417) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:560) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1890) ~[postgresql-9.3-1100-jdbc41.jar:na] at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2161) ~[postgresql-9.3-1100-jdbc41.jar:na] org.postgresql.util.PSQLException: ERROR: column ak.password_hasp does not exist Position: 109 TIME

Slide 11

Slide 11 text

De-Mangle Names fan.resources.common$db_query.doInvoke fan.resources.common/db-query

Slide 12

Slide 12 text

Omission at io.aviso.rook.dispatcher$construct_namespace_table_entry$fn__11402.invoke(dispatcher.clj:437) [na:na] at clojure.core$apply.invoke(core.clj:626) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.AFn.applyTo(AFn.java:144) [clojure-1.7.0-alpha5.jar:na] at clojure.lang.AFn.applyToHelper(AFn.java:165) [clojure-1.7.0-alpha5.jar:na] at fan.auth.resources.api_keys$create_token.invoke(api_keys.clj:141) [na:na] io.aviso.rook.dispatcher/construct-namespace-table-entry/fn clojure.core/apply … fan.auth.resources.api-keys/create-token

Slide 13

Slide 13 text

Exception Properties org.postgresql.util.PSQLException: ERROR: column ak.password_hasp does not exist Position: 109 SQLState: "42703" errorCode: 0 serverErrorMessage: #

Slide 14

Slide 14 text

Columns java.lang.Thread.run Thread.java: 745 java.util.concurrent.ThreadPoolExecutor$Worker.run ThreadPoolExecutor.java: 617 java.util.concurrent.ThreadPoolExecutor.runWorker ThreadPoolExecutor.java: 1142 ... clojure.core.async/thread-call/fn async.clj: 405 fan.endpoint-tracking/operation-tracking-sync-wrapper/fn/fn endpoint_tracking.clj: 53 io.aviso.rook.dispatcher/construct-namespace-table-entry/fn dispatcher.clj: 437 clojure.core/apply core.clj: 626 ... fan.auth.resources.api-keys/create-token api_keys.clj: 141 ... clojure.java.jdbc/db-transaction* jdbc.clj: 596 ... clojure.java.jdbc/db-transaction* jdbc.clj: 580 fan.auth.resources.api-keys/create-token/fn api_keys.clj: 144 ... fan.resources.common/db-query common.clj: 306 fan.resources.common/wrap-jdbc common.clj: 285 fan.resources.common/db-query/fn common.clj: 306 clojure.core/apply core.clj: 630 ... clojure.java.jdbc/query jdbc.clj: 828 clojure.java.jdbc/db-query-with-resultset jdbc.clj: 793 clojure.java.jdbc/db-query-with-resultset/run-query-with-params jdbc.clj: 786 com.jolbox.bonecp.PreparedStatementHandle.executeQuery PreparedStatementHandle.java: 174 org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery AbstractJdbc2Statement.java: 302 org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags AbstractJdbc2Statement.java: 417 org.postgresql.jdbc2.AbstractJdbc2Statement.execute AbstractJdbc2Statement.java: 560 org.postgresql.core.v3.QueryExecutorImpl.execute QueryExecutorImpl.java: 255 org.postgresql.core.v3.QueryExecutorImpl.processResults QueryExecutorImpl.java: 1890 org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse QueryExecutorImpl.java: 2161 fan.auth.resources.api-keys/create-token/fn api_keys.clj: 144

Slide 15

Slide 15 text

Fonts & Color

Slide 16

Slide 16 text

Exception Unwrapping

Slide 17

Slide 17 text

How did I get here?

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

(track "A description of the work to perform." …)

Slide 22

Slide 22 text

(track
 #(format "Compiling `%s' to JavaScript" file-path)
 (let [^Map result
 (rhino/invoke-javascript ["META-INF/twixt/coffee-script.js" "META-INF/twixt/invoke-coffeescript.js"]
 "compileCoffeeScriptSource"
 (-> asset :content utils/as-string)
 file-path
 file-name)]
 
 ;; The script returns an object with key "exception" or key "output":
 (when (.containsKey result "exception")
 (throw (RuntimeException. (extract-value result "exception"))))
 (-> asset
 (utils/create-compiled-asset "text/javascript" (extract-value result "output") nil)
 (utils/add-attachment "source.map" "application/json" (-> result (extract-value "sourceMap") utils/as-bytes))))) Evaluation deferred

Slide 23

Slide 23 text

(let [logger (get-logger *ns*)] (binding [*operation-labels* (conj *operation-labels* label)] (try … (catch Throwable t (handle-checkpoint-exception logger t))))) logs and wraps as ex-info or rethrows

Slide 24

Slide 24 text

with core.async (go (checkpoint …)) (thread (checkpoint …)) checkpoint: just the try/catch part

Slide 25

Slide 25 text

And that's it?

Slide 26

Slide 26 text

No

Slide 27

Slide 27 text

Empathy For Your Users For Yourself

Slide 28

Slide 28 text

Links • Pretty — https://github.com/AvisoNovate/pretty • Tracker — https://github.com/AvisoNovate/tracker