Ping Ring del 3: Ruby


tirsdag 7. september 2010 Diverse prosjekter Ping Ring Ruby Samtidighet

Dette er del 2 i artikkelserien Ping Ring hvor jeg implementerer et og samme program i et utall ulike programmeringsspråk - for å se om det er noe å lære gjennom å gjøre det. Introduksjonen kan du lese her.

Neste språk ut er Ruby. Denne implementasjonen er logisk sett identisk med C#-varianten fra del 2. TCP-logikken er ørlite enklere (jeg trenger ingen StreamReader/Writer), og Ruby's kodeblokker gjør tråd-logikken mere elegant å sette opp enn med C#'s lambda-uttrykk pluss et kall til start(). Men i bunn og grunn er det liten forskjell i hvor enkelt det er å løse oppgaven.

Hovedforskjellen er syntaksen. Ruby's dynamiske typing gjør at vi unngår endel av hva jeg vil kalle støy. Syntaksen føles mere "komprimert" i Ruby, og det ser man igjen i bl.a. færre linjer kode. Min Ruby-variant "klokker seg inn" på 59 linjer – mot C# sine 90.

1 require 'socket'
2
3 class RingServer
4   def initialize
5     @self_port = ARGV.shift
6     @other_port = ARGV.shift
7     @max_delay = ARGV.shift.to_i
8     @should_send_startup_ping = ARGV.shift == "true"
9     @last_ping_time = Time.now
10     puts "** Ruby Ring Server (#{@self_port})"
11   end
12   def start
13     send_delayed_ping if @should_send_startup_ping
14     start_missing_ping_alert_thread
15     start_ping_listener_thread
16     Thread.list.each { |t| t.join unless t == Thread.main }
17   end
18   def send_delayed_ping
19     Thread.new do
20       sleep 1
21       begin
22         client = TCPSocket.new('127.0.0.1', @other_port)
23         client.print "Ping from #{@self_port}"
24       rescue
25         puts "*** Failed sending ping: #{$!}"
26       else
27         client.close
28       end     
29     end
30   end
31   def start_missing_ping_alert_thread
32     Thread.new do
33       while true
34         sleep 5
35         ping_delay = Time.now - @last_ping_time
36         if ping_delay > @max_delay
37           puts "*** ALERT, RING BROKEN! No ping in #{ping_delay} seconds."
38           send_delayed_ping # try to wake up ring
39         end
40       end 
41     end
42   end
43   def start_ping_listener_thread
44     Thread.new do
45       listener = TCPServer.new('127.0.0.1', @self_port)
46       while session = listener.accept
47         process_incoming_ping session.gets
48         session.close
49       end
50     end
51   end
52   def process_incoming_ping message
53     @last_ping_time = Time.now
54     puts "Received #{message}"
55     send_delayed_ping
56   end
57 end
58
59 RingServer.new.start

Det vil være riktig av deg å påpeke at jeg ikke bruker blanke linjer på samme måte i denne koden som jeg gjorde i C#, og at det derfor er naturlig at det blir færre linjer. Men ta en titt nedenfor, hvor jeg har satt opp de to implementasjonene side om side. Her har Ruby-varianten fått seg endel linjeskift, slik at metodene som gjør det samme starter på samme linjenummer.

pingring_csharp_vs_ruby

Det er ikke tvil om hvilken versjon jeg synes er mest elegant. Men elegansen ligger altså i at C#-syntaksen har for mye støy og staffasje, ikke at det var spesielt mye enklere å implemetere i Ruby.

Jeg må derimot få påpeke at Ruby gir meg større fleksibilitet i hvordan jeg ønsker å organisere koden. Jeg valgte å bruke den samme strukturen som C#-varianten denne gangen; én klasse med seks metoder som løste helt konkrete behov. Jeg kunne derimot for eksempel ha valgt en klasse-løs implementasjon – uten å miste noe lesbarhet av betydning. Ruby egner seg etter mening veldig godt til "små skripts" som dette, hvor kravet til "innpakning" (encapsulation) ikke er så stort.

Alt i alt: Ikke en veldig stor forskjell, men Ruby trekker det lengste strådet! I neste del vil du få se programmet i et språk som har enda finere og mere kompakt syntaks, så følg med..

Kildekoden fra denne blogposten er tilgjengelig på Github. Der står du fritt til å forgrene løsningen og gjøre egne modifikasjoner om du ønsker det (for å illustrere et poeng eller lignende). Som alt annet på bloggen er koden lisensiert under Creative Commons.


comments powered by Disqus