Dienstag, 7. Juni 2011

 

Fehlerbehandlung

Bei folgendem Code-Stück sieht man nur die Ausgabe

RemoteException
sonst nichts. Keine weitere Information, um dem Problem auf die Schliche zu kommen.
public class Server {
    public static void main(String[] args) {
        try {
            LocateRegistry.createRegistry(1099);
            System.out.println("RMI-Registry erfolgreich gestartet");
        } catch (RemoteException ex) {
            System.out.println("Fehler beim Starten der Registry: " + ex);
        }
        try {
            DB db = new Data("fahrten.db");
            //System.out.println(((Data)db).records.size());
            DB d = (DB) UnicastRemoteObject.exportObject(db, 1099);
            System.out.println("1xx");
            Naming.rebind("Data", d);
            System.out.println("2xx");

            
            Header he = ((Data)db).getHeader();
            Header h = (Header) UnicastRemoteObject.exportObject(he, 1099);
            Naming.rebind("Header", h);


            System.out.println("Alles gebunden");


        } catch (RemoteException ex) {
            System.err.println("RemoteException");
        } catch (MalformedURLException ex) {
            System.err.println("MalformedURLException");
        }
    }
}
Ein einfaches System.err.println("RemoteException: " + ex.getMessage()); liefert schon etwas mehr hilfreiche Information;
RemoteException: remote object implements illegal remote interface; nested exception is: 
        java.lang.IllegalArgumentException: illegal remote method encountered: public abstract int data.Header.length()
In der Klasse Data findet man
public class Data implements DB, Serializable {

    private final int MAGIC_COOKIE = 4223;
    private Header header;
    private String fileName="";
    private ArrayList lockedRecNo;
    private int DataOffset; //166
Wenn man weiter sucht, findet man für Header das Interface
public interface Header extends Remote {
    public Field getField(int fID);

    public int length();

    public void addField(Field f);

}
Es fehlt schlicht und ergreifend die Implementierung zu Header. Das kann nicht funktionieren!

Die Implementierung muss UnicastRemoteObject erweitern, in Data sollte man statt private Header header; direkt die Implementierung verwenden: private Fields header;.

Die Implementierung von Fields beginnt etwa so:
public class Fields extends UnicastRemoteObject implements Header, Serializable {

    private ArrayList felder;

    public Fields() throws RemoteException {
        felder = new ArrayList(0);
    }
...
Unser Server sollte dann etwa so aussehen (ob das dann funktioniert, sei dahingestellt, aber die Exceptions sind geklärt):
public class Server {

    public static void main(String[] args) {
        DB db = null;
        try {
            db = new Data("fahrten.db");
        } catch (RemoteException ex) {
            System.err.println("RemoteException (new Data()): " + ex.getMessage());
        }
        try {
            LocateRegistry.createRegistry(1099);
            System.out.println("RMI-Registry erfolgreich gestartet");
        } catch (RemoteException ex) {
            System.out.println("Fehler beim Starten der Registry: " + ex);
        }
        try {
            DB d = (DB) UnicastRemoteObject.exportObject(db, 1099);
            Naming.rebind("Data", d);
        } catch (RemoteException ex) {
            System.err.println("RemoteException (rebind(Data)): " + ex.getMessage());
        } catch (MalformedURLException ex) {
            System.err.println("MalformedURLException: " + ex.getMessage());
        }
        try {
            Fields he = (Fields) ((Data) db).getHeader();
            Header h = (Header) UnicastRemoteObject.exportObject(he, 1099);
            try {
                Naming.rebind("Header", (Fields) h);
            } catch (MalformedURLException ex) {
                System.err.println("MalformedURLException (rebind(Header)): " + ex.getMessage());
            }
        } catch (RemoteException ex) {
            System.err.println("RemoteException: " + ex.getMessage());
        }
        System.out.println("Alles gebunden");
    }
}
Der langen Rede kurzer Sinn:

Für den Programmierer muss möglichst viel Information bereitgestellt werden. Für den Anwender hilft diese Information nichts und man muss einfache Fehlermeldungen anbieten (das ist hier auf dieser Ebene sowieso nicht möglich, denn die Software ist noch weit davon entfernt, einem Endanwender seine Dienste anzubieten).
Sinnvoll wäre es, die Exceptions zu loggen.
Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
Am schlimmsten sind jedoch solche Konstrukte:

try {
                    while(true) {
                        records.add(read(nr));
                        nr++;
                    }
                } catch (RemoteException ex) {
                } catch (RecordNotFoundException ex) {
                }
Fehler sind praktisch unauffindbar. Auch nicht mit dem Debugger, weil man nicht einmal einen Breakpoint setzen kann, um die Variable ex zu inspizieren (die enthält ja Infos zur Exception).
Nachsatz: das alles kostet mich Stunden, die ich sinnvoller verbringen könnte. Einfach den Kandidaten durchfallen lassen, denn die Unit-Tests sind zu 93% (einer von dreizehn hat funktioniert) schief gegangen.

Labels: , ,


Montag, 6. Juni 2011

 

Autoboxing in Java

Folgendes Beispiel zeigt ein Problem beim Autoboxing in Java. Der Autor des Code-Fragments will int-Werte in der LinkedList lockedRecords speichern. Das funktioniert beim Speichern in Zeile 16 auch super, weil der int-Wert recNo automatisch in ein Integer-Objekt gepackt wird.

LinkedList<Integer> lockedRecords = new LinkedList<Integer>();

    public long lock(int recNo) throws RemoteException,
            RecordNotFoundException {
        long lockCookie = 0;
        if(existsRec(recNo)){
            synchronized(records.get(recNo)){
                while(lockedRecords.contains(recNo)){
                    try {
                        records.get(recNo).wait();
                    } catch (InterruptedException ex) {
                        System.err.println("Lock Interrupted!");
                    }
                }
                if(existsRec(recNo)){
                    lockedRecords.add(recNo);
                    boolean lock_getted = true;
                    lockCookie = lockedRecords.size() -1;
                    return lockCookie;
                }
            }
        }else{
            throw new RecordNotFoundException();
        }
        return lockCookie;
    }

    public void unlock(int recNo, long lockCookie) throws RemoteException,
            RecordNotFoundException, SecurityException {
        if(recNo > 0 && recNo < records.size()){
            if(lockCookie >= 0 && lockCookie < lockedRecords.size()
                    && lockedRecords.contains((int)lockCookie)){
                synchronized(records.get(recNo)){
                    lockedRecords.remove(recNo);
                    records.get(recNo).notifyAll();
                }
            }else{
                throw new SecurityException();
            }
        }else{
            throw new RecordNotFoundException();
        }
    }
unlock() stürzt (praktisch) immer in Zeile 34 ab! Und zwar mit einer IndexOutOfBoundsException.

Warum?

Die Klasse LinkedList (sowie viele andere Klassen des Java Collection Frameworks) besitzt zwei remove()-Methoden:

  1. E remove(int index), welche das Element mit dem Index index entfernt und eine IndexOutOfBoundsException wirft, wenn der Index nicht existiert.
  2. boolean remove(Object o), welche das entsprechende Objekt entfernt, falls es existiert (und in diesem Fall true liefert).
Ein int-Parameter wird nicht automatisch in ein Integer-Objekt verpackt, da zuerst die zu int passende Methode verwendet wird! E remove(int index) wirft aber eine IndexOutOfBoundsException, wenn der int recNo kein gültiger Index ist. Das wäre eher zufällig passend.

Autoboxing ist also im Allgemeinen sehr praktisch, kann aber zu unerwarteten Problemen führen.

Übrigens ist eine Lösung mit Verwendung von Integer nicht zielführend, da beim Autoboxing nur bei kleinen Werten tatsächlich dasselbe Objekt verwendet wird:

Integer i1 = 23;
Integer i2 = 23;
Integer i3 = 2132321423;
Integer i4 = 2132321423;
System.out.println(i1 == i2);
System.out.println(i1.equals(i2));
System.out.println(i3 == i4);
System.out.println(i3.equals(i4));
liefert:
true
true
false
true

Labels: ,


Samstag, 4. Juni 2011

 

Programmierrichtlinien haben doch Sinn (II)

Noch ein Bonmot, diesmal eine Endlosschleife:
    public int[] find(String[] criteria) throws RemoteException {
        ArrayList erg = new ArrayList();
        for(int i=0; i< records.size();i++){
            boolean match=true;
            if(criteria != null){
                for(int j=0; j< criteria.length;i++){
                    if(criteria[j] != null){
                        if(!records.get(i).getField(j).contains(criteria[j])){
                            match=false;
                            break;
                        }  }  }  }
            
            if(records.get(i).isDeleted()){
                match=false;
            }
            if(match){
                erg.add(i);
            }
        }
        int[] back = new int[erg.size()];
        for(int i=0; i< erg.size();i++){
            back[i]=(int)erg.get(i);
        }
        return back;
    }
Wo ist der Fehler? (Abgesehen von den schlecht formatierten schließenden Klammern in Zeile 11)

Labels: , ,


Donnerstag, 2. Juni 2011

 

Programmierrichtlinien haben doch Sinn

Eine Stunde Fehlersuche! Suche in einem fremden Code, der gewisse Anforderungen erfüllen muss. Der Unit-Test für eine Methode, die einen CSV-String liefern ist fehlgeschlagen. Das Feld an der Position 5 war immer leer. Der Test erwartete den String "42995";"47.01708,16.93225";"20.73708,29.96312";" 196";"201105132101";" 405";"9634090160";"O", die Methode lieferte jedoch 42995;"47.01708,16.93225";"20.73708,29.96312"; 196;201105132101;"";9634090160;"O"

OK, dieser "Fehler" war schnell gefunden. Die Methode hielt sich besser an das CSV-Format (nur Strings sind in Hochkomma eingeschlossen, Zahlen nicht) als mein Test. Das war schnell korrigiert und nun lieferte die Methode alles als String:
"42995";"47.01708,16.93225";"20.73708,29.96312";" 196";"201105132101";"";"9634090160";"O"

Fast richtig. Das ist ein wirklicher Fehler. Durchforsten und Debuggen hat ergeben, dass dieses Feld schon beim Lesen immer leer wird. Der Code dazu ist folgender (das Leerzeichen bei den Bedingungen "kleiner als" habe ich für den Blog hinzugefügt, damit da nicht ein ungültiges HTML-Tag ensteht, grundsätzlich erschwert das Fehlen solcher Leerzeichen das Lesen des Codes):

public Data(String fn) throws FileNotFoundException, IOException{
        fileName=fn;

        rf = new RandomAccessFile(fn, "rw");
            int mC=rf.readInt();
            if(mC==4223){
                int dataOffset=0;
                short count=0;

                dataOffset=rf.readInt();
                firstDataOffset=dataOffset;
                count=rf.readShort();
                information = new Header(count);
                for(int i=0;i< count;i++){
                    short fNameLength = rf.readShort();
                    byte[] roughData = new byte[fNameLength];
                    rf.read(roughData/*, (int)rf.getFilePointer(), fNameLength*/);

                    String fieldName = new String(roughData,"ISO-8859-1");
                    short fDescLength = rf.readShort();
                    roughData = new byte[fDescLength];
                    rf.read(roughData/*, (int)rf.getFilePointer(), fDescLength*/);
                    String fieldDescription = new String(roughData,"ISO-8859-1");

                    char type = (char)rf.readByte();

                    short fDataLength = rf.readShort();

                    Field f = new Field(fNameLength, fieldName, fDescLength, fieldDescription, type, fDataLength);

                    information.setHeaderField(i, f);
                }
                records = new ArrayList();
                while(dataOffset< rf.length()){
                    short flag = rf.readShort();
                    String[] data = new String[information.getLength()];
                    for(int i=0;i< data.length;i++){
                        int readLen = information.getField(i).length;
                        byte[] roughData = new byte[readLen];
                        rf.read(roughData/*, dataOffset, readLen*/);
                        switch(information.getField(i).type){
                            case'f':
                            case'F':
                                data[i] = new String(roughData,"ISO-8859-1");
                                break;
                            case'v':
                            case'V':
                                StringBuilder sb = new StringBuilder();
                                for(int j=0;i< readLen && (char)roughData[j]!='\0';j++){
                                    sb.append((char)roughData[j]);
                                }
                                data[i] = sb.toString();
                                break;
                            case'c':
                            case'C':
                                byte[] d = new byte[1];
                                d[0]=roughData[0];
                                data[i] = new String(d);
                                break;
                        }
                        dataOffset+=(2+readLen);
                    }
                    if(flag==0){
                        records.add(new RecordData(data, true, information));
                    }else{
                        records.add(new RecordData(data, false, information));
                    }

                }
                rf.close();
                information.recAnz = records.size();
            }else{
                throw new FileNotFoundException("Keine DB-Datei");
            } 
    }
Soviel konnte ich aufgrund der Rahmenbedingungen auch gleich herausfinden: das Feld mit Index 5 ist ein "V"-Feld, daher ist der Code ab Zeile 48 relevant. Der schaut eigentlich richtig aus. Erstaunlich ist jedoch, dass die "V"-Felder davor korrekt waren, das "F"-Feld auch. Aber das fehlerhafte "V"-Feld ist gleich nach dem ersten "F"-Feld. Daher war meine Hypothese: das "F"-Feld "erzeugt" den Fehler. Aber die Zeilen 44 und 45 sind korrekt.

Also nächste Hypothese: das Lesen der Daten ist fehlerhaft! Zeilen 38 bis 40. Aber die sind m.E. korrekt. Vielleicht wurde der Dateiheader falsch gelesen (dort sind die Feldlängen und Feldtypen spezifiziert).

Zeilen 5 bis 32. Aber auch dort ist nichts verdächtiges. Es ist schon zum Verzweifeln! Alles schaut richtig aus und trotzdem schlägt der Test fehl (zu Recht, denn das Ergebnis ist falsch).

Jetzt ist einmal Zurücklehnen angesagt. Das ganze mal von der Entfernung betrachten. Breakpoint auf Zeile 48 setzen und schauen, was sich tut.

Erst beim 5. Feld (0-basiert) tritt der Fehler auf. Die Schleife wird sofort verlassen, obwohl roughData tatsächlich mehr als 0 Bytes enthält, 5, um genau zu sein. readLen enthält sogar den richtigen Wert. Wie so zum Teufel ist dann die Bedingung j < readLen && roughData[j] != 0 nicht erfüllt (das Casten auf (char) hatte ich schon entfernt, weil '\0' tatsächlich den Wert 0 hat)?

Ich habe bei dem Ausdruck Leerzeichen eingebaut, die im Original nicht waren. Jetzt fällt es wie Schuppen aus den HaarenAugen! Im Original auf Zeile 49 steht i < roughData && roughData[j] != 0. i statt j - optisch fast nicht zu unterscheiden!

i zählt die Felder, j die einzelnen Bytes in einem Feld. Daher ist ab dem Feld 5, welches eine Länge von 5 hat, der erste Teil der Bedingung nicht mehr erfüllt und es wird ein leerer String erzeugt.

Ja, Programmieranfängern wird immer gepredigt: "sprechende" Namen verwenden!

Das hätte geholfen! Statt i könnte man z.B. fieldno verwenden und statt j wäre byteno angebracht. Damit wäre der Fehler nie passiert! Die Schleife hätte gleich falsch ausgesehen:

for(int byteno=0;fieldno< readLen && roughData[byteno]!=0;byteno++){
    sb.append((char)roughData[byteno]);
}
Wenn dann noch Leerzeichen dazwischen sind, springt der Fehler sofort ins Auge, denn warum wird in der Schleife einmal fieldno und einmal byteno verwendet?

Selbst wenn man in kurzen Schleifen i und j als Laufvariable erlaubt, dann müsste aber in der äußeren Schleife immer noch ein "sprechender" Name verwendet werden. Das stäche auch ins Auge (die Leerzeichen verbessern das auch noch!):

for (int i = 0; fieldno < readLen && roughData[i] != 0; i++){
    sb.append((char)roughData[byteno]);
}
Wenn schon kurze Laufvariable, dann besser nie i und j gemeinsam verwenden sondern "unterschiedlichere" Buchstaben wie i und k oder i und m. Die kann man nicht so leicht verwechseln!

Die Variante mit "sprechenden" Namen für Schleifen, die länger als ein paar Zeilen sind, und kurze Laufvariable für kurze Schleifen (Dreizeiler) ist die beste Möglichkeit, dann das Beispiel mit i und fieldno schaut gleich irgendwie falsch aus. Zumindest schaut man sich so etwas gleich näher an.

Leerzeichen und sprechende Namen bringen's!

Siehe auch Programmierrichtlinien allgemein. Dort steht leider nichts über die Verwendung von Leerzeichen.

Java Guidelines findet man z.B. hier: http://www.oracle.com/technetwork/java/codeconv-138413.html

Google findet auch etwas: www.google.com

Labels: , ,


Donnerstag, 3. Juni 2010

 

IDEs und Projektverzeichnisse und wie Maturanten damit umgehen

Ich versuche gerade, die Programmierarbeiten der Projektwoche der Reife- und Diplomprüfung zu korrigieren. Die Maturanten haben mich da vor ziemlich großes Problem gestellt, denn ich muss von 17 Abgegebenen Projekten
Zur Erläuterung habe ich ein paar Screenshots gemacht. Das erste zeigt die Paket-Struktur eines richtig abgegebenen Netbeans-Projekt:
Das nächste Bild zeigt ein falsch abgegebenes Projekt. Es gibt in den Paket-Verzeichnissen keine *.java-Dateien!
Die *.java-Dateien waren im übergeordneten Verzeichnis zu finden. Die Verzeichnisse mit den Sourcen beginnen alle mit einem Leerzeichen und müssen erst wieder umbenannt werden:
Das Verzeichnis, das mit "-brz" endet, enthält das Projekt, aber eben ohne die Sourcen.

Ich denke, man muss schon ziemlich Hand anlegen, um aus den Projektverzeichnissen diese kaputten Projekte zu erzeugen.

Ich frage mich, was so kompliziert ist, wenn es heißt: "Das gesamte Projekt-Verzeichnis (Name-brz) in eine Jar-Datei (Name-brz.jar) packen und ins Abgabeverzeichnis kopieren (Name-brz.jar enthält also u.a. die „ausführbare“ Datei brz.jar)."

Die Angabe für das Projekt war so:
"Verwenden Sie als Standard-Encoding UTF-8 (Unicode)!
Nennen Sie das Projekt Name-brz, wobei Name Ihr Familienname ist.
Erstellen Sie eine geeignete Paket-Struktur.
Erstellen Sie die Verzeichnisse build und doc. Im Verzeichnis build muss das fertige Programm brz.jar abgelegt werden. In doc muss die generierte JavaDoc abgelegt werden. Legen Sie im Projektverzeichnis eine Datei readme.txt an, die Hinweise zum Erzeugen von brz.jar enthält.
brz.jar soll alle nötigen Informationen zum Betrieb mit Ausnahme der Datenbankdateien enthalten, d.h. brz.jar soll in jedem beliebigen Verzeichnis aufgerufen werden können.
Programmaufruf: über ein Argument der Kommandozeile soll festgelegt werden, ob das Programm als Server (Argument server), Client (Argument client) oder AdminClient (Argument admin) läuft.
Erstellen Sie ein Ant-Script, mit dem die Applikation und die Dokumentation (neu) erstellt werden kann."

Es durfte frei zwischen Eclipse und Netbeans gewählt werden.

Die Leute haben 5 oder mehr Jahre Programmieren hinter sich. Und immer wieder Tests in dieser Arbeitsumgebung (Java unter Linux). Ich verstehe das nicht!

Diese Umstände kosteten bis jetzt ein paar Stunden Arbeit (überhaupt, wenn man diesen Blog-Eintrag mitrechnet). Wirklich korrigiert habe ich noch keine der Arbeiten.

Labels: , , , ,


Mittwoch, 26. Mai 2010

 

Softwarezuverlässigkeit - "Warum explodierten Mariner 1, Ariane 5, ..." oder: "Was kümmern mich die Probleme der Datenverarbeitung"

Dieser Artikel von Ingolf Giese ist zwar schon etwas älter, aber er enthält eine Reihe interessanter Beispiele zu Fehlern in Softwaresystemen:
Warum explodierten Mariner 1, Ariane 5, ...
oder:
Was kümmern mich die Probleme der Datenverarbeitung

Labels: , ,


Freitag, 19. März 2010

 

Probleme mit python's random

Wenn man Zufallszahlen benötigt oder Funktionen, die auf zufällige Zahlenfolgen basieren, verwendet man in Python das Modul random (ähnliche Module, Klassen oder Libraries gibt es im Prinzip für alle Programmiersprachen). Dahinter steckt keine echte Zufallszahlenerzeugung sondern vielmehr eine Funktion, welche Pseudozufallszahlen liefert, die sich statistisch wie Zufallszahlen verhalten.
Manchmal benötigt man immer wieder die selbe Zahlenfolge (z.B. beim Testen). Das erreicht man durch Angabe eines "seeds" (Samen) bei der Methode random.seed(), z.B. random.seed(23). Damit wird der Zufallsgenerator mit diesem Wert initialisiert. Bei selbem seed bekommt man immer die selbe Folge von Pseudozufallszahlen. Verwendet man diese Funktion nicht, so wird (zumindest bei Python) automatisch die Systemzeit als Anfangswert genommen. Damit kann man die Folge nicht vorhersagen (für einen bestimmten Zeitpunkt schon, dann nimmt man diesen Zeitstempel einfach als seed).

Nun zu meinem Problem mit random:
Python 2.5.2 auf meinem Debian- Rechner, den ich regelmäßig auf neuen Stand bringe
>>> from random import seed, random
>>> seed("23")
>>> random()
0.41111702255082994
Python 2.5.1 auf der Dreambox
>>> from random import seed, random
>>> seed("23")
>>> random()
0.09256672412670508

Bis vor Kurzem waren die beiden Werte identisch. Mein Python-Programm verhielt sich auf der Dreambox genauso wie auf meinem Entwicklungsrechner.

Offensichtlich gab es da Änderungen (Korrekturen) in der Python-Bibliothek.

In der Dokumentation von Python kann man lesen, dass für den seed ganze Zahlen (long, int) oder jedes "hashable" Objekt verwenden kann. Und dort dürfte das Problem liegen:
Python 2.5.2
>>> "23".__hash__()
6400038450057767
Python 2.5.1
>>> "23".__hash__()
308105767

Es ist einiges an Zeit vergangen, bis ich dieses Problem identifizieren konnte, denn der Bytecode von Python ist auf jeder Maschine gleich (ähnlich der VM von Java). Es muss also m.E. an der Version liegen.

Als Lösung bietet sich an, direkt eine Zahl zu verwenden:
Python 2.5.2
>>> seed(23)
>>> random()
0.92486525162594524
Python 2.5.1
>>> seed(23)
>>> random()
0.92486525162594524

Quellen:

Labels: , ,


Montag, 15. Februar 2010

 

eclipse und cvs

Um mit CVS bequem zu arbeiten, eignet sich eclipse normalerweise ganz gut. Aber ab und zu habe ich Probleme mit dem Einchecken von Java-Projekten auf meinem Rechner zu Hause und dem Auschecken in der Schule und umgekehrt. Das Problem manifestiert sich so, dass das ausgecheckte Java-Projekt kein Java-Projekt mehr ist und man eclipse praktisch nicht mehr verwenden kann.
Ich habe herausgefunden, was falsch ist, aber ich weiß noch nicht, warum das passiert. Es gibt in einem Java-Projekt zwei Konfigurationsdateien .project und .classpath. In beiden Dateien treten beim Auschecken (oder schon beim Einchecken) Fehler auf. Ich möchte die Unterschiede anhand eines Beispiels zeigen:
.project nach dem Auschecken:
<projectdescription>
  <name>start-demo</name>
  <comment></comment>
  <projects>
  </projects>
  <buildspec>
  </buildspec>
  <natures>
  </natures>
</projectdescription>
.project wie es funktioniert:
<projectdescription>
  <name>start-demo</name>
  <comment></comment>
  <projects>
  </projects>
  <buildspec>
    <buildcommand>
      <name>org.eclipse.jdt.core.javabuilder</name>
      <arguments>
      </arguments>
    </buildcommand>
  </buildspec>
  <natures>
    <nature>org.eclipse.jdt.core.javanature</nature>
  </natures>
</projectdescription>
Irgendwie wurde die buildSpec und die nature unterschlegen.

Die zweite Datei .classpath fehlt komplett:
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
    <classpathentry kind="src" path="src"/>
    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
    <classpathentry kind="output" path="bin"/>
</classpath>

Man kann die Dateien nach obigem Muster anlegen und muss dann ein "Refresh" des Projekts machen.

Labels: , ,


Donnerstag, 30. April 2009

 

cvs up & running

cvs.htlwrn.ac.at wurde wiederhergestellt. Stand der Daten: Montag 27.4.

Labels: , , ,


Mittwoch, 29. April 2009

 

cvs zerstört

Der cvs-Server cvs.htlwrn.ac.at wurde leider zerstört. Wahrscheinlich sind einige Daten verlorengegangen. Möglichweise sogar alle!

Ausständige Abgaben bitte per Mail als ZIP oder JAR.

Ihr Workspace ist wahrscheinlich die einzige Sicherung.

Labels: , , ,


Mittwoch, 4. März 2009

 

CVS - "cvs commit: nothing known about ..."

Ein Schüler fragte mich, was die Fehlermeldung "cvs commit: nothing known about ..." beim commit aus Netbeans bedeutet. Ich konnte das nicht beantworten, also googeln: Diese Fehlermeldung kommt, wenn man eine Datei "commiten" will, die dem CVS noch nicht bekanntgegeben wurde (cvs add file). Der Schüler hatte die Datei schon gelöscht. Also ist auch kein cvs add nötig. Aber aus einem mir (noch) unbekannten Grund versucht Netbeans doch ein "commit" auf diese (nicht existierende) Datei zu machen.

Folgenden Workaround habe ich gefunden:
  1. Alle einzelnen Dateien des Projekts händisch commiten.
  2. Das Projekt in einem neuen Verzeichnis auschecken.
  3. Prüfen, ob alles da ist.
  4. Das alte/originale Projekt löschen.
  5. Das neue verwenden.
Wenn man CVS von der Shell aus verwenden würde, müsste man sich immer selbst um jedes cvs add kümmern. Da würde man verstehen, warum man eine nicht existierende Datei nicht "commiten" kann. Hier war aber die Datei offensichtlich nicht vorhanden und trotzdem versuchte Netbeans ein "commit".

Grundsätzlich vereinfacht aber Netbeans (und auch eclipse) die Verwendung von CVS - schon alleine die umständlichen Schritte beim Anlegen (import) entfallen. cvs add braucht man nicht machen.

Scheinbar ein Bug im Netbeans.

Links:

Labels: , , ,


Sonntag, 4. Januar 2009

 

Schaltjahr-Bug in Zune

Zune - das ist das Microsoft-Pendant zu iTunes - ist zu Silvester abgestürzt, genauer hängen geblieben. Genauere Informationen finden sich auf zum Beispiel auf Heise1 bzw. Heise2. Es gibt in diversen Foren plausible Erklärungen für den Ausfall des Ur-Zune. Für den Fehler ist wahrscheinlich nicht direkt Microsoft verantwortlich sondern der Hersteller der im Player verbauten Hardware.

Ich präsentiere hier den relevanten Ausschnit des gesamten Quelltexts.

Zunächst die gesamte Funktion, in der der Fehler zu finden ist:

//------------------------------------------------------------------------------
//
// Function: ConvertDays
//
// Local helper function that split total days since Jan 1, ORIGINYEAR into
// year, month and day
//
// Parameters:
//
// Returns:
// Returns TRUE if successful, otherwise returns FALSE.
//
//------------------------------------------------------------------------------
BOOL ConvertDays(UINT32 days, SYSTEMTIME* lpTime)
{
int dayofweek, month, year;
UINT8 *month_tab;

//Calculate current day of the week
dayofweek = GetDayOfWeek(days);

year = ORIGINYEAR;

while (days > 365)
{
if (IsLeapYear(year))
{
if (days > 366)
{
days -= 366;
year += 1;
}
}
else
{
days -= 365;
year += 1;
}
}


// Determine whether it is a leap year
month_tab = (UINT8 *)((IsLeapYear(year))? monthtable_leap : monthtable);

for (month=0; month<12; month++)
{
if (days <= month_tab[month])
break;
days -= month_tab[month];
}

month += 1;

lpTime->wDay = days;
lpTime->wDayOfWeek = dayofweek;
lpTime->wMonth = month;
lpTime->wYear = year;

return TRUE;
}



Hier das relevante Code-Stück noch mal herausgenommen:


year = ORIGINYEAR; // == 1980
while (day > 365)
{
if (IsLeapYear(year))
{
numOfLeap++;
if (day > 366)
{
day -= 366;
year += 1;
}
}
else
{
day -= 365;
year += 1;
}
}


Das Jahr 2008 ist ein Schaltjahr (IsLeapYear(year) liefert true), d.h. der 31.12.2008 ist der 366 Tag. Die Schleife while (day > 365) wird nie beendet, da in der Abfrage if (day > 366) der Tag mit der Nummer 366 nie berücksichtigt wird. Man hat in diesem Fall also eine Endlosschleife (day wird in diesem Fall nie verändert!).

Labels: ,


This page is powered by Blogger. Isn't yours?

Abonnieren Posts [Atom]