Lately the pratice of Kata seems spreading out very quickly, thanks to work of well known Software Craftsmen as Corey Haynes or the Clean Code evangelist Uncle Bob.
It all started waiting for a kid’s karate lesson, and now it’s a well know practice to become a better developer.
Basicly a code kata is a small problem to be resolved without any pressure and requests, but to play with different techniques or programming language.
Another interesting point of view considers a code kata as a training for muscles of memory, focusing on repetitions, memorizations of decisions and keyboard shortcuts; the point is: if I get trained to do design decisions at subconscious level, when I’ll met similar problems I’ll be very productive, doing the right decision without any logical think.
Amazing, isn’t it?
The main argument of last Milan Xpug meeting was the Birthday Greetings Kata, a workshop Matteo Vaccari will submit to next Xp Days Benelux 2009.
Unfortunately I couldn’t be present, but I implemented the kata on my own, using Ruby instead of Java.
I enjoyed it very much, and I start thinking to try it still a couple of times and then screencasting it: I’m far behind this level, but review my actions can help me get better.
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
require 'rumbster'
require 'message_observers'
require 'net/smtp'
require 'gserver'
require 'birthday_service'
require 'employee_repository'
describe "Greetings Service" do
NON_STANDARD_PORT = 10015
def send_message(to, message)
Net::SMTP.start('localhost', NON_STANDARD_PORT) do |smtp|
smtp.send_message message, 'your@mail.address', to
end
end
before :each do
@rumbster = Rumbster.new(NON_STANDARD_PORT)
@message_observer = MailMessageObserver.new
@rumbster.add_observer @message_observer
@rumbster.start
@birthdayService = BirthdayService.new("localhost", NON_STANDARD_PORT);
end
after :each do
@rumbster.stop
end
context "with a file with one person born today" do
before :each do
@birthdayService.send_greetings EmployeeRepository.new("employee_data.txt"), "2008/10/08"
end
it "should send one email" do
@message_observer.messages.size.should == 1
end
it "should send correct message" do
@message_observer.messages.first.subject.should == "Happy Birthday!"
@message_observer.messages.first.body.chomp.should == "Happy Birthday, dear John!"
@message_observer.messages.first.to.should == ["john.doe@foobar.com"]
end
end
end
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
require "employee_repository"
describe "EmployeeRepository" do
it "should read Employees from a file" do
repository = EmployeeRepository.new "employee_data.txt"
end
it "should have a well know size" do
repository = EmployeeRepository.new "employee_data.txt"
repository.size.should == 3
end
it "should return first employee" do
repository = EmployeeRepository.new "employee_data.txt"
expected_employee = Employee.new("Doe, John, 1982/10/08, john.doe@foobar.com")
repository.first.should == expected_employee
end
it "should return employees born an a given date" do
repository = EmployeeRepository.new "employee_data.txt"
expected_employee = Employee.new("Doe, John, 1982/10/08, john.doe@foobar.com")
repository.born_on('2008/10/08').first.should == expected_employee
end
end
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
require "message"
require "employee"
describe "Message" do
before :each do
@message = Message.new Employee.new("Doe, John, 1982/10/08, john.doe@foobar.com")
end
it "should have employee's email as destination" do
@message.to.should == "john.doe@foobar.com"
end
it "should construct a complete body" do
@message.body.should == "To: john.doe@foobar.com\nSubject: Happy Birthday!\n\nHappy Birthday, dear John!"
end
end
require "message"
class BirthdayService
def initialize(host, port)
@host = host
@port = port
end
def send_greetings(employees, date)
employees.born_on(date).each do |employee|
send_message(Message.new employee)
end
end
def send_message(message)
Net::SMTP.start(@host, @port) do |smtp|
smtp.send_message message.body, 'your@mail.address', message.to
end
end
end
class Employee
attr_reader :firstname
attr_reader :lastname
attr_reader :birthdate
attr_reader :email
def initialize(args)
tokens = args.split(',').map { |e| e.strip }
@firstname = tokens[1]
@lastname = tokens[0]
@birthdate = tokens[2]
@email = tokens[3]
end
def ==(other)
other.instance_of?(self.class) &&
@firstname == other.firstname &&
@lastname == other.lastname &&
@birthdate == other.birthdate &&
@email == other.email
end
end
require 'employee'
class String
def same_day?(other)
date1 = split('/')
date2 = other.split('/')
date1[1] == date2[1] && date1[2] == date2[2]
end
end
class EmployeeRepository
private
def skip_header
@employees.shift
end
public
def initialize(employees_filename)
@employees = []
File.open(employees_filename).each_line do |line|
@employees << Employee.new(line)
end
skip_header
end
def size
@employees.size
end
def first
@employees.first
end
def born_on(current_date)
@employees.find_all { |e| e.birthdate.same_day?(current_date) }
end
end
class Message
def initialize(employee)
@employee = employee
end
def to
@employee.email
end
def body
["To: #{to}",
"Subject: Happy Birthday!",
"",
"Happy Birthday, dear #{@employee.firstname}!"].join("\n")
end
end
Interesting I wrote more specs code than production code.
I do like I didn't used any IF, but I don't like to open String to model date and the use of split to tokenize things: I'll focus on thant in next attempt!


