Archiv für den Monat: Februar 2010

Suchfunktion in eclipse richtig (aus)nutzen

Die Suchfunktion in eclipse bzw. ZendStudio kann mehr, als ein simples STRG+F vermuten lässt. Also mal Zeit, kurz was darüber zu schreiben, was einem das (Programmierer)-Leben leichter macht; Programmierer sind ja bekanntlich faule Wesen 🙂

Also, in eclipse und anderen IDEs, die auf eclipse basieren – z.B. ZendStudio, Aptana, usw. – gibt es im Prinzip zwei Suchfunktionen.

  1. Die Suche über STRG+F
  2. Die Suche über STRG+H

Die Suche über STRG+F…

… ist schnell erklärt und man sollte wissen, dass diese Suche nur funktioniert, wenn man ein Dokument geöffnet hat.

sufu1

Den zu findenden Text schreiben und ENTER drücken und man springt zum nächsten Vorkommen. Die Optionen sollten alle selbsterklärend sein.
Hinweis: Die Suche beschränkt sich auf das aktuelle Dokument, auch wenn mehrere Dateien geöffnet sind!

Die Suche über STRG+H…

…ist das eigentliche Highlight in eclipse und ist äußerst mächtig, wenn man sich ein wenig Zeit nimmt, die Optionen zu studieren.

Erstmal ein Einblick in die Oberfläche:

sufu2

Das ganze funktioniert erst, wenn ihr ein Projekt geöffnet habt, allerdings muss keine Datei geöffnet sein.

Sollte euch der Reiter “Remote Search” fehlen, keine Panik, den habe ich deaktiviert. Unten Link den Button “Customize” drücken und dort den Reiter “Remote Search” ausschalten.

sufu3

(Mich hat das ständig genervt, dass dieser Reiter zuerst ausgewählt ist und ich nicht direkt lokal suchen konnte.)

Wie ihr seht, könnt ihr zwar die Suchfunktionen ein- und ausschalten, allerdings nicht die Reihenfolge ändern. Hinweise diesbezüglich nehme ich gerne entgegen.

Okay, meine Erfahrung beschränken sich auf den Suchreiter “File Search”, aber der hat es schon in sich und reicht für meine Zwecke mehr als nur aus.

Ein Beispiel zur Suche

Ich habe ein (PHP)Projekt mit 2.000 Dateien und suche dort alle Vorkommen der mail()-Funktion; evtl. will ich die Funktion durch eine Klasse ersetzen. Also STRG+H, in das Feld “Containing Text” trage ich “mail(“ ein, bei “File name patterns” sage ich “*.php” und den “Scope” setze ich auf “Workspace”. Nun ein Klick auf “Search” und kurz danach erhalte ich alle Ergebnisse sauber nach Vorkommen gegliedert und kann so jeden Treffer sehen. Dabei bekomme ich auch eine Vorschau der betreffenden Zeile und kann anhand dieser Information schon schnell beurteilen, ob dieser Treffer für mein Vorhaben interessant ist oder nicht.

Ich denke mal, bis hierhin kennen sich die meisten schon aus; Interessant wird es dann für folgenden Fall – gleiches Projekt (2.000 Dateien).

Ersetzen Advanced

Es fällt auf, dass es zwar ganz unten einen “Replace” Button gibt, aber kein Feld, mit was denn ersetzt werden soll. An dieser Stelle muss man wissen, dass dieses Ersetzen sich auf den gewählten “Scope” bezieht. Setzt man den Scope auf “Workspace”, dann geht es um das ganze Project, also 2.000 Dateien. Da sollte man vorsichtig sein … und eclipse hilft einem dabei.

Also, erst mal eingeben, was man ersetzen möchte. Als Beispiel benutze ich mal den Fall, dass ich in meinem (PHP)Projekt ca. 230 Links auf ein pdf Dokument habe. Der Name des Dokuments wurde nun kundenseitig geändert (kann ja mal passieren) und das ganze soll nun ASAP korrigiert werden.
Die Aufgabe lautet also: Korrigiere ca. 230 Links in ca. 2.000 Dateien und das schnellstmöglich.

Um das Beispiel gleich komplex zu machen – so lernt ihr wirklich alles kennen – nenne ich das alte Dokument “MeinReport_022010_de.pdf” und ein zweites Dokument heißt “MeinReport_022010_en.pdf” und das ganze soll nun so heißen: “report_de_februar_2010.pdf” und “report_en_februar_2010.pdf”.

Man sieht, die Namen sind sich ähnlich bis auf das de und en.

Der Weg ist nun folgender: Wir benutzen ein Such-RegEx, um die beiden PDF-Dokumente zu finden und ersetzen diese dann in den Dateien automatisch, allerdings wollen wir eine Vorkontrolle, was alles ersetzt werden soll. Und das geht so:

sufu4

Also, erst mal den RegEx eingeben, das ganze auch als “Regular expression” markieren, den “Scope” auf “Workspace” setzen und dann unten auf “Replace…” klicken.

eclipse sucht nun die Treffer und in der nächsten Maske kann man nun die Ersetzung angeben. Diese wäre in unserem Fall “report_$1_februar_2010.pdf”, damit de und en auch richtig eingesetzt werden.

Das ganz besondere ist nun der Button “Preview”, der uns nun alle Treffer in einem Diff anzeigt. So können wir sehen, ob alles richtig läuft, man kann einzelnen Ersetzungsschritte wählen oder ganz unten “Ok” klicken, dann werden alle Treffer ersetzt.

Mit der entsprechenden FTP-Synchronisation – die es leider im “normalen” eclipse nicht gibt, ich benutze ja ZendStudio – kann man nun automatisch die Änderungen an den Server übertragen.

Fazit: Das Problem klang nach viel Arbeit, aber die Powersuche in eclipse bzw. ZendStudio hilft uns dabei, das ganze gelassen in 15 Minuten zu erledigen. Und das war nur ein Reiter der Suche … wie gesagt, die anderen habe ich bisher nicht gebraucht.

return-path bei php mail() erzwingen

Bei machen Webhostern kann es sein, dass die sendmail Konfiguration einen header Eintrag in der Form von

$header .= „Reply-To: test@example.com\n“;

einfach nicht akzeptiert (Nein, es liegt nicht am fehlenden \r, soviel sei gesagt).

Ursache hierfür ist die Einstellung von sendmail, die solche Einträge mit den eigenen Einträgen überschreibt und heraus kommt dann sowas:

Reply-To: meinpc@localhost

Das ist natürlich Müll.

Allerdings kann man diesen Parameter mit der 5ten Option von PHP’s mail() überschreiben. Dazu fügt man am Ende den ‘-f’ Paramater für sendmail an, das Ganze sieht dann so aus:

mail($zieladresse,$subject,$body,$header,” –f test@example.com”);

Somit wird der Mail der gewünschte return-path verpasst.

MySQL – Den richtigen (Daten)typ kennen

Am Wochenende sah ich mir ein Script eines anderen Programmierers an … der Webmaster des Scriptes hatte eine Frage und zur Beantwortung benötigte ich Zugang zum Script. Nach langen Erklärungen, was denn genau passieren soll und was denn aktuell wirklich passiert, stieß ich auf einige Besonderheiten im Script.

Nun, den Fehler konnte ich beseitigen und der Webbi bat mich dann gleich mal, ob ich denn mal über den Code an sich sehen könnte, “ob man da noch was rausholen kann, so, Geschwindigkeitstechnisch”. “Immer”, denke ich mir und sehe mir das ganze so.

Okay, optimal war der Code nicht, eher genau das Gegenteil. Mein Kommentar war dann, dass, wenn der Webbi das ganze schnell haben will, er sich doch bitte einen _guten_ Programmierer holen soll, der das ganze mal neu macht. Der alte Code wäre dafür nicht geeignet. Der Webbi war nachher froh, dass der alte Code das macht, was er wollte und damit ist die Geschichte erstmal beendet.

Warum schreibe ich das ganze?
Nun, im Code fiel mir auf, dass ziemlich viele Variablen gegen die Datenbank geprüft wurden, nur für den Zweck, ob in einem Datenbankfeld – sinngemäß – ja oder nein steht. Dieses ja-oder-nein wurde dann aber in einem Zahlenfeld in mySQL gespeichert – soweit, so gut – nur eben ein INT(1).

Und nun zum Sinn dieses Postings: Lieber Programmierer, du brauchst in der DB einen Platz um “ja” oder “nein” zu speichern? Dann minimiere “ja” zu 1 und “nein” zu 0, soweit kamst du ja schon. Nun noch die Auswahl des geeigneten DB-Typen und da hast du gepatzt.

Ein INT(1) speichert eben NICHT nur eine Zahl mit der Länge 1 (also 0 bis 9), sondern einen 4 Byte großen Integer Wert, den du mittels “ZEROFILL” dann auf die in den Klammern angegebene Zahl mit 0 auffüllen kannst. Habe ich ein INT(3) Feld und speichere dort die Zahl 4 rein, dann steht da 004; in ein INT(3) kann ich aber auch die Zahl 123456 schreiben und habe kein Problem damit, da ein 4-Byte INT Zahlen von 0 bis 4294967295 (UNSIGNED) oder von –2147483648 bis 2147483647 (SIGNED) speichern kann, egal was in Klammern steht.

Um Speicherplatz zu sparen nimm bitte den Datentyp TINYINT, der speichert nur 1 Byte große Zahlen, also von 0 bis 255 (UNSIGNED) oder –128 bis 127 (SIGNED).

Zur weiteren Info lies dir die folgenden beiden Links durch:
http://dev.mysql.com/doc/refman/5.1/de/numeric-types.html
http://dev.mysql.com/doc/refman/5.1/de/storage-requirements.html

Art of Code, Refactoring, Sinn und Unsinn von Funktionen

Gegeben – aus welchem Grund auch immer – sei folgender Code:

function getRights(){
  $arr_rights = array();
  array_push($arr_rights, ‚admin‘);
  array_push($arr_rights, ‚client‘);
  array_push($arr_rights, ’superadmin‘);
  return $arr_rights;
}
function getRights_Superadmin(){
  $zws_rights = array();
  $zws_rights = getRights();
  return $zws_rights[2];
}
function getRights_Admin(){
  $zws_rights = array();
  $zws_rights = getRights();
  return $zws_rights[0];
}
function getRights_Client(){
  $zws_rights = array();
  $zws_rights = getRights();
  return $zws_rights[1];
}

Die Aufrufe der Funktionen getRights_Client(), …Admin() und …Superadmin() kommen im gesamten Script 16 mal vor, insgesamte Laufzeit der Datei, in denen diese Funktionen sind, liegt bei 0,113ms.

1

Folgendes Problem ergibt sich:
Bei jedem Aufruf einer der 3 Funktionen wird jedes Mal die Funktion getRights() aufgerufen, ergo wird diese Funktion 16 mal gerufen und verbraucht damit eine Laufzeit von 0.046ms, allein durch diese 16 Aufrufe.
An dieser Stelle – mal abgesehen davon dass es einfach “bad style” und ziemlich unoptimiert ist – sollte man refactorisieren.

Zuerst muss man den Sinn und Zweck verstehen, also: Was machen die Funktionen und warum?

Was machen die Funktionen?
Die Aufrufe der 3 Funktionen geben den Namen des jeweiligen aktuellen Benutzerrechtes wieder, der Name spiegelt sich in einer Datenbank wieder (ja, auch das hätte man anders machen können und sollen, aber dafür bleibt bei einer Bearbeitung von bestehendem Code nicht soviel Zeit) und diese Namen müssen auch 1:1 übernommen werden, will Mensch nicht den ganzen Code ändern (wozu Mensch normalerweise keine Zeit hat).

Welchen Sinn hat das ganze?
Die Funktion wie Sie oben stehen haben schon einen – wie ich meine – akademischen Sinn. Ziel des ganzen war es bestimmt, die jeweiligen Rechte einmal festzulegen und diese dann, mithilfe der Funktionen, an vielen Stellen abzufragen und zu vergleichen.

Der Weg über Funktionen und immer wieder kehrende Aufrufe anderer Funktionen ist jedoch im besten Falle als unpassend zu bezeichnen. Sicher: Es funktioniert, allerdings sollte ein guter Entwickler auch immer versuchen, seinen Code nicht nur funktional zu tippen, sondern auch “gut” und effizient.
Und effizient ist dieser Code nicht.

Kommen wir also zur Verbesserung: Wir refactorisieren den Code!

Aus Funktion und Sinn kann man leicht ersehen, dass das Mittel der Wahl am besten mit Konstanten umgesetzt werden kann.

Dazu definieren in der Datei im Kopfbereich diese Konstanten:

define(‚RIGHT_CLIENT‘, ‚client‘);
define(‚RIGHT_ADMIN‘, ‚admin‘);
define(‚RIGHT_SUPERADMIN‘, ’superadmin‘);

Nun ändern wir zuerst die Funktion getRights() entsprechend ab:


function getRights() {
  $arr_rights = array();
  array_push($arr_rights,RIGHT_ADMIN);
  array_push($arr_rights,RIGHT_CLIENT);
  array_push($arr_rights,RIGHT_SUPERADMIN);
  return $arr_rights;
}

Und nun können wir in den einzelnen Funktionen die Konstanten ins Spiel bringen.
Dadurch entfallen natürlich auch die Aufrufe von getRights() innerhalb der Funktionen, was eine Entlastung der Aufrufe von getRights() von 8 Aufrufen erbringt:

function getRights_Superadmin() {
  return RIGHT_SUPERADMIN;
}
function getRights_Admin() {
  return RIGHT_ADMIN;
}
function getRights_Client() {
  return RIGHT_CLIENT;
}
Und nun wird wieder profiliert und es ergibt sich folgendes Bild:
2

Nun wird die Funktion getRights() gar nicht mehr aufgerufen (der Aufruf erfolgt aber an anderen Stellen noch, die Ausführzeit nur dieser einen Datei liegt nun bei 0,054ms, also knapp 50% unter der eigentlichen Zeit und die 3 Funktionen haben ebenfalls verkürzte Ausführzeiten.

Die Funktionen selbst sind erhalten geblieben, der “Sinn” auch, denn die Rechte können zentral verwaltet und geändert werden.

Der nächste Schritt wäre, im gesamten Script die Aufrufe von getRight_Client(), …Admin() und Superadmin() gegen die entsprechenden Konstanten zu tauschen, so dass kein Aufruf mehr an diese Datei gehen würde. Dies kann man machen, ohne dabei den “Sinn” oder Funktion zu beeinträchtigen, allerdings fällt das wieder unten den Punkt “Zeit, die einem keiner bezahlt”, aber sinnvoll aus Sicht der Refactorisierung und des “Good styled code” wäre es sicherlich.

Noch am Rande: Alle Profilierungen wurden mit dem Zend Studio 7 vorgenommen, die Screenshots entstammen den jeweiligen Reports der “Execution Statistic”.

Kommentare dazu sind gern gesehen 😉

Refactoring – Alter Code im neuen Kleid … nicht nur für PHP

Grade in einem Code gefunden, der Aufruf der Funktion ‘getSession()’. Und nun mal die Preisfrage an alle PHP-Pro’s: Wieviel Code davon muss wirklich sein?
Aber erstmal der Originalcode:

function getSession(){
  $akt_timestamp = getTimestamp();
  $ses_timestamp = $_SESSION[getSession_Timeout()];
  if($akt_timestamp > $ses_timestamp){
    return false;
  }
else{
    if($akt_timestamp < $ses_timestamp){
      return true;
    }
  }
}
function getTimestamp() {
  return time();
}
function getSession_Timeout(){ return „usrTimeout“; }

*wow* Das sind 3 Funktionen, die ineinandergreifen, was?
Und nun – nochmal – die Preisfrage: Wieviel Code davon muss sein?
Ich behaupte: Nicht so viel, wie oben steht.
Beweis? Ich refaktorisiere den Code oder neudenglisch: Ich mache refactoring! (Ich red’ da lieber deutsch, aber jeder wie er/sie will).
Betrachten wir die Anforderungen:

  • beim Aufruf der Funktion getSession() wird eine Rückgabe vom Typ Boolean erwartet, dies sollten wir beachten.
  • Vereinfacht wird das refactoring (hier finde ich das passend) dadurch, dass es keine Übergabeparameter an die Funktion gibt.

So, die Hülle der Funktion steht, nun zum Inhalt.
Was macht die Funktion eigentlich?

  • Sie legt eine lokale Variable an, in der die aktuelle Zeit gespeichert wird.
  • Sie legt eine lokale Variable an, in der ein Wert aus der aktuellen Session gespeichert wird.
  • Wenn die aktuelle Zeit größer ist als der Wert der Session, gibt die Funktion false zurück.
  • Ist die aktuelle Zeit kleiner als der Wert der Session, dann gibt die Funktion true zurück.
  • Hier schon der erste Fehler: Was ist, wenn beide Werte gleich sind? Dieser Fall wird nicht berücksichtigt.

Nun das refactoring:

  1. Die Funktion getTimestamp() wird in der Funktion getSession() durch den Aufruf von time() ersetzt, somit fällt diese Funktion weg.
  2. Die Funktion getSession_Timeout() wird in der Funktion getSession() durch die Rückgabe von getSession_Timeout() ersetzt, somit fällt auch diese Funktion weg.
  3. Ziel der Funktion ist es, ein true oder false bei o.a. Bedingungen zurückzugeben.

Während unseres Refactorings verbessern wir auch gleich die Funktion der Funktion (*g*) und legen auch gleich fest, was passieren soll:

  • Ist die aktuelle Zeit größer wie der Wert in der Session, dann gibt die Funktion true zurück, sonst false.

Klingt einfach? Ist es auch.
Verkürzt man nun sukzessive die if…else Schleife und bedenkt, dass man die beiden lokalen Variablen nicht braucht und ersetzt diese mit ihren Entsprechungen (time() und Session-Wert),  so erhält man folgende Funktion:

function getSession()
{
  return (time() < $_SESSION[‚usrTimeout‘]);
}

Kurz, knackig und präzise. Diese Funktion erfüllt alle Anforderungen (und übererfüllt sogar diese, in dem es die alte Funktion um den Bereich “Zeit ist gleich Sessionwert” erweitert).

Für meine Behauptung oben auf die Frage “Wieviel Code davon ist nötig” würde ich nun sagen: q.e.d.

So, das war ein kleiner Ausflug in die Welt des Refactoring.
Ich hoffe, ihr habt alle was gelernt …