DCBABCD
DCBABCD
DCBABCD
DCBABCD
DCBABCD
DCBABCD
DCBABCD
Record of experiments, readings, links, videos and other things that I find on the long road.
Registro de experimentos, lecturas, links, vídeos y otras cosas que voy encontrando en el largo camino.
Thursday, December 31, 2015
Interesting Talk: "Computational Meta-Psychology"
I've just watched this wonderful talk by Joscha Bach:
Wednesday, December 30, 2015
Books I read (2015)
January
- Software Architecture for Developers, Simon Brown
- Functional Programming Patterns in Scala and Clojure, Michael Bevilacqua-Linn
- Working Effectively with Unit Tests, Jay Fields
February
- Vida y Destino (Жизнь и судьба), Vasili Grossman. (2nd time)
- Primer Libro de Lankhmar (The First Book of Lankhmar), Fritz Leiber
- Drown, Junot Díaz
- Los girasoles ciegos, Alberto Méndez
- Smalltalk Best Practice Patterns, Kent Beck
March
- Las puertas del paraiso (The Vagrants), Yiyun Li
- Growing Object-Oriented Software Guided by Tests, Steve Freeman and Nat Pryce. (2nd time)
- The Joy of Clojure, 2nd edition, Michael Fogus and Chris Houser
April
- Las ciudades carnales (Les cités charnelles), Zoé Oldenbourg
- Refactoring: Improving the Design of Existing Code, Fowler, Beck, Brant, Opdyke and Roberts. (2nd time)
- Hasta aquí hemos llegado (Τίτλοι τέλους), Petros Márkaris
- The Childhood of Jesus, J. M. Coetzee
May
- Refactoring to Patterns, Joshua Kerievsky
- You Don't Know JS: Scope & Closures, Kyle Simpson
- Ser, Ignacio Terzano
- Slow Man, J. M. Coetzee
- Número Cero (Numero Zero), Umberto Eco
- La vieja sirena, José Luis Sampedro
- The Death of Bunny Munro, Nick Cave
June
- Martha and Hanwell, Zadie Smith
- You Don't Know JS: this & Object Prototypes, Kyle Simpson
- Functional Programming for the Object-Oriented Programmer, Brian Marick
July
- Object Thinking, David West
- The RSpec Book: Behaviour Driven Development with RSpec, Cucumber, and Friends. Chelimsky, Astels, Helmkamp, North, Dennis and Hellesoy
- The Nature of Software Development: Keep It Simple, Make It Valuable, Build It Piece by Piece, Ron Jeffries
- Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans
- Momo, Michael Ende
August
- Los Surcos del Azar, Paco Roca
- En el pueblo del gato vampiro (Village of the Vampire Cat), Lensey Namioka (2nd time)
- El valle de los cerezos rotos (Valley of the Broken Cherry Trees), Lensey Namioka (2nd time)
- Design Patterns in Ruby, Russ Olsen
- Practical Object-Oriented Design in Ruby, Sandi Metz (2nd time)
- En el café de la juventud perdida (Dans le café de la jeunesse perdue), Patrick Modiano
September
- Your Code as a Crime Scene: Use Forensic Techniques to Arrest Defects, Bottlenecks, and Bad Design in Your Programs, Adam Tornhill
- Cien años de soledad, Gabriel García Márquez
- Eloquent Ruby, Russ Olsen (2nd time)
October
- Understanding Computation. From Simple Machines to Impossible Programs, Tom Stuart
- A Mind For Numbers: How to Excel at Math and Science, Barbara Oakley
November
- The Well-Grounded Rubyist, 2nd edition, David A. Black
- Hackers & Painters: Big Ideas from the Computer Age, Paul Graham
- Effective Programming: More than Writing Code, Jeff Atwood
December
- The Psychology of Computer Programming: Silver Anniversary, Gerald Weinberg
- Cuando el árbol canta (When the tree sings), Stratis Haviaras
- Nineteen Eighty-Four, George Orwell
- Thinking, Fast and Slow, Daniel Kahneman
- Software Architecture for Developers, Simon Brown
- Functional Programming Patterns in Scala and Clojure, Michael Bevilacqua-Linn
- Working Effectively with Unit Tests, Jay Fields
February
- Vida y Destino (Жизнь и судьба), Vasili Grossman. (2nd time)
- Primer Libro de Lankhmar (The First Book of Lankhmar), Fritz Leiber
- Drown, Junot Díaz
- Los girasoles ciegos, Alberto Méndez
- Smalltalk Best Practice Patterns, Kent Beck
March
- Las puertas del paraiso (The Vagrants), Yiyun Li
- Growing Object-Oriented Software Guided by Tests, Steve Freeman and Nat Pryce. (2nd time)
- The Joy of Clojure, 2nd edition, Michael Fogus and Chris Houser
April
- Las ciudades carnales (Les cités charnelles), Zoé Oldenbourg
- Refactoring: Improving the Design of Existing Code, Fowler, Beck, Brant, Opdyke and Roberts. (2nd time)
- Hasta aquí hemos llegado (Τίτλοι τέλους), Petros Márkaris
- The Childhood of Jesus, J. M. Coetzee
May
- Refactoring to Patterns, Joshua Kerievsky
- You Don't Know JS: Scope & Closures, Kyle Simpson
- Ser, Ignacio Terzano
- Slow Man, J. M. Coetzee
- Número Cero (Numero Zero), Umberto Eco
- La vieja sirena, José Luis Sampedro
- The Death of Bunny Munro, Nick Cave
June
- Martha and Hanwell, Zadie Smith
- You Don't Know JS: this & Object Prototypes, Kyle Simpson
- Functional Programming for the Object-Oriented Programmer, Brian Marick
July
- Object Thinking, David West
- The RSpec Book: Behaviour Driven Development with RSpec, Cucumber, and Friends. Chelimsky, Astels, Helmkamp, North, Dennis and Hellesoy
- The Nature of Software Development: Keep It Simple, Make It Valuable, Build It Piece by Piece, Ron Jeffries
- Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans
- Momo, Michael Ende
August
- Los Surcos del Azar, Paco Roca
- En el pueblo del gato vampiro (Village of the Vampire Cat), Lensey Namioka (2nd time)
- El valle de los cerezos rotos (Valley of the Broken Cherry Trees), Lensey Namioka (2nd time)
- Design Patterns in Ruby, Russ Olsen
- Practical Object-Oriented Design in Ruby, Sandi Metz (2nd time)
- En el café de la juventud perdida (Dans le café de la jeunesse perdue), Patrick Modiano
September
- Your Code as a Crime Scene: Use Forensic Techniques to Arrest Defects, Bottlenecks, and Bad Design in Your Programs, Adam Tornhill
- Cien años de soledad, Gabriel García Márquez
- Eloquent Ruby, Russ Olsen (2nd time)
October
- Understanding Computation. From Simple Machines to Impossible Programs, Tom Stuart
- A Mind For Numbers: How to Excel at Math and Science, Barbara Oakley
November
- The Well-Grounded Rubyist, 2nd edition, David A. Black
- Hackers & Painters: Big Ideas from the Computer Age, Paul Graham
- Effective Programming: More than Writing Code, Jeff Atwood
December
- The Psychology of Computer Programming: Silver Anniversary, Gerald Weinberg
- Cuando el árbol canta (When the tree sings), Stratis Haviaras
- Nineteen Eighty-Four, George Orwell
- Thinking, Fast and Slow, Daniel Kahneman
Monday, December 21, 2015
Interesting Podcast: "Rich Hickey and core.async"
Saturday, December 12, 2015
Interesting Talk: "A Deep Specification for Dropbox"
I've recently watched this great talk by Benjamin Pierce:
Saturday, December 5, 2015
Interesting Talk: "Kekkonen, commands and queries for the web"
I've just watched this interesting talk by Tommi Reiman:
Friday, November 20, 2015
Interesting Talk: "Bottom Up vs Top Down Design in Clojure"
I've just watched this very interesting talk by Mark Bastian:
Thursday, November 19, 2015
Interesting Talk: "Debugging with the Scientific Method"
I've just watched this great talk by Stuart Halloway:
Interesting Podcast: "Rails 4 Test Prescriptions with Noel Rappin"
I've just listened to this great Ruby Rogues with Noel Rappin podcast about TDD and testing:
Wednesday, November 18, 2015
Interesting Talk: "ClojureScript for Skeptics"
I've just watched this wonderful talk by Derek Slager:
Thursday, November 12, 2015
Interesting talk: "Why is a Monad Like a Writing Desk?"
I've just watched this wonderful talk by Carin Meier:
Monday, October 26, 2015
Interesting Talk: "Twenty years of Design Patterns"
I've just watched this great talk by Ralph Johnson:
Monday, October 19, 2015
Monday, October 12, 2015
Sunday, October 11, 2015
Saturday, October 10, 2015
Interesting Talk: "Lambdas and a List of Cats: Test-Driving Lambdas"
I've just watched this wonderful talk by Corey Haines:
Thursday, October 8, 2015
Interesting Talk: "Blending Functional and OO Programming in Ruby"
I've just watched this great talk by Piotr Solnica:
Tuesday, October 6, 2015
Interesting Talk: "Consistency without consensus in production systems"
I've just watched this great talk by Peter Bourgon:
Interesting Talk: "Prismatic's Schema for Server and Client-Side Data Shape Declaration and Validation"
I've just watched this wonderful talk by Aria Haghighi:
Monday, October 5, 2015
Software Craftsmanship Barcelona 2015
Last weekend I attended the Software Craftsmanship Barcelona 2015. This was the first time I participate as an attendee (I had worked in the organization of the first two editions).
It was a great event. I attended very interesting talks and had amazing conversations with many people. Some of them I already knew from the Barcelona, Zaragoza, Bilbao, Canarias and Valencia community or from abroad but many of them I just met there.
If I had to highlight my favorite talk, I'd choose Accesibilidad en el software. ¿Qué es? ¿Por qué es importante? (Software Accessibility. What is it? Why is it important?) by Juanjo
Juanjo is a great developer and an accessibility specialist. He also happens to be blind.
He showed us how he programs with Visual Studio using a screen reader and knowing nearly every keyword shortcut. It was very impressive.
Personally, I found it amazing how the human brain adapts to different situations. He has developed a great memory and is able to mentally "visualize" the code with just hearing it once (at a really fast speed).
He also told us about several applications he'd made in his spare time to improve his and another blind people's daily life.
I also enjoyed very much Vicenç García-Altés' talk about Mutation Testing, Álvaro García's talk Tu carrera como una marathon (Your career as a marathon) and the Yatzy refactoring kata also facilitated by Álvaro.
We (the No Flop Squad) also participated in the SCBCN15.
The afternoon before the SCBCN15 we facilitated a Pre-SCBCN15 coding dojo.
We practiced doing the Business Rules kata but with the constraint of having to develop an internal DSL that make the tests read nearly as the business rules it was testing. The constraint was based on an exercise Xavi Gost
and I did to practice together in one of my last visits to Valencia.
This constraint forces you to stay more time on the problem space before
passing to the solution space.
We received a lot of positive feedback.
On Saturday morning we gave a talk in which we spoke about an ongoing experiment that we've been developing during the last month: Hybrid Persistence.
In this experiment, we are exploring the idea of persisting different parts of the state of an aggregate on different data storage technologies depending on the kind of state. The idea is similar to Polyglot Persistence but it works at a finer grain persisting separately the identity and mutable state of each member of the aggregate. The relationships among members of the aggregate can also be persisted independently.
The experiment is still in an initial phase, so we haven't yet reached to any sound conclusions. In the talk, we explained the ideas behind this exploration, what we have seen so far (including dead-ends, mistakes and successes) and the things we'd like to try next.
On Sunday afternoon there was an Open Space .
A session that I really enjoyed and took a lot of ideas from, was Dinamizando las comunidades locales (Energizing local communities)
in which organizers and members of several communities across Spain, shared our experiments, successes and failures in trying to improve and sustain our communities.
Also while we were selecting the open space grid, someone nominated a session asking me to give a short introduction to Clojure. Surprisingly it received enough votes to be selected so I had to improvise it.
I showed some code examples and used LightTable to write and evaluate expressions on the fly while I was explaining some language features. Álvaro helped me (thanks!) writing on Juanjo's laptop the code I was writing on my laptop and showing on the screen so that he could also follow the talk using his screen reader.
I tried to demystify parentheses and prefix notation, and explain some of the simplest goodies of the language. Even though it wasn't a good talk, I think I managed to at least arise the curiosity of some of the attendees. I also invited all to come to the Clojure Developers Barcelona's Introduction to Clojure event on next October 27th.
There were so many other interesting talks I couldn't see. I can't wait for the videos to be published.
All in all, it was a wonderful event.
I'd like to thank from here all the attendees and the organization for making it possible. I'd also like to thank Netmind for letting us use their facilities for free one more time.
It was a great event. I attended very interesting talks and had amazing conversations with many people. Some of them I already knew from the Barcelona, Zaragoza, Bilbao, Canarias and Valencia community or from abroad but many of them I just met there.
If I had to highlight my favorite talk, I'd choose Accesibilidad en el software. ¿Qué es? ¿Por qué es importante? (Software Accessibility. What is it? Why is it important?) by Juanjo

Juanjo is a great developer and an accessibility specialist. He also happens to be blind.
He showed us how he programs with Visual Studio using a screen reader and knowing nearly every keyword shortcut. It was very impressive.
Personally, I found it amazing how the human brain adapts to different situations. He has developed a great memory and is able to mentally "visualize" the code with just hearing it once (at a really fast speed).
He also told us about several applications he'd made in his spare time to improve his and another blind people's daily life.
I also enjoyed very much Vicenç García-Altés' talk about Mutation Testing, Álvaro García's talk Tu carrera como una marathon (Your career as a marathon) and the Yatzy refactoring kata also facilitated by Álvaro.
We (the No Flop Squad) also participated in the SCBCN15.
The afternoon before the SCBCN15 we facilitated a Pre-SCBCN15 coding dojo.

We received a lot of positive feedback.
On Saturday morning we gave a talk in which we spoke about an ongoing experiment that we've been developing during the last month: Hybrid Persistence.

In this experiment, we are exploring the idea of persisting different parts of the state of an aggregate on different data storage technologies depending on the kind of state. The idea is similar to Polyglot Persistence but it works at a finer grain persisting separately the identity and mutable state of each member of the aggregate. The relationships among members of the aggregate can also be persisted independently.
The experiment is still in an initial phase, so we haven't yet reached to any sound conclusions. In the talk, we explained the ideas behind this exploration, what we have seen so far (including dead-ends, mistakes and successes) and the things we'd like to try next.
On Sunday afternoon there was an Open Space .
A session that I really enjoyed and took a lot of ideas from, was Dinamizando las comunidades locales (Energizing local communities)

Also while we were selecting the open space grid, someone nominated a session asking me to give a short introduction to Clojure. Surprisingly it received enough votes to be selected so I had to improvise it.
I showed some code examples and used LightTable to write and evaluate expressions on the fly while I was explaining some language features. Álvaro helped me (thanks!) writing on Juanjo's laptop the code I was writing on my laptop and showing on the screen so that he could also follow the talk using his screen reader.

There were so many other interesting talks I couldn't see. I can't wait for the videos to be published.
All in all, it was a wonderful event.
I'd like to thank from here all the attendees and the organization for making it possible. I'd also like to thank Netmind for letting us use their facilities for free one more time.
Saturday, October 3, 2015
Kata: Cellular Automata in Clojure
I just did the Cellular Automata kata in Clojure.
These are the tests using Midje:
and this is the resulting code:
where I added a function print-evolution to visualize the evolution on the REPL
As usual I used a mix of TDD and REPL-driven development committing after each green and each refactoring.
I also committed the REPL history.
See all the commits here if you want to follow the process.
You can find all the code on GitHub.
These are the tests using Midje:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns elementary-cellular-automaton.core-test | |
(:use midje.sweet) | |
(:use [elementary-cellular-automaton.core])) | |
(def rule-30 {[1 1 1] 0 | |
[1 1 0] 0 | |
[1 0 1] 0 | |
[1 0 0] 1 | |
[0 1 1] 1 | |
[0 1 0] 1 | |
[0 0 1] 1 | |
[0 0 0] 0}) | |
(facts | |
"An elementary cellular automaton" | |
(fact | |
"evolves for several generations following a given rule | |
and some initial cells" | |
(evolve rule-30 [1] 1) => [[1]] | |
(evolve rule-30 [1] 2) => [[1] [1 1 1]] | |
(evolve rule-30 [1] 3) => [[1] [1 1 1] [1 1 0 0 1]] | |
(evolve | |
rule-30 [1] 4) => [[1] [1 1 1] [1 1 0 0 1] [1 1 0 1 1 1 1]]) | |
(fact | |
"can be rendered as text lines" | |
(render rule-30 [1] 1) => ["x"] | |
(render rule-30 [1] 2) => [" x" "xxx"] | |
(render rule-30 [1] 4) => [" x" " xxx" " xx x" "xx xxxx"])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns elementary-cellular-automaton.core | |
(:require [clojure.string :as string])) | |
(defn- pad [cells] | |
(concat [0 0] cells [0 0])) | |
(defn- cell-neighbors [cells] | |
(partition 3 1 (pad cells))) | |
(defn- next-generation [cells rule] | |
(map rule (cell-neighbors cells))) | |
(defn- spaces [num-generations generation-number] | |
(let [num-spaces (- num-generations (inc generation-number))] | |
(apply str (repeat num-spaces \space)))) | |
(defn- center [num-generations generation-number line] | |
(str (spaces num-generations generation-number) line)) | |
(defn- render-zeros [line] | |
(string/replace line #"0" " ")) | |
(defn- render-ones [line] | |
(string/replace line #"1" "x")) | |
(defn- render-generation | |
[num-generations generation-number generation] | |
(->> generation | |
(apply str) | |
(render-ones) | |
(render-zeros) | |
(center num-generations generation-number))) | |
(defn evolve [rule initial-cells num-generations] | |
(take num-generations | |
(iterate #(next-generation % rule) initial-cells))) | |
(defn render [rule initial-cells num-generations] | |
(->> (evolve rule initial-cells num-generations) | |
(map-indexed #(render-generation num-generations %1 %2)))) | |
(defn print-evolution [rule initial-cells num-generations] | |
(doseq [line (render rule initial-cells num-generations)] | |
(println line))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(def rule-30 {[1 1 1] 0 | |
[1 1 0] 0 | |
[1 0 1] 0 | |
[1 0 0] 1 | |
[0 1 1] 1 | |
[0 1 0] 1 | |
[0 0 1] 1 | |
[0 0 0] 0}) | |
# => #'user/rule-30 | |
(elementary-cellular-automaton.core/print-evolution rule-30 [1] 20) | |
x | |
xxx | |
xx x | |
xx xxxx | |
xx x x | |
xx xxxx xxx | |
xx x x x | |
xx xxxx xxxxxx | |
xx x xxx x | |
xx xxxx xx x xxx | |
xx x x xxxx xx x | |
xx xxxx xx x x xxxx | |
xx x xxx xx xx x x | |
xx xxxx xx xxx xxx xx xxx | |
xx x x xxx x xxx x x | |
xx xxxx xx x x xxxxx xxxxxxx | |
xx x xxx xxxx x xxx x | |
xx xxxx xx xxx xx xx x xxx | |
xx x x xxx x xx xxx xxxx xx x | |
xx xxxx xx x xxxxxx x x xxx xxxx | |
# => nil |
See all the commits here if you want to follow the process.
You can find all the code on GitHub.
Friday, October 2, 2015
Interesting Talk: "One Ring to Bind Them"
I've just watched this wonderful talk by Mark McGranaghan:
It contains very interesting ideas on library design.
These are the slides.
These are the slides.
Revisiting Writing Readable Clojure code
After reading this wonderful post by Adam Bard, Writing Friendlier Clojure, I've revisited the second Clojure talk I attended to.
It was Writing Readable Clojure code by Jiří Knesl.
It impressed me and influenced my Clojure code a lot.
It was Writing Readable Clojure code by Jiří Knesl.
It impressed me and influenced my Clojure code a lot.
Tuesday, September 29, 2015
Interesting Talk: "Deconstructing the Database"
I've just watched this wonderful talk by Rich Hickey:
Monday, September 28, 2015
Kata: Integer Ranges in Clojure
I just did the Integer Ranges kata in Clojure.
These are the tests using Midje:
and this is the resulting code:
As usual I used a mix of TDD and REPL-driven development committing after each green and each refactoring.
I also committed the REPL history.
See all the commits here if you want to follow the process.
You can find all the code on GitHub.
These are the tests using Midje:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns integer-ranges.core-test | |
(:use midje.sweet) | |
(:use [integer-ranges.core])) | |
(facts | |
"about integer-ranges" | |
(fact | |
"it knows which numbers an interval includes" | |
(includes? "[2, 5]" "{2,3,4,5}") => true | |
(includes? "[2, 5]" "{2,-1}") => false | |
(includes? "[2, 5)" "{5}") => false | |
(includes? "(2, 5]" "{2}") => false | |
(includes? "(2, 5)" "{2}") => false | |
(includes? "(2, 5)" "{5}") => false) | |
(fact | |
"it tells all numbers an interval includes" | |
(all-numbers "[2,5]") => [2 3 4 5] | |
(all-numbers "[1,5]") => [1 2 3 4 5] | |
(all-numbers "(1,5]") => [2 3 4 5] | |
(all-numbers "(1,5)") => [2 3 4]) | |
(fact | |
"it knows when an interval contains another interval" | |
(contains-range? "[2,10)" "[2,5]") => true | |
(contains-range? "(2,10]" "[2,5]") => false | |
(contains-range? "[2,4]" "[2,5]") => false | |
(contains-range? "[2,4]" "[2,5)") => true) | |
(fact | |
"it knows an interval's end points" | |
(end-points "[3,8]") => [3 8]) | |
(fact | |
"it knows when two intervals overlap" | |
(overlaps? "[2,10)" "[9,10)") => true | |
(overlaps? "[2,10)" "[1,2)") => false | |
(overlaps? "[2,10)" "[10,12)") => false | |
(overlaps? "[2,10]" "[10,12)") => true | |
(overlaps? "[2,10]" "[3,5)") => true | |
(overlaps? "[3,5)" "[2,10]") => true | |
(overlaps? "[9,10)" "[2,10)") => true | |
(overlaps? "[1,2)" "[2,10)") => false | |
(overlaps? "[1,2)" "[5,10)") => false | |
(overlaps? "[2,10]" "(10,20)") => false | |
(overlaps? "[2,10]" "[10,20)") => true | |
(overlaps? "[2,10)" "[10,20)") => false) | |
(fact | |
"it knows if two intervals are equal or not | |
(being equal means that they include the same numbers)" | |
(equals? "[2,10)" "[9,10)") => false | |
(equals? "[5,8]" "[5,8]") => true | |
(equals? "[5,8]" "[5,9)") => true | |
(equals? "[4,8]" "(3,9)") => true | |
(equals? "(4,8]" "[5,9)") => true)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns integer-ranges.core | |
(:require [clojure.string :as string])) | |
(defn- remove-spaces [s] | |
(string/replace s #" " "")) | |
(defn- remove-brackets [descriptor] | |
(apply str (drop-last (drop 1 descriptor)))) | |
(defn- numbers-descriptors [descriptor] | |
(-> descriptor | |
remove-spaces | |
remove-brackets | |
(string/split #","))) | |
(defn- numbers [numbers-list-descriptor] | |
(map #(Integer/parseInt (str %)) | |
(numbers-descriptors numbers-list-descriptor))) | |
(defn- brackets [descriptor] | |
(let [stripped_descriptor (string/replace descriptor #" " "")] | |
[(first stripped_descriptor) (last stripped_descriptor)])) | |
(defn- closed-open-interval [descriptor] | |
(let [[lower upper] (numbers descriptor) | |
[opening-bracket closing-bracket] (brackets descriptor)] | |
[(if (= opening-bracket \[) lower (inc lower)) | |
(if (= closing-bracket \]) (inc upper) upper)])) | |
(defn- includes-number? [[lower upper] number] | |
(<= lower number (dec upper))) | |
(defn includes? [interval-descriptor numbers-descriptor] | |
(every? | |
#(includes-number? (closed-open-interval interval-descriptor) %) | |
(numbers numbers-descriptor))) | |
(defn all-numbers [descriptor] | |
(apply range (closed-open-interval descriptor))) | |
(defn contains-range? [descriptor other-descriptor] | |
(let [[lower upper] (closed-open-interval descriptor) | |
[other-lower other-upper] (closed-open-interval other-descriptor)] | |
(<= lower other-lower other-upper upper))) | |
(def end-points numbers) | |
(defn overlaps? [descriptor other-descriptor] | |
(let [[lower upper] (closed-open-interval descriptor) | |
[other-lower other-upper] (closed-open-interval other-descriptor)] | |
(and (< lower other-upper) (> upper other-lower)))) | |
(defn equals? [descriptor other-descriptor] | |
(= (closed-open-interval descriptor) | |
(closed-open-interval other-descriptor))) |
See all the commits here if you want to follow the process.
You can find all the code on GitHub.
Saturday, September 26, 2015
Interesting Talk: "Maintaining Balance While Reducing Duplication"
I've just watched this great talk by David Chelimsky:
Thursday, September 24, 2015
Kata: Password validation in Python
Yesterday I did the Password validation kata in Python.
I used TDD to write the code.
These are the tests using Pytest:
and this is the resulting code:
If you want to follow the process step by step, have a look at the commits.
You can find all the code in GitHub.
I used TDD to write the code.
These are the tests using Pytest:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import pytest | |
from password_validator import PasswordValidator | |
@pytest.fixture | |
def validator(): | |
return PasswordValidator(8) | |
def test_a_strong_password(validator): | |
assert validator.is_strong_password("#Ab3cccc") is True | |
def test_that_only_passwords_with_the_minimum_length_are_strong(validator): | |
assert validator.is_strong_password("#Ab3ccc") is False | |
def test_that_only_passwords_including_numbers_are_strong(validator): | |
assert validator.is_strong_password("#Abccccc") is False | |
def test_that_only_passwords_including_upper_case_letters_are_strong(validator): | |
assert validator.is_strong_password("#ab3cccc") is False | |
def test_that_only_passwords_including_lower_case_letters_are_strong(validator): | |
assert validator.is_strong_password("#AB3CCCC") is False | |
def test_that_only_passwords_including_special_characters_are_strong(validator): | |
assert validator.is_strong_password("cAb3cccc") is False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
class PasswordValidator(object): | |
def __init__(self, minimum_length): | |
self.minimum_length = minimum_length | |
def is_strong_password(self, password): | |
includes_special_characters = self._includes_any( | |
self.REQUIRED_SPECIAL_CHARACTERS, password | |
) | |
includes_lower_case_letters = self._includes_any( | |
self.LOWER_CASE_LETTERS, password | |
) | |
includes_upper_case_letters = self._includes_any( | |
self.UPPER_CASE_LETTERS, password | |
) | |
includes_numbers = self._includes_any(self.NUMBERS, password) | |
has_minimum_length = len(password) >= self.minimum_length | |
return \ | |
has_minimum_length and \ | |
includes_numbers and \ | |
includes_upper_case_letters and \ | |
includes_lower_case_letters and \ | |
includes_special_characters | |
@staticmethod | |
def _includes_any(pattern, password): | |
return re.search(pattern, password) is not None | |
REQUIRED_SPECIAL_CHARACTERS = '[%#]' | |
UPPER_CASE_LETTERS = '[A-Z]' | |
LOWER_CASE_LETTERS = '[a-z]' | |
NUMBERS = '[0-9]' |
If you want to follow the process step by step, have a look at the commits.
You can find all the code in GitHub.
Wednesday, September 23, 2015
Kata: Word wrap in Clojure
Last night I did the Word Wrap kata in Clojure.
It was proposed in the last Barcelona Software Craftsmanship coding dojo but I couldn't attend, so I did it at home.
These are the tests using Midje:
and this is the resulting code:
As usual I used a mix of TDD and REPL-driven development committing after each green and each refactoring.
I also committed the REPL history. See all the commits here to follow the process.
Once I got to a tail recursive solution, I tried to make it more readable by extracting some explanatory helpers and working in the naming of bindings, functions and function arguments.
You can find all the code in GitHub.
It was proposed in the last Barcelona Software Craftsmanship coding dojo but I couldn't attend, so I did it at home.
These are the tests using Midje:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns word-wrap.core-test | |
(:use midje.sweet) | |
(:use [word-wrap.core])) | |
(facts | |
"about wrapping words" | |
(fact | |
"a text that fits in the given columns number are not wrapped" | |
(wrap "koko koko" 9) => "koko koko" | |
(wrap "ko" 9) => "ko" | |
(wrap "" 9) => "") | |
(fact | |
"a text without spaces that doesn't fit in the given columns number are wrapped" | |
(wrap "kokokoko" 4) => "koko\nkoko") | |
(fact | |
"a text with spaces that doesn't fit in the given columns number are wrapped | |
at the space that is closest to the maximum column" | |
(wrap "koko koko" 7) => "koko\nkoko" | |
(wrap "koko koko koko" 12) => "koko koko\nkoko" | |
(wrap "koko koko koko koko koko koko" 12) => "koko koko\nkoko koko\nkoko koko" | |
(wrap | |
"This koko should be easy unless there are hidden, or not so hidden, obstacles. Let's start!" | |
12) => "This koko\nshould be\neasy unless\nthere are\nhidden, or\nnot so\nhidden,\nobstacles.\nLet's start!") | |
(fact | |
"a text already splitted in lines gets each of its lines re-wrapped" | |
(wrap | |
"kokokoko\nkaka koko\nkoko koko" | |
6) => "kokoko\nko\nkaka\nkoko\nkoko\nkoko")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns word-wrap.core | |
(:require [clojure.string :as string])) | |
(def ^:private to-trimmed-string | |
(comp string/trim (partial apply str))) | |
(def ^:private rest-of-line | |
(comp to-trimmed-string (partial drop))) | |
(defn- wrap-line-at [index line] | |
(str (to-trimmed-string (take index line)) \newline)) | |
(defn- index-of-last-fitting-space [max-columns line] | |
(.lastIndexOf (take max-columns line) \space)) | |
(def ^:private valid-index? pos?) | |
(defn- compute-wrapping-index [line max-columns] | |
(let [index (index-of-last-fitting-space max-columns line)] | |
(if (valid-index? index) | |
index | |
max-columns))) | |
(defn- fits? [line max-columns] | |
(<= (count line) max-columns)) | |
(defn- line->wrapped-lines [wrapped-lines line max-columns] | |
(if (fits? line max-columns) | |
(conj wrapped-lines line) | |
(let [index (compute-wrapping-index line max-columns)] | |
(recur (conj wrapped-lines (wrap-line-at index line)) | |
(rest-of-line index line) | |
max-columns)))) | |
(defn- wrap-line [line max-columns] | |
(apply str (line->wrapped-lines [] line max-columns))) | |
(defn- extract-lines [text] | |
(string/split text #"\n")) | |
(def ^:private join-lines (partial string/join \newline)) | |
(defn wrap [text max-columns] | |
(->> text | |
extract-lines | |
(map #(wrap-line % max-columns)) | |
join-lines)) |
Once I got to a tail recursive solution, I tried to make it more readable by extracting some explanatory helpers and working in the naming of bindings, functions and function arguments.
You can find all the code in GitHub.
Monday, September 21, 2015
Interesting Podcast: "When to use modules"
I've just listened to this great Ruby Rogues podcast about Ruby modules:
Sunday, September 20, 2015
Kata: Yatzi refactoring kata in Ruby
Last night I did the Yatzy refactoring kata in Ruby.
I mainly used it to practice with Enumerable functions.
This is the original code:
and this is the refactored one:
I committed after every small refactoring (the commits step by step).
You can find the code in this repository in GitHub.
I mainly used it to practice with Enumerable functions.
This is the original code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Yatzy | |
def self.chance(d1, d2, d3, d4, d5) | |
total = 0 | |
total += d1 | |
total += d2 | |
total += d3 | |
total += d4 | |
total += d5 | |
return total | |
end | |
def self.yatzy(dice) | |
counts = [0]*(dice.length+1) | |
for die in dice do | |
counts[die-1] += 1 | |
end | |
for i in 0..counts.size do | |
if counts[i] == 5 | |
return 50 | |
end | |
end | |
return 0 | |
end | |
def self.ones( d1, d2, d3, d4, d5) | |
sum = 0 | |
if (d1 == 1) | |
sum += 1 | |
end | |
if (d2 == 1) | |
sum += 1 | |
end | |
if (d3 == 1) | |
sum += 1 | |
end | |
if (d4 == 1) | |
sum += 1 | |
end | |
if (d5 == 1) | |
sum += 1 | |
end | |
sum | |
end | |
def self.twos( d1, d2, d3, d4, d5) | |
sum = 0 | |
if (d1 == 2) | |
sum += 2 | |
end | |
if (d2 == 2) | |
sum += 2 | |
end | |
if (d3 == 2) | |
sum += 2 | |
end | |
if (d4 == 2) | |
sum += 2 | |
end | |
if (d5 == 2) | |
sum += 2 | |
end | |
return sum | |
end | |
def self.threes( d1, d2, d3, d4, d5) | |
s = 0 | |
if (d1 == 3) | |
s += 3 | |
end | |
if (d2 == 3) | |
s += 3 | |
end | |
if (d3 == 3) | |
s += 3 | |
end | |
if (d4 == 3) | |
s += 3 | |
end | |
if (d5 == 3) | |
s += 3 | |
end | |
return s | |
end | |
def initialize(d1, d2, d3, d4, _5) | |
@dice = [0]*5 | |
@dice[0] = d1 | |
@dice[1] = d2 | |
@dice[2] = d3 | |
@dice[3] = d4 | |
@dice[4] = _5 | |
end | |
def fours | |
sum = 0 | |
for at in Array 0..4 | |
if (@dice[at] == 4) | |
sum += 4 | |
end | |
end | |
return sum | |
end | |
def fives() | |
s = 0 | |
i = 0 | |
for i in (Range.new(0, @dice.size)) | |
if (@dice[i] == 5) | |
s = s + 5 | |
end | |
end | |
s | |
end | |
def sixes | |
sum = 0 | |
for at in 0..@dice.length | |
if (@dice[at] == 6) | |
sum = sum + 6 | |
end | |
end | |
return sum | |
end | |
def self.score_pair( d1, d2, d3, d4, d5) | |
counts = [0]*6 | |
counts[d1-1] += 1 | |
counts[d2-1] += 1 | |
counts[d3-1] += 1 | |
counts[d4-1] += 1 | |
counts[d5-1] += 1 | |
at = 0 | |
(0...6).each do |at| | |
if (counts[6-at-1] >= 2) | |
return (6-at)*2 | |
end | |
end | |
return 0 | |
end | |
def self.two_pair( d1, d2, d3, d4, d5) | |
counts = [0]*6 | |
counts[d1-1] += 1 | |
counts[d2-1] += 1 | |
counts[d3-1] += 1 | |
counts[d4-1] += 1 | |
counts[d5-1] += 1 | |
n = 0 | |
score = 0 | |
for i in Array 0..5 | |
if (counts[6-i-1] >= 2) | |
n = n+1 | |
score += (6-i) | |
end | |
end | |
if (n == 2) | |
return score * 2 | |
else | |
return 0 | |
end | |
end | |
def self.four_of_a_kind( _1, _2, d3, d4, d5) | |
tallies = [0]*6 | |
tallies[_1-1] += 1 | |
tallies[_2-1] += 1 | |
tallies[d3-1] += 1 | |
tallies[d4-1] += 1 | |
tallies[d5-1] += 1 | |
for i in (0..6) | |
if (tallies[i] >= 4) | |
return (i+1) * 4 | |
end | |
end | |
return 0 | |
end | |
def self.three_of_a_kind( d1, d2, d3, d4, d5) | |
t = [0]*6 | |
t[d1-1] += 1 | |
t[d2-1] += 1 | |
t[d3-1] += 1 | |
t[d4-1] += 1 | |
t[d5-1] += 1 | |
for i in [0,1,2,3,4,5] | |
if (t[i] >= 3) | |
return (i+1) * 3 | |
end | |
end | |
0 | |
end | |
def self.smallStraight( d1, d2, d3, d4, d5) | |
tallies = [0]*6 | |
tallies[d1-1] += 1 | |
tallies[d2-1] += 1 | |
tallies[d3-1] += 1 | |
tallies[d4-1] += 1 | |
tallies[d5-1] += 1 | |
(tallies[0] == 1 and | |
tallies[1] == 1 and | |
tallies[2] == 1 and | |
tallies[3] == 1 and | |
tallies[4] == 1) ? 15 : 0 | |
end | |
def self.largeStraight( d1, d2, d3, d4, d5) | |
tallies = [0]*6 | |
tallies[d1-1] += 1 | |
tallies[d2-1] += 1 | |
tallies[d3-1] += 1 | |
tallies[d4-1] += 1 | |
tallies[d5-1] += 1 | |
if (tallies[1] == 1 and tallies[2] == 1 and tallies[3] == 1 and tallies[4] == 1 and tallies[5] == 1) | |
return 20 | |
end | |
return 0 | |
end | |
def self.fullHouse( d1, d2, d3, d4, d5) | |
tallies = [] | |
_2 = false | |
i = 0 | |
_2_at = 0 | |
_3 = false | |
_3_at = 0 | |
tallies = [0]*6 | |
tallies[d1-1] += 1 | |
tallies[d2-1] += 1 | |
tallies[d3-1] += 1 | |
tallies[d4-1] += 1 | |
tallies[d5-1] += 1 | |
for i in Array 0..5 | |
if (tallies[i] == 2) | |
_2 = true | |
_2_at = i+1 | |
end | |
end | |
for i in Array 0..5 | |
if (tallies[i] == 3) | |
_3 = true | |
_3_at = i+1 | |
end | |
end | |
if (_2 and _3) | |
return _2_at * 2 + _3_at * 3 | |
else | |
return 0 | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Yatzy | |
def self.chance *dies | |
dies.reduce(:+) | |
end | |
def self.yatzy *dies | |
return 50 if all_equal?(dies) | |
return 0 | |
end | |
def self.ones *dies | |
compute_score(dies, 1) | |
end | |
def self.twos *dies | |
compute_score(dies, 2) | |
end | |
def self.threes *dies | |
compute_score(dies, 3) | |
end | |
def self.fours *dies | |
compute_score(dies, 4) | |
end | |
def self.fives *dies | |
compute_score(dies, 5) | |
end | |
def self.sixes *dies | |
compute_score(dies, 6) | |
end | |
def self.one_pair *dies | |
compute_group_of_a_kind_score(dies, 2) | |
end | |
def self.two_pairs *dies | |
pairs = extract_groups_with_equal_or_larger_size(dies, 2) | |
pairs.keys.reduce(:+) * 2 | |
end | |
def self.four_of_a_kind *dies | |
compute_group_of_a_kind_score(dies, 4) | |
end | |
def self.three_of_a_kind *dies | |
compute_group_of_a_kind_score(dies, 3) | |
end | |
def self.small_straight *dies | |
return 15 if small_straight?(dies) | |
return 0 | |
end | |
def self.large_straight *dies | |
return 20 if large_straight?(dies) | |
return 0 | |
end | |
def self.full_house *dies | |
return 0 unless full_house?(dies) | |
compute_full_house_score(dies) | |
end | |
private | |
def self.compute_score dies, die_value | |
dies_with_value = dies.select {|die| die == die_value} | |
die_value * dies_with_value.size | |
end | |
def self.compute_frequencies dies | |
dies.inject({}) do |frequencies_so_far, die| | |
if frequencies_so_far.include?(die) | |
frequencies_so_far[die] += 1 | |
else | |
frequencies_so_far.merge!({die => 1}) | |
end | |
frequencies_so_far | |
end | |
end | |
def self.extract_groups_with_equal_or_larger_size dies, size | |
extract_groups( | |
dies, | |
lambda { |frequency| frequency >= size } | |
) | |
end | |
def self.compute_group_of_a_kind_score dies, group_size | |
group = extract_groups_with_equal_or_larger_size(dies, group_size) | |
compute_group_score(group, group_size) | |
end | |
def self.compute_group_score group, group_size | |
(group.keys.max || 0) * group_size | |
end | |
def self.small_straight? dies | |
frequencies = compute_frequencies(dies) | |
frequencies.all? do |die, frequency| | |
frequency == 1 && die != 6 | |
end | |
end | |
def self.large_straight? dies | |
frequencies = compute_frequencies(dies) | |
frequencies.all? do |die, frequency| | |
frequency == 1 && die != 1 | |
end | |
end | |
def self.all_equal? dies | |
dies.uniq.size == 1 | |
end | |
def self.compute_full_house_score dies | |
pairs_score = compute_full_house_group(dies, 2) | |
triplets_score = compute_full_house_group(dies, 3) | |
pairs_score + triplets_score | |
end | |
def self.full_house? dies | |
triplets = extract_groups_with_equal_size(dies, 3) | |
pairs = extract_groups_with_equal_size(dies, 2) | |
only_one_pair = pairs.size == 1 | |
only_one_triplet = triplets.size == 1 | |
only_one_pair && only_one_triplet | |
end | |
def self.extract_groups_with_equal_size dies, size | |
extract_groups( | |
dies, | |
lambda { |frequency| frequency == size } | |
) | |
end | |
def self.compute_full_house_group dies, group_size | |
group = extract_groups_with_equal_size(dies, group_size) | |
compute_group_score(group, group_size) | |
end | |
def self.extract_groups dies, predicate | |
compute_frequencies(dies).select do |_, frequency| | |
predicate.call(frequency) | |
end | |
end | |
end |
You can find the code in this repository in GitHub.
Friday, September 18, 2015
Friday, September 11, 2015
Last Pet Project: Glowworm Swarm Optimization in Clojure
Lately I've been working bit by bit in an implementation of the Glowworm Swarm Optimization algorithm (GSO) in Clojure.
Some time ago I implemented the GSO algorithm in C++ to practice TDD.
I decided to implement it it again in Clojure to practice with something larger that Exercism exercises or katas an yet small and familiar enough to finish it in spare bits of time.
This is the code of the resulting Clojure GSO on GitHub.
This GSO Clojure version is much shorter than its C++ version.
Aside from practicing Clojure, I've learned many other things while doing it.
I'll try to enumerate them here:
I'd like to thank Álvaro García, Natxo Cabré and Francesc Guillén from the Clojure Developers Barcelona meetup for their feedback and Brian Jiménez for rubbing elbows with me in the first C++ version.
Some time ago I implemented the GSO algorithm in C++ to practice TDD.
I decided to implement it it again in Clojure to practice with something larger that Exercism exercises or katas an yet small and familiar enough to finish it in spare bits of time.
This is the code of the resulting Clojure GSO on GitHub.
This GSO Clojure version is much shorter than its C++ version.
Aside from practicing Clojure, I've learned many other things while doing it.
I'll try to enumerate them here:
- Mixing Java and Clojure code in order to use a Sean Luke's Mersenne Twister Java implementation.
- I learned more about Midje's checkers.
- dire library.
- Decomposing a bigger Clojure application in several name spaces according to roles and responsibilities.
- Using maps to model the data.
- Struggling to make the code more readable and keeping functions at the same level of abstraction.
- Using only higher-order functions to compose the algorithm (I set myself the constraint to use no global configuration maps).
- Taking advantage of those higher-order functions to test the different parts of the algorithm in isolation.
- Separating the code that builds the graph of functions that compose the algorithm from the algorithm itself.
- Where would I apply a library like component instead of relying only n higher-order functions.
- Many other little Clojure things.
I'd like to thank Álvaro García, Natxo Cabré and Francesc Guillén from the Clojure Developers Barcelona meetup for their feedback and Brian Jiménez for rubbing elbows with me in the first C++ version.
Wednesday, September 9, 2015
Kata: "Sieve of Eratosthenes" test-driven and explained step by step
Yesterday we practiced doing the Sieve of Eratosthenes kata at a Barcelona Software Craftsmanship event.
My partner Fernando Mora and I used TDD in Scala to write the code.
Today I did it once again in Clojure.
I'd like to explain here how I did it step by step in order to share it with the Barcelona Software Craftsmanship members.
I started by writing this test:
which I quickly got to green by just hard-coding the response:
In this first test I used wishful thinking to get the function and signature I wished.
Then I wrote the following test:
which drove me to generalize the code substituting the hard-coded list by a code that generated a list that was valid for both the first and the second test:
The next test was the first one that drove me to start eliminating multiples of a number, in this case the multiples of 2:
I made it quickly pass by using filter to only keep those that are not multiples of 2 and 2 itself:
Alternatively, I could have taken a smaller step by just eliminating 4, then go on triangulating to also eliminate 6 and finally refactor out the duplication by eliminating all the multiples of 2 that are different from 2.
Since the implementation was obvious and I could rely on filter, I decided not to follow that path and took a larger step.
I've noticed that my TDD baby steps tend to be larger in functional languages. I think the reason is, on one hand, the REPL which complements TDD by providing a faster feedback loop for triangulation and trying things out and, on the other hand, the power of sequence functions in those languages (in the case of this kata the Scala and Clojure ones).
Once the test was passing I started to refactor that ugly one-liner and got to this more readable version:
in which I extracted two helpers and used remove instead of filter to better express the idea of sieving.
My next goal was to write a test to drive me to generalize the code a bit more by eliminating the hard-coded number 2 in line 10 of the code.
To do it I just needed a test that forced the code to also eliminate just the multiples of 3:
Again I could have triangulated to first eliminate 9 and then 12 before refactoring but there was an easier way at this point: to introduce recursion.
But before doing that, I quickly went to green by calling the sieve function twice to eliminate the multiples of 2 and 3:
With this tiny step I both highlighted the recursive pattern and provided a safe place from which to start using refactoring to introduce recursion.
In this case I think that having tried to directly introduce recursion to make the test pass would have been too large a step to take, so I played safe.
Once in green again I safely refactored the code to introduce recursion:
Notice that this version of the code is the first one that solves the kata.
From this point on I just refactored the code trying to make it a bit more readable until I got to this version:
Finally, I wrote a more thorough test that made the stepping-stone tests that helped me drive the solution redundant, so I deleted them:
If you want to have a closer look at the process I followed, please check the commits list where I've also included the REPL history. You can also see all the code in this GitHub repository.
That's all.
I'd like to thank Barcelona Software Craftsmanship members for practicing together every two weeks, especially Álvaro García for facilitating this last kata, and eBay España for kindly having us yesterday (and on many previous events) in their Barcelona office.
My partner Fernando Mora and I used TDD in Scala to write the code.
Today I did it once again in Clojure.
I'd like to explain here how I did it step by step in order to share it with the Barcelona Software Craftsmanship members.
I started by writing this test:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 2) => [2])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn primes-up-to [n] | |
[2]) |
Then I wrote the following test:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 2) => [2] | |
(primes-up-to 3) => [2 3])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn primes-up-to [n] | |
(range 2 (inc n))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 2) => [2] | |
(primes-up-to 3) => [2 3] | |
(primes-up-to 5) => [2 3 5])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn primes-up-to [n] | |
(filter #(or (not= 0 (mod % 2)) (= % 2)) | |
(range 2 (inc n)))) |
Since the implementation was obvious and I could rely on filter, I decided not to follow that path and took a larger step.
I've noticed that my TDD baby steps tend to be larger in functional languages. I think the reason is, on one hand, the REPL which complements TDD by providing a faster feedback loop for triangulation and trying things out and, on the other hand, the power of sequence functions in those languages (in the case of this kata the Scala and Clojure ones).
Once the test was passing I started to refactor that ugly one-liner and got to this more readable version:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn- integers-up-to [n] | |
(range 2 (inc n))) | |
(defn- multiple-of? [n p] | |
(and (zero? (mod n p)) (not= n p))) | |
(defn primes-up-to [n] | |
(remove #(multiple-of? % 2) (integers-up-to n))) |
My next goal was to write a test to drive me to generalize the code a bit more by eliminating the hard-coded number 2 in line 10 of the code.
To do it I just needed a test that forced the code to also eliminate just the multiples of 3:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 2) => [2] | |
(primes-up-to 3) => [2 3] | |
(primes-up-to 5) => [2 3 5] | |
(primes-up-to 11) => [2 3 5 7 11])) |
But before doing that, I quickly went to green by calling the sieve function twice to eliminate the multiples of 2 and 3:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn- integers-up-to [n] | |
(range 2 (inc n))) | |
(defn- multiple-of? [n p] | |
(and (zero? (mod n p)) (not= n p))) | |
(defn- sieve [numbers prime] | |
(remove #(multiple-of? % prime) numbers)) | |
(defn primes-up-to [n] | |
(sieve (sieve (integers-up-to n) 2) 3)) |
In this case I think that having tried to directly introduce recursion to make the test pass would have been too large a step to take, so I played safe.
Once in green again I safely refactored the code to introduce recursion:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn- integers-up-to [n] | |
(range 2 (inc n))) | |
(defn- multiple-of? [n p] | |
(and (zero? (mod n p)) (not= n p))) | |
(defn- sieve [numbers prime] | |
(let [sieved (remove #(multiple-of? % prime) numbers) | |
next-prime (first (drop-while #(<= % prime) sieved))] | |
(if (nil? next-prime) | |
sieved | |
(recur sieved next-prime)))) | |
(defn primes-up-to [n] | |
(sieve (integers-up-to n) 2)) |
From this point on I just refactored the code trying to make it a bit more readable until I got to this version:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core) | |
(defn- integers-up-to [n] | |
(range 2 (inc n))) | |
(defn- multiple-of? [n p] | |
(and (zero? (mod n p)) (not= n p))) | |
(defn- next-prime [prime numbers] | |
(first (drop-while #(<= % prime) numbers))) | |
(defn- sieve-multiples-of [prime numbers] | |
(remove #(multiple-of? % prime) numbers)) | |
(defn- sieve [numbers] | |
(loop [primes numbers prime 2] | |
(let [sieved (sieve-multiples-of prime primes)] | |
(if-let [prime (next-prime prime sieved)] | |
(recur sieved prime) | |
primes)))) | |
(defn primes-up-to [n] | |
(sieve (integers-up-to n))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns eratosthenes-sieve.core-test | |
(:use midje.sweet) | |
(:use [eratosthenes-sieve.core])) | |
(facts | |
"about Eratosthenes sieve" | |
(fact | |
"it returns all the primes up to a given number" | |
(primes-up-to 100) => [2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97])) |
That's all.
I'd like to thank Barcelona Software Craftsmanship members for practicing together every two weeks, especially Álvaro García for facilitating this last kata, and eBay España for kindly having us yesterday (and on many previous events) in their Barcelona office.
Saturday, September 5, 2015
Interesting Podcast: "Object Oriented Programming in Rails with Jim Weirich"
I've just listened to this great Ruby Rogues podcast with Jim Weirich talking about Object Oriented Programming in Rails:
Interesting Talk: "Writing Testable JavaScript"
I've just watched this great talk by Rebecca Murphey:
These are the slides.
There is also a great article about the techniques and the example shown in the talk.
And here is the final version of the example code.
These are the slides.
There is also a great article about the techniques and the example shown in the talk.
And here is the final version of the example code.
Monday, August 31, 2015
Interesting Podcast: "Living Clojure, ClojureScript, and more with Carin Meier"
I've just listened to this very interesting ChangeLog podcast in which they interview Carin Meier:
Books I read (January - August 2015)
January
- Software Architecture for Developers, Simon Brown
- Functional Programming Patterns in Scala and Clojure, Michael Bevilacqua-Linn
- Working Effectively with Unit Tests, Jay Fields
February
- Vida y Destino (Жизнь и судьба), Vasili Grossman. (2nd time)
- Primer Libro de Lankhmar (The First Book of Lankhmar), Fritz Leiber
- Drown, Junot Díaz
- Los girasoles ciegos, Alberto Méndez
- Smalltalk Best Practice Patterns, Kent Beck
March
- Las puertas del paraiso (The Vagrants), Yiyun Li
- Growing Object-Oriented Software Guided by Tests, Steve Freeman and Nat Pryce. (2nd time)
- The Joy of Clojure, 2nd edition, Michael Fogus and Chris Houser
April
- Las ciudades carnales (Les cités charnelles), Zoé Oldenbourg
- Refactoring: Improving the Design of Existing Code, Fowler, Beck, Brant, Opdyke and Roberts. (2nd time)
- Hasta aquí hemos llegado (Τίτλοι τέλους), Petros Márkaris
- The Childhood of Jesus, J. M. Coetzee
May
- Refactoring to Patterns, Joshua Kerievsky
- You Don't Know JS: Scope & Closures, Kyle Simpson
- Ser, Ignacio Terzano
- Slow Man, J. M. Coetzee
- Número Cero (Numero Zero), Umberto Eco
- La vieja sirena, José Luis Sampedro
- The Death of Bunny Munro, Nick Cave
June
- Martha and Hanwell, Zadie Smith
- You Don't Know JS: this & Object Prototypes, Kyle Simpson
- Functional Programming for the Object-Oriented Programmer, Brian Marick
July
- Object Thinking, David West
- The RSpec Book: Behaviour Driven Development with RSpec, Cucumber, and Friends. Chelimsky, Astels, Helmkamp, North, Dennis and Hellesoy
- The Nature of Software Development: Keep It Simple, Make It Valuable, Build It Piece by Piece, Ron Jeffries
- Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans
- Momo, Michael Ende
August
- Los Surcos del Azar, Paco Roca
- En el pueblo del gato vampiro (Village of the Vampire Cat), Lensey Namioka (2nd time)
- El valle de los cerezos rotos (Valley of the Broken Cherry Trees), Lensey Namioka (2nd time)
- Design Patterns in Ruby, Russ Olsen
- Practical Object-Oriented Design in Ruby, Sandi Metz (2nd time)
- En el café de la juventud perdida (Dans le café de la jeunesse perdue), Patrick Modiano
- Software Architecture for Developers, Simon Brown
- Functional Programming Patterns in Scala and Clojure, Michael Bevilacqua-Linn
- Working Effectively with Unit Tests, Jay Fields
February
- Vida y Destino (Жизнь и судьба), Vasili Grossman. (2nd time)
- Primer Libro de Lankhmar (The First Book of Lankhmar), Fritz Leiber
- Drown, Junot Díaz
- Los girasoles ciegos, Alberto Méndez
- Smalltalk Best Practice Patterns, Kent Beck
March
- Las puertas del paraiso (The Vagrants), Yiyun Li
- Growing Object-Oriented Software Guided by Tests, Steve Freeman and Nat Pryce. (2nd time)
- The Joy of Clojure, 2nd edition, Michael Fogus and Chris Houser
April
- Las ciudades carnales (Les cités charnelles), Zoé Oldenbourg
- Refactoring: Improving the Design of Existing Code, Fowler, Beck, Brant, Opdyke and Roberts. (2nd time)
- Hasta aquí hemos llegado (Τίτλοι τέλους), Petros Márkaris
- The Childhood of Jesus, J. M. Coetzee
May
- Refactoring to Patterns, Joshua Kerievsky
- You Don't Know JS: Scope & Closures, Kyle Simpson
- Ser, Ignacio Terzano
- Slow Man, J. M. Coetzee
- Número Cero (Numero Zero), Umberto Eco
- La vieja sirena, José Luis Sampedro
- The Death of Bunny Munro, Nick Cave
June
- Martha and Hanwell, Zadie Smith
- You Don't Know JS: this & Object Prototypes, Kyle Simpson
- Functional Programming for the Object-Oriented Programmer, Brian Marick
July
- Object Thinking, David West
- The RSpec Book: Behaviour Driven Development with RSpec, Cucumber, and Friends. Chelimsky, Astels, Helmkamp, North, Dennis and Hellesoy
- The Nature of Software Development: Keep It Simple, Make It Valuable, Build It Piece by Piece, Ron Jeffries
- Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans
- Momo, Michael Ende
August
- Los Surcos del Azar, Paco Roca
- En el pueblo del gato vampiro (Village of the Vampire Cat), Lensey Namioka (2nd time)
- El valle de los cerezos rotos (Valley of the Broken Cherry Trees), Lensey Namioka (2nd time)
- Design Patterns in Ruby, Russ Olsen
- Practical Object-Oriented Design in Ruby, Sandi Metz (2nd time)
- En el café de la juventud perdida (Dans le café de la jeunesse perdue), Patrick Modiano
Saturday, August 29, 2015
Interesting Panel: "Understanding Coupling and Cohesion" revisited
I've just re-watched this interesting discussion about Coupling and Cohesion:
Friday, August 28, 2015
Interesting Podcast: "Refactoring Ruby with Martin Fowler"
I've just listened this great Ruby Rogues podcast with Martin Fowler talking about his Refactoring books (both Java and Ruby editions):
Thursday, August 27, 2015
Interesting Talk: "Introduction to Logic Programming with Clojure"
I've just watched this very interesting talk by Ambrose Bonnaire-Sergeant:
Wednesday, August 26, 2015
Solving the Tire Pressure Monitoring System exercise (IV)
8. Achieving Dependency Inversion by extracting an interface.
Even though we are already injecting into Alarm the dependency on Sensor, we haven't inverted the dependency yet. Alarm still depends on a concrete implementation.Now we'll show how to invert the dependency by extracting an interface.
Normally, it's better to defer this refactoring until you have more information, i.e., until the need for another sensor types arises, to avoid falling in the Speculative Generality code smell.
However, despite having only one type of sensor, we extract the interface anyway, as a demonstration of the refactoring technique.
So first, we rename the method that is being called on Sensor from Alarm, to make it less related with the concrete implementation of Sensor.
Then, following Kent Beck's guidelines in his Implementation Patterns book to name interface and classes, we renamed the Sensor class to TelemetryPressureSensor.
This renaming, on one hand, frees "Sensor" name so that we can use it to name the interface and, on the other hand, gives a more accurate name to the concrete implementation.
Then we extract the interface which is very easy relying on an IDE such as Eclipse or IntelliJ IDEA.
This is the generated interface:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
public interface Sensor { | |
double probe(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
public class Alarm { | |
private final SafetyRange safetyRange; | |
private Sensor sensor; | |
private boolean alarmOn; | |
public Alarm(Sensor sensor, SafetyRange safetyRange) { | |
this.sensor = sensor; | |
this.safetyRange = safetyRange; | |
this.alarmOn = false; | |
} | |
public void check() { | |
double value = sensor.probe(); | |
if (isNotWithinSafetyRange(value)) { | |
alarmOn = true; | |
} | |
} | |
protected boolean isNotWithinSafetyRange(double value) { | |
return safetyRange.isNotWithin(value); | |
} | |
public boolean isAlarmOn() { | |
return alarmOn; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.Before; | |
import org.junit.Test; | |
import tddmicroexercises.tirepressuremonitoringsystem.Alarm; | |
import tddmicroexercises.tirepressuremonitoringsystem.SafetyRange; | |
import tddmicroexercises.tirepressuremonitoringsystem.Sensor; | |
import static org.hamcrest.MatcherAssert.assertThat; | |
import static org.hamcrest.Matchers.is; | |
import static org.mockito.Mockito.doReturn; | |
import static org.mockito.Mockito.mock; | |
public class AlarmShould { | |
private SafetyRange safetyRange; | |
@Before | |
public void setUp() throws Exception { | |
safetyRange = new SafetyRange(17, 21); | |
} | |
@Test | |
public void be_on_when_probed_value_is_too_low() { | |
Alarm alarm = new Alarm(sensorThatProbes(5.0), safetyRange); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_on_when_probed_value_is_too_high() { | |
Alarm alarm = new Alarm(sensorThatProbes(25.0), safetyRange); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_off_when_probed_value_is_within_safety_range() { | |
Alarm alarm = new Alarm(sensorThatProbes(20.0), safetyRange); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(false)); | |
} | |
protected Sensor sensorThatProbes(double value) { | |
Sensor sensor = mock(Sensor.class); | |
doReturn(value).when(sensor).probe(); | |
return sensor; | |
} | |
} |
We achieved context independence by inverting the dependency which makes Alarm depend on an abstraction (Sensor) instead of a concrete type (TelemetryPressureSensor) and also by moving the specificity of the SafetyRange configuration details towards its clients.
By programming to an interface we got to a loosely coupled design which now respects both DIP and OCP from SOLID.
In a dynamic language, we wouldn't have needed to extract an interface, thanks to duck typing. In that case Sensor would be a duck type and any object responding to the probe method would behave like a sensor. What we would have needed to do anyway, is the process of renaming the method called on sensor and eliminating any references to pressure from the names used inside Alarm, so that, in the end we have names that make sense for any type of sensor.
9. Using a builder for the Alarm.
Finally to make the Alarm tests a bit more readable we create a builder for the Alarm class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.Test; | |
import tddmicroexercises.tirepressuremonitoringsystem.Alarm; | |
import tddmicroexercises.tirepressuremonitoringsystem.Sensor; | |
import static helpers.AlarmBuilder.anAlarm; | |
import static org.hamcrest.MatcherAssert.assertThat; | |
import static org.hamcrest.Matchers.is; | |
import static org.mockito.Mockito.doReturn; | |
import static org.mockito.Mockito.mock; | |
public class AlarmShould { | |
private Alarm alarm; | |
@Test | |
public void be_on_when_probed_value_is_too_low() { | |
alarm = anAlarm(). | |
usingSensor(thatProbes(5.0)). | |
andWithSafetyRange(5.5, 21). | |
build(); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_on_when_probed_value_is_too_high() { | |
alarm = anAlarm(). | |
usingSensor(thatProbes(25.0)). | |
andWithSafetyRange(17.0, 24.5). | |
build(); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_off_when_probed_value_is_within_safety_range() { | |
alarm = anAlarm(). | |
usingSensor(thatProbes(20.0)). | |
andWithSafetyRange(19.5, 20.3). | |
build(); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(false)); | |
} | |
protected Sensor thatProbes(double value) { | |
Sensor sensor = mock(Sensor.class); | |
doReturn(value).when(sensor).probe(); | |
return sensor; | |
} | |
} |
If you want to follow the whole refactoring process in more detail, I committed the code after every passing test and every refactoring. You can find the step by step commits here. The current version of the code is in this GitHub repo.
I hope this would be useful for the people who couldn't attend to the events (the SCBCN one or the Gran Canaria Ágil one)and also as a remainder for the people who could.
I'd also like to thank Luca Minudel for creating and sharing these great exercises and Álvaro García for pairing regularly with me and solved this exercise together.
This is the last post in a series of posts about solving the Tire Pressure Monitoring System exercise in Java:
- Solving the Tire Pressure Monitoring System exercise (I)
- Solving the Tire Pressure Monitoring System exercise (II)
- Solving the Tire Pressure Monitoring System exercise (III)
- Solving the Tire Pressure Monitoring System exercise (IV)
Solving the Tire Pressure Monitoring System exercise (III)
6. Improving the semantics inside Alarm and adding a new concept to enrich the domain.
Now we turn our attention to the code inside the Alarm class.We first rename a local variable inside the check method and the method we are calling on Sensor so that we have new names that have less to do with the implementation of Sensor.
Next, we extract the condition inside the check method to an explanatory helper: isNotWithinSafetyRange.
This is the resulting code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
public class Alarm { | |
private final double LowPressureThreshold = 17; | |
private final double HighPressureThreshold = 21; | |
private Sensor sensor; | |
private boolean alarmOn; | |
public Alarm(Sensor sensor) { | |
this.sensor = sensor; | |
this.alarmOn = false; | |
} | |
public void check() { | |
double pressureValue = sensor.probePressureValue(); | |
if (isNotWithinSafetyRange(pressureValue)) { | |
alarmOn = true; | |
} | |
} | |
protected boolean isNotWithinSafetyRange(double value) { | |
return return value < LowPressureThreshold || HighPressureThreshold < value; | |
} | |
public boolean isAlarmOn() { | |
return alarmOn; | |
} | |
} |
The constants LowPressureThreshold and HighPressureThreshold don't make any sense the one without the other. They together define a range, to which we have already referred both in production and test code as a safety range.
We remove the data clump by creating a new concept, the SafetyRange value object:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
public class Alarm { | |
private final double LowPressureThreshold = 17; | |
private final double HighPressureThreshold = 21; | |
private final SafetyRange safetyRange; | |
private Sensor sensor; | |
private boolean alarmOn; | |
public Alarm(Sensor sensor) { | |
this.sensor = sensor; | |
this.alarmOn = false; | |
this.safetyRange = new SafetyRange(LowPressureThreshold, HighPressureThreshold); | |
} | |
public void check() { | |
double pressureValue = sensor.probePressureValue(); | |
if (isNotWithinSafetyRange(pressureValue)) { | |
alarmOn = true; | |
} | |
} | |
protected boolean isNotWithinSafetyRange(double pressureValue) { | |
return safetyRange.isNotWithin(pressureValue); | |
} | |
public boolean isAlarmOn() { | |
return alarmOn; | |
} | |
private class SafetyRange { | |
private final double lowerThreshold; | |
private final double higherThreshold; | |
public SafetyRange(double lowerThreshold, double higherThreshold) { | |
this.lowerThreshold = lowerThreshold; | |
this.higherThreshold = higherThreshold; | |
} | |
public boolean isNotWithin(double value) { | |
return value < lowerThreshold || higherThreshold < value; | |
} | |
} | |
} |
7. Moving Specificity Towards the Tests.
If you check the tests in AlarmShould class, you'll see that it's difficult to understand the tests at a glance.Why is the alarm on in some cases and off in some other cases?
To understand why, we have to check Alarm's constructor in which a SafetyRange object is created. This SafetyRange is an implicit configuration of Alarm.
We can make the code clearer and more reusable by moving this configuration details towards the tests.
J. B. Rainsberger explains this concept of moving specificity towards the tests in this video which is embedded in his Demystifying the Dependency Inversion Principle post.
So we change the signature of the Alarm constructor so that the SafetyRange is injected through it.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
public class Alarm { | |
private final SafetyRange safetyRange; | |
private Sensor sensor; | |
private boolean alarmOn; | |
public Alarm(Sensor sensor, SafetyRange safetyRange) { | |
this.sensor = sensor; | |
this.safetyRange = safetyRange; | |
this.alarmOn = false; | |
} | |
public void check() { | |
double pressureValue = sensor.probePressureValue(); | |
if (isNotWithinSafetyRange(pressureValue)) { | |
alarmOn = true; | |
} | |
} | |
protected boolean isNotWithinSafetyRange(double pressureValue) { | |
return safetyRange.isNotWithin(pressureValue); | |
} | |
public boolean isAlarmOn() { | |
return alarmOn; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.Test; | |
import tddmicroexercises.tirepressuremonitoringsystem.Alarm; | |
import tddmicroexercises.tirepressuremonitoringsystem.SafetyRange; | |
import tddmicroexercises.tirepressuremonitoringsystem.Sensor; | |
import static org.hamcrest.MatcherAssert.assertThat; | |
import static org.hamcrest.Matchers.is; | |
import static org.mockito.Mockito.doReturn; | |
import static org.mockito.Mockito.mock; | |
public class AlarmShould { | |
private SafetyRange safetyRange; | |
@Before | |
public void setUp() throws Exception { | |
safetyRange = new SafetyRange(17, 21); | |
} | |
@Test | |
public void be_on_when_pressure_value_is_too_low() { | |
Alarm alarm = new Alarm( | |
sensorThatProbes(5.0), safetyRange | |
); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_on_when_pressure_value_is_too_high() { | |
Alarm alarm = new Alarm( | |
sensorThatProbes(25.0), safetyRange | |
); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_off_when_pressure_value_is_within_safety_range() { | |
Alarm alarm = new Alarm( | |
sensorThatProbes(20.0), safetyRange | |
); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(false)); | |
} | |
protected Sensor sensorThatProbes(double value) { | |
Sensor sensor = mock(Sensor.class); | |
doReturn(value).when(sensor).probePressureValue(); | |
return sensor; | |
} | |
} |
Moreover this change makes Alarm more reusable.
This is the third post in a series of posts about solving the Tire Pressure Monitoring System exercise in Java:
- Solving the Tire Pressure Monitoring System exercise (I)
- Solving the Tire Pressure Monitoring System exercise (II)
- Solving the Tire Pressure Monitoring System exercise (III)
- Solving the Tire Pressure Monitoring System exercise (IV)
Solving the Tire Pressure Monitoring System exercise (II)
4. Introducing a test harness.
To be able to refactor Alarm, we first need to protect its current behavior (its check method) from regressions by writing tests for it.The implicit dependency of Alarm on Sensor makes Alarm difficult to test. However, it's the fact that Sensor returns random values that makes Alarm impossible to test because the measured pressure values it gets are not deterministic.
It seems we're trapped in a vicious circle: in order to refactor the code (improving its design without altering its behavior) we must test it first, but in order to test it, we must change it first.
We can get out of this problem by applying a dependency-breaking technique called Extract and Override call.
4.1. Extract and Override call.
This is a dependency-breaking technique from Michael Feather's Working Effectively with Legacy code book. These techniques consist of carefully making a very small modification in the production code in order to create a seam:A seam is a place where you can alter behavior in your program without editing in that place.The behavior we want to test is the logic in Alarm's check method. This logic is very simple, just an if condition and a mutation of a property, but as we saw its dependence on Sensor makes it untestable.
To test it, we need to alter the collaboration between Alarm and Sensor so that it becomes deterministic. That would make Alarm testable. For that we have to create a seam first.
4.1.1. Extract call to create a seam.
First, we create a seam by extracting the collaboration with Sensor to a protected method, probeValue. This step must be made with a lot of caution because we have no tests yet.
Thankfully in Java, we can rely on the IDE to do it automatically.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
public class Alarm { | |
private final double LowPressureThreshold = 17; | |
private final double HighPressureThreshold = 21; | |
private Sensor sensor = new Sensor(); | |
private boolean alarmOn = false; | |
public void check() { | |
double psiPressureValue = probeValue(); | |
if (psiPressureValue < LowPressureThreshold || HighPressureThreshold < psiPressureValue) { | |
alarmOn = true; | |
} | |
} | |
protected double probeValue() { | |
return sensor.popNextPressurePsiValue(); | |
} | |
public boolean isAlarmOn() { | |
return alarmOn; | |
} | |
} |
Next, we take advantage of the new seam, to alter the behavior of Alarm without affecting the logic we want to test.
To do it, we create a FakeAlarm class inside the AlarmShould tests that inherits from Alarm and overrides the call to the protected probeValue method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.Test; | |
import tddmicroexercises.tirepressuremonitoringsystem.Alarm; | |
import static org.hamcrest.MatcherAssert.assertThat; | |
import static org.hamcrest.Matchers.is; | |
public class AlarmShould { | |
private class FakeAlarm extends Alarm { | |
private final double samplePressure; | |
public FakeAlarm(double samplePressure) { | |
super(); | |
this.samplePressure = samplePressure; | |
} | |
@Override | |
protected double probeValue() { | |
return samplePressure; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.Test; | |
import tddmicroexercises.tirepressuremonitoringsystem.Alarm; | |
import static org.hamcrest.MatcherAssert.assertThat; | |
import static org.hamcrest.Matchers.is; | |
public class AlarmShould { | |
@Test | |
public void be_on_when_pressure_value_is_too_low() { | |
Alarm alarm = new FakeAlarm(5.0); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_on_when_pressure_value_is_too_high() { | |
Alarm alarm = new FakeAlarm(25.0); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_off_when_pressure_value_is_within_safety_range() { | |
Alarm alarm = new FakeAlarm(20.0); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(false)); | |
} | |
private class FakeAlarm extends Alarm { | |
/// ... | |
} | |
} |
5. Making the dependency on Sensor explicit.
Now our goal is to inject the dependency on Sensor into Alarm through its constructor.To remain in the green all the time, we use the Parallel Change technique.
With TDD we drive a new constructor without touching the one already in place by writing a new behavior test with the help of a mocking library (mockito):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.Test; | |
import tddmicroexercises.tirepressuremonitoringsystem.Alarm; | |
import tddmicroexercises.tirepressuremonitoringsystem.Sensor; | |
import static org.hamcrest.MatcherAssert.assertThat; | |
import static org.hamcrest.Matchers.is; | |
import static org.mockito.Mockito.doReturn; | |
import static org.mockito.Mockito.mock; | |
import static org.mockito.Mockito.verify; | |
public class AlarmShould { | |
// Tests using FakeAlarm | |
// ... | |
@Test | |
public void collaborate_with_an_injected_sensor() { | |
Sensor sensor = mock(Sensor.class); | |
Alarm alarm = new Alarm(sensor); | |
alarm.check(); | |
verify(sensor).popNextPressurePsiValue(); | |
} | |
private class FakeAlarm extends Alarm { | |
// has not changed | |
// ... | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
public class Alarm { | |
private final double LowPressureThreshold = 17; | |
private final double HighPressureThreshold = 21; | |
private Sensor sensor; | |
private boolean alarmOn; | |
public Alarm(Sensor sensor) { | |
this.sensor = sensor; | |
this.alarmOn = false; | |
} | |
public Alarm() { | |
this(new Sensor()); | |
} | |
// code that has not changed | |
// ... | |
} |
Then we use the new constructor in the rest of the tests one by one in order to stop using FakeAlarm.
Once there are no test using FakeAlarm, we can delete it. This makes the default constructor become obsolete, so we delete it too.
Finally, we also inline the previously extracted probeValue method.
This is the resulting test code after introducing dependency injection in which we have also deleted the test used to drive the new constructor because we think it was redundant:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.Test; | |
import tddmicroexercises.tirepressuremonitoringsystem.Alarm; | |
import tddmicroexercises.tirepressuremonitoringsystem.Sensor; | |
import static org.hamcrest.MatcherAssert.assertThat; | |
import static org.hamcrest.Matchers.is; | |
import static org.mockito.Mockito.doReturn; | |
import static org.mockito.Mockito.mock; | |
public class AlarmShould { | |
@Test | |
public void be_on_when_pressure_value_is_too_low() { | |
Alarm alarm = new Alarm(sensorThatProbes(5.0)); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_on_when_pressure_value_is_too_high() { | |
Alarm alarm = new Alarm(sensorThatProbes(25.0)); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(true)); | |
} | |
@Test | |
public void be_off_when_pressure_value_is_within_safety_range() { | |
Alarm alarm = new Alarm(sensorThatProbes(20.0)); | |
alarm.check(); | |
assertThat(alarm.isAlarmOn(), is(false)); | |
} | |
protected Sensor sensorThatProbes(double value) { | |
Sensor sensor = mock(Sensor.class); | |
doReturn(value).when(sensor).popNextPressurePsiValue(); | |
return sensor; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
public class Alarm { | |
private final double LowPressureThreshold = 17; | |
private final double HighPressureThreshold = 21; | |
private Sensor sensor; | |
private boolean alarmOn; | |
public Alarm(Sensor sensor) { | |
this.sensor = sensor; | |
this.alarmOn = false; | |
} | |
public void check() { | |
double psiPressureValue = sensor.popNextPressurePsiValue(); | |
if (psiPressureValue < LowPressureThreshold || HighPressureThreshold < psiPressureValue) { | |
alarmOn = true; | |
} | |
} | |
public boolean isAlarmOn() { | |
return alarmOn; | |
} | |
} |
This is the second post in a series of posts about solving the Tire Pressure Monitoring System exercise in Java:
- Solving the Tire Pressure Monitoring System exercise (I)
- Solving the Tire Pressure Monitoring System exercise (II)
- Solving the Tire Pressure Monitoring System exercise (III)
- Solving the Tire Pressure Monitoring System exercise (IV)
Solving the Tire Pressure Monitoring System exercise (I)
1. Introduction.
Last week I facilitated a guided kata for a Gran Canaria Ágil event in Aplicaciones Informáticas Domingo Alonso (thanks to both for inviting me) in which I explained a possible way to solve Luca Minudel's Tire Pressure Monitoring System exercise.This exercise is part of his TDD with Mock Objects: Design Principles and Emergent Properties exercises.
I like these exercises very much because they contain clear violations of the SOLID principles but they are sill small enough to be able to finish the refactoring in a short session at a slow pace. This makes possible to explain, answer questions and debate about design principles, dependency-breaking techniques and refactoring techniques as you apply them.
I'd like to thank Luca Minudel for creating and sharing these great exercises.
I co-facilitated this exercise with Álvaro García several month ago in a Software Craftsmanship Barcelona event and we received a lot of positive feedback.
That time, many people couldn't attend due to space limits, so I'd like to make a summary of the exercise here for them.
2. The initial code.
The initial code has two classes: Alarm and Sensor.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
public class Alarm { | |
private final double LowPressureThreshold = 17; | |
private final double HighPressureThreshold = 21; | |
private Sensor sensor = new Sensor(); | |
private boolean alarmOn = false; | |
public void check() { | |
double psiPressureValue = sensor.popNextPressurePsiValue(); | |
if (psiPressureValue < LowPressureThreshold || HighPressureThreshold < psiPressureValue) { | |
alarmOn = true; | |
} | |
} | |
public boolean isAlarmOn() { | |
return alarmOn; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package tddmicroexercises.tirepressuremonitoringsystem; | |
import java.util.Random; | |
public class Sensor { | |
public static final double OFFSET = 16; | |
public double popNextPressurePsiValue() { | |
double pressureTelemetryValue; | |
pressureTelemetryValue = samplePressure(); | |
return OFFSET + pressureTelemetryValue; | |
} | |
private static double samplePressure() { | |
// placeholder implementation that simulate a real sensor in a real tire | |
Random basicRandomNumbersGenerator = new Random(); | |
double pressureTelemetryValue = 6 * basicRandomNumbersGenerator.nextDouble() * basicRandomNumbersGenerator.nextDouble(); | |
return pressureTelemetryValue; | |
} | |
} |
3. SOLID violations, hidden dependencies.
All the logic of the Alarm class is in the check method.Alarm's main problem is that it depends on a concrete class, the sensor that measures pressure values.
This is a clear violation of the Dependency Inversion principle (DIP) which states that:
Abstractions should not depend on details.To make things worse this dependency is hidden inside Alarm's check method (see J. B. Rainsberger's The Pain of Implicit Dependencies post).
Details should depend on abstractions.
The violation of the DIP and the implicit dependency makes also impossible to fulfill the Open Closed Principle (OCP).
Fulfilling those two principles will be one of the main goals of this refactoring.
This is the first post in a series of posts about solving the Tire Pressure Monitoring System exercise in Java:
- Solving the Tire Pressure Monitoring System exercise (I)
- Solving the Tire Pressure Monitoring System exercise (II)
- Solving the Tire Pressure Monitoring System exercise (III)
- Solving the Tire Pressure Monitoring System exercise (IV)
Tuesday, August 25, 2015
Sunday, August 23, 2015
Updated my A bit about JavaScript functions talk (with videos in Spanish)
Before the summer, I updated the contents of my talk about JavaScript functions to prepare a master class (that finally became two) for the wonderful Devscola project.
These are the updated slides and these are the two recorded master classes (in Spanish):
I hope you'll find them useful.
These are the updated slides and these are the two recorded master classes (in Spanish):
I hope you'll find them useful.
Interesting Podcast: "Functional and Object Oriented Programming with Jessica Kerr"
I've just listened this great Ruby Rogues podcast with Jessica Kerr talking about functional and object oriented programming:
Interesting Podcast: "Growing Object Oriented Software Guided by Tests with Steve Freeman and Nat Pryce"
I've just listened this great Ruby Rogues podcast with Steve Freeman and Nat Pryce talking about their wonderful book Growing Object Oriented Software Guided by Tests:
Wednesday, August 5, 2015
Contract tests for interfaces discovered through TDD
We were working through the following iterations of an exercise:
First iteration
A user can register with a user name.
For instance: @foolano
If anyone else has already registered with that name there is an error.
Second iteration
A user can follow another users.
To do so it's only required to know the user name of the user to be followed.
Anyone can query the followers of any given user just knowing its user name.
Third iteration
The registered users and their followers must be persisted.
(source: Diseño modular dirigido por pruebas workshop at PyConEs 2014)
We produced several application services for this features that at some point collaborated with a users repository that we hadn't yet created so we mocked it in their specs.
In these tests, every time we allow or expect a method call on our repository double, we are defining not only the messages that the users repository can respond to (its public interface) but also what its clients expect from each of those messages, i.e. its contract.
In other words, at the same time we were testing the application services, we defined from the point of view of its clients the responsibilities that the users repository should be accountable for.
The users repository is at the boundary of our domain.
It's a port that allows us to not have to know anything about how users are stored, found, etc. This way we are able to just focus on what its clients want it to do for them, i.e., its responsibilities.
This results in more stable interfaces. As I heard Sandi Metz say once:
"You can trade the unpredictability of what others do for the constancy of what you want."
which is a very nice way to explain the "Program to an interface, not an implementation" design principle.
How those responsibilities are carried out is something that each different implementation (or adapter) of the users repository must be responsible of.
However, the terms of the contract that its clients rely on, must be respected by all of the adapters.
In this sense, any adapter must be substitutable by any other without the clients being affected, (yes, you're right, it's the Liskov substitution principle).
The only way to ensure this substitutability is by testing each new adapter to see if it also respects the terms of the contract.
This is related to J. B. Rainsberger's idea of contract tests mentioned in his Integrated Tests Are A Scam talk and in his great TDD course, and also to Jason Gorman's idea of polymorphic testing.
Ok, but how can we test that all the possible implementations of the user repository respect the contract without repeating a bunch of tests?
This is one way to do it in Ruby using RSpec.
We created a RSpec shared example in a file named users_repository_contract.rb where we wrote the tests that characterize the behavior that users repository clients were relying on:
Then for each implementation of the users repository you just need to include the contract using RSpec it_behaves_like method, as shown in the following two implementations:
You could still add any other test that only had to do with a given implementation in its spec.
This solution is both very readable and reduces a lot of duplication in the tests.
However, the idea of contract tests is not only important from the point of view of testing.
In dynamic languages, such as Ruby, they also serve as a mean to highlight and document the role of duck types that might otherwise go unnoticed.
First iteration
A user can register with a user name.
For instance: @foolano
If anyone else has already registered with that name there is an error.
Second iteration
A user can follow another users.
To do so it's only required to know the user name of the user to be followed.
Anyone can query the followers of any given user just knowing its user name.
Third iteration
The registered users and their followers must be persisted.
(source: Diseño modular dirigido por pruebas workshop at PyConEs 2014)
We produced several application services for this features that at some point collaborated with a users repository that we hadn't yet created so we mocked it in their specs.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
require 'cos/actions/users/register_user' | |
require 'cos/core/users/errors' | |
describe Actions::RegisterUser do | |
let(:users_repository) { double('UsersRepository') } | |
let(:new_user_name) {'@foolano'} | |
before do | |
stub_const('Users::Repository', users_repository) | |
end | |
describe "Registering a user" do | |
it "can register a user that is not already registered" do | |
allow(users_repository). | |
to receive(:registered?). | |
with(new_user_name).and_return(false) | |
expect(users_repository). | |
to receive(:register).with(new_user_name) | |
Actions::RegisterUser.do(new_user_name) | |
end | |
it "fails when trying to register a user that is already registered" do | |
allow(users_repository). | |
to receive(:registered?).with(new_user_name).and_return(true) | |
expect{Actions::RegisterUser.do(new_user_name)}. | |
to raise_error(Users::Errors::AlreadyRegistered) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
require 'cos/actions/users/follow_user' | |
require 'cos/core/users/errors' | |
describe Actions::FollowUser do | |
let(:follower_name) { "foolano" } | |
let(:followed_name) { "mengano" } | |
let(:users_repository) { double('UsersRepository') } | |
before do | |
stub_const('Users::Repository', users_repository) | |
end | |
describe "Following a user" do | |
describe "when both users are registered" do | |
it "succesfully adds a follower to a followed user" do | |
allow(users_repository). | |
to receive(:registered?).with(follower_name).and_return(true) | |
allow(users_repository). | |
to receive(:registered?).with(followed_name).and_return(true) | |
expect(users_repository). | |
to receive(:add_follower).with(follower_name, followed_name) | |
Actions::FollowUser.do follower_name, followed_name | |
end | |
end | |
describe "when any of them is not registered" do | |
it "raises an error when trying to add a registered follower to a followed user that does not exist" do | |
allow(users_repository). | |
to receive(:registered?).with(follower_name).and_return(true) | |
allow(users_repository). | |
to receive(:registered?).with(followed_name).and_return(false) | |
expect {Actions::FollowUser.do follower_name, followed_name}. | |
to raise_error(Users::Errors::NonRegistered) | |
end | |
it "raises an error when trying to add a follower that does not exist to a registered followed user" do | |
allow(users_repository). | |
to receive(:registered?).with(follower_name).and_return(false) | |
allow(users_repository). | |
to receive(:registered?).with(followed_name).and_return(true) | |
expect{Actions::FollowUser.do follower_name, followed_name}. | |
to raise_error(Users::Errors::NonRegistered) | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
require 'cos/queries/users/followers_of_user' | |
describe Queries::FollowersOfUser do | |
let(:follower_names) {["pepe", "juan"]} | |
let(:followed_name) {"koko"} | |
let(:users_repository) { double('UsersRepository') } | |
before do | |
stub_const('Users::Repository', users_repository) | |
end | |
describe "Getting the followers of a user" do | |
it "returns the list of followers" do | |
allow(users_repository).to receive(:followers_of) | |
.with(followed_name) | |
.and_return(follower_names) | |
expect(Queries::FollowersOfUser.do(followed_name)).to eq follower_names | |
end | |
end | |
end |
In other words, at the same time we were testing the application services, we defined from the point of view of its clients the responsibilities that the users repository should be accountable for.
The users repository is at the boundary of our domain.
It's a port that allows us to not have to know anything about how users are stored, found, etc. This way we are able to just focus on what its clients want it to do for them, i.e., its responsibilities.
This results in more stable interfaces. As I heard Sandi Metz say once:
"You can trade the unpredictability of what others do for the constancy of what you want."
which is a very nice way to explain the "Program to an interface, not an implementation" design principle.
How those responsibilities are carried out is something that each different implementation (or adapter) of the users repository must be responsible of.
However, the terms of the contract that its clients rely on, must be respected by all of the adapters.
In this sense, any adapter must be substitutable by any other without the clients being affected, (yes, you're right, it's the Liskov substitution principle).
The only way to ensure this substitutability is by testing each new adapter to see if it also respects the terms of the contract.
This is related to J. B. Rainsberger's idea of contract tests mentioned in his Integrated Tests Are A Scam talk and in his great TDD course, and also to Jason Gorman's idea of polymorphic testing.
Ok, but how can we test that all the possible implementations of the user repository respect the contract without repeating a bunch of tests?
This is one way to do it in Ruby using RSpec.
We created a RSpec shared example in a file named users_repository_contract.rb where we wrote the tests that characterize the behavior that users repository clients were relying on:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
RSpec.shared_examples "users repository" do | |
let(:user_name) { '@foolano' } | |
let(:follower_name) { '@zutano' } | |
let(:followed_name) { '@mengano' } | |
it "knows when a user is registered" do | |
given_already_registered(user_name) | |
expect(@repo.registered?(user_name)).to be_truthy | |
end | |
it "knows when a user is not registered" do | |
expect(@repo.registered?(user_name)).to be_falsy | |
end | |
it "registers a new user" do | |
@repo.register(user_name) | |
expect(@repo.registered?(user_name)).to be_truthy | |
end | |
it "adds a follower to a user" do | |
given_both_already_registered(follower_name, followed_name) | |
@repo.add_follower(follower_name, followed_name) | |
expect(@repo.followers_of(followed_name)).to eq [follower_name] | |
end | |
def given_both_already_registered(follower_name, followed_name) | |
given_already_registered(follower_name) | |
given_already_registered(followed_name) | |
end | |
def given_already_registered(user_name) | |
@repo.register(user_name) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
require 'infrastructure/users/repositories/in_memory' | |
require_relative '../cos/repositories_contracts/users_repository_role' | |
describe "In memory users repository" do | |
before do | |
@repo = Users::Repositories::InMemory.new | |
end | |
it_behaves_like "users repository" | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
require 'infrastructure/users/repositories/mongoid' | |
require_relative '../cos/repositories_contracts/users_repository_role' | |
describe "Mongoid users repository" do | |
before do | |
@repo = Users::Repositories::Mongoid.new | |
end | |
it_behaves_like "users repository" | |
end |
This solution is both very readable and reduces a lot of duplication in the tests.
However, the idea of contract tests is not only important from the point of view of testing.
In dynamic languages, such as Ruby, they also serve as a mean to highlight and document the role of duck types that might otherwise go unnoticed.
Kata: Print Diamond in Clojure
Yesterday we did the Print Diamond kata in the Clojure Developers Barcelona group.
I paired with Rafa Gómez. We managed to complete a recursive solution to the problem using our usual mix of TDD and REPL-driven development.
Later, we showed our solution and realize that, even though, it worked fine, we had to improve the names of some helper functions we had created to make our solution easier to understand.
After that, Samuel Lê, presented on the whiteboard (we couldn't connect his laptop to the projector) a completely different and very elegant approach.
I'll try to explain it:
Say, for instance, that you're trying to create the diamond for D.
You first create the following sequence repeating the string "DCBABCD" as many times as lines there are in the diamond:
Only one letter is allowed to appear in each line of the diamond.
Let's represent this in a table:
Now the only thing we have to do to get the correct diamond lines is to substitute by a space (we used an underscore to make it easier to visualize)
every letter of the line that is different from the allowed letter for that line:
This is a very elegant approach that only uses sequences functions.
When I got back home I redid the kata again using both approaches: the recursive one and the one that Samuel had explained on the whiteboard.
These are the tests I used to test drive the recursive code (sorry I can't show the REPL history because I haven't found where Cursive saves it):
I'm using an underscore instead of a space to make the results easier to visualize.
This is the recursive code:
Then I deleted the recursive code and started playing in the REPL to build a new solution following Samuel's approach and using the previous tests as acceptance tests to know when I was done.
Once I had it working again:
I refactored the code a bit introducing some helpers to separate responsibilities and make the code more readable and renamed some bindings.
This is the resulting code:
You can find all the code in this GitHub repository.
As usual the Clojure meetup and the conversations having a drink afterwards have been both great fun and very interesting.
We also met Alejandro Gómez which is writing this great ClojureScript book and has recently moved to Barcelona.
I paired with Rafa Gómez. We managed to complete a recursive solution to the problem using our usual mix of TDD and REPL-driven development.
Later, we showed our solution and realize that, even though, it worked fine, we had to improve the names of some helper functions we had created to make our solution easier to understand.
After that, Samuel Lê, presented on the whiteboard (we couldn't connect his laptop to the projector) a completely different and very elegant approach.
I'll try to explain it:
Say, for instance, that you're trying to create the diamond for D.
You first create the following sequence repeating the string "DCBABCD" as many times as lines there are in the diamond:
Let's represent this in a table:
A -> DCBABCD
B -> DCBABCD
C -> DCBABCD
D -> DCBABCD
C -> DCBABCD
B -> DCBABCD
A -> DCBABCD
A -> ___A___
B -> __B_B__
C -> _C___C_
D -> D_____D
C -> _C___C_
B -> __B_B__
A -> ___A___
When I got back home I redid the kata again using both approaches: the recursive one and the one that Samuel had explained on the whiteboard.
These are the tests I used to test drive the recursive code (sorry I can't show the REPL history because I haven't found where Cursive saves it):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns print-diamond.core-test | |
(:use midje.sweet) | |
(:require [print-diamond.core :refer [print-diamond]])) | |
(facts | |
"about printing diamonds" | |
(print-diamond "A") => "A" | |
(print-diamond "B") => "_A_\nB_B\n_A_" | |
(print-diamond "C") => "__A__\n_B_B_\nC___C\n_B_B_\n__A__" | |
(print-diamond "D") => "___A___\n__B_B__\n_C___C_\nD_____D\n_C___C_\n__B_B__\n___A___") |
This is the recursive code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns print-diamond.core) | |
(defn- char-range [start end] | |
(map char (range (int start) (inc (int end))))) | |
(def ^:private alphabet (map str (char-range \A \Z))) | |
(defn- spaces [n] | |
(clojure.string/join (repeat n "_"))) | |
(defn- current-line [letter pos given-letter-pos] | |
(let [spaces-side (spaces (- given-letter-pos pos)) | |
spaces-middle (spaces (dec (* 2 pos)))] | |
(if (= letter "A") | |
(str spaces-side letter spaces-side) | |
(str spaces-side letter spaces-middle letter spaces-side)))) | |
(def ^:private previous-lines take) | |
(defn- diamond-lines [pos lines middle-line] | |
(clojure.string/join | |
"\n" | |
(concat (previous-lines pos lines) | |
[middle-line] | |
(reverse (previous-lines pos lines))))) | |
(defn print-diamond [letter] | |
(let [letter-pos (.indexOf alphabet letter)] | |
(loop [letters alphabet lines [] pos 0] | |
(let [current-letter (first letters) | |
new-line (current-line current-letter pos letter-pos)] | |
(if (= letter current-letter) | |
(diamond-lines pos lines new-line) | |
(recur (rest letters) | |
(conj lines new-line) | |
(inc pos))))))) |
Once I had it working again:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
print-diamond.core=> (print (print-diamond "Z")) | |
_________________________A_________________________ | |
________________________B_B________________________ | |
_______________________C___C_______________________ | |
______________________D_____D______________________ | |
_____________________E_______E_____________________ | |
____________________F_________F____________________ | |
___________________G___________G___________________ | |
__________________H_____________H__________________ | |
_________________I_______________I_________________ | |
________________J_________________J________________ | |
_______________K___________________K_______________ | |
______________L_____________________L______________ | |
_____________M_______________________M_____________ | |
____________N_________________________N____________ | |
___________O___________________________O___________ | |
__________P_____________________________P__________ | |
_________Q_______________________________Q_________ | |
________R_________________________________R________ | |
_______S___________________________________S_______ | |
______T_____________________________________T______ | |
_____U_______________________________________U_____ | |
____V_________________________________________V____ | |
___W___________________________________________W___ | |
__X_____________________________________________X__ | |
_Y_______________________________________________Y_ | |
Z_________________________________________________Z | |
_Y_______________________________________________Y_ | |
__X_____________________________________________X__ | |
___W___________________________________________W___ | |
____V_________________________________________V____ | |
_____U_______________________________________U_____ | |
______T_____________________________________T______ | |
_______S___________________________________S_______ | |
________R_________________________________R________ | |
_________Q_______________________________Q_________ | |
__________P_____________________________P__________ | |
___________O___________________________O___________ | |
____________N_________________________N____________ | |
_____________M_______________________M_____________ | |
______________L_____________________L______________ | |
_______________K___________________K_______________ | |
________________J_________________J________________ | |
_________________I_______________I_________________ | |
__________________H_____________H__________________ | |
___________________G___________G___________________ | |
____________________F_________F____________________ | |
_____________________E_______E_____________________ | |
______________________D_____D______________________ | |
_______________________C___C_______________________ | |
________________________B_B________________________ | |
_________________________A_________________________nil |
This is the resulting code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns print-diamond.core) | |
(defn- char-range [start end] | |
(map char (range (int start) (inc (int end))))) | |
(def ^:private alphabet (map str (char-range \A \Z))) | |
(defn build-line-to-be-repeated [letter] | |
(let [letters-until (concat (take-while #(not= % letter) alphabet) [letter])] | |
(apply str (concat (reverse (drop 1 letters-until)) letters-until)))) | |
(defn substitute-spaces [line letter] | |
(apply str (map #(if (= (str %) letter) letter "_") line))) | |
(defn lines-number-in-uppper-triangle [letter] | |
(inc (.indexOf alphabet letter))) | |
(defn upper-diamond-lines [letter] | |
(let [line (build-line-to-be-repeated letter) | |
times (lines-number-in-uppper-triangle letter)] | |
(map substitute-spaces (repeat times line) (take times alphabet)))) | |
(defn diamond-lines [letter] | |
(let [upper-part (upper-diamond-lines letter)] | |
(concat upper-part (reverse (drop-last upper-part))))) | |
(defn print-diamond [letter] | |
(clojure.string/join "\n" (diamond-lines letter))) |
As usual the Clojure meetup and the conversations having a drink afterwards have been both great fun and very interesting.
We also met Alejandro Gómez which is writing this great ClojureScript book and has recently moved to Barcelona.
Tuesday, July 28, 2015
Using pattern matching in the Binary Search Tree Clojure solution
A member of SCBCN coded a beautiful solution in F# for the Binary Search Tree kata.
I wanted to try using pattern matching to get to a similar solution in Clojure.
So I watched Sean Johnson's Pattern Matching in Clojure talk and read a bit the documentation of clojure/core.match.
My intention was to use defrecord matching, but I couldn't make it work.
Later I found out in an answer to this StackOverflow question, Pattern matching on records in Clojure, that this feature hadn't been implemented yet.
So I followed the advice given in StackOverflow of using map pattern matching and coded this version of the Binary Search Tree:
Even though is not as nice as the F# one, it reads very well and allowed me to get rid of the value, left and right private helpers and all ifs.
Then I tried another thing that Sean Johnson had mentioned in his talk.
I coded another Binary Search Tree solution using the defun macro which creates functions with pattern matching like in Erlang, or Elixir
This is the resulting code using defun:
which also reads very nice.
I wanted to try using pattern matching to get to a similar solution in Clojure.
So I watched Sean Johnson's Pattern Matching in Clojure talk and read a bit the documentation of clojure/core.match.
My intention was to use defrecord matching, but I couldn't make it work.
Later I found out in an answer to this StackOverflow question, Pattern matching on records in Clojure, that this feature hadn't been implemented yet.
So I followed the advice given in StackOverflow of using map pattern matching and coded this version of the Binary Search Tree:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bst-with-pattern-matching.core | |
(:require [clojure.core.match :refer [match]])) | |
(defrecord BinarySearchTree [value left right]) | |
(defn singleton [value] | |
(map->BinarySearchTree {:value value})) | |
(defn insert [new-value tree] | |
(match [tree] | |
[nil] (singleton new-value) | |
[{:value value :left left :right right}] | |
(if (<= new-value value) | |
(->BinarySearchTree value (insert new-value left) right) | |
(->BinarySearchTree value left (insert new-value right))))) | |
(defn from-list [values] | |
(match [values] | |
[([] :seq)] nil | |
[([first-value & rest-values] :seq)] | |
(reduce #(insert %2 %1) (singleton first-value) rest-values))) | |
(defn in-order [tree] | |
(match [tree] | |
[nil] [] | |
[{:value value :left left :right right}] | |
(concat (in-order left) [value] (in-order right)))) |
Then I tried another thing that Sean Johnson had mentioned in his talk.
I coded another Binary Search Tree solution using the defun macro which creates functions with pattern matching like in Erlang, or Elixir
This is the resulting code using defun:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bst-with-functions-with-pattern-matching.core | |
(:use [defun :only [defun]])) | |
(def ^:private value :value) | |
(def ^:private left :left) | |
(def ^:private right :right) | |
(defn singleton [val] {:value val}) | |
(defun in-order | |
([tree :guard nil?] | |
[]) | |
([tree] | |
(concat (in-order (left tree)) | |
[(value tree)] | |
(in-order (right tree))))) | |
(defun insert | |
([val tree :guard nil?] | |
(singleton val)) | |
([val tree] | |
(if (<= val (value tree)) | |
(assoc tree :left (insert val (left tree))) | |
(assoc tree :right (insert val (right tree)))))) | |
(defun from-list | |
([elems :guard empty?] | |
nil) | |
([elems] | |
(reduce #(insert %2 %1) | |
(singleton (first elems)) | |
(rest elems)))) |
Monday, July 27, 2015
Interesting Talk: "Pattern Matching in Clojure"
Yesterday I watched this very interesting talk by Sean Johnson:
Sunday, July 26, 2015
Applying property-based testing on my binary search tree implementation
Some weeks ago I did the the Binary Search Tree problem from Exercism in Clojure.
I used TDD using some of the sample tests that Exercism provided and some other that I added.
These were the tests, after deleting some redundant ones:
And this was the binary search tree code:
I was just testing a few cases.
To gain confidence in my solution and play a bit more with the exercise, I decided to apply a bit of property-based testing on it.
So I added the clojure/test.check dependency to my project:
and added a new spec to the tests:
The in-order-traversal-of-bst-sorts-elements spec is checking the in-order-traversal-of-bst-sorts-elements-property 100 times.
For each check, the generator creates a random vector of integers and checks if the result of calling in-order on the binary search tree created from the vector is equal to the sorted vector.
The macro clojure.test.check.clojure-test/defspec allows to integrate clojure/test.check with clojure.test, so that properties can run under the clojure.test runner.
Midje, the test framework I'm using, can also detect and run clojure.test tests.
Ok, after running the tests, this is what I got (formatted so it fits without a lot of scrolling):
There was a case that was failing.
A really nice thing about clojure/test.check is that it tells you the smallest case that makes your code fail. See the value of the :smallest key inside the map associated to the :shrunk key of the map in the output.
I had forgotten to write a test for the empty vector!
So I added a new failing test for that case:
and modified the code to make it pass:
Now the tests were all passing:
At this moment, I could have deleted all the unit tests and kept only the property-based ones, because the former were now redundant.
I chose not to do it, because I really like the readability of midje's facts.
In this case, I've used property-based testing to thoroughly check my solution after I had finished doing TDD. I wasn't wearing the TDD hat when I used it, I was just doing testing.
I think property-based testing and TDD can complement each other very nicely.
Another way to combine property-based testing and TDD is using the capability of clojure/test.check of giving you the smallest case that fails to help you choose the next test to drive your code.
Jan Stępień talked about this in his talk in last EuroClojure conference:
Another thing to consider is that the smallest case that makes the code fail given by property-based testing may not always correspond to the test that allows the code to grow in a small and easy to implement step.
I think that chances are that many times it might be, though.
Another thing to explore :)
I used TDD using some of the sample tests that Exercism provided and some other that I added.
These were the tests, after deleting some redundant ones:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bst-properties-based-testing.binary-search-tree-test | |
(:use midje.sweet) | |
(:require [bst-properties-based-testing.binary-search-tree :as binary-search-tree])) | |
(facts | |
"about binary search tree in-order traversal" | |
(fact | |
"sorts the elements of a tree without any sons" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [3])) => [3]) | |
(fact | |
"sorts the elements of a tree with left and right sons" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [2 5 1])) => [1 2 5]) | |
(fact | |
"sorts the elements of a complex tree created inserting values one by one" | |
(let [tree (->> (binary-search-tree/singleton 4) | |
(binary-search-tree/insert 6) | |
(binary-search-tree/insert 5) | |
(binary-search-tree/insert 8) | |
(binary-search-tree/insert 2) | |
(binary-search-tree/insert 3) | |
(binary-search-tree/insert 1))] | |
(binary-search-tree/in-order tree) => [1 2 3 4 5 6 8])) | |
(fact | |
"sorts the elements of a complex tree created from a list" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [4 6 5 8 2 3 1])) => [1 2 3 4 5 6 8])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bst-properties-based-testing.binary-search-tree) | |
(defn singleton [val] | |
{:value val}) | |
(def value :value) | |
(def left :left) | |
(def right :right) | |
(defn insert [val tree] | |
(let [add-node | |
(fn [location node] | |
(assoc tree location | |
(if node (insert val node) | |
(singleton val))))] | |
(if (<= val (value tree)) | |
(add-node :left (left tree)) | |
(add-node :right (right tree))))) | |
(defn from-list [[root & rest-nodes :as nodes]] | |
(reduce #(insert %2 %1) (singleton root) rest-nodes)) | |
(defn in-order [tree] | |
(if tree | |
(concat (in-order (left tree)) | |
[(value tree)] | |
(in-order (right tree))) | |
[])) |
To gain confidence in my solution and play a bit more with the exercise, I decided to apply a bit of property-based testing on it.
So I added the clojure/test.check dependency to my project:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(defproject bst-properties-based-testing "0.0.1-SNAPSHOT" | |
:description "Cool new project to do things and stuff" | |
:dependencies [[org.clojure/clojure "1.6.0"]] | |
:profiles {:dev {:dependencies [[midje "1.6.3"] | |
[org.clojure/test.check "0.7.0"]]}}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bst-properties-based-testing.binary-search-tree-test | |
(:use midje.sweet) | |
(:require [bst-properties-based-testing.binary-search-tree :as binary-search-tree] | |
[clojure.test.check.clojure-test :refer [defspec]] | |
[clojure.test.check.generators :as gen] | |
[clojure.test.check.properties :as prop])) | |
(def in-order-traversal-of-bst-sorts-elements-property | |
(prop/for-all [v (gen/vector gen/int)] | |
(= (binary-search-tree/in-order | |
(binary-search-tree/from-list v)) | |
(sort v)))) | |
(defspec in-order-traversal-of-bst-sorts-elements | |
100 | |
in-order-traversal-of-bst-sorts-elements-property) | |
(facts | |
"about binary search tree" | |
(fact | |
"creates a tree without any sons" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [3])) => [3]) | |
(fact | |
"creates a tree with left and right sons" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [2 5 1])) => [1 2 5]) | |
(fact | |
"creates a complex tree inserting values one by one" | |
(let [tree (->> (binary-search-tree/singleton 4) | |
(binary-search-tree/insert 6) | |
(binary-search-tree/insert 5) | |
(binary-search-tree/insert 8) | |
(binary-search-tree/insert 2) | |
(binary-search-tree/insert 3) | |
(binary-search-tree/insert 1))] | |
(binary-search-tree/in-order tree) => [1 2 3 4 5 6 8])) | |
(fact | |
"creates a complex tree from a list" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [4 6 5 8 2 3 1])) => [1 2 3 4 5 6 8])) |
For each check, the generator creates a random vector of integers and checks if the result of calling in-order on the binary search tree created from the vector is equal to the sorted vector.
The macro clojure.test.check.clojure-test/defspec allows to integrate clojure/test.check with clojure.test, so that properties can run under the clojure.test runner.
Midje, the test framework I'm using, can also detect and run clojure.test tests.
Ok, after running the tests, this is what I got (formatted so it fits without a lot of scrolling):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
====================================================================== | |
Loading (bst-properties-based-testing.binary-search-tree-test) | |
{:test-var "in-order-traversal-of-bst-sorts-elements", | |
:result false, | |
:seed 1437929928970, | |
:failing-size 0, | |
:num-tests 1, | |
:fail [[]], | |
:shrunk {:total-nodes-visited 0, | |
:depth 0, | |
:result false, | |
:smallest [[]]}} | |
>>> Output from clojure.test tests: | |
FAIL in (in-order-traversal-of-bst-sorts-elements) (clojure_test.clj:18) | |
expected: result | |
actual: false | |
1 failures, 0 errors. | |
>>> Midje summary: | |
All checks (4) succeeded. |
A really nice thing about clojure/test.check is that it tells you the smallest case that makes your code fail. See the value of the :smallest key inside the map associated to the :shrunk key of the map in the output.
I had forgotten to write a test for the empty vector!
So I added a new failing test for that case:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bst-properties-based-testing.binary-search-tree-test | |
(:use midje.sweet) | |
(:require [bst-properties-based-testing.binary-search-tree :as binary-search-tree] | |
[clojure.test.check.clojure-test :refer [defspec]] | |
[clojure.test.check.generators :as gen] | |
[clojure.test.check.properties :as prop])) | |
(def in-order-traversal-of-bst-sorts-elements-property | |
(prop/for-all [v (gen/vector gen/int)] | |
(= (binary-search-tree/in-order | |
(binary-search-tree/from-list v)) | |
(sort v)))) | |
(defspec in-order-traversal-of-bst-sorts-elements | |
100 | |
in-order-traversal-of-bst-sorts-elements-property) | |
(facts | |
"about binary search tree in-order traversal" | |
(fact | |
"sorts the elements of a tree without any sons" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [3])) => [3]) | |
(fact | |
"sorts the elements of a tree with left and right sons" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [2 5 1])) => [1 2 5]) | |
(fact | |
"sorts the elements of a complex tree created inserting values one by one" | |
(let [tree (->> (binary-search-tree/singleton 4) | |
(binary-search-tree/insert 6) | |
(binary-search-tree/insert 5) | |
(binary-search-tree/insert 8) | |
(binary-search-tree/insert 2) | |
(binary-search-tree/insert 3) | |
(binary-search-tree/insert 1))] | |
(binary-search-tree/in-order tree) => [1 2 3 4 5 6 8])) | |
(fact | |
"sorts the elements of a complex tree created from a list" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [4 6 5 8 2 3 1])) => [1 2 3 4 5 6 8]) | |
(fact | |
"sorts the elements of an empty tree created from an empty list" | |
(binary-search-tree/in-order | |
(binary-search-tree/from-list [])) => [])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bst-properties-based-testing.binary-search-tree) | |
(defn singleton [val] | |
{:value val}) | |
(def value :value) | |
(def left :left) | |
(def right :right) | |
(defn insert [val tree] | |
(let [add-node | |
(fn [location node] | |
(assoc tree location | |
(if node (insert val node) | |
(singleton val))))] | |
(if (<= val (value tree)) | |
(add-node :left (left tree)) | |
(add-node :right (right tree))))) | |
(defn from-list [[root & rest-nodes :as nodes]] | |
(if (empty? nodes) | |
nil | |
(reduce #(insert %2 %1) (singleton root) rest-nodes))) | |
(defn in-order [tree] | |
(if tree | |
(concat (in-order (left tree)) | |
[(value tree)] | |
(in-order (right tree))) | |
[])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading (bst-properties-based-testing.binary-search-tree-test) | |
{:test-var "in-order-traversal-of-bst-sorts-elements", :result true, :num-tests 100, :seed 1437930264045} | |
>>> Output from clojure.test tests: | |
0 failures, 0 errors. | |
>>> Midje summary: | |
All checks (5) succeeded. |
I chose not to do it, because I really like the readability of midje's facts.
In this case, I've used property-based testing to thoroughly check my solution after I had finished doing TDD. I wasn't wearing the TDD hat when I used it, I was just doing testing.
I think property-based testing and TDD can complement each other very nicely.
Another way to combine property-based testing and TDD is using the capability of clojure/test.check of giving you the smallest case that fails to help you choose the next test to drive your code.
Jan Stępień talked about this in his talk in last EuroClojure conference:
Another thing to consider is that the smallest case that makes the code fail given by property-based testing may not always correspond to the test that allows the code to grow in a small and easy to implement step.
I think that chances are that many times it might be, though.
Another thing to explore :)
Interesting Talk: "Interaction Driven Design"
I've just watched this great talk by Sandro Mancuso:
This is an update and enhanced version of his previous talk: Crafted Design.
Thursday, July 23, 2015
Interesting Talk: "Immutability, interactivity & JavaScript"
I've just watched this great talk by David Nolen:
Wednesday, July 22, 2015
Kata: Binary Search Tree in Ruby
Yesterday in the Barcelona Software Craftsmanship coding dojo we did the Binary Search Tree kata.
Raúl Lorca and I paired using TDD in C# to get to a pretty OO solution.
We didn't finish the exercise. We got to create the binary search tree but we didn't have time to code the in-order depth-first traversal.
Well, it doesn't matter. The important thing is that we practiced TDD, recursion and OO by test-driving a recursive data structure.
Today, with a bit more of time, I did the the whole kata in Ruby.
These are the resulting tests, after deleting some I used as scaffolding to get to the final version of the code (check the commits if you want to see the evolution of the tests):
And this is the final version of the code:
To document the whole TDD process I committed the code after every passing test and every refactoring.
You can check the commits step by step here and all the code in this repository in GitHub.
I had done the same exercise in Clojure several weeks ago as part of the Exercism problems. Compare the code there with this one if you like.
Raúl Lorca and I paired using TDD in C# to get to a pretty OO solution.
We didn't finish the exercise. We got to create the binary search tree but we didn't have time to code the in-order depth-first traversal.
Well, it doesn't matter. The important thing is that we practiced TDD, recursion and OO by test-driving a recursive data structure.
Today, with a bit more of time, I did the the whole kata in Ruby.
These are the resulting tests, after deleting some I used as scaffolding to get to the final version of the code (check the commits if you want to see the evolution of the tests):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require './binary_search_tree' | |
describe BinarySearchTree do | |
it "creates a tree without any sons" do | |
tree = BinarySearchTree.new(3) | |
expect(tree.in_order()).to eq [3] | |
end | |
it "creates a tree with left and right sons" do | |
tree = BinarySearchTree.new(2) | |
tree.insert(5) | |
tree.insert(1) | |
expect(tree.in_order()).to eq [1, 2, 5] | |
end | |
it "creates a complex tree inserting values one by one" do | |
tree = BinarySearchTree.new(4) | |
tree.insert(6) | |
tree.insert(5) | |
tree.insert(8) | |
tree.insert(2) | |
tree.insert(3) | |
tree.insert(1) | |
expect(tree.in_order()).to eq [1, 2, 3, 4, 5, 6, 8] | |
end | |
it "creates a complex tree from a list" do | |
tree = BinarySearchTree.create_from_list([4, 6, 5, 8, 2, 3, 1]) | |
expect(tree.in_order()).to eq [1, 2, 3, 4, 5, 6, 8] | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class BinarySearchTree | |
def initialize(value) | |
@value = value | |
end | |
def insert(new_value) | |
if new_value < value | |
self.left_tree = add_value_at(left_tree, new_value) | |
else | |
self.right_tree = add_value_at(right_tree, new_value) | |
end | |
end | |
def self.create_from_list(numbers) | |
tree = BinarySearchTree.new(numbers.first) | |
numbers.drop(1).reduce(tree) do |tree, value| | |
tree.insert(value) | |
tree | |
end | |
end | |
def in_order() | |
sorted_list = tree_in_order left_tree | |
sorted_list.push(value) | |
sorted_list.concat(tree_in_order right_tree) | |
end | |
private | |
attr_accessor :left_tree, :right_tree | |
attr_reader :value | |
def tree_in_order tree | |
if tree | |
tree.in_order() | |
else | |
[] | |
end | |
end | |
def add_value_at(tree, new_value) | |
if tree | |
tree.insert(new_value) | |
tree | |
else | |
BinarySearchTree.new(new_value) | |
end | |
end | |
end |
You can check the commits step by step here and all the code in this repository in GitHub.
I had done the same exercise in Clojure several weeks ago as part of the Exercism problems. Compare the code there with this one if you like.
Tuesday, July 14, 2015
Exercism: "Largest Series Product in Clojure"
I solved the Largest Series Product problem in Clojure.
This is my solution:
This Exercism problem is based on a Project Euler's exercise I solved some time ago.
You can nitpick my solution here and/or see all the exercises I've done so far in this repository.
This is my solution:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns largest-series-product) | |
(defn digits [string-of-digits] | |
(map #(Integer/parseInt (str %)) | |
string-of-digits)) | |
(defn slices [size string-of-digits] | |
(partition size 1 (digits string-of-digits))) | |
(defn- compute-largest-product [size string-of-digits] | |
(->> string-of-digits | |
(slices size) | |
(map (partial apply *)) | |
(apply max))) | |
(defn- invalid-input? [size string-of-digits] | |
(let [too-small-input? #(> size (count string-of-digits)) | |
empty-input? #(clojure.string/blank? string-of-digits)] | |
(or (empty-input?) (too-small-input?)))) | |
(defn largest-product [size string-of-digits] | |
(if (invalid-input? size string-of-digits) | |
1 | |
(compute-largest-product size string-of-digits))) |
You can nitpick my solution here and/or see all the exercises I've done so far in this repository.
Monday, July 13, 2015
Exercism: "Hexadecimal in Clojure"
I solved the Hexadecimal problem in Clojure.
As usual I extracted some helpers and played with partial and the thread last macro (->>) to try to make the solution more readable:
You can nitpick my solution here and/or see all the exercises I've done so far in this repository.
As usual I extracted some helpers and played with partial and the thread last macro (->>) to try to make the solution more readable:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns hexadecimal | |
(:require [clojure.string :as str])) | |
(def ^:private ints-by-hex-digit | |
(zipmap ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "b" "c" "d" "e" "f"] | |
[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15])) | |
(defn- hex-digits [hex] | |
(drop 1 (str/split (str/lower-case hex) #""))) | |
(def ^:private hex-digit->int-digit | |
(partial get ints-by-hex-digit)) | |
(defn- valid-hex? [hex-digits] | |
(every? (complement nil?) | |
(map hex-digit->int-digit hex-digits))) | |
(defn- pow [base exp] | |
(reduce * (repeat exp base))) | |
(defn- hex-in-position->int | |
[position hex-digit] | |
(* (hex-digit->int-digit hex-digit) | |
(pow 16 position))) | |
(defn- reversed-hex-digits->int | |
[reversed-hex-digits] | |
(if (valid-hex? reversed-hex-digits) | |
(reduce + (map-indexed hex-in-position->int | |
reversed-hex-digits)) | |
0)) | |
(defn hex-to-int [hex] | |
(->> hex | |
hex-digits | |
reverse | |
reversed-hex-digits->int)) |
Interesting Talk: "Narcissistic Design: 10 Steps to Complex Code and Job Security"
I've just watched this great talk by Stuart Halloway:
Thursday, July 9, 2015
Interesting Talk: "Serverside Isomorphic Javascript Rendering with ReactJS & Node"
I've just watched this very interesting short talk by David Walsh:
Wednesday, July 8, 2015
Exercism: "Difference Of Squares in Clojure"
I solved the Difference Of Squares problem in Clojure.
This exercise was very easy.
I played with partial and the thread last macro (->>) to try to make the solution more readable:
It turns out that I had done this exercise before.
You can see that version in Euler Project: Problem 6 in Clojure
You can nitpick my solution here and/or see all the exercises I've done so far in this repository.
This exercise was very easy.
I played with partial and the thread last macro (->>) to try to make the solution more readable:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns difference-of-squares) | |
(def ^:private naturals | |
(drop 1 (range))) | |
(def ^:private sum | |
(partial reduce +)) | |
(defn- square [n] | |
(* n n)) | |
(def ^:private square-all | |
(partial map square)) | |
(defn square-of-sums [n] | |
(->> (take n naturals) | |
sum | |
square)) | |
(defn sum-of-squares [n] | |
(->> (take n naturals) | |
square-all | |
sum)) | |
(defn difference [n] | |
(- (square-of-sums n) | |
(sum-of-squares n))) |
You can see that version in Euler Project: Problem 6 in Clojure
You can nitpick my solution here and/or see all the exercises I've done so far in this repository.
Sunday, July 5, 2015
Exercism: "Binary Search Tree in Clojure"
I solved the Binary Search Tree problem in Clojure.
I wrote three versions of the to-list function which makes a depth-first traversal of the tree returning the sequence of visited values.
This is my first version:
This is the second one using concat:
And the last one using the tree-seq function that I discovered looking at other solutions in Exercism:
You can nitpick my solutions here and/or see all the exercises I've done so far in this repository.
I wrote three versions of the to-list function which makes a depth-first traversal of the tree returning the sequence of visited values.
This is my first version:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns binary-search-tree) | |
(defn singleton [val] | |
{:value val}) | |
(def value :value) | |
(def left :left) | |
(def right :right) | |
(defn insert [val tree] | |
(let [add-node | |
(fn [location node] | |
(assoc tree location | |
(if node (insert val node) | |
(singleton val))))] | |
(if (<= val (value tree)) | |
(add-node :left (left tree)) | |
(add-node :right (right tree))))) | |
(defn from-list [[root & rest-nodes]] | |
(reduce #(insert %2 %1) (singleton root) rest-nodes)) | |
(defn to-list | |
([tree] (to-list [] tree)) | |
([acc tree] | |
(cond | |
(and (left tree) (not (right tree))) | |
(conj (to-list acc (left tree)) (value tree)) | |
(and (not (left tree)) (right tree)) | |
(to-list (conj acc (value tree)) (right tree)) | |
(and (left tree) (right tree)) | |
(to-list (conj (to-list acc (left tree)) (value tree)) (right tree)) | |
:else (conj acc (value tree))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns binary-search-tree) | |
(defn singleton [val] | |
{:value val}) | |
(def value :value) | |
(def left :left) | |
(def right :right) | |
(defn insert [val tree] | |
(let [add-node | |
(fn [location node] | |
(assoc tree location | |
(if node (insert val node) | |
(singleton val))))] | |
(if (<= val (value tree)) | |
(add-node :left (left tree)) | |
(add-node :right (right tree))))) | |
(defn from-list [[root & rest-nodes]] | |
(reduce #(insert %2 %1) (singleton root) rest-nodes)) | |
(defn to-list [tree] | |
(if tree | |
(concat (to-list (left tree)) | |
[(value tree)] | |
(to-list (right tree))) | |
[])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns binary-search-tree) | |
(defn singleton [val] | |
{:value val}) | |
(def value :value) | |
(def left :left) | |
(def right :right) | |
(defn insert [val tree] | |
(let [add-node | |
(fn [location node] | |
(assoc tree location | |
(if node (insert val node) | |
(singleton val))))] | |
(if (<= val (value tree)) | |
(add-node :left (left tree)) | |
(add-node :right (right tree))))) | |
(defn from-list [[root & rest-nodes]] | |
(reduce #(insert %2 %1) (singleton root) rest-nodes)) | |
(def ^:private children (juxt left value right)) | |
(defn- leaf? [node] | |
(not (or (map? node) (nil? node)))) | |
(defn to-list [tree] | |
(filter leaf? (tree-seq map? children tree))) |
Saturday, July 4, 2015
Exercism: "Accumulate in Clojure"
I solved the Accummulate problem in Clojure.
This is an easy one in Clojure, the only restriction is that you can't use map.
I did 3 versions to practice:
A recursive one:
Another one using reduce:
And another one using a sequence comprehension:
You can nitpick the solutions here and/or see all the exercises I've done so far in this repository.
This is an easy one in Clojure, the only restriction is that you can't use map.
I did 3 versions to practice:
A recursive one:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns accumulate) | |
(defn accumulate [func coll] | |
(let [accumulate-rec | |
(fn [acc [first-elem & rest-elems]] | |
(if (nil? first-elem) | |
acc | |
(recur (conj acc (func first-elem)) | |
rest-elems)))] | |
(accumulate-rec [] coll))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns accumulate) | |
(defn accumulate [func coll] | |
(reduce #(conj %1 (func %2)) [] coll)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns accumulate) | |
(defn accumulate [func coll] | |
(for [x coll] (func x))) |
Tuesday, June 30, 2015
Interesting Podcast: "Smalltalk Best Practice Patterns with Kent Beck"
I've just listened this great Ruby Rogues podcast with Kent Beck talking about Beck's wonderful book Smalltalk Best Practice Patterns:
Monday, June 29, 2015
Interesting Podcast: "Functional Programming for the Object-Oriented Programmer with Brian Marick"
I've just listened this great Ruby Rogues podcast with Brian Marick talking about Marick's book Functional Programming for the Object-Oriented Programmer:
Saturday, June 27, 2015
Interesting Interview: "Passionate voices with Katrina Owen"
I've just watched this great interview to Katrina Owen:
Sunday, June 7, 2015
Interesting Talk: "Migrating to Clojure, So much fn"
Several days ago I watched this really interesting talk by Jan Stępień:
Friday, June 5, 2015
Interesting Talk: "Eliminate JavaScript Code Smells"
I've just watched this very interesting talk by Elijah Manor:
This is the short version of the talk, in which he talks about half the code smells that appear in the long version.
It seems that the long version of the talk hasn't been recorded yet (or so it says here).
In any case, these are the slides of the long version talk,
It seems that the long version of the talk hasn't been recorded yet (or so it says here).
In any case, these are the slides of the long version talk,
Wednesday, June 3, 2015
Interesting Talk: "Stop being Perfect"
I've just watched this great talk by Angus Croll about not letting best practices do your thinking for you:
Monday, June 1, 2015
Interesting Paper: "Mock Roles, not Objects"
I've just read this very interesting paper by Steve Freeman, Nat Pryce, Tim Mackinnon and Joe Walnes about mock objects as a design technique for identifying types in a system based on the roles the objects play:
They talk about the importance of RDD, the Law of Demeter and Tell Don't Ask in this technique and how it can lead to an OO style based in composition rather than inheritance, encouraging designs with good separation of concerns and modularity.
It also gives many heuristics to help drive your design using mock objects and fights against some typical misconceptions surrounding mock objects.
It also gives many heuristics to help drive your design using mock objects and fights against some typical misconceptions surrounding mock objects.
Interesting Podcast: "Hexagonal Rails with Matt Wynne and Kevin Rutherford"
I've just listened this great Ruby Rogues podcast with Matt Wynne and Kevin Rutherford:
Sunday, May 31, 2015
Interesting Talk: "Red, Green, ... now what ?!"
I've just watched this wonderful practical demonstration of using connascence to guide the refactoring step of TDD by James Jeffries and Kevin Rutherford:
These are the slides.
These other slides explain more about each type of connascence.
These other slides explain more about each type of connascence.
Friday, May 29, 2015
Interesting Talk: "Integrated Tests Are A Scam"
I've just watched a newer version of this wonderful talk by J. B. Rainsberger:
Compare it with this old version of the same talk
Thursday, May 28, 2015
Interesting Talk: "The New New Software Development Game"
I've just watched this wonderful talk by Mary Poppendieck:
Tuesday, May 26, 2015
Interesting Talk: "Functional Programming Design Patterns"
I've just watched this wonderful talk by Scott Wlaschin:
Monday, May 25, 2015
Interesting Talk: "Design Patterns in Dynamic Languages"
I've just watched this great talk by Neal Ford:
Wednesday, May 20, 2015
Exercism: "Queen Attack in Clojure"
I solved the Queen Attack problem in Clojure.
This is my solution:
I think that the can-attack function should be called can-attack?. I've kept its original name because it's being exercised from the tests given by Exercism.
You can nitpick the solution here and/or see all the exercises I've done so far in this repository.
This is my solution:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns queen-attack | |
(:require [clojure.string :as clj-str])) | |
(def ^:private empty-board | |
(vec (repeat 8 (vec (repeat 8 "O"))))) | |
(defn- add-to-board [position representation board] | |
(if position | |
(assoc-in board position representation) | |
board)) | |
(defn- board [{white-queen-position :w black-queen-position :b}] | |
(->> empty-board | |
(add-to-board white-queen-position "W") | |
(add-to-board black-queen-position "B"))) | |
(defn board-string [queens] | |
(str (clj-str/join | |
"\n" | |
(map #(clj-str/join " " %) (board queens))) | |
"\n")) | |
(defn- in-same-rank? [[white-queen-rank _] [black-queen-rank _]] | |
(= white-queen-rank black-queen-rank)) | |
(defn- in-same-column? [[_ white-queen-col] [_ black-queen-col]] | |
(= white-queen-col black-queen-col)) | |
(defn- in-same-diagonal? [[white-queen-rank white-queen-col] | |
[black-queen-rank black-queen-col]] | |
(= 1 (quot (- white-queen-rank black-queen-rank) | |
(- white-queen-col black-queen-col)))) | |
(defn can-attack [{white-queen-pos :w black-queen-pos :b}] | |
(or (in-same-column? white-queen-pos black-queen-pos) | |
(in-same-rank? white-queen-pos black-queen-pos) | |
(in-same-diagonal? white-queen-pos black-queen-pos))) |
You can nitpick the solution here and/or see all the exercises I've done so far in this repository.
Exercism: "Robot Simulator in Clojure"
I solved the Robot Simulator problem in Clojure.
This is my solution:
I think that the turn-right and turn-left functions aren't necessary. I've kept them because they are being exercised from the tests given by Exercism.
You can nitpick the solution here and/or see all the exercises I've done so far in this repository.
This is my solution:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns robot-simulator) | |
(defn robot [coordinates bearing] | |
{:coordinates coordinates | |
:bearing bearing}) | |
(def ^:private turns | |
{:right {:north :east | |
:east :south | |
:south :west | |
:west :north} | |
:left {:north :west | |
:east :north | |
:south :east | |
:west :south}}) | |
(defn- turn [direction current-bearing] | |
(get-in turns [direction current-bearing])) | |
(defn turn-right [bearing] | |
(turn :right bearing)) | |
(defn turn-left [bearing] | |
(turn :left bearing)) | |
(def ^:private displacements | |
{:north [0 1] | |
:east [1 0] | |
:south [0 -1] | |
:west [-1 0]}) | |
(defn- advance [{{:keys [x y]} :coordinates bearing :bearing}] | |
(let [displacement (get displacements bearing [0 0]) | |
[x y] (map + [x y] displacement)] | |
(robot {:x x :y y} bearing))) | |
(defn- right [{bearing :bearing :as rb}] | |
(assoc rb :bearing (turn-right bearing))) | |
(defn- left [{bearing :bearing :as rb}] | |
(assoc rb :bearing (turn-left bearing))) | |
(def ^:private commands | |
{\R right | |
\A advance | |
\L left}) | |
(defn simulate [[first-msg & rest-msg] rb] | |
(if-let [comm (get commands first-msg)] | |
(recur rest-msg (comm rb)) | |
rb)) |
You can nitpick the solution here and/or see all the exercises I've done so far in this repository.
Tuesday, May 19, 2015
Exercism: "Kindergarten Garden in Clojure"
I solved the Kindergarten Garden problem in Clojure.
This is my solution:
You can nitpick this solution here and/or see all the exercises I've done so far in this repository.
This is my solution:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns kindergarten-garden) | |
(def ^:private plants | |
{\R :radishes \C :clover \G :grass \V :violets}) | |
(def ^:private default-children | |
[:alice :bob :charlie :david | |
:eve :fred :ginny :harriet | |
:ileana :joseph :kincaid :larry]) | |
(def ^:private get-plants | |
(partial map #(get plants %))) | |
(defn- plants-rows [diagram] | |
(map #(vec (get-plants %)) | |
(clojure.string/split diagram #"\n"))) | |
(defn- plants-per-child-in-rows [child-index rows] | |
(vec | |
(flatten | |
(map #(vector (get % child-index) (get % (inc child-index))) | |
rows)))) | |
(defn- plants-per-child [diagram] | |
(let [num-cols (.indexOf diagram "\n")] | |
(map #(plants-per-child-in-rows (* 2 %) (plants-rows diagram)) | |
(range 0 (quot num-cols 2))))) | |
(def ^:private lowercase-all | |
(partial map #(clojure.string/lower-case %))) | |
(def ^:private strs->keys | |
(partial map #(keyword %))) | |
(defn- children-keys [children-strs] | |
(->> children-strs | |
lowercase-all | |
sort | |
strs->keys)) | |
(defn- make-garden [children diagram] | |
(zipmap children (plants-per-child diagram))) | |
(defn garden | |
([diagram] | |
(make-garden default-children diagram)) | |
([diagram children-strs] | |
(make-garden (children-keys children-strs) diagram))) |
Saturday, May 16, 2015
Exercism: "Crypto Square in Clojure"
I solved the Crypto Square problem in Clojure.
This is my solution:
You can nitpick this solution here or see all the exercises I've done so far in this repository.
This is my solution:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns crypto-square | |
(:require [clojure.string :as clj-str])) | |
(defn- no-punctuation [c] | |
(or (Character/isLetter c) | |
(Character/isDigit c))) | |
(defn- remove-punctuation [text] | |
(clj-str/join "" (filter no-punctuation text))) | |
(defn normalize-plaintext [text] | |
(clj-str/lower-case (remove-punctuation text))) | |
(defn square-size [text] | |
(int (Math/ceil (Math/sqrt (count text))))) | |
(defn plaintext-segments [text] | |
(let [normalized-text (normalize-plaintext text) | |
segment-size (square-size normalized-text)] | |
(map clj-str/join (partition-all segment-size normalized-text)))) | |
(defn- pad-segments [segments segments-size] | |
(map #(format (str "%-" segments-size "s") %) segments)) | |
(defn- segments-in-columns [text] | |
(let [segments (plaintext-segments text) | |
segment-size (count (first segments))] | |
(apply map | |
#(clj-str/trim (apply str %&)) | |
(pad-segments segments segment-size)))) | |
(defn- remove-spaces [text] | |
(clj-str/replace text " " "")) | |
(defn normalize-ciphertext [text] | |
(->> text | |
segments-in-columns | |
(clj-str/join " "))) | |
(defn ciphertext [text] | |
(remove-spaces (normalize-ciphertext text))) |
Wednesday, May 13, 2015
Exercism: "Bank Account in Clojure"
In the last event of Clojure Developers Barcelona, we practiced mob programming to solve the Bank Account problem in Clojure.
This is our solution:
You can nitpick our solution here or see all the exercises I've done so far in this repository.
This is our solution:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns bank-account) | |
(defn open-account [] | |
(atom 0)) | |
(defn close-account [_]) | |
(defn get-balance [acct] | |
@acct) | |
(defn update-balance [account amount] | |
(swap! account + amount)) |
Monday, May 11, 2015
Friday, May 8, 2015
Interesting Panel: "TDD and Software Design"
I've just watched this wonderful discussion between Sandro Mancuso and J. B. Rainsberger about the relationship between TDD and Software Design which was organized by Carlos Blé:
Tuesday, May 5, 2015
Kata: String Calculator in Clojure
Last week we started working on the String Calculator kata at the Clojure Developers Barcelona meetup.
Finally, this week I found some time to finish it.
These are the tests using Midje:
The resulting code which is divided in several name spaces.
The string-calculator.core name space:
The string-calculator.numbers-parser which is where most of the logic lives:
The string-calculator.numbers-validation name space:
Finally, the string-calculator.numbers-filter name space:
I used a mix of TDD and REPL-driven development to code it.
To document the process I committed the code after every passing test and every refactoring.
This time I didn't commit the REPL history because I used Cursive and I didn't find how to save it. Apart from that, I really enjoyed the experience of using Cursive.
You can find the commits step by step here and the code in this repository in GitHub.
Finally, this week I found some time to finish it.
These are the tests using Midje:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns string-calculator.core-test | |
(:use midje.sweet) | |
(:use [string-calculator.core])) | |
(facts | |
"about string-calculator" | |
(fact | |
"It returns 0 for an empty string" | |
(add "") => 0) | |
(fact | |
"It returns the number itself when the string contains only a number" | |
(add "1") => 1 | |
(add "2") => 2) | |
(fact | |
"It adds strings containing several numbers separated by commas" | |
(add "1,2") => 3 | |
(add "1,2,3") => 6) | |
(fact | |
"It adds numbers separated by new lines and/or commas" | |
(add "1\n2,3") => 6) | |
(fact | |
"It can also use any given delimiter" | |
(add "//;\n1;2,3") => 6) | |
(fact | |
"It throws and exception when trying to add negative numbers" | |
(add "1,-2,3,-4") => (throws Exception | |
"Detected negative numbers: -2, -4")) | |
(fact | |
"It ignores any number greater than 1000" | |
(add "4,5,1001,3") => 12) | |
(fact | |
"It can use delimiters of any length" | |
(add "//[***]\n1***2***3") => 6) | |
(fact | |
"It can use multiple delimiters of any length" | |
(add "//[***][%%]\n1***2%%3,4") => 10)) |
The string-calculator.core name space:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns string-calculator.core | |
(:require [string-calculator.numbers-parser :as numbers-parser] | |
[string-calculator.numbers-validation :as numbers-validation] | |
[string-calculator.numbers-filter :as numbers-filter])) | |
(def ^:private sum (partial apply +)) | |
(defn add [input-str] | |
(-> input-str | |
numbers-parser/parse | |
numbers-validation/validate | |
numbers-filter/remove-too-big-numbers | |
sum)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns string-calculator.numbers-parser | |
(:require [clojure.string :as string])) | |
(def ^:private default-delimiters ["," "\n"]) | |
(def ^:private escaped-chars-by-metachar | |
(let [esc-chars "()*&^%$#!"] | |
(zipmap esc-chars | |
(map #(str "\\" %) esc-chars)))) | |
(defn- escape-meta-characters [delimiters-str] | |
(reduce str (map #(get escaped-chars-by-metachar % %) | |
delimiters-str))) | |
(defn- get-matches [pattern string] | |
(mapcat (partial drop 1) (re-seq pattern string))) | |
(defn- extract-delimiters [delimiters-str] | |
(let [delimiters (get-matches #"\[(.*?)\]" delimiters-str)] | |
(if (empty? delimiters) | |
delimiters-str | |
delimiters))) | |
(defn- create-delimiters-pattern [delimiters-str] | |
(->> delimiters-str | |
extract-delimiters | |
(concat default-delimiters) | |
(string/join "|") | |
escape-meta-characters | |
re-pattern)) | |
(defn- numbers-and-delimiters-pattern [input] | |
(let [delimiters-and-numbers (get-matches #"//(.+)\n(.*)" input)] | |
[(or (second delimiters-and-numbers) input) | |
(create-delimiters-pattern | |
(or (first delimiters-and-numbers) ""))])) | |
(defn- extract-nums-str [input-str] | |
(apply string/split | |
(numbers-and-delimiters-pattern input-str))) | |
(defn parse [input-str] | |
(if (string/blank? input-str) | |
[0] | |
(map #(Integer/parseInt %) | |
(extract-nums-str input-str)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns string-calculator.numbers-validation) | |
(def ^:private any-negative? | |
(partial not-every? #(>= % 0))) | |
(defn- throw-negative-numbers-exception [numbers] | |
(throw | |
(Exception. | |
(str "Detected negative numbers: " | |
(clojure.string/join ", " (filter neg? numbers)))))) | |
(defn validate [numbers] | |
(if (any-negative? numbers) | |
(throw-negative-numbers-exception numbers) | |
numbers)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns string-calculator.numbers-filter) | |
(defn remove-too-big-numbers [numbers] | |
(remove #(> % 1000) numbers)) |
To document the process I committed the code after every passing test and every refactoring.
This time I didn't commit the REPL history because I used Cursive and I didn't find how to save it. Apart from that, I really enjoyed the experience of using Cursive.
You can find the commits step by step here and the code in this repository in GitHub.
Thursday, April 30, 2015
Interesting Talk: "Developing ClojureScript With Figwheel"
I've just watched this wonderful talk by Bruce Hauman:
Books I read (January - April 2015)
January
- Software Architecture for Developers, Simon Brown
- Functional Programming Patterns in Scala and Clojure, Michael Bevilacqua-Linn
- Working Effectively with Unit Tests, Jay Fields
February
- Vida y Destino (Жизнь и судьба), Vasili Grossman. (2nd time)
- Primer Libro de Lankhmar (The First Book of Lankhmar), Fritz Leiber
- Drown, Junot Díaz
- Los girasoles ciegos, Alberto Méndez
- Smalltalk Best Practice Patterns, Kent Beck
March
- Las puertas del paraiso (The Vagrants), Yiyun Li
- Growing Object-Oriented Software Guided by Tests, Steve Freeman and Nat Pryce. (2nd time)
- The Joy of Clojure, 2nd edition, Michael Fogus and Chris Houser
April
- Las ciudades carnales (Les cités charnelles), Zoé Oldenbourg
- Refactoring: Improving the Design of Existing Code, Fowler, Beck, Brant, Opdyke and Roberts. (2nd time)
- Hasta aquí hemos llegado (Τίτλοι τέλους), Petros Márkaris
- The Childhood of Jesus, J. M. Coetzee
- Software Architecture for Developers, Simon Brown
- Functional Programming Patterns in Scala and Clojure, Michael Bevilacqua-Linn
- Working Effectively with Unit Tests, Jay Fields
February
- Vida y Destino (Жизнь и судьба), Vasili Grossman. (2nd time)
- Primer Libro de Lankhmar (The First Book of Lankhmar), Fritz Leiber
- Drown, Junot Díaz
- Los girasoles ciegos, Alberto Méndez
- Smalltalk Best Practice Patterns, Kent Beck
March
- Las puertas del paraiso (The Vagrants), Yiyun Li
- Growing Object-Oriented Software Guided by Tests, Steve Freeman and Nat Pryce. (2nd time)
- The Joy of Clojure, 2nd edition, Michael Fogus and Chris Houser
April
- Las ciudades carnales (Les cités charnelles), Zoé Oldenbourg
- Refactoring: Improving the Design of Existing Code, Fowler, Beck, Brant, Opdyke and Roberts. (2nd time)
- Hasta aquí hemos llegado (Τίτλοι τέλους), Petros Márkaris
- The Childhood of Jesus, J. M. Coetzee
Interesting Talk: "The Art of the Javascript Metaobject Protocol"
I've jut watched this great talk by Reginald Braithwaite:
Clojure Developers Barcelona: Short talk about sequence comprehensions
Last Tuesday I gave a short talk about sequence comprehensions in Clojure for some of the Clojure Developers Barcelona study group members.
This is the "clean version" of the code I wrote on Lightable to explain it:
As usual I had a great time and learned a lot preparing this talk.
After the talk, we talked about topics we'd like to learn about in the next months and the started to practice in pairs programming the String Calculator kata.
Thanks all for coming!
This is the "clean version" of the code I wrote on Lightable to explain it:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;-------------- | |
;; | |
;; Sequence comprehensions | |
;; | |
;;-------------- | |
;; Clojure generalizes the notion of list comprehension to sequence comprehension | |
;; Clojure comprehension uses the for macro | |
;; It takes a vector of binding-form - coll-expr (generator), | |
;; plus optional filtering expressions | |
;; and then yields a sequence of expressions | |
;; (for [binding-form coll-expr filter-expr? ...] expr) | |
;; 1. Generators | |
;; 1.1. An example using only one generator | |
(def three-digits (seq [1 2 3])) | |
(for [x1 three-digits] | |
x1) | |
(def three-letters ["A" "B" "C"]) | |
;; 1.2. An example using two generators | |
(for [x1 three-letters | |
x2 three-digits] | |
[x1 x2]) | |
;; | |
;; 2. :when clause | |
;; | |
;; It filters the elements that are used in the expression | |
(for [x1 three-digits | |
x2 three-digits | |
:when (and (even? x1) (odd? x2))] | |
[x1 x2 (* x1 x2)]) | |
(defn neighbors [[x-cell y-cell]] | |
(set (for [x (range (dec x-cell) (+ x-cell 2)) | |
y (range (dec y-cell) (+ y-cell 2)) | |
:when (not (and (= x x-cell) (= y y-cell)))] | |
[x y]))) | |
(neighbors [0 0]) | |
;; | |
;; 3. :while clause | |
;; | |
;; The evaluation continues while its expression holds true | |
(def integers (iterate inc 0)) | |
(for [n integers | |
:while (even? n)] n) | |
(for [n integers | |
:while (odd? n)] n) | |
;; | |
;; 4. :let allows you to make bindings with derived values | |
;; | |
(for [x1 three-digits | |
x2 three-digits] | |
(* x1 x2)) | |
(for [x1 three-digits | |
x2 three-digits | |
:let [y (* x1 x2)] | |
:when (> y 5)] | |
y) | |
;; Example from Michiel Borkent answer to a question on Stack Overflow (see Reference 2) | |
;;--------------------------------- | |
;; Although | |
(for [i (range 10) | |
:let [x (* i 2)]] | |
x) | |
;; is equivalent to: | |
(for [i (range 10)] | |
(let [x (* i 2)] | |
x)) | |
;; you can see the difference when used in combination with :when (or :while): | |
(for [i (range 10) | |
:let [x (* i 2)] | |
:when (> i 5)] | |
[i x]) | |
(for [i (range 10)] | |
(let [x (* i 2)] | |
(when (> i 5) x))) | |
;;--------------------------------- | |
;; | |
;; 5. List comprehensions are more general than filter and map, and can in fact emulate them | |
;; | |
;; 5.1 Map | |
(map #(* % %) (range 1 10)) | |
(for [num (range 1 10)] | |
(* num num)) | |
;; 5.2 Filter (using :when) | |
(filter odd? (range 1 10)) | |
(for [num (range 1 10) | |
:when (odd? num)] | |
num) | |
;; 5.3 More than map and filter | |
;(map #(* % %) (filter #(< % 10) integers)) ; <- can't run! | |
(for [num integers | |
:while (< num 10) | |
:when (odd? num)] | |
(* num num)) | |
;; References | |
;; 1. Programming Clojure, 2nd edition. Stuart Halloway and Aaron Bedra | |
;; 2. Use of :let modifier in Clojure, Stack Overflow (http://bit.ly/1Iq2Hld) | |
;; 3. Project Euler: Problem 4 in Clojure, Leonardo Borges (http://bit.ly/1Et2rPz) | |
;; 4. for - clojure.core in ClojureDocs (https://clojuredocs.org/clojure.core/for) |
After the talk, we talked about topics we'd like to learn about in the next months and the started to practice in pairs programming the String Calculator kata.
Thanks all for coming!
Monday, April 20, 2015
Interesting Talk: "Why You Don't Get Mock Objects"
I've just watched this wonderful talk by Gregory Moeck:
Saturday, April 18, 2015
Kata: Gilded Rose in Clojure (III) -> Updating conjured items by decoration
After the two previous posts (Clarifying conditional logic and Replacing conditional with polymorphism using multimethods), I had this version of the code:
that was being used from the gilded-rose.core name space:
Then I started introducing the new conjured items functionality using TDD.
These are the new tests for conjured items:
and these is the resulting code of the gilded-rose.item-quality name space:
Notice the change in the update multimethod dispatch function. Now instead of being :name as before, it's the function type-of-item that returns the dispatch value :conjured if the item is a conjured one (i. e., if its name contains "Conjured"), or the type of item corresponding to each item name otherwise (which it looks up in the item-types-by-name map).
I also added a defmethod for the :conjured dispatch values which decorates update by calling it twice passing the not conjured version of the item and modified the other defmethod functions to use the type of item instead of its name. This made possible a better way of removing the duplication for regular items than the previous update-regular-item-quality private function.
This simple decoration made all the tests shown before pass, except the "Conjured Sulfuras is still immutable" one. For this test to pass I had to modify the degradable-item? query in the gilded-rose.core name space:
That's all. You can follow the whole process I've just described having a look at the commits I did after every small refactoring (look at commits from Conjured items quality decreases by two each day before sell date on)
Starting from the polymorphic version of update, we had got through refactoring, made it easy to add the new conjured items functionality as a decoration of update.
Compare this Clojure version of Gilded Rose with the Java version I did some time ago.
This is the last post in this series about the Gilded Rose kata in Clojure:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns gilded-rose.item-quality) | |
(defn- update-quality [item value] | |
(merge item {:quality value})) | |
(defn- increase-quality [{:keys [quality] :as item} times] | |
(update-quality item (min 50 (reduce + quality (repeat times 1))))) | |
(defn- decrease-quality [{:keys [quality] :as item} times] | |
(update-quality item (max 0 (reduce - quality (repeat times 1))))) | |
(defn- set-quality-to-zero [{:keys [quality] :as item}] | |
(update-quality item 0)) | |
(defn- after-selling-date? [{sell-in :sell-in}] | |
(< sell-in 0)) | |
(defn- ten-or-more-days-to-selling-date? [{sell-in :sell-in}] | |
(>= sell-in 10)) | |
(defn- between-days-to-selling-date? [lower higher {sell-in :sell-in}] | |
(and (>= sell-in lower) (< sell-in higher))) | |
(defn- update-regular-item-quality [item] | |
(if (after-selling-date? item) | |
(decrease-quality item 2) | |
(decrease-quality item 1))) | |
(defmulti update :name) | |
(defmethod update :default [item] | |
item) | |
(defmethod update "Aged Brie" [item] | |
(increase-quality item 1)) | |
(defmethod update "Backstage passes to a TAFKAL80ETC concert" [item] | |
(cond | |
(ten-or-more-days-to-selling-date? item) (increase-quality item 1) | |
(between-days-to-selling-date? 5 10 item) (increase-quality item 2) | |
(between-days-to-selling-date? 0 5 item) (increase-quality item 3) | |
(after-selling-date? item) (set-quality-to-zero item) | |
:else item)) | |
(defmethod update "+5 Dexterity Vest" [item] | |
(update-regular-item-quality item)) | |
(defmethod update "Elixir of the Mongoose" [item] | |
(update-regular-item-quality item)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns gilded-rose.core | |
(:require [gilded-rose.item-quality :refer [update]])) | |
(defn- degradable-item? [{name :name}] | |
(not= "Sulfuras, Hand of Ragnaros" name)) | |
(defn- age-one-day [{sell-in :sell-in :as item}] | |
(merge item {:sell-in (dec sell-in)})) | |
(def ^:private all-age-one-day | |
(partial map #(if (degradable-item? %) (age-one-day %) %))) | |
(defn update-quality [items] | |
(map update (all-age-one-day items))) | |
(defn item [item-name, sell-in, quality] | |
{:name item-name, :sell-in sell-in, :quality quality}) | |
(defn update-current-inventory[] | |
(let [inventory | |
[(item "+5 Dexterity Vest" 10 20) | |
(item "Aged Brie" 2 0) | |
(item "Elixir of the Mongoose" 5 7) | |
(item "Sulfuras, Hand of Ragnaros" 0 80) | |
(item "Backstage passes to a TAFKAL80ETC concert" 15 20)]] | |
(update-quality inventory))) |
These are the new tests for conjured items:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#... | |
#... | |
(facts | |
"Conjured items" | |
(fact | |
"Quality decreases by two each day before sell date" | |
(pass-days | |
2 | |
[(item "Conjured Elixir of the Mongoose" 17 20)]) | |
=> [(item "Conjured Elixir of the Mongoose" 15 16)]) | |
(fact | |
"Quality decreases by four each day after sell date" | |
(pass-days | |
2 | |
[(item "Conjured Elixir of the Mongoose" 0 20)]) | |
=> [(item "Conjured Elixir of the Mongoose" -2 12)]) | |
(fact | |
"Quality can't be less than zero" | |
(pass-days | |
1 | |
[(item "Conjured Elixir of the Mongoose" 2 1)]) | |
=> [(item "Conjured Elixir of the Mongoose" 1 0)]) | |
(fact | |
"Conjured Sulfuras is still immutable" | |
(pass-days | |
1 | |
[(item "Conjured Sulfuras, Hand of Ragnaros" 0 80)]) | |
=> [(item "Conjured Sulfuras, Hand of Ragnaros" 0 80)]) | |
(fact | |
"Conjured Aged Brie quality increases by two eachs day before sell date" | |
(pass-days | |
2 | |
[(item "Conjured Aged Brie" 2, 0)]) | |
=> [(item "Conjured Aged Brie" 0 4)]) | |
(fact | |
"Conjured Aged Brie quality also increases by two eachs day after sell date" | |
(pass-days | |
2 | |
[(item "Conjured Aged Brie" 0, 0)]) | |
=> [(item "Conjured Aged Brie" -2 4)]) | |
(fact | |
"Quality can't be greater than 50" | |
(pass-days | |
100 | |
[(item "Conjured Aged Brie" 100, 0)]) | |
=> [(item "Conjured Aged Brie" 0 50)]) | |
(fact | |
"Conjured Backstage Passes quality increases twice faster before sell date" | |
(pass-days | |
15 | |
[(item "Conjured Backstage passes to a TAFKAL80ETC concert" 15, 0)]) | |
=> [(item "Conjured Backstage passes to a TAFKAL80ETC concert" 0 50)]) | |
(fact | |
"Conjured Backstage Passes quality is zero after sell date" | |
(pass-days | |
16 | |
[(item "Conjured Backstage passes to a TAFKAL80ETC concert" 15, 0)]) | |
=> [(item "Conjured Backstage passes to a TAFKAL80ETC concert" -1 0)]))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns gilded-rose.item-quality) | |
(defn- update-quality [item value] | |
(assoc item :quality value)) | |
(defn- increase-quality [{:keys [quality] :as item} times] | |
(update-quality item (min 50 (reduce + quality (repeat times 1))))) | |
(defn- decrease-quality [{:keys [quality] :as item} times] | |
(update-quality item (max 0 (reduce - quality (repeat times 1))))) | |
(defn- set-quality-to-zero [item] | |
(update-quality item 0)) | |
(defn- after-selling-date? [{sell-in :sell-in}] | |
(< sell-in 0)) | |
(defn- ten-or-more-days-to-selling-date? [{sell-in :sell-in}] | |
(>= sell-in 10)) | |
(defn- between-days-to-selling-date? [lower higher {sell-in :sell-in}] | |
(and (>= sell-in lower) (< sell-in higher))) | |
(defn- type-of-item [{name :name}] | |
(let [item-types-by-name | |
{"Aged Brie" :aged-brie | |
"Backstage passes to a TAFKAL80ETC concert" :backstage-pass | |
"+5 Dexterity Vest" :regular-item | |
"Elixir of the Mongoose" :regular-item}] | |
(if (.contains name "Conjured") | |
:conjured | |
(item-types-by-name name)))) | |
(defmulti update type-of-item) | |
(defmethod update :conjured [{name :name :as item}] | |
(let | |
[not-conjured-item-name (clojure.string/replace name #"Conjured " "") | |
not-conjured-item (assoc item :name not-conjured-item-name)] | |
(assoc (update (update not-conjured-item)) | |
:name name))) | |
(defmethod update :default [item] | |
item) | |
(defmethod update :aged-brie [item] | |
(increase-quality item 1)) | |
(defmethod update :backstage-pass [item] | |
(cond | |
(ten-or-more-days-to-selling-date? item) (increase-quality item 1) | |
(between-days-to-selling-date? 5 10 item) (increase-quality item 2) | |
(between-days-to-selling-date? 0 5 item) (increase-quality item 3) | |
(after-selling-date? item) (set-quality-to-zero item) | |
:else item)) | |
(defmethod update :regular-item [item] | |
(if (after-selling-date? item) | |
(decrease-quality item 2) | |
(decrease-quality item 1))) |
I also added a defmethod for the :conjured dispatch values which decorates update by calling it twice passing the not conjured version of the item and modified the other defmethod functions to use the type of item instead of its name. This made possible a better way of removing the duplication for regular items than the previous update-regular-item-quality private function.
This simple decoration made all the tests shown before pass, except the "Conjured Sulfuras is still immutable" one. For this test to pass I had to modify the degradable-item? query in the gilded-rose.core name space:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns gilded-rose.core | |
(:require [gilded-rose.item-quality :refer [update]])) | |
(defn- degradable-item? [{name :name}] | |
(not (.contains name "Sulfuras, Hand of Ragnaros"))) | |
(defn- age-one-day [{sell-in :sell-in :as item}] | |
(merge item {:sell-in (dec sell-in)})) | |
(def ^:private all-age-one-day | |
(partial map #(if (degradable-item? %) (age-one-day %) %))) | |
(defn update-quality [items] | |
(map update (all-age-one-day items))) | |
(defn item [item-name, sell-in, quality] | |
{:name item-name, :sell-in sell-in, :quality quality}) | |
(defn update-current-inventory[] | |
(let [inventory | |
[(item "+5 Dexterity Vest" 10 20) | |
(item "Aged Brie" 2 0) | |
(item "Elixir of the Mongoose" 5 7) | |
(item "Sulfuras, Hand of Ragnaros" 0 80) | |
(item "Backstage passes to a TAFKAL80ETC concert" 15 20)]] | |
(update-quality inventory))) |
Starting from the polymorphic version of update, we had got through refactoring, made it easy to add the new conjured items functionality as a decoration of update.
Compare this Clojure version of Gilded Rose with the Java version I did some time ago.
This is the last post in this series about the Gilded Rose kata in Clojure:
Kata: Gilded Rose in Clojure (II) -> Replacing conditional with polymorphism using multimethods
At the end of the previous post (Clarifying conditional logic), I had this version of the code:
Even though the conditional logic in the update-item-quality function read much better than the original one, I completely got rid of it using the replace conditional with polymorphism refactoring. I used multimethods which is one of the ways of achieving polymorphism in Clojure.
To start this refactoring, I first renamed the update-item-quality function to update-item-quality-old. Then I created a multimethod called update-item-quality with :name as dispatch function and with a method associated to its default dispatch that called the update-item-quality-old function:
This new version behaved exactly like the previous one and it was a good base to do the refactoring in small steps (one branch at a time) because every dispatch value not associated yet with a function was calling the old code thanks to the default dispatch.
Next I wrote a defmethod associated to the Age Brie item, copied the code from the Age Brie branch in update-item-quality-old into it and deleted the branch and the aged-brie? query helper. After this change all the tests were still passing.
Then I continued eliminating the rest of the branches in the cond of the update-item-quality-old function and their associated query functions.
Once I finished wit all the branches, I had replaced the conditional with polymorphism and could safely delete update-item-quality-old. I also changed the method associated with the default dispatch to make it just returned the received item (this was what got executed for the Sulfuras item and it corresponded to the :else branch of the cond in the old code).
To finish the refactoring I extracted the update-regular-item-quality helper to remove some duplication in some defmethods and moved the code that updated the quality into a separate name space, gilded-rose.item-quality:
You can follow the whole process I've just described having a look at the commits I did after every small refactoring (look at commits between Introduced a multimethod which now it's defaulting to the old update item quality function and Added helper to make all items age one day)
After this refactoring I had a nice polymorphic solution that made it easier to add the new conjured items functionality.
The next and last post will show how I modified the multimethod dispatch function to decorate the update quality behavior when it was updating a conjured item.
This is the second post in a series of posts about the Gilded Rose kata in Clojure:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns gilded-rose.core) | |
(defn- regular? [{name :name}] | |
(or (= "+5 Dexterity Vest" name) | |
(= "Elixir of the Mongoose" name))) | |
(defn- aged-brie? [{name :name}] | |
(= name "Aged Brie")) | |
(defn- backstage-passes? [{name :name}] | |
(= name "Backstage passes to a TAFKAL80ETC concert")) | |
(defn- increase-quality [{:keys [quality] :as item} times] | |
(merge item | |
{:quality (min 50 (reduce + quality (repeat times 1)))})) | |
(defn- decrease-quality [{:keys [quality] :as item} times] | |
(merge item | |
{:quality (max 0 (reduce - quality (repeat times 1)))})) | |
(defn- set-quality-to-zero [{:keys [quality] :as item}] | |
(merge item {:quality 0})) | |
(defn- after-selling-date? [{sell-in :sell-in}] | |
(< sell-in 0)) | |
(defn- ten-or-more-days-to-selling-date? [{sell-in :sell-in}] | |
(>= sell-in 10)) | |
(defn- between-days-to-selling-date? [lower higher {sell-in :sell-in}] | |
(and (>= sell-in lower) (< sell-in higher))) | |
(defn- update-item-quality [item] | |
(cond | |
(aged-brie? item) (increase-quality item 1) | |
(backstage-passes? item) | |
(cond | |
(ten-or-more-days-to-selling-date? item) (increase-quality item 1) | |
(between-days-to-selling-date? 5 10 item) (increase-quality item 2) | |
(between-days-to-selling-date? 0 5 item) (increase-quality item 3) | |
(after-selling-date? item) (set-quality-to-zero item) | |
:else item) | |
(regular? item) | |
(if (after-selling-date? item) | |
(decrease-quality item 2) | |
(decrease-quality item 1)) | |
:else item)) | |
(defn- degradable-item? [{name :name}] | |
(not= "Sulfuras, Hand of Ragnaros" name)) | |
(defn- age-one-day [{sell-in :sell-in :as item}] | |
(merge item {:sell-in (dec sell-in)})) | |
(def ^:private all-age-one-day | |
(partial map #(if (degradable-item? %) (age-one-day %) %))) | |
(defn update-quality [items] | |
(map update-item-quality | |
(all-age-one-day items))) | |
(defn item [item-name, sell-in, quality] | |
{:name item-name, :sell-in sell-in, :quality quality}) | |
(defn update-current-inventory[] | |
(let [inventory | |
[(item "+5 Dexterity Vest" 10 20) | |
(item "Aged Brie" 2 0) | |
(item "Elixir of the Mongoose" 5 7) | |
(item "Sulfuras, Hand of Ragnaros" 0 80) | |
(item "Backstage passes to a TAFKAL80ETC concert" 15 20)]] | |
(update-quality inventory))) |
To start this refactoring, I first renamed the update-item-quality function to update-item-quality-old. Then I created a multimethod called update-item-quality with :name as dispatch function and with a method associated to its default dispatch that called the update-item-quality-old function:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#... code before | |
#... | |
(defn- update-item-quality-old [item] | |
(cond | |
(aged-brie? item) (increase-quality item 1) | |
(backstage-passes? item) | |
(cond | |
(ten-or-more-days-to-selling-date? item) (increase-quality item 1) | |
(between-days-to-selling-date? 5 10 item) (increase-quality item 2) | |
(between-days-to-selling-date? 0 5 item) (increase-quality item 3) | |
(after-selling-date? item) (set-quality-to-zero item) | |
:else item) | |
(regular? item) | |
(if (after-selling-date? item) | |
(decrease-quality item 2) | |
(decrease-quality item 1)) | |
:else item)) | |
(defmulti update-item-quality :name) | |
(defmethod update-item-quality :default [item] | |
(update-item-quality-old item)) | |
#... | |
#... code after |
Next I wrote a defmethod associated to the Age Brie item, copied the code from the Age Brie branch in update-item-quality-old into it and deleted the branch and the aged-brie? query helper. After this change all the tests were still passing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ... code before | |
# ... | |
(defn- update-item-quality-old [item] | |
(cond | |
(backstage-passes? item) | |
(cond | |
(ten-or-more-days-to-selling-date? item) (increase-quality item 1) | |
(between-days-to-selling-date? 5 10 item) (increase-quality item 2) | |
(between-days-to-selling-date? 0 5 item) (increase-quality item 3) | |
(after-selling-date? item) (set-quality-to-zero item) | |
:else item) | |
(regular? item) | |
(if (after-selling-date? item) | |
(decrease-quality item 2) | |
(decrease-quality item 1)) | |
:else item)) | |
(defmulti update-item-quality :name) | |
(defmethod update-item-quality :default [item] | |
(update-item-quality-old item)) | |
(defmethod update-item-quality "Aged Brie" [item] | |
(increase-quality item 1)) | |
#... | |
#... code after |
Once I finished wit all the branches, I had replaced the conditional with polymorphism and could safely delete update-item-quality-old. I also changed the method associated with the default dispatch to make it just returned the received item (this was what got executed for the Sulfuras item and it corresponded to the :else branch of the cond in the old code).
To finish the refactoring I extracted the update-regular-item-quality helper to remove some duplication in some defmethods and moved the code that updated the quality into a separate name space, gilded-rose.item-quality:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns gilded-rose.item-quality) | |
(defn- update-quality [item value] | |
(merge item {:quality value})) | |
(defn- increase-quality [{:keys [quality] :as item} times] | |
(update-quality item (min 50 (reduce + quality (repeat times 1))))) | |
(defn- decrease-quality [{:keys [quality] :as item} times] | |
(update-quality item (max 0 (reduce - quality (repeat times 1))))) | |
(defn- set-quality-to-zero [{:keys [quality] :as item}] | |
(update-quality item 0)) | |
(defn- after-selling-date? [{sell-in :sell-in}] | |
(< sell-in 0)) | |
(defn- ten-or-more-days-to-selling-date? [{sell-in :sell-in}] | |
(>= sell-in 10)) | |
(defn- between-days-to-selling-date? [lower higher {sell-in :sell-in}] | |
(and (>= sell-in lower) (< sell-in higher))) | |
(defn- update-regular-item-quality [item] | |
(if (after-selling-date? item) | |
(decrease-quality item 2) | |
(decrease-quality item 1))) | |
(defmulti update :name) | |
(defmethod update :default [item] | |
item) | |
(defmethod update "Aged Brie" [item] | |
(increase-quality item 1)) | |
(defmethod update "Backstage passes to a TAFKAL80ETC concert" [item] | |
(cond | |
(ten-or-more-days-to-selling-date? item) (increase-quality item 1) | |
(between-days-to-selling-date? 5 10 item) (increase-quality item 2) | |
(between-days-to-selling-date? 0 5 item) (increase-quality item 3) | |
(after-selling-date? item) (set-quality-to-zero item) | |
:else item)) | |
(defmethod update "+5 Dexterity Vest" [item] | |
(update-regular-item-quality item)) | |
(defmethod update "Elixir of the Mongoose" [item] | |
(update-regular-item-quality item)) |
After this refactoring I had a nice polymorphic solution that made it easier to add the new conjured items functionality.
The next and last post will show how I modified the multimethod dispatch function to decorate the update quality behavior when it was updating a conjured item.
This is the second post in a series of posts about the Gilded Rose kata in Clojure:
Subscribe to:
Posts (Atom)