Stud.IP Coding Standard

Übersicht

(in Kraft treten: xx.0x.2008)

––

(deprecated)

Vorschlag für einen StudIP Coding Standard: PEAR Coding Standard

Diskussion über den PEAR Coding Standard

Einleitung
Zusammenfassung
Änderungswünsche & mehr

Inhaltsverzeichnis: PEAR Coding Standard

Einrückungen und Zeilenlänge
Kontrollstrukturen
Funktionsaufrufe
Klassen-Definition
Funktionsdefinitionen
Kommentare
Code einbinden
PHP-Code-Tags
Kopf-Kommentare
CVS-Nutzung
Beispiel-URLs
Namens-Konventionen
Dateiformate
E_STRICT-kompatibler Code
Richtlinen zur Fehlerbehandlung
Best practices
Beispieldatei inklusive Docblock-Kommentaren

Diskussion über den PEAR Coding Standard

Einleitung

Die Einhaltung eines Coding Standards ist ein wichtiger Bestandteil guter Projektarbeit. Gerade in Open Source Projekten mit vielen Entwicklern wird damit gewährleistet, dass Source Code eine hohe Qualität erhält/behält, weniger Bugs enthält, und einfacher zu warten ist.

Darüber hinaus gibt es jedoch auch andere wichtige Faktoren:

  • Kommunikation
  • funktionierender Code
  • übersichtlicher Code
  • Module und Modultests
  • Wiederverwendbarkeit des Codes
  • einfache Wartung des Codes
  • Code, der leicht zu erweitern ist
  • Unterstützung von Refactoring

um nur einige zu nennen. Ein Coding Standard unterstützt alle genannten Punkte. Neue Entwickler können sich schnell einarbeiten, denn wenn jeder den Code des anderen versteht, kann man auch schnell Bug-Fixes vornehmen oder Module des anderen verwenden ohne das Rad neu zu erfinden.

Um diese Ziele zu erreichen, muss eine PHP-Applikation:

  • leicht zu lesen sein
  • leicht verständlich sein
  • gut dokumentiert sein
  • durch verschiedene Programmierer wartbar
  • frei von typischen Fehlern

sein. Im besten Falle kann sogar der Anwender die Module der PHP-Applikation verstehen und weiter verwenden.

Das Ziel von leicht verständlichem Source-Code ist erreicht, wenn jedes Mitglied des Teams, jedes Modul aus dem Stehgreif erklären kann. Nicht zuletzt soll jede Arbeit am Projekt, dem Code und der Dokumentation Spaß machen. Je höher der Frustfaktor ist, je unübersichtlicher Strukturen und Code sind, desto höher ist die Wahrscheinlichkeit, das die Arbeit keinen Spaß mehr macht. Das gilt es auf jeden Fall zu verhindern.

Wenn sich ein Projekt an Coding Standards hält, dann sind einige gute Effekte zu beobachten:

  • Entwickler können in jeden Code hineienschauen und sofort heraus finden was dort vor sich geht.
  • Die Lebenszeit eines Softwareprojektes wird verlängert und überlebt die Lebenszeit der erfahrenen Entwickler.
  • Neue Leute können sich schnell einarbeiten.
  • PHP Einsteiger brauchen keinen persönlichen Style enwickeln und diesen bis zum bitteren Ende verteidigen.
  • PHP Einsteiger werden davor bewahrt die gleichen Fehler wieder und wieder zu machen.
  • In konsistenten Umgebungen werden weniger Fehler gemacht.

Ich freue mich auf eine früchtetragende Diskussion mit guten Ergebnissen hinsichtlich der Verwirklichung des StudIP Coding Standards.

PEAR Coding Standard
Note: Sehr zu empfehlen!

Zusammenfassung

Feature

Ausprägung

Euer Kommentar (mit Namen bitte)

Einrückungen

  • 4 Leerzeichen, keine Tabulatoren
  • find ich gut so (chueser)

Zeilenlänge

  • ca. 75 - 85 Zeichen

Kontrollstrukturen

  • Leerzeichen zwischen den Schlüsselwörtern und der öffnenden Klammer
  • immer geschweifte Klammern verwenden
  • öffnende geschweifte Klammer in gleicher Zeile

Funktionsaufrufe

  • kein Leerzeichen zwischen dem Funktionsnamen, der öffnenden Klammer und dem ersten Parameter

Klassen-Definition

  • öffnende Klammer einer Klassen-Deklaration beginnt auf einer neuen Zeile

Funktionsdefinitionen

  • öffnende Klammer einer Funktionsdefinition beginnt auf einer neuen Zeile
  • Argumente mit Standardwerten werden am Ende der Argumentenliste platziert
  • wenn möglich, immer einen aussagekräftigen Wert zurückliefern

Kommentare

  • vollständige Inline-Dokumentation (Docblocks): File, Includes, Konstanten, Variablen, Methoden, Klasse
  • unbedingt (!) auch abseits der offziellen API-Dokumentation Kommentare im Quellcode einsetzen
  • Als Daumenregel gilt, wenn Sie auf einen Code-Abschnitt schauen und denken: „Wow, Ich würde nicht versuchen herauszufinden, wie es funktioniert“, sollten Sie auf jeden Fall einen Kommentar ergänzen, bevor Sie vergessen, wie es funktioniert.

Code einbinden

PHP-Code-Tags

  • immer die Langform <?php ?> benutzen
  • während deiner Präsentation hatten wir schon angemerkt, dass das natürlich nicht für die Templates gilt.. (mlunzena)

Kopf-Kommentare

  • Alle Quellcode-Dateien sollen einen „Page-level“ Docblock am Datei-Anfang besitzen und einen „Class-level“ Docblock unmittelbar vor jeder Klasse.
  • Docblock = short decription + long description + tag list
  • Finde ich sehr gut. Bitte auch einheitlich wie weiter unten beschrieben. Momentan ist „Page-level“ Docblock in vielen Dateien sehr unterschiedlich. (mriehe)

CVS-Nutzung

  • $Id$-CVS-Schlüsselwort in jede Datei.
  • in diesem Fall ist wohl das SVN- Schlüsselwort gemient. (mriehe)

Beispiel-URLs

Namens-Konventionen

  • beschreibende Klassennamen und Abkürzungen vermeiden
  • Klassennamen beginnen immer mit einen Großbuchstaben
  • Klassenhierarchie spiegelt sich im Klassennamen wieder, jede Ebene der Hierarchie wird durch einen einzelnen Unterstrich getrennt
  • Funktionen und Methoden im „Camel Caps“
  • Funktionsnamen sollte der Package-Name vorangestellt werden
  • erster Buchstabe nach Prefix wird klein geschrieben, und jeder Buchstabe eines neuen „Wortes“ wird großgeschrieben
  • privaten Klassenelemente wird ein Unterstrich vorangestellt
  • private Klassenelemente sollten keinen Unterstrich vorangestellt bekommen; das ist doch PHP5! (mlunzena)

Dateiformate

E_STRICT-kompatibler Code

  • Der Code darf keine Fehler oder Warnungen erzeugen, wenn das Fehler-Reporting von PHP auf E_STRICT eingestellt ist.
  • das dürfte kaum durchsetzbar sein.. (mlunzena)
  • vielleicht nicht für den alten code, aber auf jeden fall für alle neuen änderungen und plugins. würde das debuggen sehr vereinfachen… (mriehe)

Richtlinen zur Fehlerbehandlung

Änderungswünsche & mehr

Feature (Name)

Ausprägung

Euer Kommentar (Namen)

Namenskonvention für Accessor-Methoden (chueser)

  • getter Methoden: get+Variablenname
  • setter Methoden: set+Variablenname
  • erster Buchstabe des Variablennamens groß schreiben

Anzahl Klassen in einem File (chueser)

  • nur eine Klasse pro File (wegen Eindeutigkeit der Packages)
  • Da seh ich nicht den Sinn, in Java ok, aber das ist PHP, warum sollte man das machen? Es gibt doch gar keine "import" Anweisung? (mlunzena)
  • Ich finde das sehr sinnvoll, so findet man dann auch leichter, wo die Klassen sind. (tgloeggl)
  • Ich stimme da tgloeggl vollkommen zu. (mriehe)
  • Das macht es auch phpDocumentor leichter eine Paket-Struktur zu parsen. (chueser)

File-Name, wenn File eine Klasse enthält (Feature geändert) (chueser)

  • Name des Files gleich dem Klassen-Namen
  • In PHP gibt's doch aber auch Dateien, die keine Klassen enthalten.. Wie sollte das denn funktionieren? Grundsätzlich macht es keinen Sinn, einfach nur alles so zu machen wie in Java. Java ist nicht unbedingt immer ein Vorbild. Ansonsten sollte man eben Java nehmen. (mlunzena)
  • Hä? Alles Dateien mit der Endung .class.php enthalten genau eine Klasse, alle anderen Dateien sind normale Skripte wie bisher. (tgloeggl)
  • Ich stimme da tgloeggl vollkommen zu. (mriehe)

phpDocumentor zur Dokumentation (chueser)

  • Dokumentation & Tags im phpDocumentor-Stil.

Paket-Struktur (chueser)

  • Hier geht es um die Paket-Struktur (@package, @subackage) auf Page- & Class-Level:
    • Functions, Constants and Global Variables are grouped into files (by the filesystem), which are in turn grouped into packages using the @package tag in a page-level DocBlock.
    • Methods and Class Variables are grouped into classes (by PHP), which are in turn grouped into packages in a Class DocBlock.
PHP Documentor wird den Code nicht parsen, wenn die Paket-Struktur nicht stimmt. (chueser)

Sichtbarkeitsschlüselwörter (chueser)

  • Bei Methoden und Klassenvariablen werden Sichtbarkeitsschlüsselwörter benutzt: public, protected, private

Sprache

  • Die bevorzugte Sprache für Code und Kommentare ist Englisch.

PEAR Coding Standard

Einrückungen und Zeilenlänge

Benutzen Sie Einrückungen mit 4 Leerzeichen, keine Tabulatoren. Damit helfen Sie, Probleme zu vermeiden, die mit Diffs, Patches und anderen CVS-Funktionen entstehen.

Wir empfehlen einen Zeilenumbruch bei ca. 75 - 85 Zeichen durchzuführen, um die Lesbarkeit zu erhöhen.

Kontrollstrukturen (if, for, while, switch usw.)

Bsp für if-Kosntruktionen:

<?php
if ((condition1) || (condition2)) {
    action1;
} elseif ((condition3) && (condition4)) {
    action2;
} else {
    defaultaction;
}
?>

Kontroll-Ausdrücke sollten ein Leerzeichen zwischen den Schlüsselwörtern und der öffnenden Klammer haben, um sie von Funktionsaufrufen unterscheiden zu können.

Sie sollten unbedingt geschweifte Klammern verwenden, auch wenn sie technisch nur optional sind. Damit verbessern Sie die Lesbarkeit und vermeiden logische Fehler, wenn neue Zeilen hinzugefügt werden.

Bsp für switch-Ausdrücke:

<?php
switch (condition) {
case 1:
    action1;
    break;

case 2:
    action2;
    break;

default:
    defaultaction;
    break;
}
?>

Funktionsaufrufe

Funktionen sollten ohne Leerzeichen zwischen dem Funktionsnamen, der öffnenden Klammer und dem ersten Parameter aufgerufen werden. Leerzeichen sollten gesetzt werden zwischen Komma und jedem Parameter. Zwischen dem letzten Parameter, der schliessenden Klammer und Semikolon sollten keine Leerzeichen gesetzt werden.

Beispiel:

<?php
$var = foo($bar, $baz, $quux);
?>

Wie oben gezeigt, sollte ein Leerzeichen vor und hinter das Gleichheitszeichen gesetzt werden, wenn der Rückgabewert der Funktion einer Variable zugewiesen wird. Wenn ein Block zusammenhängender Zuweisungen durchgeführt wird, empfehlen wir mehrere Leerzeichen einzufügen, um die Lesbarkeit zu verbessern:

<?php
$short         = foo($bar);
$long_variable = foo($baz);
?>

Klassen-Definition

Die öffnende Klammer einer Klassen-Deklaration beginnt auf einer neuen Zeile:

<?php
class FooBar
{

    //... ihr Code

}
?>

Funktionsdefinitionen

Funktionsdeklarationen folgen dem „K&R-Stil“ (Programmierstil der C-Erfindern Brian W. Kernighan und Dennis Ritchie):

<?php
function fooFunction($arg1, $arg2 = '')
{
    if (condition) {
        statement;
    }
    return $val;
}
?>

Argumente mit Standardwerten werden am Ende der Argumentenliste platziert. Eine Funktion sollte immer einen aussagekräftigen Wert zurückliefern, soweit wie es möglich ist.

Beispiel:

<?php
function connect(&$dsn, $persistent = false)
{
    if (is_array($dsn)) {
        $dsninfo = &$dsn;
    } else {
        $dsninfo = DB::parseDSN($dsn);
    }

    if (!$dsninfo || !$dsninfo['phptype']) {
        return $this->raiseError();
    }

    return true;
}
?>

Kommentare

Sie müssen vollständige Inline-Dokumentation bereitstellen (Docblocks).

Sie sollten auch abseits der offziellen API-Dokumentation Kommentare im Quellcode einsetzen. Als Daumenregel gilt, wenn Sie auf einen Code-Abschnitt schauen und denken: „Wow, Ich würde nicht versuchen herauszufinden, wie es funktioniert“, sollten Sie auf jeden Fall einen Kommentar ergänzen, bevor Sie vergessen, wie es funktioniert.

Kommentare im C-Stil (/* */) und von Standard-C++ (//) sollten verwendet werden. Kommentare im Perl/shell-Stil (#) sollten Sie vermeiden.

Code einbinden

Immer wenn Sie eine Klassendatei unbedingt inkludieren müssen, dann benutzen Sie require_once. Benötigen Sie hingegen eine Datei nur bedingt, z.B. in Factory-Methoden, verwenden Sie include_once. Beide stellen sicher, dass die Datei nur einmal eingebunden wird. Beide Funktionen benutzen die gleiche Liste zur Verwaltung der bereits eingebundenen Dateien. Sie müssen sich über eine Mischung aus beiden Funktionsaufrufen keine Gedanken machen - eine Datei, die per require_once eingebunden wurde, wird nicht erneut eingebunden mit include_once.

Anmerkung: include_once und require_once sind Anweisungen, nicht Funktionen. Deshalb sollten die Dateinamen nicht in Klammern eingeschlossen werden.

PHP-Code-Tags

Benutzen Sie immer <?php ?> um Ihren PHP-Code zu markieren, niemale die Kurzform <? ?>. Die erste Variante funktioniert unabhängig vom Betriebssystem und der PHP-Konfiguration.

Kopf-Kommentare

Alle Quellcode-Dateien sollen einen „Page-level“ Docblock am Datei-Anfang besitzen und einen „Class-level“ Docblock unmittelbar vor jeder Klasse.

Beispiele:

<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Short description for file
 *
 * Long description for file (if any)...
 *
 * PHP versions 4 and 5
 *
 * LICENSE: This source file is subject to version 3.0 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @category   CategoryName
 * @package    PackageName
 * @author     Original Author <author@example.com>
 * @author     Another Author <another@example.com>
 * @copyright  1997-2005 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    CVS: $Id:$
 * @link       http://pear.php.net/package/PackageName
 * @see        NetOther, Net_Sample::Net_Sample()
 * @since      File available since Release 1.2.0
 * @deprecated File deprecated in Release 2.0.0
 */ 

/*
 * Place includes, constant defines and $_GLOBAL settings here.
 * Make sure they have appropriate docblocks to avoid phpDocumentor
 * construing they are documented by the page-level docblock.
 */

/**
 * Short description for class
 *
 * Long description for class (if any)...
 *
 * @category   CategoryName
 * @package    PackageName
 * @author     Original Author <author@example.com>
 * @author     Another Author <another@example.com>
 * @copyright  1997-2005 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    Release: @package_version@
 * @link       http://pear.php.net/package/PackageName
 * @see        NetOther, Net_Sample::Net_Sample()
 * @since      Class available since Release 1.2.0
 * @deprecated Class deprecated in Release 2.0.0
 */
class Foo_Bar
{
}

?>

Erforderliche Tags

Kurzbeschriebung

Die Kurzbeschreibung muss in allen Docblocks existieren. Sie umfasst einen Satz, und sollten keinesfalls nur aus dem Namen des beschreibenden Objektes stehen.

PHP Version

Folgende Zeile muss in den Page-level-Docblock eingefügt werden. Sie beschreibt, unter welcher PHP-Version der Quellcode läuft:

  • PHP version 4
  • PHP version 5
  • PHP versions 4 and 5

@license

Sie können verschiedene Lizenzen für ihr Package verwenden. Sie müssen eine auswählen und diese sowohl im Page-level-, wie auch dem Class-level-Docblock einfügen:

@link

Die Link-Angabe muss im Page-level- and Class-level-Docblock erfolgen. Sie müssen den Eintrag für „PackageName“ natürlich durch den Namen Ihres Packages ersetzen. Auf diese Weise ist sicher gestellt, dass die API-Dokumentation die Links auf die Package-Homepage enthält.

@author

Es gibt keine klare Regel, wann jemand zur Liste der Quellcode-Autoren hinzugefügt werden sollte. Allgemein sollten sie substanziell beigetragen haben, ca. 10% bis 20% des Codes. Sie können Ausnahmen machen, wenn Methoden neu geschrieben wurden oder neue Logik beigesteuert wurde.

Einfache Code-Neuformatierungen oder Bug-Fixes sollten nicht zur Aufnahme in die Liste führen.

@since

Dieses Tag ist erforderlich, wenn eine Datei oder Klasse nach dem ersten Release zum Package ergänzt wurde. Verwenden Sie es nicht beim ersten Release.

@deprecated

Dieses Tag ist erforderlich, wenn eine Datei oder Klasse nicht mehr benutzt wird, aber für die Rückwärtskompatibilität noch verfügbar ist.

Optionale Tags

@copyright

Sie können dieses Tag setzen, wenn Sie Copyright-Anmerkungen ergänzen wollen. Das Format des Tags: Die vierstellige Jahreszahl bzw. bei mehreren Jahren, nur das erste und letzte Jahr getrennt mit einem Querstrich; danach der Copyright-Inhaber, also die betreffenden Personen, Firma oder z.B. die PHP Group.

Beispiele:

  • @copyright 2003 John Doe and Jennifer Buck
  • @copyright 2001-2004 John Doe
  • @copyright 1997-2004 The PHP Group
  • @copyright 2001-2004 XYZ Corporation

License Summary

If you are using the PHP License, use the summary text provided above. If another license is being used, please remove the PHP License summary. Feel free to substitute it with text appropriate to your license, though to keep things easy to locate, please preface the text with LICENSE: .

@see

Fügen Sie ein @see-Tag hinzu, wenn Sie auf andere Abschnitte in der Package-Dokumentation verweisen. Wenn Sie mehrere Einträge hinzufügen, verwenden Sie nicht mehrere Tags, sondern trennen diese in einem Tag per Komma.

Abfolge und Leerzeichen

Um die langfristige Lesbarkeit zu gewährleisten, sollten Texte und Tags dem gegebenem Beispiel folgen. Diese Festlegungen entsprechen dem JavaDoc-Standard.

Verwendung von @package_version@

Es gibt zwei Wege @package_version@ zu ersetzen. Das ist abhängig davon, ob Sie die Package-Datei package.xml von Hand erzeugen oder das Package PackageFileManager benutzen.

Wenn Sie die Datei direkt bearbeiten, ergänzen Sie ein <replace>-Tag für jede Datei. Das XML sieht dann ähnlich wie dieses aus:

<file name="Class.php">
  <replace from="@package_version@" to="version" type="package-info" />
</file>

Wenn PackageFileManager verwenden, rufen Sie addReplacement() für jede Datei auf:

<?php
$pkg->addReplacement('filename.php', 'package-info',
                     '@package_version@', 'version');
?>

Übergangsregeln

Für existierende kleine Packages

Existierende Packages mit nur wenigen Dateien sollten die DocBlöcke im nächst möglichen Release enthalten.

Für existierende große Packages

Existierende Packages mit vielen Dateien sollten die neuen DocBlöcke sobald wie möglich integrieren. Auf jeden Fall aber mit den nächsten Major-Release.

Neue und unveröffentliche Packages

Neue Packages und existierende Package, für welche noch kein Release existiert, müssen über die DocBlöcke verfügen, wenn ihr erstes Release erscheint.

CVS-Nutzung

Ergänzen Sie das $Id$-CVS-Schlüsselwort in jeder Datei.

Beispiel-URLs

Benutzen Sie example.com, example.org und example.net für alle Beispiel-URLs und Email-Adressen entsprechend RFC 2606.

Namens-Konventionen

Klassen

Verwenden Sie beschreibende Klassennamen und vermeiden Sie Abkürzungen. Klassennamen beginnen immer mit einen Großbuchstaben. Die Klassenhierarchie in PEAR spiegelt sich auch im Klassennamen wieder, jede Ebene der Hierarchie wird durch einen einzelnen Unterstrich getrennt.

Beispiele für Klassennamen:

  • Log
  • Net_Finger
  • HTML_Upload_Error

Funktionen und Methoden

Funktionen und Methoden sollten dem „Studly Caps“-Stil folgen (auch bekannt als „Bumpy Base“ oder „Camel Caps“). Funktionsnamen sollte der Package-Name vorangestellt werden, um Kollisionen mit anderen Funktionsnamen zu vermeiden. Der erste Buchstabe nach dem Prefix wird klein geschrieben, und jeder Buchstabe eines neuen „Wortes“ im Namen wird großgeschrieben.

Beispiele:

  • connect()
  • getData()
  • buildSomeWidget()
  • XML_RPC_serializeData()

Privaten Klassenelemente wird ein Unterstrich vorangestellt.

Zum Beispiel:

  • _sort()
  • _initTree()

$this->_status

Anmerkung: Unter PHP 5 gilt: Klassenelemente, welche protected sind, werden nicht mit einem Unterstrich versehen:

  • protected $somevar
  • protected function initTree()

Konstanten

Konstanten werden immer vollständig groß geschrieben, mit Unterstrichen zwischen den Worten. Ihre Namen müssen mit dem Klassen- bzw. Package-Namen beginnen. Zum Beispiel beginnen alle Konstanten des DB::-Packages mit DB_.

Anmerkung: Die Konstanten true, false und null werden als einzige Ausnahme immer klein geschrieben.

Globale Variablen

Wenn Ihr Package globale Variablen benötigt, sollte deren Name mit einem Unterstrich beginnen, gefolgt vom Package-Namen und einem weiteren Unterstrich. Zum Beispiel benutzt das PEAR-Package die globale Variable $_PEAR_destructor_object_list.

Dateiformate

Alle Skripte in PEAR müssen:

  • als ASCII gespeichert werden.
  • mit dem Zeichensatz ISO-8859-1 kodiert werden.
  • den UNIX-Konventionen folgen. „Unix-Konventionen“ bedeutet:
    • Zeilen dürfen nur mit dem Line-Feed-Zeichen (LF) enden. Das Zeichen entspricht dem Ordinal-Wert 10, Octal 012 und hexadezimal 0A. Benutzen Sie kein Carriage-Return-Zeichen (CR) wie unter Apple Macintosh-Computern oder die CRLF-Kombination unter Windows.
    • Fügen Sie ein Zeilenende-Zeichen nach dem schliessendem PHP-Tag (?>) ein. Das bedeutet, der Cursor im Editor steht eine Zeile nach dem schliessendem Tag.

E_STRICT-kompatibler Code

Jeder Code muss E_STRICT-kompatibel sein. Das heisst, er darf keine Fehler oder Warnungen erzeugen, wenn das Fehler-Reporting von PHP auf E_STRICT eingestellt ist.

Die Entwicklung existierender Packages, die dieser Konvention noch nicht entsprechen, müssen dies auch noch nicht. Erst wenn eine neue major-Version eines Packages erscheint, dann muss diese Version der E_STRICT-Konvention entsprechen.

Richtlinen zur Fehlerbehandlung

Richtlinen zur Fehlerbehandlung

Dieser Abschnitt beschreibt wie Fehler in PEAR-Packages behandelt werden sollen, die für PHP 5 und 6 entwickelt werden. Fehler werden über Exceptions abgewickelt, sie wurden mit PHP 5.0 und der Zend Engine 2 eingeführt.

Was ist ein Fehler?

Ein Fehler ist definiert als ein unerwartet, nicht korrekter Programmzustande, der nicht wieder behoben werden kann. Zur Vereinfachung dieser Definition gilt, dass die Behebung des Fehlers nicht innerhalb einer Methode möglich ist. Eine unvollständige Behebung gilt trotzdem als Behebung.

Beispiel 4-1. Eine typische Fehlersituation

<?php
/**
 * Verbinde zur angegebenen Datenbank
 *
 * @throws Example_Datasource_Exception wenn der Verbindungsaufbau fehlschlägt
 */
function connectDB($dsn) {
    $this->db =& DB::connect($dsn);
    if (DB::isError($this->db)) 
    {
        throw new Example_Datasource_Exception(
                "Unable to connect to $dsn:" . $this->db->getMessage()
        );
    }
}
?>

In diesem Beispiel soll die Methode die Verbindung mit der Datenbank mit dem gegegebenem DSN herstellen. Die Methode ruft ihrerseits nur PEAR::DB, wenn dieses Package einen Fehler wirft, kann nur eine Exception erzeugt und geworfen werden, ohne weiter Einfluß nehmen zu können.

Beispiel 4-2. Fehlerbehandlung mit Behebung

<?php
/*
 * Verbinde mit einer der möglichen Datenbanken
 *
 * @throws Example_Datasource_Exception wenn keine der gewählten
 *         Datenbank angesprochen werden konnte.
 *
 * @throws Example_Config_Exception wenn keine Datenbanken
 *         konfiguriert wurden
 */

function connect(Config $conf) 
{
    $dsns =& $conf->searchPath(array('config', 'db'));
    if ($dsns === FALSE) throw new Example_Config_Exception(
        'Unable to find config/db section in configuration.'
    );

    $dsns =& $dsns->toArray();

    foreach($dsns as $dsn) {
        try {
            $this->connectDB($dsn);
            return;
        } catch (Example_Datasource_Exception e) {
            // Warn-/Logging-Code um den Verbindungsfehler
            // aufzuzeichnen
        }
    }
    throw new Example_Datasource_Exception(
        'Unable to connect to any of the configured databases'
    );
}
?>

Das zweite Beispiel zeigt, wie eine Exception empfangen und behandelt wird. Die verwendete connectDB()-Methode kann nur einen Fehler melden, wenn die Verbindung fehlschlägt. Die übergeordnete Methode connect() hingegen weiss, dass das Objekt auch mit einer der anderen Datenbank-Verbindungen lauffähig ist. Deshalb kann der Fehler als behoben angesehen werden und die Exception wird nicht weitergeleitet.

Beispiel 4-3. Unvollständige Behebung

<?php
/**
 * loadConfig wertet die angegebene Konfiguration aus. Wenn die
 * Konfiguration unkorrekt ist, dann wird auf die Standard-
 * zurückgegriffen
 *
 */
function loadConfig(Config $conf) 
{
    try {
        $this->config = $conf->parse();
    } catch (Config_Parse_Exception e) {
        // Warn-/Logging-Code
        // Unvollständige Fehlerbehebung
        $this->config = $this->defaultConfig;
    }
}
?>

Die Fehlerbehebung führt zu Seiteneffekten, deshalb ist sie nicht vollständig. Das Programm kann weiterlaufen, die Exception gilt als behandelt und muss nicht weitergeleitet werden. Wie im vorherigen Beispiel sollte die aufgetretene Exception aber trotzdem geloggt werden oder eine andere Form der Warnung stattfinden.

Fehler-Benachrichtigung PHP 5 PEAR-Packages

Fehlerhafte Zustände in PEAR-Packages für PHP 5 müssen über Exceptions gemeldet werden. Nicht mehr verwendet werden sollten Fehlercodes oder ein PEAR_Error-Objekt. Diese Regel gilt natürlich nicht, wenn das Package kompatibel mit PHP 4 bleiben muss. In diesem Fall gelten die Konvention der PEAR Coding Standards unter PHP 4 weiter.

Eine Exception sollte immer geworfen werden, wenn ein fehlerhafter Zustand auftritt, entsprechend der Definition im vorherigen Abschnitt. Die geworfene Exception sollte genügend Informationen enthalten, um den Fehler debuggen zu können und schnell dessen Grund herauszufinden. Bedenken Sie, dass in Produktionsumgebungen keine Exception an den Endanwender durchdringen sollten. Deshalb muss man sich keine Gedanken machen über die Komplexität der Fehlermeldung.

Die Basis-Klasse PEAR_Exception enthält eine wörtliche Beschreibung des Fehlers, womit der Programmzustand beschrieben wird, der zum Fehler führte, und - optional - Execptions, die durch untergeordnete Programmaufrufe herbei geführten wurden und die ursprüngliche Ursache des Fehlers darstellen können.

Die Arten von Informationen die in einer Exception enthalten sein müssen, hängt von der Art des Fehlers ab. Es gibt drei Varianten von Exceptions:

  1. Fehler, die während der Vorabprüfung auftreten können.
  2. Fehler, die durch untergeordneten Bibliotheksaufrufe auftreten und durch Fehlercodes oder -Objekte signalisiert werden
  3. Nicht-korrigierbare Exceptions von untergeordneten Bibliotheken.

Fehler, die während der Vorabprüfung auftreten können, sollten eine Beschreibung der fehlgeschlagenen Prüfung enthalten. Wenn möglich sollte der fehlerhafte Wert mit angegeben werden.

Beispiel 4-4.

<?php
function divide($x, $y)
{
    if ($y == 0) {
        throw new Example_Aritmetic_Exception('Division by zero');
    }
}
?>

Fehler, die durch untergeordneten Bibliotheksaufrufe auftreten und durch Fehlercodes oder -Objekte signalisiert werden, sollten in Exceptions umgewandelt werden, wenn diese nicht behoben werden können. Die Fehlerbeschreibung sollte die originale Fehlerinformationen enthalten bzw. entsprechend konvertiert werden. Am Beispiel der obigen connect()-Methode:

Beispiel 4-5.

<?php
/**
 * Verbinde zur angegebenen Datenbank
 *
 * @throws Example_Datasource_Exception wenn der Verbindungsaufbau fehlschlägt
 */
function connectDB($dsn) {
    $this->db =& DB::connect($dsn);
    if (DB::isError($this->db)) {
        throw new Example_Datasource_Exception(
                "Unable to connect to $dsn:" . $this->db->getMessage()
        );
    }
}
?>

Nicht-korrigierbare Exceptions von untergeordneten Bibliotheken sollten weitergeleitet oder erneut geworfen werden. Wenn sie weitergeleitet werden soll, dann behandeln Sie die Exception nicht weiter. Wenn Sie die Exception erneut werfen, dann müssen Sie die originale Exception in der neuen Exception verpacken.

Beispiel 4-6. Eine Exception neu verpacken

<?php
function preTaxPrice($retailPrice, $taxRate) 
{
    try {
        return $this->divide($retailPrice, 1 + $taxRate);
    } catch (Example_Aritmetic_Exception e) {
        throw new Example_Tax_Exception('Invalid tax rate.', e);
    }
}
?>

Beispiel 4-7. Eine Exception weiterleiten

<?php
function preTaxPrice($retailPrice, $taxRate) 
{
    return $this->divide($retailPrice, 1 + $taxRate);
}
?>

Die Entscheidung, ob eine Exception neu verpackt oder weitergeleitet werden soll, ist eine Frage der Software-Architektur. Exceptions sollten weitergeleitet werden, ausser in zwei Fällen:

  1. Die originale Excpetion ist von einem anderen Package. Wenn diese weitergeleitet wird, dann würden Details der Implementierung nach aussen dringen.
  2. Die Methode kann nützliche Debug-Informationen ergänzen.

Exceptions und der normale Programmfluß

Exceptions sollten niemals als Bestandteil des normalen Programmflußes benutzt werden. Wenn alle Logik zur Behandlung von Exceptions entfernt würde (try-catch-Statements), dann sollte der verbliebende Code den "wahren Pfad" repräsentieren – dem Programmfluß, wenn keinerlei Fehler auftreten würden.

Diese Forderung entspricht der Erwartung, dass Exceptions nur bei fehlerhaften Zuständen geworfen werden sollten und niemals bei regulären Zuständen.

Ein Beispiel für die falsche Benutzung der Exception-Weiterleitung ist die Rückgabe eines Wertes eines rekursiven Aufrufs:

Beispiel 4-8.

<?php
/**
 * Rekursive Suche in einem Baum nach einem String
 * @throws ResultException
 */
public function search(TreeNode $node, $data)  
{
    if ($node->data === $data) {
         throw new ResultException( $node );
    } else {
         search( $node->leftChild, $data );
         search( $node->rightChild, $data );
    }
}
?>

Im Beispiel wird die ResultException benutzt, um "schnell" wieder aus der Rekursion heraus zu kommen. Das ist im Fehlerfall tatsächlich praktisch, in diesem Fall, aber nur ein Beispiel für einen faulen Programmierer. Die Klassen-Hierarchie von Exceptions

Alle Exceptions, die von Packages geworfen werden, müssen von PEAR_Exception abstammen. PEAR_Exception bietet zusätzliche Fähigkeiten, um andere Exceptions zu verpacken. Sie finden diese nicht in der obersten PHP Exception-Klasse, sind aber notwendig, um die oben gestellten Anforderungen zu erfüllen.

Zusätzlich sollte jedes PEAR-Package seine eigene Exception-Klasse definieren; der Name der Klasse entspricht dem Muster: <Package_Name>_Exception. Jede Exception sollte von dieser Klasse abgeleitet werden. Exceptions dokumentieren

Da PHP, im Gegensatz zu Java, es nicht erfordert, mögliche Exceptions in der Funktionssignatur aufzunehmen, ist deren sorgfältige Dokumentation im Methodenkopf wichtig.

Beispiel 4-9. Exceptions werden dokumentiert mit dem @throws-Schlüsselwort

<?php
/**
 * Diese Methode sucht nach Aliens.
 *
 * @return array Array von Alien-Objekten.
 * @throws AntennaBrokenException wenn die Impedanz-Leser anzeigt, dass die
 *         Antenne nicht funktioniert
 *
 * @throws AntennaInUseException wenn ein anderer Prozess die Antenne
 *         bereits benutzt
 */
public function findAliens($color = 'green');
?>

In vielen Fällen wandelt die mittlere Schicht einer Anwendung Exceptions von untergeordneten Methoden in aussagekräftiger, anwendungsspezifische Exceptions. Das sollte ebenfalls angesprochen werden:

Beispiel 4-10.

<?php
/**
 * Lade Session-Objekte in den Shared-Memory
 *
 * @throws LoadingException Jede untergeordnete IOException wird als
 *         LoadingException neu verpackt.
 */
public function loadSessionObjects();
?>

In anderen Fällen kann ihre Methode als Filter fungieren, der nur bestimmte Exceptions weiterleitet. Dann sollten Sie dokumentieren, welche Exceptions nicht von Ihrer Methode abgefangen werden.

Beispiel 4-11.

<?php
/**
 * Führt eine Reihe von Datenbankanfragen aus (atomar, nicht innerhalb einer Transaktion).
 * @throws SQLException Low-level SQL-Fehler werden direkt weitergeleitet.
 */
public function batchExecute();
?>

Exceptions als Teil der API

Exceptions spielen eine kritische Rolle in der API ihrer Bibliothek. Entwickler, die Ihre Bibliothek verwenden, sind abhängig von der angemessenen Beschreibung wo und warum Exceptions auftreten in Ihrem Package. Auch die sorgfältige Planung der Fehlermeldungen ist ein wichtiger Faktor für die Erhaltung der Rückwärts-Kompatibilität.

Da Exceptions ein integraler Bestandteil der API ihres Packages sind, darf bei Änderungen daran die Rückwärts-Kompatibilität (BC) nicht grundlos gebrochen werden.

Dinge, die zum Bruch führen:

  • Jede Änderung an Methoden, die Exceptions werfen.
  • Wenn eine Exception-Klasse verwendet wird, die höher in der Vererbungskette liegt, als die ursprüngliche. Zum Beispiel, wenn Sie in einer neueren Version eine PEAR_Exception werfen würden, in der alten aber z.B. PEAR_IOException verwendet haben.

Dinge, die nicht zum Bruch führen:

  • Wenn eine abgeleitete Klasse der originalen Exception verwendet wird. Zum Beispiel, wenn Sie in der aktuellen Version eine PEAR_IOException werfen, und in älteren Versionen PEAR_Exception. Natürlich nur unter der Voraussetzung, dass PEAR_IOException von PEAR_Exception) abgeleitet ist.

Best practices

Es gibt einige andere Dinge, die nicht vom PEAR Coding Standard betroffen sind, sondern von den persönlichen Gewohnheiten abhängen und nicht die Lesbarkeit unmittelbar berühren. Das betrifft z.B. die Frage 'Einfache oder doppelte Anführungszeichen'. Es handelt sich um Konstrukte, welche die Programmierung vereinfachen ohne konkrete Probleme zu verursachsen. Welche dieser 'üblichen' Praktiken genutzt wird, diese Entscheidung ist dem Programmierer überlassen. Wichtiger ist die konsequente Anwendung der eigenen Entscheidung in einem Package. Respektieren Sie die Entscheidung des verantwortlichen Programmierers, wenn Sie fremde Packages verändern.

Beispieldatei inklusive Docblock-Kommentaren

Der Quellcode von PEAR-Packages wird von tausenden von Menschen gelesen. Genauso kann es passieren, dass andere einmal an Ihrem Package entwicklen werden. Um es ihnen leichter zu machen, ist die Formatierung des Quellcode und der Docblöcke standarisiert. Damit können Andere Informationen einfacher finden, das sie wissen, wo sie suchen müssen.

Jeder Docblock im Beispiel enthält viele Details wie Sie die Kommentare schreiben sollten. Diese Instruktionen sind aus zwei Gründen wichtig. Erstens können Benutzer und Entwickler schneller verstehen, was Ihr Code tut. Zweitens wird auf der PEAR-Webseite die Quellcode-Dokumentation für jedes Release von jedem Package bereitgestellt und Sie sollten auch dafür sorgen, dass diese Möglichkeit sinnvoll genutzt werden kann.

Bitte achten Sie auch auf den Einsatz von vertikalen und horizontalen Leerräumen. Sie sind Bestandteil der Standards.

Die „Fold Markers“ (// {{{ und // }}}) sind optional. Wenn Sie nicht benötigt werden, entfernen Sie die Einstellung foldmethod=marker aus dem Block mit Vim-Einstellungen.

<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Kurze Beschreibung des Datei-Inhalts
 *
 * Lange Beschreibung des Datei-Inhaltes, wenn erforderlich.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: This source file is subject to version 3.0 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @category   CategoryName
 * @package    PackageName
 * @author     Original Author <author@example.com>
 * @author     Another Author <another@example.com>
 * @copyright  1997-2005 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    CVS: $Id:$
 * @link       http://pear.php.net/package/PackageName
 * @see        NetOther, Net_Sample::Net_Sample()
 * @since      File available since Release 1.2.0
 * @deprecated File deprecated in Release 2.0.0
 */

/**
 * Das ist ein "Docblock-Kommentar", auch bekannt als "docblock"/"Docblock"
 * Der Klassen-Docblock weiter unten enthält eine Beschreibung wie sie
 * geschrieben werden.
 */
require_once 'PEAR.php';

// {{{ constants

/**
 * Methoden geben diesen Wert im Erfolgsfall zurück.
 */
define('_NET_SAMPLE_OK', 1);

// }}}
// {{{ GLOBALS

/**
 * Die Anzahl der erzeugten Objekte
 * @global int $GLOBALS['NET_SAMPLE_Count']
 */
$GLOBALS['_NET_SAMPLE_Count'] = 0;

// }}}
// {{{ Net_Sample

/**
 * Ein Beispiel wird Code entsprechend der PEAR-Standards aussieht
 *
 * Anmerkung des Übersetzers: Quelltext-Kommentare sollten natürlich
 * durchgängig in Englisch erfolgen!
 *
 * Ein Docblock-Kommentar beginnt mit "/**" am Anfang.  Beachten Sie, das "/"
 * beginnt mit der normalen Einrückung und die Sterne darunter stehen in der
 * selben Textspalte wie der erste Stern. Der letzte Zeile des Kommentar-Blocks
 * sollte unmittelbar das zu dokumentierende Element folgen,
 * Vermeiden Sie zusätzliche Leerzeilen. Ergänzen Sie eine Leerzeile
 * zwischen Absätzen im Kommentartext, genauso zwischen Kommentartext und dem
 * ersten @-Tag. Ein Zeilenumbruch im Kommentartext sollte nach 80 Zeichen
 * erfolgen.
 *
 * Docblöcke können nur für eine begrenzte Anzahl an Elementen verwendet
 * werden (Klassen, Eigenschaften, Methoden, Konstanten, Include und
 * globale Variablen. In der Dokumentation zu phpDocumentor finden Sie mehr
 * Informationen dazu:
 * http://phpdoc.org/docs/HTMLSmartyConverter/default/phpDocumentor/tutorial_phpDocumentor.howto.pkg.html
 *
 * Der Javadoc Style Guide ist eine exzellente Quelle wie man Kommentare
 * formulieren sollte. Letztlich sind diese Informationen eine Zusammenfassung
 * davon, andererseits gibt es aber auch einige Abweichungen davon.
 * http://java.sun.com/j2se/javadoc/writingdoccomments/index.html#styleguide
 *
 * Diese erste Zeile jedes Docblocks ist eine Zusammenfassung. Sie sollte
 * genau einen Satz umfassen, ohne Punkt am Ende. Zusammenfassungen für
 * Klassen, Eigenschaften und Konstanten sollten nicht deren Namen enthalten
 *
 * Die unten aufgeführten Tag werden üblicherweise für Klassen benutzt.
 * Die Tags @category bis @version sind erforderlich. Die Übrigen
 * sollten ergänzt werden, wenn erforderlich. Verwenden Sie die Tags
 * in der Reihenfolge, wie hier aufgeführt. phpDocumentor bietet
 * weitere Tags, verwenden Sie diese, wenn erforderlich.
 *
 * @category   CategoryName
 * @package    PackageName
 * @author     Original Author <author@example.com>
 * @author     Another Author <another@example.com>
 * @copyright  1997-2005 The PHP Group
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    Release: @package_version@
 * @link       http://pear.php.net/package/PackageName
 * @see        NetOther, Net_Sample::Net_Sample()
 * @since      Class available since Release 1.2.0
 * @deprecated Class deprecated in Release 2.0.0
 */
class Net_Sample
{
    // {{{ properties

    /**
     * Der Zustand von foo's universe
     *
     * Potentielle Werte sind 'good', 'fair', 'poor' und 'unknown'.
     *
     * @var string
     */
    var $foo = 'unknown';

    /**
     * The status of life
     *
     * Vergessen Sie nicht, dass private Elemente mit einem
     * Unterstrich beginnen müssen.
     *
     * @var bool
     * @access private
     */
    var $_good = true;

    // }}}
    // {{{ setFoo()

    /**
     * Registriert den Zustand des Foo-Universums
     *
     * Die Zusammenfassung für eine Methode sollte besser in der 3. Person 
     * Einzahl statt in der 2. Person formuliert werden.
     * Das Verb sollte am Anfang stehen.
     *
     * Die Zusammenfassung sollte mehr Informationen liefern, die über den
     * Methodennamen hinaus gehen. Die besten Methodennamen sind
     * selbst beschreibend, sie sagen was die Methode im Grunde macht.
     * Wenn die Zusammenfassung den Methodennamen nur in Form eines
     * Satzes wiederholt, liefert sie keine zusätzlichen Informationen.
     *
     * Beispiele für die Zusammenfassung:
     *   + Sets the label              (empfohlen)
     *   + Set the label               (vermeiden)
     *   + This method sets the label  (vermeiden)
     *
     * Als nächstes werden die üblichen Tags für Methoden angeführt.
     * Ein @param-Tag muss für jeden Parameter angegeben werden.
     * Die Tags @return und @access sind wahlfrei. Das @throw-Tag
     * muss angegeben werden, wenn die Methode Exceptions wirft.
     * Das Tag @static ist erforderlich, wenn die Methode statisch
     * aufgerufen werden kann. Die übrigen Tags brauchen Sie nur
     * angeben, wenn diese benötigt werden. Bitte geben Sie die
     * Tags in der Reihenfolge an, wie sie hier aufgeführt sind.
     * phpDocumentor bietet weitere Tags, benutze Sie diese, wenn
     * es sinnvoll ist.
     *
     * Das @param-Tag umfasst den Datentypen, den Parameter-Namen, gefolgt
     * von einer Beschreibung. Per Konvention sollte die Beschreibung beginnen
     * mit dem Datentyp, dem ein Artikel ("a", "an" oder "the") vorangestellt
     * werden kann. Setzen Sie zwei Leerzeichen zwischen dem Namen
     * des Parameters und seiner Beschreibung für eine bessere Lesbarkeit.
     *
     * Wenn Sie einen Satz schreiben, starten Sie nicht mit einem
     * Großbuchstaben und beenden Sie ihn nicht mit einem Punkt:
     *   + the string to be tested
     *
     * Wenn Sie mehr als einen Satz schreiben, setzen Sie einen Punkt und
     * beginnen Sie den zweiten Satz mit einem Großbuchstaben:
     *   + the string to be tested. Must use UTF-8 encoding.
     *
     * Das @return-Tag sollte den Datentyp und eine Beschreibung des
     * zurückgegebenen Wertes enthalten. Der Datentyp kann einer von PHP's
     * Datentyp sein (int, float, bool, string, array, object, resource, mixed)
     * und sollte den primär zurückgegebenen Wert enthalten. Z.B.
     * eine Methode gibt korrekterweise ein Objekt zurück und nur im
     * Fehlerfall 'false', dann sollten Sie 'object' angeben statt
     * 'mixed'. Verwenden Sie 'void' wenn nicht zurückgegeben wird.
     *
     * Hier ein Beispiel für Quellcode-Texte:
     * <code>
     * require_once 'Net/Sample.php';
     *
     * $s = new Net_Sample();
     * if (PEAR::isError($s)) {
     *     echo $s->getMessage() . "\n";
     * }
     * </code>
     *
     * Hier ein Beispiel für Nicht-PHP-Code:
     * <samp>
     * pear install net_sample
     * </samp>
     *
     * @param string $arg1  the string to quote
     * @param int    $arg2  an integer of how many problems happened.
     *                       Rücken Sie nachfolgende Zeilen in der Beschreibung
     *                       entsprechend ein.
     *
     * @return int  the integer of the set mode used. FALSE if foo
     *               foo could not be set.
     * @throws exceptionclass  [description]
     *
     * @access public
     * @static
     * @see Net_Sample::$foo, Net_Other::someMethod()
     * @since Method available since Release 1.2.0
     * @deprecated Method deprecated in Release 2.0.0
     */
    function setFoo($arg1, $arg2 = 0)
    {
        /*
         * Das ist ein "Block Kommentar". Das Format entspricht
         * den obigen Kommentaren, mit der Ausnahme, das der Kommentarbeginn
         * nur einen Stern enthält.
         * phpDocumentor wertet diese nicht aus.
         */
        if ($arg1 == 'good' || $arg1 == 'fair') {
            $this->foo = $arg1;
            return 1;
        } elseif ($arg1 == 'poor' && $arg2 > 1) {
            $this->foo = 'poor';
            return 2;
        } else {
            return false;
        }
    }

    // }}}
}

// }}}

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * c-hanging-comment-ender-p: nil
 * End:
 */

?>

(Quelle: PEAR Coding Standard, 05.02.2008)

 

 

Quelle: Basis-Wiki-Hilfe | Letzte Änderung: 04.03.2008 11:26 Uhr, chueser | Local view: Basis-Hilfe