About November 2007

This page contains all entries posted to /blog/wvk in November 2007. They are listed from oldest to newest.

October 2007 is the previous archive.

December 2007 is the next archive.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.31

« October 2007 | Main | December 2007 »

November 2007 Archives

November 3, 2007

Der SLOC-SCHOCK

Gerade schaute ich mich ein wenig auf koders.com um. Wer die Seite kennt, weiß bestimmt, dass zu den dort aufgelisteten Projekten immer eine kleine Statistik angezeigt wird, die u.A. über den Anteil der im jeweiligen Projekt verwendeten Programmiersprachen, geschätzte Entwicklungskosten etc. informiert. Und da blieb mein Auge mehrfach hängen, als ich (mal wieder) feststellte, welche Kosten da auch für kleine Projekte (ein paar wenige Tausend Zeilen Code) aufgeführt werden. Da packte mich die Neugier: ich installierte mir David A. Wheeler's Programm sloccount. Dieses Programm leistet genau oben Beschriebenes, sprich es zählt die Physical Source Lines of Code (SLOC), also Programmquelltext minus Kommentare minus trivialem wie HTML, SQL, CSS usw., listet eine kleine Statistik über die verwendeten Programmiersprachen auf und wagt einige Schätzungen über den Entwicklungsaufwand anzustellen.

Für meinen bisher im Rahmen der Diplomarbeit entstandenen Code der wäre das also:

SLOC    Directory       SLOC-by-Language (Sorted)
2020    app             ruby=2020
1319    test            ruby=1319
603     db              ruby=603
61      config          ruby=61
4       public          ruby=4
0       components      (none)
0       doc             (none)
0       lib             (none)
0       log             (none)
0       script          (none)
0       templates       (none)
0       tmp             (none)
0       top_dir         (none)
0       vendor          (none)

Totals grouped by language (dominant language first):
ruby:          4007 (100.00%)

Total Physical Source Lines of Code (SLOC)                = 4,007
Development Effort Estimate, Person-Years (Person-Months) = 0.86 (10.31)
(Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05))
Schedule Estimate, Years (Months)                         = 0.51 (6.07)
(Basic COCOMO model, Months = 2.5 * (person-months**0.38))
Estimated Average Number of Developers (Effort/Schedule)  = 1.70
Total Estimated Cost to Develop                           = $ 20,616
(average salary = $10,000/year, overhead = 2.40).

WHOW! sag ich da nur! Bei den Zahlen kriegt man ja locker einen SLOCkauf ;-) -- also mal abgesehen davon, dass die Entwicklungszeit schonmal um bald eine Größenodnung höher angesetzt wird als was ich bisher aufgewendet habe, sind die Entwicklungskosten (wie ich mal mit einem Jahresgehalt von 10'000$ bzw. €) angesetzt habe) von rund 20'000 Währungseinheiten schon beachtlich. Wer bezahlt das am Ende?

Aber mal abgesehen vom Preis: Ich arbeite seit nunmehr rund 1.5 Monaten an dem Programm. Bei meiner tatsächlichen täglichen Arbeitszeit kommt man vielleicht auf 2.5-3 Monate. Das würde also entweder bedeuten, dass ich ein überaus kongenialer Superprogrammierer wäre (oh yeah...!), oder -- vielleicht geringfügig realistischer -- mir das RAILS-Framework bislang rund 7-8 Monate Arbeit abgenommen hat.

Wenn man sich obige Zahlen genauer anschaut, fällt auf, dass für das gesamte app/views-Verzeichnis nicht ein SLOC gezählt wird -- OK, es handelt sich dabei hauptsächlich um HTML, aber hey -- das ist längst nicht alles a) autogeneriert und b) mit einem dubiosen WYSIWYWETG-Programm erstellt! ein [cat find app/views/ | wc -l ] ergab, dass es sich dabei um immerhin 6607 Zeilen handelt, wovon ich mal behaupte, mindestens 1800-2000 Zeilen selber geschrieben (OK, und Copy&Pasted :-)) zu haben. Da fällt eigentlich auch nicht mehr so sehr ins Gewicht, dass von den 1300 Zeilen unter test/ vielleicht nur 350 Zeilen von mir selbst stammen.

Interessehalber hab ich auch mal ein mittlerweile verabschiedetes Projekt von vor 1-3 Jahren (eine Vereins-Internetplattform in PHP) in sloccount gefüttert. Unter dem Strich kamen da fast runde 24'000 Zeilen Code, 67 Monate Entwicklungsaufwand und ca. 107'000 € Entwicklungskosten bei heraus (mit Jahresgehalt für Studi = schlappe 8'000 EUR gerechnet) -- Kann man sowas eigentlich als Spende von den Steuern absetzen? ;-) (interessanterweise wurde die Plattform innerhalb von rund 3.5 Jahren entwickelt -- 67 Monate entsprechen aber mehr als 5 Jahren ... also doch Superkräfte? :-D)

Was sagen hier die erfahrenen Entwickler von InnoQ zu? Wie schätzt ihr euren Aufwand ab? Sind diese Zahlen reine Fantasie? Der eine oder andere wie auch immer geartete Kommentar würde mich da schon interessieren.

November 12, 2007

ActiveRecord::BaseWithoutTable

Gestern brauchte ich (für ein Mailformular) so etwas wie ActiveRecord, aber ohne Tabelle. Wozu das, mag man fragen. Ganz einfach: ich brauchte einfache Validierung der eingebenen Formulardaten und demnächst auch Aggregation. Nach kurzem googlen nach "ActiveRecord without table" fand ich ActiveRecord::BaseWithoutTable. Dieses Plugin liefert einem alles, was ActiveRecord hat, nur eben ohne eine Tabelle für eine Entity aufbauen zu müssen. Toll, was? :)

Ich denke, dass dieses Plugin auch genau das ist, was ich bei meinen Apache-Configfiles einsetzen werde -- man erinnere sich -- auch da wollte ich "etwas wie ActiveRecord" einsetzen, mit Validierung, Aggregierung usw. Nun, wir kommen dem Ziel ein wenig näher...

Wenn die Arbeit fließt

Ich denke jeder kennt das: man sitzt vor dem Rechner, muss an irgend einer Software irgend etwas schreiben und man kommt einfach auf keinen grünen Zweig. man trinkt einen Kaffee nach dem anderen, scrollt sich durch den Quelltext, schaut sich das halbfertige Produkt im Browser an, sieht dass da Dinge nicht funktionieren, und ... kommt einfach trotzdem zu nichts.

Solche Tage habe ich üblicherweise 2-3 Mal pro Woche. An und für sich genommen denke ich, dass sie ein Zeichen sind, dass ich auch mal was anderes tun sollte, und wenn es nur ein anderes Nebenprojekt ist (abgesehen davon dass ich vielleicht auch viel zu viele, unwichtige, Nebenprojekte betreibe...). Üblicherweise aber -- egal wie wenig Schlaf ich hatte oder wie träge ich tagsüber war -- beginnt immer ab rund 0:00 Uhr eine hellwache, sehr konzentrierte Arbeitsphase. Es fällt mir schwer, nicht jeden Tag um 5:00 Uhr ins Bett zu gehen und erst um 14 Uhr wieder aufzustehen. Dann brauche ich auch üblicherweise 1-1.5 Stunden um vollständig aufzuwachen, aber die Produktivität Nachts macht das eigentlich wieder wett.

Gestern Nacht (also eigentlich heute Morgen) wurde ein lokaler Spitzenwert der Produktivität erreicht. Philipp und ich arbeiten gerade an einem sehr attraktiven Projekt, wo innerhalb einer Woche eine durchaus ansehnliche Webapplikation aus dem Boden gestampft werden musste. Heute war Deadline für eine brauchbare Beta, und somit war diese Nacht nicht ganz ohne Arbeitsdruck. Ich fasse zusammen, welche Eindrücke ich u.A. hieraus gewonnen habe:

  • Ohne konkreten, kurzfristigen Termindruck läuft bei mir so gut wie nichts.
  • Mit konkreten Vorgaben, wie das ganze am Ende auszusehen hat (das CI/Design stand schon) und was (und vorallem: was nicht) das Programm können soll, kann man sich auf die Funktionalität konzentrieren und wird nicht durch Einbinden von "Schnörkeleien" abgelenkt. OK, die wurden zwar trotzdem auch realisiert, aber ich merkte sehr deutlich wie ich nicht anfing herumzuspielen.
  • Mit einem festen Stundensatz und einem strikten Bugdetlimit schränkt man das Spielen und Ausprobieren weiter ein
  • Permanente Kommunikation mit dem Kunden und Mitentwickler über Skype steigert den Arbeitsdurchsatz enorm
  • Der Einsatz von Subversion zur Datensynchronisation untereinander sowie zum deployment auf den Server ist eine Arbeitserleichterung wie ich sie selten erlebt habe (warum habe ich nicht schon vor einem Jahr SVN genutzt?!?). Einfach auf den Rechner auschecken wo man gerade arbeiten will, Änderungen vornehmen, Einchecken -- unverschämt einfach und hochproduktiv.
  • (heute dazu gekommen:) Ein Bugtrackingprogramm (bei uns Mantis) senkt den Stress auf das Maß, wo die Arbeit am leichtesten fällt: Wenn alle Bugreports über Skype reinkommen, verliert man zu schnell den Überblick. Mit einem Bugtrackingprogramm kann man die Dinge erledigen sobald Zeit dafür da ist. Außerdem hat gegenseitiges Testen und Dokumentieren der Fehler etwas Kompetitives: man versucht möglichst seine eigenen Bugs zu beheben bevor der andere sie eintragen kann -- und umgekehrt natürlich! ;-)

Im Wesentlichen waren das meine Beobachtungen. Neben den projektbezogenen Sachen bich ich auch endlich dazu gekommen, einige Design-Entscheidungen, die ich für meine Diplomarbeit getroffen habe (Verzicht auf Single Table Inheritance bzw. Aggregation statt Vererbung, Vererbung statt Typ-Parameter -- in Rails eigentlich gleichbedeutend...) mal anders auszuprobieren. Einiges hat mir davon sehr gut gefallen, anderes bereue ich. Aber da es ein kleines Projekt ist (OK, es hätte auch locker für eine Diplomarbeit gereicht, aber den ganzen Schreibkram-Overhead haben wir hier jetzt nicht), kann man diese Fehlentscheidungen notfalls auch schnell umschmeißen (sprich refactorn :)).

Jedenfalls hat mir dieser kleine Projektexkurs ein paar gute Ideen und Praktiken für die Diplomarbeit gebracht und ich hoffe dass die Produktivität auch da weiter steigen wird.

Rails Helper v0.2

Soo, gerade ist Version 0.2 der Rails-helper-GUI fertig geworden. So schaut das Ding nun aus:

snap20.png

Folgendes hat sich geändert:

  • neues Tab mit RAKE-Buttons. Hier finden sich die meistverwendeten Rake-Aktionen db:migrate (optional mit Schemaversion), db:fixtures:load, db:schema:dump, db:test:prepare, log:clear, tmp:clear sowie test.
  • stdout und stderr werden in der eingebauten Konsole angezeigt
  • statt die Applikation vom Arbeitsverzeichnis aus aufrufen zu müssen, kann (muss) man nun das Arbeitsverzeichnis (Rails Root) explizit eingeben.
  • wenn nicht in das Rails Root-Verzeichnis gewechselt werden kann (z.B.weil Pfad ungültig/nicht gefunden), wird abgebrochen und eine Fehlermeldung erscheint.

Herunterladen kann man das Script hier: Download Rails Helper v0.2 Bitte daran denken, dass man zum Ausführen den KDE Kommander braucht (Aufrufen mit kmdr-executor rails-helper_0.2.kmdr).

Bugreports und Erweiterungswünsche sind stets Willkommen!

Nachtrag für Philipp ;-)

Ja, man kann die Buttons auch anders aussehen lassen, das ist ja das schöne an KDE ;-) schaustu, staunstu (über die Sinnhaftigkeit dieser Themes brauchen wir uns jetzt nicht streiten): snap21.png

snap22.png

Nachtrag:

Mittlerweile gibt Version 0.2.1 mit folgenden Fixes:

  • Tab-Reihenfolge korrigiert
  • Einblenden der Kommando-Felder bei "Scaffold" und "Controller" wiederhergestellt (muss irgendwie verloren gegangen sein bei v0.2

Der Download-Link (s.o.) ist immer noch der selbe.

November 25, 2007

Abgerundete Ecken

So, nach einer ereignisreichen Woche bin ich nun zurückgekehrt und kann endlich wieder einmal mit etwas Inhalt an dieser Stelle dienen. Es gibt momentan wenig Großartiges zu berichten, darum stelle ich einfach mal eine kleiner helper-Funktion vor, die mir schon seit langem das Leben mit Web 2.0 leichter macht. Nein, es handelt sich dabei nicht um "fancy Ajax"-Funktionalität, sondern um etwas anderes was mit dem W2.0-Hype exponentiell zuzunmehmen scheint: abgerundete Ecken bei "Boxen". Es gibt jede Menge Möglichkeiten, so etwas umzusetzen. Ich hab mich für die folgende Methode entschieden.

Als erstes benötigt man eine Hintergrundgrafik, die folgendermaßen zerschnitten wird:

text3159.png

Dabei ist zu beachten, dass der Hintergrund bei den abgerundeten ecken nicht transparent ist, sondern dem Seitenhintergrund entspricht -- Da sich die Bilder nachher überlappen, würde der ganze Effekt sonst verloren gehen.

Als nächstes definiere man sich für jedes DIV, das mit den runden Kanten versehen werden soll, ein solches Konstrukt:

<div class="rro">
  <div class="rru">
    <div class="rlo">
      <div class="rlu">
        <div class="rounded_content">
          Hier kommt der Inhalt rein!
        </div>
      </div>
    </div>
  </div>
</div>

Das Stylesteet ergänze man um folgende Zeilen:

.rro {
    display: block;
    max-width: 580px;
    background:url("/images/roundedbox_ro.png") top right no-repeat;
    margin:  .2em;
    padding: 0;
}

.rlo {
    background:url("/images/roundedbox_lo.png") top left no-repeat;
    margin:  0;
    padding: 0;
}

.rru {
    background:url("/images/roundedbox_ru.png") bottom right no-repeat;
    margin:  0;
    padding: 0;
}

.rlu {
    background:url("/images/roundedbox_lu.png") bottom left no-repeat;
    margin:  0;
    padding: 0;
}

.rounded_content {
    padding: .4em .4em;
}

Da es aber unheimlich umständlich ist, jedes Mal diese Batterie an DIVs hinzuschriben, habe ich folgende funktion in ApplicationHelper definiert:

module ApplicationHelper
  def rounded_corners(id = nil, &block)
    content = capture(&block)
    concat(content_tag_string(:div,
      content_tag_string(:div,
        content_tag_string(:div,
          content_tag_string(:div,
            content_tag_string(:div, content, :class => 'rounded_content'),
            :class => 'rlu'),
          :class => 'rlo'),
        :class => 'rru'),
      :class => 'rro', :id => id),
    block.binding)
  end
end

Will man nun in einer View eine Box mit abgerundeten Ecken darstellen, geht das ganz einfach so:

<% rounded_corners 'mein_abgerundetes_dingsda' do %>
  Hier kommt der Inhalt rein!
<% end %>

Und der Rest wird von Ruby gemacht. Ist das nicht hübsch? :)

November 29, 2007

wvk-4, consolvix+trac+svn

Hallo Welt! Um vier Weisheitszähne erleichtert und mit Kühlakkus an den Hamsterbacken ist es mir nun eine kleine Freude, ein paar Worte zu den aktuellen Errungenschaften im Rahmen von Consolvix zu verlieren. Zuerst einmal muss ich feststellen, dass die "Kern-Infrastruktur" nicht so wirklich wachsen will. Außerdem habe ich noch kein schlüssiges Konzept für eine brauchbare GUI. Zwar kann man alle in der Datenbank vorhandene Entitäten wunderbar CRUDden, aber damit ist es ja nicht getan. Ich bin jedoch zuversichtlich, dass sich das innerhalb der nächsten Wochen klären lässt.

Was hat sich also ereignet? Ich habe mich im Rahmen einer akuten Notwendigkeit zu folgendem verleiten lassen: eine einfache Weboberfläche um Subversion-Repositories zu verwalten und ihnen ein Trac-Projekt zuzuordnen sowie letztere auch detailliert konfigurieren können, zu schreiben. Das an sich ist nichtt besonders anspruchvoll, nur Auslesen einiger Shellscript-Ausgaben (svnlook z.B.), lesen einiger verzeichnisinhalte und lesen und schreiben einer Konfigurations- (INI-) Datei. In meinem judendlichen Übereifer von den eleganten Fähigkeiten des allmächtigen ActiveRecord geblendet, sollten "natürlich" SvnRepositories und TracEnvironments als solche Objekte behandelt werden, damit die Konfiguartion auch schon einfach ist. OK, zugegebenermaßen wäre das bei SvnRepository nicht nötig gewesen, aber auf die Errungenschaften bei TracEnvironment bin ich schon ein wenig stolz ;-)...

OK, was zeichnet eine TracEnvironment aus?

  • ist ein Verzeichnis, liegt zusammen mit anderen projekten in /var/trac => "Datenbank"
  • hat eine INI-Datei zur Konfiguration => Werte = Spalten
  • wird aufgesetzt durch den Aufruf von "trac-admin initenv " => "save"
  • muss nachträglich über ein Webformular mit Validierung berarbeitet werden können => klassische ActiveRecord-bezogene Aufgabe
  • beim Anlegen via "tracadmin initenv" wird _nur die Projektumgebung angelegt, nicht die Konfiguration. Also muss aus einer "sample"-Datei die konfiguration generiert werden, aber nur beim Anlegen => schöne Aufgabe für before_create
  • bis auf die initial anzugebenden Argumente (s.o.) sind alle anderen Konfigurationsdirektiven optional und theoretisch auch beliebig erweiterbar => "Spalten" müssen irgendwie dymanisch generiert werden können.

Zuerst: hier kam wieder ActiveRecord::BaseWithoutTable zum Tragen. Alle bei der Initialisierung zwingend notwendigen Spalten wurden "fest verdrahtet", der Rest wird nachher dynamisch über die Konfigurationsdatei gemacht. Alo reicht zur Datendefinition folgendes:

class TracEnvironment < ActiveRecord::BaseWithoutTable
  TRAC_BASE_PATH     = '/var/trac'
  TRAC_CONFIG_FILE   = 'conf/trac.ini'
  TRAC_TEMPLATE_PATH = '/usr/share/trac/templates'

  column :name,                   :string
  column :path,                   :string
  column :trac__database,         :string, 'sqlite:db/trac.db'
  column :trac__repository_type,  :string, 'svn'
  column :trac__repository_dir,   :string
  column :trac__templates_dir,    :string, TRAC_TEMPLATE_PATH

  validates_presence_of :name, :trac__repository_dir
  validates_inclusion_of :trac__repository_type,
                        :within => ['svn'],
                        :message => 'currently, only "svn" is supported.'
  before_create :initenv
end

die Konstanten dürften selbsterklärend sein. Doch warum diekomischen Spaltennamen mit "__"? Bei denen handelt es sich um Werte, die nachher in die Konfigurationsdatei geschrieben werden. Das Problem mit der Konfigurationsdatei ist, dass diese in "Abschnitte" unterteilt ist, also z.B. sowas:

[abschnitt1]
var1 = val1
var2 = val2
[abschnitt2]
var3 = val3
...

das ist eine zweidimensionale Struktur, die ich über die Konvention ActiveRecord-Spaltenname = "#{Abschnittsname}__{Einstellunsname}" auf das @attributes-Array des ActiveRecord-Objektes abbilde. Unter Angabe der definierten Spalten kann man eine TracEnvironment erstellen, ganz genau wie man ein ganz normales AR-objekt in der Datenbank erstellt -- mit den gleichen Methoden, callbacks etc:

...
@trac_environment = TracEnvironment.new params[:trac_environment]
@trac_environment.save
...

Handelt es ich um ein neues Objekt (@newrecord==true), wird vor dem Speichern (übe beforecreate) erst die ganze Ordnerstruktur etc. angelegt, danach die Konfiguration geschrieben:

  def update
    temp_file_name = "#{self.config_file}~"
    file = File.new(temp_file_name, 'w')
    self.config_file_struct.each do |section, value|
      file << "\n[#{section}]\n"
      value.each do |attr, attr_val|
        file << "#{attr} = #{self.send "#{section}__#{attr}"}\n"
      end
    end
    FileUtils.move temp_file_name, self.config_file
    @new_record = false
    self
  end

  def create
    command = "trac-admin #{self.path} initenv #{self.name} #{self.trac__database} #{self.trac__repository_type} #{self.trac__repository_dir} #{self.trac__templates_dir}"
    @initialisation_log = "<em>#{command}</em>\n" + `#{command}`
    case $?.exitstatus
    when 0 # everything alright
      return true
    when 1 # general failure, just clean up and return
      self.destroy
      return false
    when 2 # environment exists already
      return false
    else # unknown exit status (however, not a success): cleanup and issue an error
      raise "trac-admin initenv returned exit code #{$?.exitstatus}"
      self.destroy
    end
    self
  end

ich spare mir jetzt eine detaillierte Erklärung, fragt mich wenn etwas unklar ist. Das Lesen der Konfiguration geschieht hier:

def parse_config_file!(file=nil)
  config  = {}
  section = ''
  begin
    file = File.new(file||self.config_file)
    file.each_line do |line|
      if /^[([a-z0-9_]*)]$/.match line
        section = $1
      elsif /^\s*([a-zA-Z0-9_]*)\s*=\s*(.*)$/.match line
        self.send "#{section}__#{$1}=", $2
      end
    end
  rescue Errno::ENOENT
  end
  config
end

im Zusammenspiel mit einer überschriebenen methodmissing, die die Setter-"Methoden" für die in der Konfigurationsdatei vorhandenen Werte zu überschreiben. Das ist nötig, weil die eigentliche methodmissing eine spalte nicht anlegt, wenn diese nicht explizit definiert wurde (in diesem Fall über "column ...", ansonsten über auslesen der Datenbanktabelle). Die überschribene Methode fügt neue, unbekannte attribute einfach ins @attributes-Array ein, danach kann ActiveRecord damit arbeiten wie mit allen anderen Attributen.

def method_missing(name, *args)
  method_name = name.to_s
  if /^([a-z0-9_]+)__([a-z0-9_]+)=$/.match(method_name)
    begin
      super
    rescue NoMethodError
      key = "#{$1}__#{$2}"
      @attributes[key] = args[0]
    end
  else
    super
  end
end

Alles in allem ist das Konzept, Dateisystemartige Strukturen mit Konfigurationswerten auf ActiveRecord abzubilden, in meinen Augen ziemlich praktisch. Auf Applikationsebene arbeitet man mit genau den gleichen Strukturen wie an anderer Stelle auch, und man spart sich jede Menge Ärger mit Validierung, Flusskontrolle über Callback-Methoden, usw. Nur ein Wermutstropfen bleibt noch: so schöne Aufrufe wie

TracEnvironment.find_by_trac__repository_type "svn"

gehen NOCH nicht. Vielleicht macht es Sinn, das später einmal zu implementieren, so viel Aufwand wäre es ja nicht!

Achja, und was wäre ein so langer Post ohne ein Bild:

snap24.png

snap26.png

snap27.png

Vielen Dank für's Lesen, Ich freue mich auf Kommentare!