There is a good talk by Chris Houser, "Condition Systems in an Exceptional 
Language" [1] where he systematically goes through many of the aspects on 
handling exceptions in batch processing (like the problem Josh describes).

An interesting way to build a conditional error handler is outlined towards 
the end of that talk, the essential idea is to define dynamic functions 
which receives the exceptions and decides how to continue.

If we look into the example outlined in Josh's question, there is some 
object with a :created-field. The :created-field can be suspicious in that 
equals beginning of unix epoch.

One thing to note with detectable errors, like the zero-timestamp, that the 
check for this is probably best represented as a predicate function like 
non-zero-timestamp?, and that the call to such a function is a actually 
some kind of pre-emptive branching.

This branching could be represented as two different types, maybe like 
[:valid-date #inst "2017-01-02T10:01:22.231Z"] or [:invalid-date 
"1970-01-01T00:00:00.000Z"]. This makes it nescessary for all logic 
afterwards to be able to handle both of these types, but de-couples all 
other logic from deciding what is a valid date or not. In an application 
specialized in converting dirty data this is probably a worthy trade-off.

I think the most important thing when handling this type of situation is 
that the inconsistency has to be embraced and that the data representation 
of the system has to be able to contain both wanted and unwanted values.

In this case, it would mean that the "outer", most general spec for such a 
timestamp must be able to contain both valid dates and things which are 
sorted out as not valid. These non-valid dates may still have to live up to 
certain properties (being a "correct error object"). This distinguishes 
handle-able errors from programming errors which is usually what we want to 
detect during tests for.

It is very hard to construct specs which are certainly disjunct, if they 
are not different types or similar. When it comes to various maps it is 
certainly possible to construct specs for various types of maps, but it's 
very complicated to make dispatches based on values of keys in the map 
(which probably is the most common way to distinguish various types of 
objects in clojure).

A very useful feature of clojure.spec is conform. With conform, one 
"upsert" any value to something which is according to our specs.

Example:

parsed-date-spec is a spec accepting {:unparsable-date ...} as well as some 
representation of correct timestamps, and suspicous timestamps.

(defn parse-date
     "conform, which accepts a broad range of arguments"
     [o]
    (cond?
      (s/valid? parsed-date-spec o) o
      (inst? o) o ;; java.util.Date is ok
      (string? o) (try (parse-date o) (catch Throwable t {:unparsable-date 
o :exception t})
      :else
      :clojure.spec/invalid) ;;something is wrong with the program if it's 
not an internal date representation, a string, or inst.

When we know that the thing we have is something which we have control 
over, it is much easier to construct a predicate which checks validly 
parseable-dates for being 0 timestamps (or to far in the future, or 
anything).

I suggest you

1. create a representation that makes it possible for you and your 
application to handle correct and in-correct values in a unified manner and 
that "normal errors" are embraced by the application logic.

2. strive to use predicate- or other simple functions (rather than 
complicated clojure.spec setups) to identify invalid entities in the 
input-data.

3. handle such errors inside the scope of the function / part of the 
program that handles the correct path of the program.

4. uses :clojure.spec/invalid and informative exceptions when there are 
unforeseen programming errors which the program cannot possibly recover 
from.

The idea of using clojure spec to classify error messages in 
data-validation "all the way" is interesting. One problem is that the logic 
that want to make use of the diagnosis-data needs to know quite much about 
the general structure of the data (and the specs).

/Linus


[1] Chris Houser, "Condition Systems in an Exceptional Language"
https://www.youtube.com/watch?v=zp0OEDcAro0

On Saturday, February 18, 2017 at 12:42:25 AM UTC+1, Josh Tilles wrote:
>
> Has anyone explored using spec for “soft” failures? For example, if I’m 
> writing an ETL system to migrate legacy customer account data, all I might 
> *require* of a record’s :created field is that the value is a 
> syntactically valid date-time string. If any record claimed that it was 
> created on "1970-01-01T00:00:00.000Z", of course that would almost 
> certainly be bad data; but instead of crashing the program or refusing to 
> process the record, let’s say I want to log (at the WARN level) some of 
> that record’s data, or perhaps even store the output of s/explain-data with 
> the record in the target database. To describe it another way: I’m 
> interested in taking spec beyond “application correctness” to also encode 
> business/domain logic about data “smells”.
>
> I’ve come up with two possible approaches to implementation:
> 1. Use different specs, potentially passing over the data multiple times.
> 2. Use a single spec, but have rules for what ::s/problems are acceptable.
>
> So, what do you think? Has anyone tried anything along these lines? Does 
> this sound fundamentally wrongheaded, trying to make spec do something it’s 
> ill-suited for?
>
> Cheers,
>
>
> ------------------------------
> Josh Tilles
> [image: Signafire logo]
> 79 Madison Ave, 4th Floor
> New York, New York 10016
>
> Tel: (646) 685-8379
> signafire.com <http://www.signafire.com/>
> ------------------------------
>
>

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to