RadRails i snippety TextMate

Opublikowane przez Jarosław Zabiełło Tue, 07 Nov 2006 23:13:00 GMT

RadRails, zintegrowany edytor dla Rubiego i Railsów zaczyna coraz bardziej niwelować wcześniejsze zachwyty nad komercyjnym edytorem TextMate. Nie dość, że jest darmowy i działa na Windows, MacOS-X i Linuksie (TextMate tylko na MacOS-X), to na dodatek uzyskał to, co do tej pory było główną atrakcją TextMate – makra przyśpieszające pisanie kodu, czyli tzw. snippety. RadRaila ma już przygotowanych 199 snippetów do Rubiego i 50 do RHTML!

Do tego wystarczy dorzucić kolorowanie kodu a’la TextMate i RadRails ma już wszystko (no może do pełni doskonałości brakuje mu uzupełniania kodu tak, jak to robi komercyjny edytor Komodo)

Posted in ,  | Tagi , , ,  | brak comments

Subversion /etc/init.d script

Opublikowane przez Jarosław Zabiełło Sat, 07 Oct 2006 00:57:00 GMT

Postanowiłem troszkę poprawić napisany wcześniej skrypt startowy do Subversion. Dorzuciłem też wersję w Pythonie.

#!/usr/bin/env ruby

VERBOSE = true
USER = "nobody"
APP = "/usr/bin/svnserve"
DIR = "/home/svn-repository"
PID = "/var/run/svnserve.pid"

def script action
  def start
    unless File.exists?(PID)
      system %Q|su -c "#{APP} -d -r #{DIR}" #{USER}|
      system "echo `pidof -o %PPID #{APP}` > #{PID}"
    end
  end
  def stop
    return false unless File.exists?(PID)
    system "/bin/kill -TERM #{File.read(PID)}"
    system "/bin/rm -f #{PID}"
    true
  end
  puts "#{APP} #{action}"  if VERBOSE
  $defout.flush # unbuffered output
  case action
    when :start
      start
    when :stop
      puts "#{File.basename(APP)} cannot be stopped because it cannot find #{PID}" unless stop
    when :restart
      stop
      start
  end
end

if %w{start stop restart}.include? ARGV.first
  script ARGV.first.to_sym
else
  puts "Usage: sudo #{__FILE__} {start|stop|restart}"
end

Ten sam skrypt w Pythonie:

#!/usr/bin/env python

import os, sys

VERBOSE = True
USER = "nobody"
APP = "/usr/bin/svnserve"
DIR = "/home/svn-repository"
PID = "/var/run/svnserve.pid"

def script(action):
  def start():
    if not os.path.exists(PID):
      os.system('su -c "%s -d -r %s" %s' % (APP, DIR, USER))
      os.system('echo `pidof -o %%PPID %s` > %s' % (APP, PID))
  def stop():
    if not os.path.exists(PID): return False
    os.system('/bin/kill -TERM %s' % file(PID).read())
    return True
  if VERBOSE: print "%s %s" % (APP, action),
  if action == 'start':
    start()
  elif action == 'stop':
    if not stop():
      print "%s cannot be stopped because it cannot find %s" % (os.path.basename(APP), PID)
  elif action == 'restart':
    stop()
    start()

if len(sys.argv) == 2 and sys.argv[1] in ('start', 'stop', 'restart'):
  script(sys.argv[1])
else:
  if VERBOSE:  print "Usage: sudo %s {start|stop|restart}" % sys.argv[0]

Posted in ,  | Tagi , ,  | 2 comments

Ruby i skrypt startowy do SVN

Opublikowane przez Jarosław Zabiełło Thu, 05 Oct 2006 14:37:00 GMT

Subversion to świetny, darmowy system wersjonowania kodu. Jest znacznie lepszy od starego CVS’a. Niestety, nigdzie nie mogłem się doszukać skryptu startującego do serwera. Co gorsze, skrytp svnserve w ogóle nie ma opcji zapisu numeru swojego procesu co utrudnia pisanie kodu który go “zabije”. Na szczęście Linux posiada komendę pidof. Poniżej gotowy skrypt napisany w Ruby. Należy go umieścić w /etc/init.d, dodać atrybuty wykonywalności (chmod a+x subversion.rb) i dodać linki symboliczne aby startowała utomatycznie razem ze startem serwera (pod Debianem/Ubuntu robi to komenda update-rc.d -f subversion.rb defaults, pod RedHatem/CentOS/Fedorą jest do tego komenda chkconfig)

#!/usr/bin/env ruby

$USER = "nobody"
$APP = "/usr/bin/svnserve"
$DIR = "/home/SVN"
$PID = "/var/run/svnserve.pid"

def script action
  def start 
    system(%Q|su -c "#{$APP} -d -r #{$DIR}" #{$USER}|)
    system(%Q|echo `pidof -o %PPID #{$APP}` > #{$PID}|)
  end
  def stop
    return false if not File.exists?($PID)
    system(%Q|/bin/kill -TERM #{File.read($PID)}|)
    true
  end
  print "#{$APP} #{action}..."
  $defout.flush # unbuffered output
  case action 
    when :start 
      start()
      puts "done"
    when :stop
      if not stop()
        puts "#{File.basename($APP)} cannot be stopped because it cannot find #{$PID}"
      else
        puts "done"
      end 
    when :restart
      stop()
      start()
      puts "done"
  end
end

case ARGV.first
  when "start": script :start
  when "stop": script :stop
  when "restart": script :restart
end

unless %w{start stop restart}.include? ARGV.first
  puts "Usage: sudo #{__FILE__} {start|stop|restart}"
  exit
end

Posted in  | Tagi , ,  | 2 comments

Uniksowy skrypt /etc/init.d w Ruby

Opublikowane przez Jarosław Zabiełło Fri, 29 Sep 2006 12:04:00 GMT

Ruby całkiem dobrze nadaje się do pisania skryptów systemowych. W sumie nie wiem dlaczego do tej pory pisałem skrypty tego typu w uniksowym bash’u. :)

#!/usr/bin/env ruby

MONGREL = "/usr/bin/mongrel_rails"
PROJECT = "/home/httpd/somedomain.com/rails_project"

def execute(action, project)
  Dir.chdir(project)
  print "#{MONGREL} #{action}..."
  $defout.flush # unbuffered output
  `#{MONGREL} cluster::#{action}`
  puts 'done'
end

case ARGV.first
  when 'start': execute 'start', PROJECT
  when 'stop': execute 'stop', PROJECT
  when 'restart': execute 'restart', PROJECT
end

unless %w{start stop restart}.include? ARGV.first
  puts "Usage: sudo #{__FILE__} {start|stop|restart}"
  exit
end

Posted in  | Tagi , ,  | 1 comment

Nowa strona Rubiego

Opublikowane przez Jarosław Zabiełło Tue, 12 Sep 2006 07:42:00 GMT

Strona domowa Rubiego zmieniała swój wygląd. Jest znacznie lepiej. :)

Posted in  | Tagi  | 5 comments

Sun zatrudnił programistów JRuby

Opublikowane przez Jarosław Zabiełło Fri, 08 Sep 2006 08:33:00 GMT

W środowisku javowców Ruby jest językiem który robi trochę zamieszania (jakoś tak się składa, że Ruby bardziej przemawia do programistów Javy niż Python). Cieszy zatem, że firma Sun podjęła decyzję o wsparciu projektu JRuby zatrudniając jego czołowych developerów. Powinno to znacznie przyśpieszyć prace nad tym projektem podobnie jak stało się z IronPythonem, gdy Microsoft zatrudnił jego twórcę.

Jestem ciekaw jak długo programiści zachowają entuzjazm dla swego języka, gdy będzie można generować taki sam bytecode Javy lecz w niezrównanie prostszy sposób. :)

Przykładowy kod Javy:

public class Filter {
  public static void main(String[] args) {
    List list = new java.util.ArrayList();
    list.add("Tim"); list.add("Ike"); list.add("Tina");
    Filter filter = new Filter();
    for (String item : filter.filterLongerThan(list, 3)) { 
      System.out.println( item ); 
    }
  }
  public List filterLongerThan(List list, int length) {
    List result = new ArrayList();
    for (String item : list) {
      if (item.length() <= length) { result.add( item ); }
    }
    return result;
  }
}

A oto odpowiadający mu kod w Ruby:

list = ['Tim', 'Ike', 'Tina']
list.select {|n| n.length > 3}.each {|n| puts n}

Oczywiście to nie wszystko. Dzięki JRuby można uzyskać efekty kompletnie nieosiągalne w standardowej Javie – np. można pracować z biblioteką Swing w sposób interaktywny, z poziomu interpretera zmieniając na żywo jej obiekty.

Wkrótce ma także być gotowa wersja Railsów działająca z JRuby (Zobacz prezentację w PowerPoint). Tym samym odeszłyby wszelkie uwagi co do wydajności Railsów, bo współczesna wirtualna maszyna Javy jest tak silnie zoptymalizowna że dorównuje językowi C++. Oczywiście model wątkowy JRuby jest zgodny z wydajnym i dojrzałym modelem wątkowym Javy – po prostu z niego korzysta.

Posted in ,  | Tagi , ,  | 32 comments

Mamy pl.comp.lang.ruby!

Opublikowane przez Jarosław Zabiełło Fri, 23 Jun 2006 07:13:00 GMT

Są już oficjalne wyniki głosowania na rzecz stworzenia grupy dyskusyjnej pl.comp.lang.ruby. 96 głosów za, 4 wstrzymujące się i 6 nieważnych. Nowa grupa powinna być wkrótce dostępna na serwerach usenetowych.

Posted in ,  | Tagi  | 3 comments

pl.comp.lang.ruby - głosowanie rozpoczęte!

Opublikowane przez Jarosław Zabiełło Sat, 03 Jun 2006 14:03:00 GMT

Po długim okresie oczekiwań w końcu doczekaliśmy się rozpoczęcia głosowania nad stworzeniem grupy pl.comp.lang.ruby. Oczywistą zaletą grupy newsowej jest m.in. to, że jest indeksowana i archiwizowana przez Google oraz jest bardziej odporna na “pady” serwera, bo posty są replikowane po wielu serwerach. Formalna treść ogłoszenia (wraz z informacją o sposobie głosowania) jest dostępna tutaj…

Posted in ,  | Tagi  | brak comments

JRuby on Rails - ruszył

Opublikowane przez Jarosław Zabiełło Sun, 14 May 2006 18:21:00 GMT

JRuby – projekt będący implementacją Rubiego w języku Java został przyśpieszony w związku z konferencją JavaOne. Developerom udało się w końcu uruchomić pełną aplikację Railsów działającą na wirtualnej maszynie Javy. Na razie jeszcze nie ma informacji o jakimś znaczącym przyśpieszeniu działania, ale pierwszy krok został dokonany. Zobacz więcej…

Zobacz też listę języków potrafiących pracować w javowskiej JVM – jest ich całkiem sporo. Kto by pomyślał, że nawet napisano assembler generujący klasy Javy. :)

Posted in ,  | Tagi ,  | brak comments

Dlaczego Ruby on Rails jest wyjątkowy?

Opublikowane przez Jarosław Zabiełło Sun, 14 May 2006 04:22:00 GMT

Niektórym osobom stykającym się z po raz pierwszy z Railsami wydaje się, że jest to tylko jakieś kolejne, tradycyjne środowisko developerskie pracujące wg wzorca projektowego MVC (model-widok-kontroler). Przywiązani do swoich języków i frameworków czasami się dziwią, dlaczego temat Railsów wywołuje od jakiegoś czasu tyle emocji i komentarzy. Na pewno po częsci jest tak pewnie dlatego, że RoR jest bardzo dobrze wypromowany. Dobra strona główna, dobra dokumentacja, książki, filmy, bardzo aktywna społeczność – nic tylko naśladować. Z drugiej strony, trzeba też przyznać, że jest to środowisko świetnie zaprojektowane – w Railsach sie pracuje po prostu komfortowo.

Nie dziwią więc ciągłe próby naśladowania Railsów w innych językach (PHP, Python, Java, C# itp) Jednakże między nimi a Railsami będzie ciągle pewna, trudna do osiągnięcia, jeśli nie w ogóle niemożliwa – bariera. Railsy posiadają nie tylko wszystko, co potrzeba do bardzo produktywnego tworzenia aplikacji internetowych, ale są przy tym równocześnie bardzo eleganckie i czytelne. Eleganckie i czytelne czyli łatwe do nauki. Tak, nauka Rubiego nie jest przeszkodą. Przekona się o tym każdy, kto trochę bliżej przyjrzy się jak działa RoR.

Miałem okazję porównywać ze sobą kilka różnych frameworków. Gdy chciałem przekonać się do któregoś z nich, zawsze ostatecznie wracałem z powrotem do RoR. Po prostu żaden z nich nie jest jak elegancki i prosty w użyciu1. Co jest główną przyczyną takiego wrażenia? Twórca Railsów, David Heinemeier Hansson powiedział kiedyś, że gdyby nie Ruby to nie powstałby Ruby on Rails. Powiedział także, że uważa iż w żadnym innym języku nie da się napisać tak eleganckiego i pięknego kodu. Zatem można powiedzieć, że prawdziwą siłą Railsów jest Ruby. Razem tworzą nierozłączną parę i ta łączność nie dotyczy bynajmniej tylko nazwy. ;) Ruby posiada bowiem pewne unikalne cechy, które pozwoliły stworzyć RoR w postaci, która jest raczej mało nieosiągalna dla innych języków. Już to wyjaśniam.

Panuje powszechnie mniemanie, że Ruby to połączenie cech Pythona i Perla. Ruby (podobnie jak Perl) posiada np. wbudowaną w składnię obsługe wyrażeń regularnych. Posiada także (podobnie jak Python) pełną obiektowość i bardzo elegancką, czytelną (choć nie taką samą) składnię. Pewnym odkryciem było dla mnie to, że językie, do którego Ruby ma najwięcej podobieństw to innego języka – Smalltalk. Lektura cech, filozofii (i nawet do pewnego stopnia składni) Smalltalka pokazuje zdumiewające podobieństwo do Rubiego. Można wręcz odnieść wrażenie, że Ruby to swego rodzaju przeróbka Smalltalka2. Podobieństw jest bardzo wiele. Od słów kluczowych po symbole, sposób tworzenia instancji klas, bloki kodu (ang. closures), kontynuacje3 itp. Jak ktoś jeszcze nie rozumie filozofii Rubiego, jego modelu obiektowego i powodu istnienia otwartych klas, powinien poczytać sobie trochę o Smalltalku.

Ruby jak i Smalltalk posiadają bardzo podobny model obiektowy. Wszystkie obiekty (na drodze dziedziczenia) wywodzą się z jednej, ostateczniej klasy Object. Oba języki mają zaimplementowaną pełną obiektowość. Nie ma (tak ja w Javie) podziału na prymitywy i typy referencyjne. Wszystko jest obiektem i wszystko posiada metody. Dotyczy to nie tylko liczb i napisów ale także obiektu nil, true czy false. Każdy obiekt można przeciążyć i/lub zmodyfikować wewnetrznie (dynamicznie dodając lub usuwając jego metody w trakcie pracy programu)

Z tego wynika, że właściwie to można modyfikować sam język. Daje to możliwości zupełnie nieosiągalne nawet dla tak dobrego i obiektowego języka jakim jest Python. Ruby pozwala na łatwe dodawanie nowych metod do liczb czy napisów. Pozwala na taką modyfikację samego siebie, aby optymalnie nadawał się do realizacji pewnych, specyficznych zadań. Ruby umożliwia zatem tworzenie tego, co się określa mianem języków domenowych (Domain-specific Programming Languages). Są to języki, które w przeciwieństwie do języków ogólnego zastostosowania, zostały zaprojektowane do wykonywania specyficznego zadania/zadań. Zarówno Smalltalk jak i Ruby pozwalają na tworzenie języków domenowych.

Ruby on Rails to nic innego jak framework napisany za pomocą Rubiego zmodyfikowanego w celu uzyskania wysoce produktywnego środowiska do tworzenia nowoczesnych aplikacji internetowych.

Przykładowe helpery dostępne w Rails, które korzystają z nowych metod nie będących standardową częścią Rubiego. Poniższe przykłady pochodzą z 1-g wydania książki Agile Web Development in Rails.

puts 20.bytes #=> 20
puts 20.kilobytes #=> 20480
puts 20.megabytes #=> 20971520
puts 20.gigabytes #=> 21474836480
puts 20.terabytes #=> 21990232555520
puts 20.minutes.ago #=> Tue May 10 16:43:43 CDT 2005
puts 20.hours.from_now #=> Wed May 11 13:03:43 CDT 2005
puts 20.weeks.from_now #=> Tue Sep 27 17:03:43 CDT 2005
puts 20.months.ago #=> Thu Sep 18 17:03:43 CDT 2003
now = Time.now
puts now #=> Tue May 10 17:15:59 CDT 2005
puts now.ago(3600) #=> Tue May 10 16:15:59 CDT 2005
puts now.at_beginning_of_day #=> Tue May 10 00:00:00 CDT 2005
puts now.at_beginning_of_month #=> Sun May 01 00:00:00 CDT 2005
puts now.at_beginning_of_week #=> Mon May 09 00:00:00 CDT 2005
puts now.at_beginning_of_year #=> Sat Jan 01 00:00:00 CST 2005
puts now.at_midnight #=> Tue May 10 00:00:00 CDT 2005
puts now.change(:hour => 13) #=> Tue May 10 13:00:00 CDT 2005
puts now.last_month #=> Sun Apr 10 17:15:59 CDT 2005
puts now.last_year #=> Mon May 10 17:15:59 CDT 2004
puts now.midnight #=> Tue May 10 00:00:00 CDT 2005
puts now.monday #=> Mon May 09 00:00:00 CDT 2005
puts now.months_ago(2) #=> Thu Mar 10 17:15:59 CST 2005
puts now.months_since(2) #=> Sun Jul 10 17:15:59 CDT 2005
puts now.next_week #=> Mon May 16 00:00:00 CDT 2005
puts now.next_year #=> Wed May 10 17:15:59 CDT 2006
puts now.seconds_since_midnight #=> 62159.215938
puts now.since(7200) #=> Tue May 10 19:15:59 CDT 2005
puts now.tomorrow #=> Wed May 11 17:15:59 CDT 2005
puts now.years_ago(2) #=> Sat May 10 17:15:59 CDT 2003
puts now.years_since(2) #=> Thu May 10 17:15:59 CDT 2007
puts now.yesterday #=> Mon May 09 17:15:59 CDT 2005
puts "cat".pluralize #=> cats
puts "cats".pluralize #=> cats
puts "erratum".pluralize #=> errata
puts "cats".singularize #=> cat
puts "errata".singularize #=> erratum

Z racji tego, że Railsy zmodyfikowały Rubiego do realizacji swoich zadań, uważam, że próba sklonowania tego środowiska w jakimś innym języku (poza Smalltalkiem), jest z góry skazana na niepowodzenie. Po prostu nigdy to nie będzie tak piękny kod jak Ruby dla Railsów. Na otarcie łez dla pythonistas, mogę powiedzieć, że przy wszystkich zaletach Rubiego, nadal uważam że Python (jako język) jest nie tylko łatwiejszy do opanowania ale także bardziej produktywny. Ale jeśli chodzi o frameworki, to Rails jest nie do pobicia jeśli chodzi o komfort, możliwości i DRY[4].

1 Zupełnie identyczne wrażenia prostoty jak z Railsami miałem w przypadku szukania dobrego systemu CMS’a. Taki np. pythonowy Plone w porównaniu do ezPublish i całej masy innych, pehapowych rozwiązań jest nie tylko poteżniejszy ale także niezrównanie prostszy i wygodny (po 5 minutach od instalacji, praktycznie bez czytania dokumentacji i bez znajomości Pythona, można stworzyć prosty serwis o całkiem przyzwoitej, podstawowej funkcjonalności)

2 Smalltalk posiada tylko 5 słów kluczowych i proste zasady: wszystko jest obiektem, wszystkie operacje polegają na przesyłaniu metod (nazywanych tu: wiadomościami) między obiektami. Moim zdaniem Smalltalk trochę przesadził z tą zasadą, bo nawet bo zamiast do sterowania kontrolą kodu używane są także wiadomości. Może jest jest spójne, ale trochę dziwnie wygląda. Osobiście bardziej podobają mi się możliwości Smalltalka ale wyrażone w składni …Rubiego. :) Zobacz też Ruby Instead of Smalltalk

3 Kontynuacje są jedną z wbudowanych cech Rubiego o której może być za jakiś czas głośno. Umożliwiają bowiem budowanie tzw. internetowych serwerów kontynuacyjnych, które w pełni zachowują stan pomiędzy requestami i ogromnie upraszczają prace programistów. W tej chwili najlepiej opracowanym frameworkiem tego typu jest smalltalkowy Seaside.

4 DRY to skrót od ang. Dont’t Repeat Yourself. Railsy zostały napisane z wręcz obsesyjną ;) cechą unikania powtarzania kodu. Mniej powtórzeń to nie tylko mniej niepotrzebnej, dodatkowej pracy ale także mniejsze ryzyko popełniania błędu.

Posted in , ,  | Tagi , ,  | 32 comments

Ruby, rozszerzanie klas i docstringi

Opublikowane przez Jarosław Zabiełło Thu, 13 Apr 2006 19:22:00 GMT

W jednym z wcześniejszych tekstów opisywałem zalety docstringów – wbudowanego mechanizmu dokumentacji w Pythonie.

W języku Ruby nie ma takiego mechanizmu. Jednak Ruby posiada unikalną cechę która pozwala go rozszerzać bez konieczności sięgania do języka C. Tą cechą są otwarte klasy.

class X
  def hello
    "Witaj"
  end
end

x = X.new
puts x.hello # => Witaj

class X
  def bye
    "Żegnaj"
  end
end

x = X.new
puts x.hello # => Witaj
puts x.bye # => Żegnaj

Coś takiego jest w ogóle nie do pomyślenia w językach tak mało dynamicznych jak Java czy C++ gdzie definicje klas są ustalane na poziomie kompilacji. W Ruby klasy są obiektami i są tworzone w momencie uruchomienia programu co daje nam w efekcie znacznie większy dynamizm i siłę ekspresji kodu.

Z kolei próba napisania czegoś podobnego w innych językach dynamicznych prowadzi także do zgoła kompletnie odmiennych zachowań. W przypadku PHP 5.1.2 poniższy kod wygeneruje błąd składni:

<?php
class X {
    function hello() {
        return "Witaj";
    }
}

$x = new X();
print $x->hello() # => Witaj

class X { # => Parse error: syntax error, unexpected T_CLASS
    function bye() {
        return "Żegnaj";
    }
}
?>

Zaś w Pythonie poznikają nam definicje poprzednich metod (ale wcześniej zdefiniowane instancje klasy nie ulegną zmianie).

class X(object):
    def hello(self):
        return "Witaj"

x = X()
print x.hello() # => Witaj

class X(object):
    def bye(self):
        return "Żegnaj"

print x.hello() # => Witaj

x = X()
print x.bye() # => Żegnaj
print x.hello() # => AttributeError: X instance has no attribute 'hello'

No dobrze, wróćmy do próby zaimplementowanie w Rubym czegoś podobnego do pythonowych docstringów. Załóżmy, że chcielibyśmy korzystać z następującej składni (poniższy kod jest oparty na PixAxe2, s.389):

class Przyklad
  doc "To jest przykład doctringu"
  # reszta kodu 
end

W związku z tym, że chcielibyśmy aby taka składnia działała dla każdego modułu i klasy, kod implementujący taki mechanizm powinniśmy zdefiniować w klasie Module.

class Module
  @@docs = {}
  def doc(s)
    @@docs[self.name] = s.gsub(/^\s+/, '')
  end
  def Module::doc(aClass)
    aClass = aClass.name if aClass.class <= Module
    @@docs[aClass] || 'Nie ma dokumentacji'
  end
end

Oraz przykład użycia:

class A
end

class B
  doc "Przykład dokumentacji"
end

module C
  doc <<-edoc
    Dokumentacja
    wielowierszowa
  edoc
end

puts Module::doc(A) # => Nie ma dokumentacji
puts Module::doc(B) # => Przykład dokumentacji
puts Module::doc(C) #=> Dokumentacja\nwielowierszowa

Posted in  | Tagi  | 4 comments

Ruby vs. Python - operacje na plikach

Opublikowane przez Jarosław Zabiełło Tue, 11 Apr 2006 19:47:00 GMT

Ostatnio miałem potrzebę napisania skryptu kopiującego całe pliki z całego drzewa folderów. Problem w tym, że chciałem kopiować tylko pliki, które nie miały ustawionych atrybutów tylko do odczytu. I to musiało być zrobione pod Windowsami. Zwykle jestem przyzwyczajony że tego typu zadania to piszę w Pythonie w kilka minut. Nie spodziewałem się że tu będzie inaczej….

Okazało się że Python (2.4.3) nie ma porządnie zaimplementowanych metod sprawdzania atrybutów pliku. Na próżno szukałem w manualu, Python Cookbook , internecie.

Zgodnie z prawami Murphego rozwiązanie znalazłem jak już było niepotrzebne (w międzyczasie zdążyłem napisać to w Ruby).

>>> os.stat(r'c:\tmp\test1')[stat.ST_MODE] & stat.S_IWRITE
128
>>> os.stat(r'c:\tmp\test2')[stat.ST_MODE] & stat.S_IWRITE
0

Wygląda to raczej paskudnie na tle analogicznego kodu Rubiego:

>>> File.writable?('c:\tmp\test1')
true
>>> File.writable?('c:\tmp\test2')
false

Może to szczególny przypadek, ale jakoś nie sprawdziła się w tym wypadku prostota Pythona. Metody Rubiego w tym temacie są znacznie bardziej intuicyjne. Zresztą zobaczmy poniższy przykład z manuala Pythona:

import os, sys
from stat import *
def walktree(top, callback):
    for f in os.listdir(top):
        pathname = os.path.join(top, f)
        mode = os.stat(pathname)[ST_MODE]
        if S_ISDIR(mode):
            walktree(pathname, callback)
        elif S_ISREG(mode):
            callback(pathname)
        else:
            print 'Skipping %s' % pathname
def visitfile(file):
    print 'visiting', file
if __name__ == '__main__':
    walktree(sys.argv[1], visitfile)

Ten sam kod w języku Ruby wygląda tak:

require 'find'
if $0 == __FILE__
  Find.find(ARGV[0]) do |f|
    if File.file?(f)
      puts 'visiting ' + f 
    elsif not File.directory?(f)
      puts 'skipping ' + f
    end
  end
end

I kto tu powie, że Ruby jest mniej czytelny od Pythona?

Posted in ,  | Tagi ,  | 8 comments

Mechanizmy bezpieczeństwa - Ruby vs Python

Opublikowane przez Jarosław Zabiełło Fri, 10 Mar 2006 21:37:00 GMT

W języku Ruby (mimo jego wielkiego dynamizmu) kwestia bezpieczeństwa została potraktowana poważnie. Ruby posiada różne mechanizmy umożliwiające kontrolę nad bezpieczeństwem kodu. Generalnie są to 3 różne mechanizmy.

  • Zakresy metod: private, protected i public.
  • Możliwość nieodwracalnego zamrożenia dowolnego obiektu.
  • Możliwość nieodwracalnego ustawiania poziomów bezpieczeństwa.

Zakresy private, protected, public

  • Metody publiczne moga być wywoływane przez kogokolwiek. Domyślnie wszystkie metody są publiczne (poza konstruktorem initialize który zawsze jest prywatny)
  • Metody chronione (protected) mogą być wywoływane tylko przez obiekty w tej samej klasie i klasach potomnych.
  • Metody prywatne mogą być wywoływane tylko w kontekście bieżącego obiektu. Nie można wywoływać metod prywatnych innego obiektu.

Np.:

class C
  public
  def publiczna
    p "Metoda publiczna"
  end  
  def get_chroniona
    chroniona
  end
  def get_prywatna
    prywatna
  end
  protected
  def chroniona
   p "Metoda chroniona"
  end 
  private
  def prywatna
    p "Metoda prywatna"
  end 
end

c = C.new
c.publiczna # => Metoda publiczna
c.get_chroniona  # => Metoda chroniona
c.get_prywatna  # => Metoda prywatna
c.chroniona # => protected method `chroniona' called for #<C:0x28e6300> (NoMethodError)
c.prywatna # => private method `prywatna' called for #<C:0x28e62e8> (NoMethodError)

Dla porównania, w Pythonie wszystkie metody są publiczne. Efekt metod prywatnych i chronionych uzyskuje się za pomocą konwencji. Metody prywatne mają prefiks dwóch underscorów, a chronionie – jeden underscore. Oczywiście nadal są one formalnie publicznie ale dostęp do nich (szczególnie do metod “prywatnych” jest utrudniony)

class C(object):
  def publiczna(self):
    print "Metoda publiczna"
  def get_chroniona(self):
    self._chroniona()
  def get_prywatna(self):
    self.__prywatna()
  def _chroniona(self):
   print "Metoda chroniona"
  def __prywatna(self):
    print "Metoda prywatna"

c = C()
c.publiczna()
c.get_chroniona() # => Metoda chroniona
c.get_prywatna() # => Metoda prywatna
c._chroniona() # => Metoda chroniona
c._C__prywatna() # => Metoda prywatna
c.__prywatna() # => AttributeError: 'C' object has no attribute '__prywatna'

Python wychodzi z innych przesłanek niż Ruby. Zakłada, że “jesteśmy dorośli i wiemy co chcemy robić”. Tzn. utrudnia popełnienie pomyłki przypadkowego dostępu do metody “prywatnej” za pomocą zmodyfikowania nazwy która da dostęp do takiej metody. Jednak jak ktoś, chce to Python nie będzie utrudniał. Można dostać się do każdej metody. Jak zobaczymy dalej (przy omawianiu poziomów bezpieczeństwa Rubiego), ta zasada spowodowała daleko idące konsekwencje dla kwestii bezpieczeństwa kodu Pythona.

Zamrażanie obiektów

Jeśli chodzi o kwestie zamrażania obiektów, to opisuję to w artykule o dynamice klas Python nie posiada jednak aż tak eleganckiego mechanizmu co Ruby. Da się co prawda przyblokować zmiany w obiekcie, ale trzeba sięgnąć po bardziej zaawansowane mechanizmy:

class ImmutableClass(type):
  def __setattr__(cls, name, value):
    raise AttributeError, "'%s' is immutable" % cls.__name__

class C(object):
  def hello(self):
    return 'Hello World!'
  __metaclass__ = ImmutableClass # blokada zmiany

def hello2(self): return 'Blah!'

c = C()
print c.hello() # => Hello World!
C.hello = hello2 #=> AttributeError: 'C' is immutable
print c.hello() # => Hello World!

Poziomy bezpieczeństwa

Jedną z ciekawych cech języka Ruby jest możliwość uruchamiania kodu na różnym poziomie bezpieczeństwa. Załóżmy np, że chcemy stworzyć (w najprostszy możliwy sposób) kalkulator który następnie udostępnimy na stronie internetowej. Przykładowy kod (na podst. PixAxe2) mógłby wyglądać np. tak:
require 'cgi'
cgi = CGI.new('html4')
# pobierz warto z pola formularza 'wyrazenie'
expr = cgi['wyrazenie'].to_s
begin
  result = eval(expr)
rescue Exception => detail
  # obsłuż złe wyrażenie
end
# wyświetl wynik dla użytkownika
Wszystko pięknie, ale co się stanie jak jakiś złośliwy użytkownik wpisze:
system("rm *")

Cały nasz kod zostanie skasowany! Na szczęście Ruby udostępnia mechanizm uruchamiania kodu w specjalnym trybie tak, aby kod nie był w stanie zbytnio rozrabiać. Poziom Rubinowej paranoi bezpieczeństwa określa zmienna globalna $SAFE. Może ona przyjmować następujące wartości:

  • 0 Brak mechanizmów zabezpieczeń. Wartość domyślna.
  • >= 1 Ruby zabrania uzywanie danych w potencjalnie niebezpiecznych operacjach
  • >= 2 Ruby zabrania ładowanie innych skryptów z dostępnych globalnie miejsc
  • >= 3 Wszystkie nowo stworzone obiekty są oznakowane jako niebezpieczne
  • >= 4 Ruby dzieli uruchamiany program tak, że obiekty nie oznaczone jako niebezpieczne również nie mogą być modyfikowane.

Domyślna, zerowa wartość $SAFE jest wystarczająca w większości przypadków. Jednak, gdy skrypt Rubiego jest uruchamiany z ustawionym atrybutem suid lub setgid (co pod uniksem umożliwia uruchomienie skryptu w kontekście innego użytkownika) lub, gdy ruby pracuje w trybie modułu apache’a (mod_ruby), to poziom bezpieczeństwa jest automatycznie podnoszony do 1. Poziom bezpieczeństwa można także ustawić za pomocą parametru -T z wiersza poleceń lub poprzez przypisanie zmiennej $SAFE innej wartości. Przy czym, nie jest możliwe zmniejszenie raz ustawionego poziomu bezpieczeństwa aż do końca działania skryptu.

Bieżąca wartość $SAFE jest dziedziczona przez wszystkie nowo tworzone wątki. Jednakże wewnątrz każdego wątka, wartość zmiennej globalnej $SAFE może być zmieniana bez wpływu na tą wartość w innym wątku. Ta cecha pozwala na budowanie bezpiecznych piaskownic (sandbox), obszarów w których można odpalać potencjalnie niebezpieczny kod bez ryzyka, że wpłynie on na resztę aplikacji czy systemu. Np.
File.new(filename, 'w').write('niebezpieczny kod')
Thread.start do
  $SAFE = 4
  load(filename, true)
end

Dla porównania, Python w ogóle nie posiada takich mechanizmów. Na domiar złego, okazało się, że (obecny we wcześniejszych wersjach Pythona) moduł Bastion okazał się dziurawy Został usunięty w biblioteki podstawowej i jak na razie Python nie posiada już nic w tym temacie do zaoferowania w zamian.

Posted in ,  | Tagi ,  | 3 comments

Dynamika otwartych klas: Ruby vs. Python

Opublikowane przez Jarosław Zabiełło Sat, 04 Mar 2006 05:36:00 GMT

Jedną z cech języka Ruby są tzw. otwarte klasy. Temat ten wywołuje mieszane uczucia i jest np. przedmiotem krytyki ze strony miłośników Pythona. Najczęściej jednak ta krytyka idzie w parze z nieznajomością Rubiego.

Np. w jednym z blogów spotkałem taki zarzut: zmiana działania klasy na której oparte są liczby całkowite może prowadzić do nieprzewidywalności w kodzie. Np.
class Fixnum
  def +(num)
    5
  end
end
print 2+2 # => 5 zamiast 4!

No i faktycznie, zmieniliśmy metodę operowania na liczbach całkowitych i Ruby nas oszukuje. Hmm, czy podobnej sytuacji nie ma w Pythonie? Ależ jest! Tylko że nie dotyczy podstawowych typów wbudowanych. Problem jednak jest ten sam. Ba, jest gorszy, co zaraz udowodnię. Ale wpierw prześledźmy różne scenariusze.

Podmiana metody w klasie

Załóżmy, że budujemy sobie klasę:
class Hello(object):
  def msg(self, msg):
    return "Hello " + msg
Miło i przyjemnie. Teraz mamy duży kod i sporo modułów z których co chwilę coś importujemy i na dodatek, pracujemy w kilka osób. Dyzio w jednym ze swych modułów stworzył klase o tej samej nazwie, ale o innej treści. Np.
class Hello(object):
  def msg(self, msg):
    return "Piss off " + msg

Teraz co się stanie jak moduł Dyzia zaimportuję do swojej przestrzeni nazw? Bez żadnego ostrzeżenia, klasa Dyzia wymieni moją klasę. Program może się nie posypie ale, klient uzyska zgoła odmienne przywitanie. :) Oczywiście może być gorzej, bo klasa Dyzia kompletnie usunie moją klasę co zaowocuje usunięciem wszystkich innych metod których się pracowicie natworzyłem.

Podobna sytuacja w Ruby nie owocuje kompletnym usunięciem wszystkich wcześniej stworzonych metod. Podmieniona zostanie tylko ta jedna, inna metoda. Cała reszta zostanie bez zmian.

Zabezpieczyć przed zmianą

Jak się zabezpieczyć przed modyfikacją klas? W Pythonie jest prosta odpowiedź: nie da się, bo Python nie posiada takich mechanizmów.

A co z Ruby? Okazuje się, że jest tu całkiem nieźle. Można dowolny obiekt (z klasą włącznie, bo klasa to też obiekt) zamrozić blokując możliwość jakiejkolwiek zmiany. Wystarczy więc, że wpiszę
Hello.freeze
Od tego momentu nikt nie będzie w stanie nic zmienić w mojej definicji. Można jedną instrukcją zablokować wszystkie klasy wbudowane jak ktoś bardzo tego chce:
ObjectSpace.each_object { |o| o.freeze if o.is_a? Class }

Zamrożenie obiektu jest nieodwracalne aż do końca działania programu. Nie da się żadnym sposobem za pomocą odpowiedniej komendy odmrozić raz zamrożony obiekt. Można co najwyżej sprawdzić czy już nie jest zamrożony (print obiekt.frozen?)

System Hooks

No dobrze, ale co w sytuacji kiedy nie chcemy zamrażać obiektu ale też nie chcemy aby gdzieś ktoś nam w nim coś zmienił bez wcześniejszego powiadomienia nas o tym? Tutaj z pomocą przychodzi coś co nazywa się System Hooks. W Ruby można bowiem świetnie śledzić co się dzieje z naszą klasą. Ruby potrafi wyłapywać następujące zdarzenia:

  • dodanie do klasy nowej metody dostępnej dla instancji klasy (Module#method_added)
  • usunięcie metody instancji z klasy (Module#method_removed)
  • wywołanie metody instancji, która nie istnieje (Module#method_undefined)
  • dodanie metody do singletona (Kernel.singleton_method_added)
  • usunięcie metody z singletona (Kernel.singleton_method_removed)
  • wywołanie nie istniejącej metody dla singletona (Kernel.singleton_method_undefineded)
  • stworzenie klasy potomnej (Class#inherited)
  • dodanie metod do klasy na drodze mixingu z modułów (Module#extend_object)
Przykład:
def info(msg)
  puts msg
end

class Hello
  def msg(msg)
    "Hello " + msg
  end
  def self.method_added(name)
    info "Dodano metode #{name}"
  end
  def self.inherited(name)
    info "Jakas klasa #{name} chce przeciazyc klase Hello"
  end
end

class Druga < Hello
end

class Hello    
  def nowy  
  end
end
Wynik:
Jakas klasa Druga chce przeciazyc klase Hello
Dodano metode nowy
Nie jest więc aż tak strasznie jak to co niektórzy trąbią. To prawda, że w Ruby można dodać metody do absolutnie każdej klasy (z wbudowanymi włącznie). Prawdą jest jednak także to, że można ten mechanizm w miarę kontrolować, śledzić, lub nawet zablokować. Zmiana metod klas wbudowanych pozwala na pisanie bardzo czytelnego kodu. Np. Ruby on Rails z tego sporo korzysta. Mogę samemu b. łatwo sobie zaimplementować coś podobnego.
class String
  def pluralize
    self + "s"
  end
  def singularize
    self[0...-1]
  end
end

puts "word".pluralize # => words
puts "books".singularize # => book

class Integer
  def kilobytes
    self * 1024
  end
end

puts 2.kilobytes # => 2048

Python (i żaden inny ze znanych mi języków) takich możliwości nie ma. A odnośnie tego, że Python nie pozwala zmienić definicji stringa czy integera (ogólnie typów wbudowanych) to mała to pociecha, skoro możne zmienić dowolnie inną klasę i to bez żadnego mechanizmu kontroli.

Posted in ,  | Tagi ,  | 5 comments

Edytory dla Pythona, Ruby, PHP

Opublikowane przez Jarosław Zabiełło Sat, 11 Feb 2006 23:17:00 GMT

Osoby zaczynające pracę z językiem Python, Ruby czy PHP często pytają się na grupach dyskusyjnych na temat edytorów zalecanych do pracy. Istnieją dwie szkoły, ci którym zależy na minimalnych wymaganiach pamięci i procesora oraz ci, którzy chcieliby aby edytor nie tylko kolorował składnię, ale także podpowiadał kod, uzupełniał składnię, miał wbudowany debugger itp.

Edytory tekstowe (nie IDE):

  • SciTE – łatwy w obsłudze, kolorowanie b. wielu języków
  • Vim – powszechny na każdym linuksie, jest wersja dla win32. B. szybki, doskonały do obsługi wielkich plików, niezbyt intuicyjna obsługa, np. kto by wpadł na pomysł aby wyjść z edytora za pomocą kombinacji: Esc + :q! (klawisz Escape, potem znak cudzysłowa, małe q i znak wykrzyknika)
  • Notepad++ – prosta, intuicyjna obsługa, po instalacji podmienia dla Internet Explorera idiotycznego Notatnika do podglądu źródeł stron html, warto instalować choćby dla tej jednej cechy i wymiany starego Notatnika.
  • JEdit – napisany w Javie, mnóstwo pluginów
  • XEmacs – kompletnie nieintuicyjna obsługa, ten edytor zupełnie w niczym nie przypomina skrótów klawiszy z innych programów, ale jest b. potężny. Jak ktoś już się przekatuje i go nauczy, to podobno jest to doskonałe narzędzie. Mnie nigdy nie starczyło cierpliwości aby go opanować. Wolę już edytor Vi.

Edytory IDE – Python

Użytkownikom win32 polecam instalację ActivePython zamiast instalacji na stronie główej Pythona. Do Pythona istnieje mnóstwo niezłych edytorów tekstowych jak i edytorów IDE. Moimi ulubionymi są:

  • Eclipse + pyDEV – chyba najlepsza opcja. Edytor Eclipse jest napisany w Javie i wymaga trochę pamięci, ale jest b. dobry. Posiada intuicyjny interfejs, mnóstwo pluginów dających spójne środowisko do pracy z javą, pythonem, ruby i php. pyDEV z zintegrowanym pakietem pyLint nie tylko podpowiada kod, ale także ma możliwość wyłapania nieużytej zmiennej, czy brak zgodności stylu programowania z zaleceniem PEP8 itp. Oczywiście jest też zintegrowany debugger.
  • PythonWin – standardowo instalowany w ActivePython. Jest znacznie szybszy i lżejszy od Eclipse, bo jest napisany w języku C. Ma świetny debugger, wbudowaną konsolę, podgląd obiektów COM. Jedyną jego wadą jest brak obsługi utf-8. Zaletą jest też to, że nadaje się do szybkiej edycji pojedyńczych plików (Eclipse wymaga tworzenia projektu, co często jest b. niewygodne). PythonWin ma także ulepszoną konsolę, bo pełnoekranową. Łatwiej w nim szybko zaznaczyć fragment poprzedniej komendy i uzupełnić.
  • SPE – edytor napisany w Pythonie z wykorzystaniem biblioteki wxPython. Ma unikalne, b. ładne kolorowe zakładki (umożliwia lepsze wyświetlanie podglądu metod i atrybutów klas). Wbudowany debugger, listę Todo, wyszukiwanie po wielu plikach i co ważniejsze: lepiej podpowiada kod niż np. PythonWin.
  • Eric – napisany w Pythonie z użyciem biblioteki PyQT. Ma dużo możliwości, wspomaganie tworzenia unittestów, refactoring, debugger itp. Obsługuje nie tylko Pythona ale i Rubiego.
  • Boa Constructor – ten edytor może zainteresować osoby piszące aplikacje oparte na wxPython bo posiada wbudowane wizualne wspomaganie tego procesu. Wzorowany jest na Delphi.
  • Komodo: Kombajn podobnie jak Eclipse. Występuje w wersji IDE (płatnej, ale licencja od razu obejmuje Windows, Linux i Mac OS X) i Edit (bezpłatnej). Ma świetny debuger do Pythona. Jest szybszy od Eclipse i obsługuje dobrze Pythona, Rubiego, PHP, C++ i inne języki (nawet szablony Django). Bardzo dobrze podpowiada kod HTML, CSS, zamyka tagi HTML podobnie jak Dreamweaver. Niestety, podpowiadanie kodu pozbawione jest na razie wyświetlania zintegrowanej dokumentacji (docstringów dla Pythona czy rdoc dla Rubiego, wyświetlana jest tylko lista metod).
  • WingIDE – wymieniam go na końcu bo jest płatny. Doskonale (zdecydownie najlepiej z wszystkich) podpowiada kod, posiada możliwość edycji w Zope, doskonały debugger.

Edytory IDE – Ruby

Do Rubiego istnieje też sporo niezłych edytorów, choć nie aż tyle co do Pythona.

  • Netbeans IDE 6.x ma zdecydowanie najlepsze podpowiadanie składni do Rubiego. Najlepszy IDE do Rubiego i Rails obok Aptana IDE.
  • Aptana IDE+Rails (RadRails został przejęty) to nic innego jak Eclipse dostosowany do pracy z Ruby on Rails. Posiada wszystkie zalety Eclipse, np. po dodaniu pluginów ma dobre wsparcie dla innych języków, np. Pythona. Od chwili wchłonięcia RadRails przez Aptanę, posiada świetne dodatkowe mocne wsparcie dla kodu HTML, CSS, JavaScript.
  • Arachno IDE – bardzo ładny i szybki. Może kolorować kod (nie tylko samego Rubiego ale także szablonów ERb, czyli nadaje się dobrze do Railsów) podobnie jak TextMate, ma wbudowany debugger, generowanie dokumentacji RDoc dla projektu, przeglądarka klas Rubiego i zainstalowanych gemsów. Niestety jest płatny i bieżąca wersja nie obsługuje jeszcze utf-8.
  • TextMate – działa tylko z systemem OS-X, czyli wymaga Macintosha. Ale jest to ulubiony edytor developerów Railsów. Sam edytor jest niestety płatny. Ma bardzo rozbudowane makra (snipety) przyśpieszające wprowadzanie kodu, ale ma beznadziejnie słabe podpowiadanie kodu, nie rozwija metod do bibliotek wbudowanych Rubiego, dokumentacji RDOC w ogóle nie ma scalonej z podpowiedziami do kodu. Do Pythona jest jeszcze gorzej. Ale edytor jest szybki, lekki i ma ładne kolorowanie wielu języków.
  • RDE czyli Ruby Development Environmnent. Edytor jest b. szybki i pomocny jak ktoś chce pisać kod w czystym Ruby.

Edytory IDEPHP

Do PHP istnieje sporo edytorów, ale moim zdaniem większość to badziewie. Sam PHP jest językiem który bardzo utrudnia napisanie na niego dobrego debuggera. Właściwie w nic dobrego tu nie ma poza komercyjnym Zendem. Ale gdybym miał wybierać, to wybrałbym:

  • Eclipse + plugin phpeclipse – po pierwsze jest darmowy, po drugie doskonale zarządza kodem PHP i koloruje szablony Smarty. Po trzecie, doskonale współpracuje z SVN. Miałem okazję sprawdzić nowy Zend Studio 5.1. Mimo, że lepiej podpowiada kod, to nie koloruje Smartów i kiepsko współpracuje z SVN, beznadziejnie wręcz.
  • Zend Studio – komercyjny IDE stworzony przez twórców PHP. Najlepiej ze wszystkich podpowiada kod (lepiej od Eclipse) Niestety nie potrafi kolorować kodu popularnych szablonów Smarty (Eclipse to potrafi!) Szybkość ta sama co Eclipse, wymagania co do pamięci – też.
  • Macromedia Dreamwever 8 – komercyjny, ale nieźle koloruje i podpowiada kod. Najnowsza wersja Dreamweawera ma sporo usprawnień, np. w końcu bada końce bloków (klamr). Dreamweawer jest też oczywście szybszy od Zend Studio i Eclipse bo jest napisany w języku C.

Niestety edytory PHP mają fatalne możliwości debugowania kodu w stosunku do tego, co można spotkać dla Pythona i Ruby. Do szybkiego testowania kodu PHP, najlepiej użyć SciTE. Można mu ustawić opcję aby po wciśnięciu klawisza F5 natychmiast generował wynik bez żadnego pośrednictwa serwera www.

Posted in , , ,  | Tagi , , ,  | 14 comments

Akcesory w Javie, Pythonie, Ruby i PHP5

Opublikowane przez Jarosław Zabiełło Tue, 07 Feb 2006 22:25:00 GMT

Java, język który dobył sobie silną pozycję na rynku korporacyjnym, jest od jakiegoś czasu pod obstrzałem krytyki z różnych stron. Wyszła cała seria książek krytykujących model obiektowy oraz metodologie promowane przez Javę (od najsłynniejszej Beyond Java po Bitter Java, Bitter EJB, czy inne)

Nie umniejszając zalet Javy, jej krytycy wytykają jej niepotrzebną nadmiarowość kodu, ociężałość i małą zwrotność, która powoduje że język ten słabo nadaje się do modnej ostatnio metodologii _agile programming. _ Aby lepiej ten problem zobaczyć, przyjrzyjmy się typowej praktyce programistów Javy. Tekst ten zainspirowany jest niedawną dyskusją jaka miała miejsce w jednym z blogów mojego kolegi.

Czym są akcesory?

Akcesory (lub inaczej: gettery i settery) to slangowe okreslenie metod jakie używa obiekt do odczytu i modyfikacji swoich atrybutów. Są one tak nagminnie używane przez programistów Javy, że niektóre edytory (np. Eclipse) zostały nawet wyposażone w makra do automatycznego ich generowania.

Dorzucanie nieużywanego kodu “na wszelki wypadek”...

Nagminna (wśród programistów Javy) praktyka dodawania akcesorów do atrybutów klasy pachnie jakimś antipatternem i są głosy, które używanie akcesorów nazywają wprost złą praktyką . Jest w tym trochę racji. Ale, jak to poniżej wykażę, jest to pewnego rodzaju kompromis w związku ze słabym modelem obiektowy Javy.

Dlaczego programiści Javy generują akcesory dla każdego atrybutu nawet, jak nie przewidują potrzeby ich używania? Otóż czynią to “na wszelki wypadek” bo nie wiedzą, czy w przyszłości nie będzie im to potrzebne…

Weźmy np. taki kod (z powodu kolejnego ograniczenia Javy nie można w jednym pliku trzymać więcej, niż jednej klasy; tu dla krótkości umieszczam kod z obu plików razem)

public class First {
  public String msg = "hello";
}

public class Second extends First {
  public static void main(String[] args) {
    First obj = new First();        
    System.out.println(obj.msg);
  }
}

Jak widać, w Javie można sięgać do atrybutu w sposób bezpośredni.

Wyobraźmy sobie jednak, że projekt nam się rozrósł i mamy już całkiem sporo kodu oraz sporo plików (pomnażanych wydatnie przez javowe ograniczenia co do ilości klas mogących wystąpić w pliku). Teraz przychodzi polecenie aby w momencie odczytu i zapisu atrybutu coś dodatkowego wykonać. (Np. niech to będzie logowanie informacji o takim zdarzeniu, albo zablokowanie możliwości modyfikacji treści atrybutu. Wszystko jedno co)

Mamy zatem pierwszy problem. Trzeba w tych wszystkich milionach miejsc, gdzie odwoływaliśmy się bezpośrednio do atrybutów, wymienić kod…

Właśnie dlatego, aby takich niespodzianek uniknąć w przyszłości, programiści Javy dorzucają dodatkowe metody opakowujące odczyt i zapis atrybutów. Eclipse upraszcza ten proces zwalniając programistę od ręcznego wpisywania tego kodu.

Klasa First po zmianie:

public class First {
  public String msg = "hello";
  public String getMsg() {
    return msg;
  }
  public void setMsg(String msg) {
    this.msg = msg;
  }
}

Wydaje się, że problem jest rozwiązany. Ale to dopiero początek innych problemów.

Jeśli bowiem, automatycznie dorzucamy te metody do wszystkich tysięcy atrybutów nowo tworzonych klas, to pierwszą rzeczą którą zauważamy, jest nagłe “spuchnięcie kodu”. Mamy kupę nieużywanych linii kodu, z których prawdopodobnie większość nie będzie nigdy używana!

Ale to nie wszystko. Załóżmy, że stajemy przed potrzebą zmiany nazwy atrybutu. Oczywiście, możemy to zmienić w jednym miejscu (w ciele akcesora), ale wtedy wprowadzamy chaos w pozostałej części odnośnie… nazw (atrybut “msg” zmieniony na “title” trochę głupio wygląda z akcesorami o nazwie “getMsg” i “setMsg”) Musimy przekopać się przez miliony miejsc w kodzie i pozmieniać nazwy starym wywołaniom.

Zobaczmy jak ta sytuacja wygląda w językach dynamicznych.

Python

Python (podobnie jak Java) pozwala na bezpośredni dostęp do atrybutów klasy. A co w sytuacji kiedy chcemy opakować atrybut akcesorami? Nic prostszego. Python pozwala na dodanie akcesorów wtedy, i tylko wtedy, kiedy są potrzebne. Na dodatek czyni to w sposób całkowicie przezroczysty dla pozostałej części kodu. Programista Javy może sobie o tym tylko pomarzyć.

class X(object):
    def get_msg(self):
        return self.__msg
    def set_msg(self, val):
        self.__msg = val
    msg = property(get_msg, set_msg)

obj = X()
obj.msg = "hello" 
print obj.msg

Python pozwala także związać z dowolną metodą, atrybutem, klasą czy modułem docstring, czyli tekst z dokumentacją, objaśnieniem itp. To jedna z genialnych cech Pythona specjalnie pomyślana dla leniwych programistów, którym nie chce się pisać dokumentacji. ;)

Ruby

W języku Ruby z definicji nie ma żadnej możliwości dostępu do atrubutów klasy inaczej jak przez akcesory. Odpada więc problem zapominania aby je dodać. Ruby jednak narzuca nazwy dla akcesorów (mają nazwę taką jak atrybut!) i całość wygląda tak, jakby operowano bezpośrednio na atrybucie.

class X
  def msg
    @msg
  end
  def msg=(val)
    @msg = val
  end
end

obj = X.new
obj.msg = "hello" 
puts obj.msg

Dla osób, które nie lubią za dużo pisać, Ruby ma wygodne skróty. Powyższą definicję klasy można zapisać także w ten sposób:

class X
  attr_accessor :msg
end

Istnieją także oddzielne skróty dla getterów i setterów. No i można po przecinku dodać akcesory dla całej grupy atrybutów.

PHP5

PHP w wersji 5 ma przebudowany model obiektowy od podstaw. Twórcy języka PHP nie starali się poprawiać modelu obiektowego PHP4 (był on tak zły, że prościej było im napisać go od nowa). W nowym PHP5 mamy już możliwość przezroczystego dodania akcesora.

<?php
class X {
  private $attributes = array('msg'=>null);
  private function __get($attrname) {
    return $this->attributes[$attrname];
  }
  private function __set($attrname, $val) {
    $this->attributes[$attrname] = $val;
  }
}

$obj = new X();
$obj->msg = "hello";
print $obj->msg;
?>

Składnia może nie jest tak prosta jak w Ruby, ale (przynajmniej w tym miejscu), PHP5 zachowuje się tu sensownie i unika dylematów Javy.

Posted in , ,  | Tagi , , ,  | 25 comments

pl.comp.lang.ruby

Opublikowane przez Jarosław Zabiełło Thu, 05 Jan 2006 10:36:00 GMT

Pojawiła się inicjatywa założenia grupy dyskusyjnej pl.comp.lang.ruby. Aby grupa powstała potrzebne są głosy poparcia sympatyków tego języka na pl.news.nowe-grupy.

Posted in  | Tagi  | brak comments

Ruby, Python vs Java,C++

Opublikowane przez Jarosław Zabiełło Mon, 14 Nov 2005 20:41:00 GMT

Od czasu do czasu na grupach dyskusyjnych mają miejsce przepychanki na temat wyższości jednych języków nad drugimi. Czasami takie dyskusje wywołują sporo emocji. Zmęczony ciągłymi ogólnikowymi sloganami o tym jak to w C++ lub Javie pisze się lepiej i szybciej (dobry dowcip) programy niż w językach dynamicznych takich jak Python czy Ruby, postanowiłem rzucić małe wyzwanie miłośnikom tych języków.

Zadanie polegało na napisaniu wyszukiwarki do polskiego tekstu Koranu (w sumie nie widziałem nic takiego, stąd pomysł). Aby nie było za banalnie, trzeba było napisać taki kod który by automatycznie wyssał przekład Koranu ze strony internetowej http://www.planetaislam.com/koran.html następnie przetworzył go i wstawił do bazy mysql oraz przełączył się w tryb wyszukiwania fraz.

Wyzwanie zostało przyjęte i… mijają tygodnie za tygodniami a moi dyskutanci nie są w stanie przedstawić żadnego działającego kodu w Javie ani w C++. Tak to teoria zderza się z praktyką. Oto naprędce stworzona wersja w Ruby:

require 'mysql'
require 'open-uri'

@przeklad = 'bielawski'
@conn = Mysql.new('localhost', 'login', 'hasło', 'koran', 3308)

def download(max_chapter=15)
  def clean_html(src) # nie znam biblioteki czyszczacej html wiec robie to regexem
    src.gsub(/&nbsp;/, ' ').gsub(/(<[^>]+>)/, '').gsub(/[ ]{2,}/, ' ').gsub(/&#/, '').gsub(/&quot;/, '"')
  end   
  @lines = []
  1.upto(max_chapter) do |sura|
    url = sprintf('http://www.planetaislam.com/koran/%s/%03d.htm', @przeklad, sura)
    src = clean_html(open(url).read)
    rows = src.scan(/(\d+?)[. ]([^\d]+)/) # wyluskuj wersety i numery rozdzialow
    rows.each do |row|
      @lines << sprintf('%s:%s %s', sura, row[0], row[1].gsub(/[\s]{2,}/, ' ').strip)
    end
    f = File.new("#{@przeklad}.txt", "w") # zapisuje sobie do pliku
    @lines.each { |line| f.write("#{line}\n") }                               
  end
end
def loaded_db
  @conn.list_tables.include?(@przeklad) # Sprawdzam czy juz istnieje tabela z danymi.
end
def load_db
  @conn.query("DROP TABLE IF EXISTS #{@przeklad}")
  @conn.query("CREATE TABLE #{@przeklad} (
    id int(10) unsigned NOT NULL auto_increment,
    sura varchar(255) NOT NULL,
    nr int(10) unsigned NOT NULL,
    verse text NOT NULL,
    PRIMARY KEY (id)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci")
  @conn.query("SET CHARACTER SET latin2")
  @lines.each do |line|
    sura, nr, verse = line.scan(/(\d+?):(\d+?) (.+)/)[0]
    verse.gsub!(/'/,"''") # uzywam topornego sql bo cos mi metoda prepare nie dziala, moze zla biblioteka?
    @conn.query("INSERT INTO #{@przeklad} (sura, nr, verse) VALUES(#{sura}, #{nr}, '#{verse}')")
  end
end  
def search_result(phrase)
  @conn.query("SET CHARACTER SET cp1250")
  sql = "SELECT * FROM " + @przeklad + " WHERE verse LIKE '%" + phrase + "%' "
  c = @conn.query(sql)
  while row = c.fetch_row
    puts sprintf("Sura %s,%s %s\n", row[1], row[2], row[3])
  end
end

if $0 == __FILE__
  if !loaded_db          
    download
    load_db
  end
  search_result('kobieta')
end

A oto ostateczna wersja w Pythonie:

#!python

"""
Wyszukiwarka do Koranu

Przykłady uzycia:
    python koran.py --txt zabijajcie
    python koran.py --sura=ix
    python koran.py --sura=9 --nr=4
    python koran.py --sura=IX --nr=4    

Wymagania:
    interpreter Pythona 2.4 + standardowe biblioteki
    pythonowska biblioteka MySQLdb
    serwer MySQL 4.1

Autor:
    Jarosław Zabiełło http://zabiello.com
"""

import HTMLParser, os, re, urllib2, sys
from optparse import OptionParser
import MySQLdb

class Koran(object):
    dbname ="koran"
    przeklad = 'bielawski'
    lines = []

    def __init__(self):
        self.conn = MySQLdb.connect(
            host='localhost',
            port=3308,
            user='login',
            passwd='hasło',
            db=self.dbname)

    def download(self, max_chapter=15):
        """Sciagam dane z internetu i je troszke oczyszczam z nadmiarowego kodu html"""
        def html_clean(src):
            x = HtmlStripper()
            x.feed(src)
            return src = x.get_fed_data()            
        self.lines = []
        for i in xrange(1,max_chapter):
            url = 'http://www.planetaislam.com/koran/%s/%03d.htm' % (self.przeklad, i)
            src =  html_clean(urllib2.urlopen(url).read()) # pobrany caly plik
            rows = re.findall(r'(\d+?)[. ]([^\d]+)', src) # wyluskuj wersety i numery rozdzialow            
            for row in rows:
                self.lines.append('%s:%s %s' % (i, row[0], re.sub(r'[\s]{2,}', ' ', row[1]).strip()))
        self.lines = [unicode(line, 'iso-8859-2').encode('utf-8') for line in self.lines]
        f = open(self.przeklad+'.txt', 'w')  # zapisuje sobie kopie na dysk
        for line in self.lines:
            f.write('%s\n' % line)
        f.close()

    def loaded_db(self):
        """Sprawdzam czy juz istnieje tabela z danymi."""
        c = self.conn.cursor()
        c.execute("SHOW TABLES")
        rows = c.fetchall()
        if self.przeklad in [row[0] for row in rows]:
            c.execute("SELECT COUNT(*) FROM %s" % self. przeklad)
            return c.fetchone()[0]
        return False

    def load_db(self):
        "Zakladam ze stworzono baze o nazwie self.dbname i laduje dane do tabeli self.przeklad"
        sqls = (
            "DROP TABLE IF EXISTS %s" % self.przeklad,
            '''CREATE TABLE %s (
                id int(10) unsigned NOT NULL auto_increment,
                sura varchar(255) NOT NULL,
                nr int(10) unsigned NOT NULL,
                verse text NOT NULL,
                PRIMARY KEY (id)
            ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci''' % self.przeklad)
        c = self.conn.cursor()
        for sql in sqls: # odtwarzam baze
            c.execute(sql)
        c.execute("SET CHARACTER SET UTF8")
        sql = "INSERT INTO "+self.przeklad+" (sura, nr, verse) VALUES (%s,%s,%s)"
        for line in self.lines:
            found = re.match(r'(\d+?):(\d+?) (.+)', line)
            if found:
                c.execute(sql, found.groups())
        c.close()

    def set_i18n(self, cursor, phrase=None):
        if sys.platform == 'win32': # zakladam ze bede wyswietlal wyniki w okienku konsoli, czyli kodowanie dosowe
            if phrase:
                phrase = unicode(phrase, 'cp1250').encode('cp852')
            cursor.execute("SET CHARACTER SET cp852")
        elif sys.platform == 'linux':
            cursor.execute("SET CHARACTER SET latin2")
        return phrase

    def search_result(self, phrase):
        """Wyszukuje wersetow wg prostej zasady: znadz wszystkie wersety w ktorych gdziekolwiek
        wystepuje fraza phrase. Dla wygody dodalem obsluge polskich znakow spod konsoli dosowej
        i linuksa."""
        c = self.conn.cursor()
        phrase = self.set_i18n(c, phrase)
        sql = "SELECT * FROM " + self.przeklad + " WHERE verse LIKE '%" + phrase + "%' "
        print sql
        c.execute(sql)
        rows = c.fetchall()
        c.close()
        print "Znaleziono %d wersetow:\n" % len(rows)
        for row in rows:
            print "Sura %s,%s %s\n" % (int_to_roman(int(row[1])), row[2], row[3])

    def show_verse(self, sura, nr=0, context=0):
        c = self.conn.cursor()
        self.set_i18n(c)
        if sura.isalpha():
            sura = roman_to_int(sura)
        if sura and nr: # wyswietl pojedyncza sure
            sql = "SELECT verse FROM " + self.przeklad + " WHERE sura=%s AND nr=%s"
            c.execute(sql, (sura, nr))
            verse = c.fetchone()[0]
            print "Sura %s,%s %s\n" % (int_to_roman(int(sura)), nr, verse)
        else: # wyswietl cala sure
            sql = "SELECT nr, verse FROM " + self.przeklad + " WHERE sura=%s ORDER BY nr"
            c.execute(sql, [sura])
            rows = c.fetchall()
            for row in rows:
                print "Sura %s,%s %s\n" % (int_to_roman(int(sura)), row[0], row[1])
        c.close()

class HtmlStripper(HTMLParser.HTMLParser):
    """src: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440481"""
    def __init__(self):
        self.reset()
        self.fed = []
    def handle_data(self, d):
        self.fed.append(d)
    def get_fed_data(self):
        return ''.join(self.fed)

def int_to_roman(input):
    """src: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81611"""
    if type(input) != type(1):
        raise TypeError, "expected integer, got %s" % type(input)
    if not 0 < input < 4000:
        raise ValueError, "Argument must be between 1 and 3999"   
    ints = (1000, 900,  500, 400, 100,  90, 50,  40, 10,  9,   5,  4,   1)
    nums = ('M',  'CM', 'D', 'CD','C', 'XC','L','XL','X','IX','V','IV','I')
    result = ""
    for i in range(len(ints)):
        count = int(input / ints[i])
        result += nums[i] * count
        input -= ints[i] * count
    return result

def roman_to_int(input):
    """src: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81611"""
    if type(input) != type(""):
        raise TypeError, "expected string, got %s" % type(input)
    input = input.upper()
    nums = ['M', 'D', 'C', 'L', 'X', 'V', 'I']
    ints = [1000, 500, 100, 50,  10,  5,   1]
    places = []
    for c in input:
        if not c in nums:
            raise ValueError, "input is not a valid roman numeral: %s" % input
    for i in range(len(input)):
        c = input[i]
        value = ints[nums.index(c)]
        try:
            nextvalue = ints[nums.index(input[i +1])]
            if nextvalue > value:
                value *= -1
        except IndexError:
            pass
        places.append(value)
    sum = 0
    for n in places:
        sum += n
    return sum

if __name__ == '__main__':
    appl = Koran()
    if not appl.loaded_db():
        print 'Pobieram dane z internetu....'
        appl.download()
        print 'Wkladam dane do bazy...'
        appl.load_db()
    parser = OptionParser()
    parser.add_option('--txt', dest='txt', help='wyszukiwana fraza')
    parser.add_option('--sura', dest='sura', help='numer Sury (liczba dziesietna lub rzymska)')
    parser.add_option('--nr', dest='nr', help='numer wersetu dla Sury')
    (options, args) = parser.parse_args()
    if options.txt:
        appl.search_result(options.txt)
    elif options.sura:
        appl.show_verse(options.sura, options.nr)
    else:
        print "Wywolaj z parametrem --help"

W międzyczasie zdążyłem nawet wykorzystać swój kod Pythona aby dodać go jako moduł do Plone. :)

Posted in ,  | Tagi , ,  | 80 comments

Starsze wpisy: 1 ... 3 4 5