Here are my notes from Jen Smith's talk about code smells in Clojure, from EuroClojure 2013.
Background & Motivation
- What do smells look (smell) like in Clojure, and why do we care?
- Clojure is very recent for most of us. That may be the biggest reason we haven't really identified smells yet, though we may sometimes smugly assume there are deeper reasons for it.
- The "standard" code smells are from Refactoring by Martin Fowler and Kent Beck. For object-oriented, static languages. Mostly Java, really. People have categorized them into Couplers, Bloaters, Change Preventers, OO Abusers, and Dispensibles.
- We can try to just change the vocabulary in the OO smells: Class->function, class->namespace, method->function etc.
- Some of the OO smells don't seem to apply: Primitive Obsession - using primitives to represent domain ideas - also discusses structs/hashes as primitives. But that's pretty much what we do in Clojure.
- What smells are there for dysfunctional Clojure code specifically?
- A.K.A. data structure coupling.
- Over-sharing map structure, leads to coupling.
- One particular change to data representation may have effects everywhere else.
- Smells like: Hard to figure out cost of change for key. Unexpected errors with wrongly shaped data. Too many functions know about data structure details.
- Easy to get into.
- A tradeoff of using maps.
- Doesn't really exist in a statically typed language like Java.
- Countermeasures: Keep data-aware functions together. Minimise their number. Consider core.contracts or something like Prismatic's Schema to add checks for external data.
- A.K.A. over-nesting.
- Excessive nesting of expressions.
- Hard to read and understand.
- Smells like: Trying to do everything in one expression. Easy to evaluate, but hard to read or debug.
- A tradeoff of being expression-oriented.
- Countermeasures: Keep a balance beteen terseness and readability. Excessive closing parens are a good heuristic. Use expression threading macros.
- (codex dormientes)
- Nested levels of lazy seqs leading to errors popping up in distant places.
- Smells like: Many discrete steps with one final one that executes the whole thing. Hard to isolate failures.
- Countermeasures: Consider coarser grained steps to avoid long pipelines. Add failure mode support. Realise lazy seqs in a consistent way, maybe at namespace boundaries?
- Macromania - early on when getting into Clojure, tendency to create macros because you can. Powerful tool, should be used in the right places.
- Indirection By Partiality - lots of partial versions of functions, hard to see what's going on.
Locally scoped atoms - when you can't think of a functional solution, out of inexperience?
So do we need smells? There's one similarity between Java programming and Clojure programming: Us. We put the smells in, regardless of language
Are smells really just a sign of overdoing a good idea? Doing good things but turning it up too much. Each smell seems to be a side-effect of some good thing: Maps to magic keys, lazy seqs to lazyitis, expression-orientation to excessive parentheses.
Smells can help guide us in the right direction, also as a warning of overdoing it.
Going to be even more important as we reach mass-adoption of Clojure!
Should avoid the tendency to throw away what was learned when moving to something new. ("We don't need this, we're in Ruby now!")
Know Your AngularJS Inside Out
Build Your Own AngularJS helps you understand everything there is to understand about AngularJS (1.x). By creating your very own implementation of AngularJS piece by piece, you gain deep insight into what makes this framework tick. Say goodbye to fixing problems by trial and error and hello to reasoning your way through them
- EuroClojure 2013 - States and Nomads: Handling Software Complexity
- EuroClojure 2013: Narcissistic Design