User Tools

Site Tools


blog:enterprise_clojure_is_not_a_bad_phrase

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
Next revision Both sides next revision
blog:enterprise_clojure_is_not_a_bad_phrase [2017/05/24 22:17]
djo ['Enterprise Clojure' and Specs]
blog:enterprise_clojure_is_not_a_bad_phrase [2017/05/25 16:15]
djo 'Enterprise Clojure' and Specs
Line 7: Line 7:
 This post looks at one concern I have seen raised and proposes a direction the community could take toward a solution. This post looks at one concern I have seen raised and proposes a direction the community could take toward a solution.
  
-===== Is Clojure too hard to use at enterprise scale? ===== +Naturally, feedback is welcome.
- +
-I've come to some opinions about this myself, and as I have been refactoring and modernizing the clj-foundation library I'd like to offer some thoughts about ways Clojure developers can make Clojure easier to work with in larger projects with larger teams. +
- +
-Let's look at one concern I've run across using real code I've been working on as an example.  ​Naturally, feedback is welcome. +
- +
-===== The data expected in a function'​s parameters quickly becomes non-obvious =====+
  
 Awhile back I needed to parse a string into words--except that single or double quoted substrings must function as a single word.  Nested quotes are not supported.  ​ Awhile back I needed to parse a string into words--except that single or double quoted substrings must function as a single word.  Nested quotes are not supported.  ​
Line 23: Line 17:
 </​code>​ </​code>​
  
-Here is the Clojure code I initially wrote to parse this way:+Here is the Clojure code I initially wrote to parse this.  When I recently revisited this code, I found it more challenging than I expected to deeply understand again. ​ (I'll get into why in a bit.)
  
 <code clojure> <code clojure>
Line 63: Line 57:
 At the time I wrote the code, it made perfect sense to me.  But I had a need to revisit it recently in order to write/​update tests and needed to understand it again. At the time I wrote the code, it made perfect sense to me.  But I had a need to revisit it recently in order to write/​update tests and needed to understand it again.
  
-And I found myself staring at the parameter list and code of the <​code>​merge-strings</​code> ​function, trying to understand what values each parameter could take.  I was surprised at how non-obvious it was to me a few short months later.+And I found myself staring at the parameter list and code of the ''​%%merge-strings%%'' ​function, trying to understand what values each parameter could take.  I was surprised at how non-obvious it was to me a few short months later.
  
 To my thinking, this illustrated a common pain point my colleagues have expressed about Clojure, namely... To my thinking, this illustrated a common pain point my colleagues have expressed about Clojure, namely...
  
-===== Functions accepting (possibly-nested) structures are not self-documenting ​=====+===== As complexity increases, the data expected in a function'​s parameters can quickly become non-obvious ​=====
  
-Even though I had written a docstring for the function, notice that because this function is a reducer, and is not API, I had not rigorously described each parameter'​s possible values and usage within the function.+Even though I had written a docstring for the ''​%%merge-strings%%'' ​function, notice that because this function is a reducer, is used as internal implementation detail, and is not API, I had not rigorously described each parameter'​s possible values and usage within the function.
  
 This week, I decided to use the upcoming [[https://​clojure.org/​guides/​spec|Specs]] library from Clojure 1.9 to document each parameter'​s possible values and see if this helped with the readability and maintainability of this particular example. This week, I decided to use the upcoming [[https://​clojure.org/​guides/​spec|Specs]] library from Clojure 1.9 to document each parameter'​s possible values and see if this helped with the readability and maintainability of this particular example.
Line 89: Line 83:
 </​code>​ </​code>​
  
-After trying this in a few places, I became dissatisfied with the repetitiveness of manually calling ​<​code>​s/valid?</​code> ​for each (destructured) parameter value, so I wrote a macro to DRY this pattern up.  (The code is in the <​code>​clj-foundation</​code> ​project) ​ With the macro, the above defn can be rewritten in either of the following two ways:+After trying this in a few places, I became dissatisfied with the repetitiveness of manually calling ​''​%%s/valid?%%'' ​for each (destructured) parameter value, so I wrote a macro to DRY this pattern up.  (The code is in the ''​%%clj-foundation%%'' ​project.)  With the macro, the above defn can be rewritten in either of the following two ways:
  
 <code clojure> <code clojure>
 (=> person-name [::person] string? (=> person-name [::person] string?
-  "​person->​String"​ 
   [person]   [person]
   (str (::​first-name person) " " (::​last-name person)))   (str (::​first-name person) " " (::​last-name person)))
Line 100: Line 93:
  
 (defn person-name (defn person-name
-  "​person->​String"​ 
   [person]   [person]
   (str (::​first-name person) " " (::​last-name person)))   (str (::​first-name person) " " (::​last-name person)))
Line 107: Line 99:
 </​code>​ </​code>​
  
-To my eyes, this significantly enhanced the readability of the spec information added to the <​code>​person-name</​code> ​function, so I applied the macro to my string parsing functions. ​ That code now reads as follows:+To my eyes, this significantly enhanced the readability of the spec information added to the ''​%%person-name%%'' ​function, so I applied the macro to my string parsing functions. ​ That code now reads as follows:
  
 <code clojure> <code clojure>
Line 151: Line 143:
 </​code>​ </​code>​
  
-With this code, it becomes easy to see that the <​code>​result</​code> ​(destructured) parameter contains a word-vector,​ which must be a collection of strings. ​ Similarly, without reading the function body, one can immediately note that the <​code>​delimiter</​code> ​parameter is a character from the delimiter-set or nil.+With this code, it becomes easy to see that the ''​%%result%%'' ​(destructured) parameter contains a word-vector,​ which must be a collection of strings. ​ Similarly, without reading the function body, one can immediately note that the ''​%%delimiter%%'' ​parameter is a character from the delimiter-set or nil.   
 + 
 +And skipping to the end (of the line), one can immediately see that the entire function returns a ''​%%merge-result%%'',​ which is defined as a Tuple containing a word-vector,​ delimiter (or nil), and String--all without reading the function body.
  
-And this bit of up-front information made (re)reading the body of the <​code>​merge-strings</​code> ​function much easier.+I found that this bit of up-front information made (re)reading the body of the ''​%%merge-strings%%'' ​function much easier, even after only a few days away from it.
  
 ===== Retrospective ===== ===== Retrospective =====
Line 159: Line 153:
 With this in mind, I would like to offer the following thoughts about this experiment: With this in mind, I would like to offer the following thoughts about this experiment:
  
-* I felt the experiment was successful. ​ I believe the code I wound up with explains the original author'​s intentions better than the original code. +  ​* I felt the experiment was successful. ​ I believe the code I wound up with explains the original author'​s intentions better than the original code. 
-* Only time will validate the <​code>​=></​code> ​macro, and I'm sure it will evolve over time.  But I sincerely hope something like it makes it into Specs in the end. +  * Only time will validate the ''​%%=>%%'' ​macro, and I'm sure it will evolve over time.  But I sincerely hope something like it makes it into Specs in the end. 
-* More generally, I feel that this code illustrates how even quite straightforward functions can become opaque very quickly, and how providing explicit specifications describing what data a function accepts ​and provides can significantly enhance communication.+  * More generally, I feel that this code illustrates how even quite straightforward functions can become opaque very quickly, and how providing explicit specifications describing what data a function accepts/provides can significantly enhance communication.
  
 ==== ...and a word from our sponsors ==== ==== ...and a word from our sponsors ====
  
-In closing, I'm available for new Clojure ​gigs right now.  If this kind of thinking and expertise is welcome on your Clojure ​team or on your Clojure ​project, feel free to email me using the address on the "​Contacts"​ page off my home page.+In closing, I'm available for new gigs right now.  If this kind of thinking and expertise is welcome on your team or on your project ​(whatever the language), feel free to email me using the address on the "​Contacts"​ page off my home page.