Hi Timur,

I find almost all of my pain is from changing shapes of the data that flows 
through the system. As you say “I pass the results in a map and each function 
updates the map” so even if you unit test each fn that doesn’t actually help 
because the tests provide the (now incorrect) data. Stepping back a problem 
this is a problem of visibility and enforcement - the required structures are 
opaque (otherwise it wouldn’t be painful) and they aren’t enforced.

On a related note, I don’t see why you can’t still write atomic tests here...

I find Prismatic Schema a great answer to both of these. I also think it is 
worth viewing fns as a graph rather than a list - I frequently have a fn which 
then delegates to a bunch of other fns. Add a few of the ‘top level’ fns in a 
single namespace and it gets really hard really quickly to see this graph 
making it hard to visualise an impact analysis. Again, one trick is to document 
these ‘gateway’ fns with the schema and simply use pre/post (or not, depending 
on the complexity) on the ‘inner fns’. Namespaces are also an obvious tool but 
I don’t think one ns per high level fn is viable.

I also find in that function graph at the bottom are ‘shape-a->shape-b’, in the 
middle are higher level fn which takes some sequence or graph, untangles it and 
then maps/reduces the ‘bottom’ shape changer fns. At the top are higher 
level/entry point fns which are more often than not push a data structure 
through a sequence of the ‘middle’ fns using (-> ) or (->> ).

I deliberately don’t document anything other than the high level fns as a 
starting point - everything else should be _so_ obvious (assuming you 
understand Clojure of course) that a ‘what does this do’ is a smell. The higher 
level fns are documented in terms of the schema (which with good naming is 
self-documenting) and _why_ it is doing what it is doing.

Another cause of pain is the cognitive load of the code itself. Initially I 
found myself writing so much code that was in the core library but I didn’t 
know it. Once I learnt the core library it wasn’t unusual to delete 10s of line 
to replace with a 1 or 2 liner using the core data structures/idioms. This has 
a non-obvious benefit which is a non-trivial amount of code is ‘just’ using the 
core fns. This means there is nothing new - the core fns become a significant 
part of your code’s ubiquitous language. I recall a senior member of the 
community saying something like ‘whenever I write my own code I consider it a 
smell’ (or something like it). The point is familiarity - the more core fns you 
use the more domain-agnostic and familiar that code will be.

My final point (waffled too much already I think) is that of ignorance - you 
want your fns to be as ignorant as possible. For each fn follow this process 
_ruthlessly_:
 - what assumptions does this fn make?
 - what can change outside this fn which will break this fn?
 - how many times do I use the word ‘and’ when I describe this fn
 - can I replace it with a core fn

For the higher level fns the answer is usually ‘many ands’ because they 
implement a process but that is fine.

Once you are happy that your code is composed of tiny little ignorant and 
bullet proof bricks, all composed together in readable top level fns and 
feeling all great about it, go get somebody to review it, even if you have 
paired on the code, always get somebody else to read it, not for correctness, 
just for consistency (using same idioms, style etc.) with the rest of the code 
base.

All of this is to say the pain comes from obfuscation, I find the above goes 
along way to writing self documenting code.

In summary, what I find helpful:

 - the pain comes from the opaqueness of the really important concepts: data 
shapes and function graphs
 - comments explaining _what_ the code are doing are really helpful when you 
are writing fns that do too much
 - Prismatic Schema to document _and_ enforce (runtime enforcement isn’t really 
that inefficient I find) the abstract shapes
 - fns further up the graph tend to be very well documented, non-private and 
almost certainly have a schema attached
 - delegate fns tend to be trivial one or two liners that typically map/reduce 
something over a sequence
 - actual worker fns tend to take one element of a sequence and are likely to 
be `defmulti`s. These take one fairly trivial ‘shape’
 - always get somebody else’s eyes - they might be able to say ‘why’ the code 
is doing it but they should be able to say _what_ the code is doing - WITHOUT 
you prompting them :-)

HTH - knee jerk and written stream of consciousness so apologies for numptyness

> On 5 Dec 2015, at 14:10, Timur <timurha...@gmail.com> wrote:
> 
> Hi all,
> 
> I'm using Clojure to build a set of services. 
> 
> Development with Clojure is fun but most of the time, I lose time with fixing 
> regressions due to changing small parts of the codes. Do you have any best 
> practices to avoid regressions occurring due to changes in the code? I use 
> :pre and :post conditions. Sometimes, they are not sufficient.  do not write 
> so far regression tests as they are quite time consuming. Writing these tests 
> will be probably an answer, I guess. Do you have any guidelines to write good 
> regression tests? It would be nice if I could re-use the code that I type 
> into the REPL to test separate functions, into the test code but most of the 
> time it somehow does not work. Another problem I'm facing is many functions 
> are dependent on each other, so I cannot test each separately, I pass the 
> results in a map and each function updates the map, so I cannot write really 
> atomic tests.
> 
> Any suggestions? 
> 
> 
> 
> -- 
> 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 
> <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 
> <mailto:clojure+unsubscr...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout 
> <https://groups.google.com/d/optout>.

-- 
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