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.
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.
Subscribe to:
Posts (Atom)