Objektorientiertes PHP sofort verbessern: immer kurze Methoden schreiben

Ich habe es im eBook zu Best-Practices kurz erwähnt.

Es ist Best-Practice Deinen Code zu unterteilen. Wenn Du sauberen Code schreiben willst, ist das hier die einfachste Methode. Die Regel lässt sich ganz einfach anwenden.

Du musst einfach …

Kürzere Methoden schreiben

Kennst Du das Sprichwort “Code is poetry”? Da ist was dran.

Nur …

Ein “Meisterwerk” abzuliefern ist schwierig. Es muss alles stimmen:

  • Du musst Code übersichtlich strukturieren (Namespaces & Co)
  • Deine Klassen aufeinander abstimmen
  • Kannst Du das Projekt ohne Refactoring erweitern?
  • Kannst Du Klassen exakt in andere Projekte übernehmen?
  • Sind Deine Methoden übersichtlich?

Unter dem Strich musst Du Aufgaben bezahlbar lösen. Kunden zahlen nicht mehrere Manntage, wenn Du es in zwei Stunden lösen kannst.

Wenn unser Code Fehler erzeugt, bezahlt uns niemand, wenn wir sie beheben. Gewinnt unsaubere Arbeit die Überhand, drehen wir uns nur noch im Kreis

  • Kunden verlangen (zurecht), dass wir nachbessern
  • die Zeit fehlt für bezahlte Aufgaben

Außerdem stoßen wir auf Seiteneffekte. Du änderst Stelle A und Stelle B geht kaputt.

Mehrere Ideen lösen das Problem. Unsere Best-Practices findest Du im eBook:

Hier eine Low-Hanging Fruit, quasi ein …

Quickwin: Gestalte Methoden übersichtlich

Du musst übersichtliche Methoden schreiben. Der schnellste Weg zu sauberem Code.

Zu schwammig? Hier ein Beispiel.

Die Aufgabe: Du musst alle Benutzer aus der Datenbank lesen, ein Array erstellen und zurückgeben. Viele Einsteiger schreiben die Methode in der alles passiert.

  • sie lesen Daten aus der Datenbank
  • durchlaufen der Daten in einer Schleife
  • die Datenstruktur (ein Array) für den Benutzer erzeugen
  • die Informationen zum übergeordneten Array hinzufügen
  • Daten zurückgeben

Im Proof of Concept ist das OK. Es fällt beim Code Review durch. Es gibt gleich mehrere Probleme:

  • die Methode wird zu lang
  • die Logik lässt sich nicht wiederverwenden
  • ändert sich die Datenquelle (API statt Datenbank), musst Du Code editieren
  • das führt schnell zu Seiteneffekten und Du testet die Klasse erneut
  • wir leiten von Klassen ab und erben Methoden, um sie zu überschreiben.

Steht alles in einer Methode, musst Du die Methode in die Elternklasse kopieren um sie zu ändern. Inklusive der nicht geänderten Teile. 

Unsauber. Und schlecht bei Updates.

Gute Nachricht: Du kannst es verhindern. Lager jeden Schritt in Methoden aus.

Ein Beispiel

Im Beispiel geht es um eine Klasse von einem Magento Projekt. AJAX liest über eine Controller-Klasse (siehe MVC Pattern) Infos aus einem Produkt.

Rückgabewert ist in JSON formatiert.

Neben der Dependency Injection im Konstruktor (__construct()), ist der Code ab der execute()-Methode interessant:

<?php

namespace Custom\Description\Controller\Index;

use Magento\Catalog\Model\ProductRepository;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\View\Result\PageFactory;

class Index extends Action
{
    protected $_pageFactory;
    protected $_productRepository;
    protected $_jsonFactory;

    public function __construct(
        Context $context,
        JsonFactory $resultJsonFactory,
        ProductRepository $productRepository,
        PageFactory $pageFactory)
    {
        $this->_pageFactory = $pageFactory;
        $this->_productRepository = $productRepository;
        $this->_jsonFactory = $resultJsonFactory;
        return parent::__construct($context);
    }

    // (1)
    public function execute()
    {
        $result = $this->_jsonFactory->create();

        // (2)
        $id = (int)$this->getRequest()->getParam('product_id');
        if ($id) {
            // (5)
            return $result->setData([
                'description' => $this->getProductDescription($id)
            ]);

        } else {
            // (6)
            return $result->setData(['description' => false]);
        }
    }

    // (3)
    protected function getProductDescription($id)
    {
        $product = $this->getProductById($id);
        if ($product->getId()) {
            return $product->getDescription();
        } else {
            return false;
        }
    }

    // (4)
    protected function getProductById($id)
    {
        return $this->_productRepository->getById($id);
    }
}

Der Ablauf im Code:

  • der Einstieg ist die execute()-Methode (1)
  • der Controller liest eine Produkt ID aus der URL (2)
  • Produktbeschreibung wird gelesen (3)
  • dafür wird das Produkt geladen (4)
  • Rückgabe gefundener Daten (5) oder false (6)

Beachte: jeder Schritt geschieht in einer Methode:

  • execute(): der Einstiegspunkt
  • getProductDescription(): Wert zu einer ID zurückgeben
  • getProductById(): Produkt zu der ID laden / zurückgeben

Versuch Deine Methoden wie gezeigt zu unterteilen:

  • der Code wird übersichtlicher
  • Methoden können überlagert werden
  • Seiteneffekte nahezu ausgeschlossen

Beschränke Dich auf einen Aktion, bewerte den Rückgabewert. Über if und else kannst Du verzweigen. Solcher Code dokumentiert sich von selbst.

Fazit

Gewöhne Dir an Code zu strukturieren. Es führt zu übersichtlichen Klassen. Und Du kannst ab sofort damit loslegen.

Lange Methoden werden beim Code-Review nicht durchgewunken. In Feature-Branches in Git ist das OK. Bitte vor einem Pull-Request auflösen.

Wenn Du Hilfe beim Refactoring benötigst, melde Dich bei Jan.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.