Prepare your PHP Code for Static Analysis

Three years ago I got a new job as PHP developer, before that I called myself web developer because I build ReactJS, jQuery, CSS, HTML, … and PHP  stuff for a web agency. So now I am a full-time PHP developer and I converted a non typed  (no PHPDoc + no native types) project with ~ 10.000 classes into a project with ~ 90% type coverage. Here is what I have learned.

1. Write code with IDE autocompletion support.

If you have autocompletion in the IDE most likely the Static Analysis can understand the code as well. 

Example:

bad:

->get('DB_Connection', true, false);

still bad:

->get(DB_Connection::class);

good:

getDbConnection(): DB_Connection

2. Magic in Code is bad for the long run!

Magic methods (__get, __set, …) for example can help to implement new stuff very fast, but the problem is nobody will understand it, you will have no autocompletion, no refactoring options, other developers will need more time to read and navigate in the code and in the end it will cost you much more time than you can save with it.

3. Break the spell on Magic Code …

… by explaining to everyone (Devs > IDE > Tools) what it does.

Example 1:

We use a simple Active Record Pattern, but we put all SQL stuff into the Factory classes, so that the Active Record class can be simple. (Example) But because of missing support for Generics we had no autocompletion without adding many dummy methods into the classes. So one of my first steps was to introduce a “PhpCsFixer” that automatically adds the missing methods of the parent class with the correct types via “@method”-comments into these classes. 

Example 2:

Sometimes you can use more modern PHPDocs to explain the function. Take a look at the “array_first” function in the linked Post.

Example 3:

/**
* Return an array which has the Property-Values of the given Objects as Values.
*
* @param object[] $ObjArray
* @param string $PropertyName
* @param null|string $KeyPropertyName if given uses this Property as key for the returned Array otherwise the keys from the
* given array are used
*
* @throws Exception if no property with the given name was found
*
* @return array
*/
function propertyArray($ObjArray, $PropertyName, $KeyPropertyName = null): array {
// init
$PropertyArray = [];

foreach ($ObjArray as $key => $Obj) {
if (!\property_exists($Obj, $PropertyName)) {
throw new Exception('No Property with Name ' . $PropertyName . ' in Object Found - Value');
}

$usedKey = $key;
if ($KeyPropertyName) {
if (!\property_exists($Obj, $KeyPropertyName)) {
throw new Exception('No Property with Name ' . $PropertyName . ' in Object Found - Key');
}
$usedKey = $Obj->{$KeyPropertyName};
}

$PropertyArray[$usedKey] = $Obj->{$PropertyName};
}

return $PropertyArray;
}

Sometimes it’s hard to describe the specific output types, so here you need to extend the  functions of your Static Code Analyze Tool, so that it knows what you are doing. ⇾ for example here you can find a solution for PHPStan ⇽  but there is still no support for the IDE and so maybe it’s not the best idea to use magic like that at all. And I am sure it’s more simple to use specific and simple methods instead:  e.g. BillCollection->getBillDates()

4. Try to not use strings for the code.

Strings are simple and flexible, but they are also bad for the long run. Mostly strings are used because it looks like a simple solution, but often they are redundant, you will have typos everywhere and the IDE and/or Static Analysis can’t analyze them because it’s just text.

Example:

bad: 

AjaxEditDate::generator($bill->bill_id, $bill->date, 'Bill', 'date');
  • “Bill” ⇾ is not needed here, we can call e.g. get_class($bill) in the “generator” method
  • “date” ⇾ is not needed here, we can fetch the property name from the class
  • “$bill->bill_id” ⇾ is not needed here, we can get the primary id value from the class

good:

AjaxEditDate::generator($bill, $bill->m()->date);

5. Automate stuff via git hook and check it via CI server.

Fixing bad code is only done if you disallow the same bad code for the future. With a pre-commit hook for git it’s simple to run these checks, but you need to check it again in the CI server because the developers can simply skip these checks.

Example:

I introduced a check for disallowing global variables (global $foo && $GLOBALS[‘foo’]) via “PHP_CodeSniffer”. 

Links:

6. Use array shapes (or collections) if you need to return an array, please.

Array shapes are like arrays but with fixed keys, so that you can define the types of each key in the PHPDocs.

You will have autocompletion for the IDE + all other devs can see what the method will return + you will notice if you’re better retuning an object because it will be very hard to describe the output for complex data structures and Static Analysis can use and check the types. 

Example:

/**
* @return array{
* missing: array<ShoppingCartLib::TAB_*,string>,
* disabled: array<ShoppingCartLib::TAB_*,string>
* }
*/
private static function getShoppingCartTabStatus(): array {
...
}

Generics in PHP via PHPDocs

If you did not know that you can use Generics in PHP or you do not exactly know how to use it or why you should use it, then the next examples are for you.

Type variables via @template

The @template tag allows classes and functions to declare a generic type parameter. The next examples starts with simple functions, so that we understand how it works, and then we will see the power of this in classes.


A dummy function that will return the input.

https://phpstan.org/r/1922279b-9786-4523-939d-dddcfd4ebb86

    <?php    

    /**
     * @param \Exception $param
     * @return \Exception
     *
     * @template T of \Exception
     * @psalm-param T $param
     * @psalm-return T
     */
    function foo($param) { ... }

    foo(new \InvalidArgumentException()); // The static-analysis-tool knows that 
                                          // the type is still "\InvalidArgumentException" 
                                          // because of the type variable.

@template T of \Exception // here we create a new type variable, and we force that it must be an instance of \Exception

@phpstan-param T $param // here we say that the static-analysis-tool need to remember the type that this variable had before (you can use @psalm-* or @phpstan-* both works with both tools)

@phpstan-return T // and that the return type is the same as the input type 


A simple function that gets the first element of an array or a fallback. 

In the @param PHPDocs we write “mixed” because this function can handle different types. But this information is not very helpful if you want to understand programmatically what the code does, so we need to give the static-analysis-tools some more information. 

https://phpstan.org/r/1900a2af-f5c1-4942-939c-409928a5ac4a

    <?php
     
    /**
     * @param array<mixed> $array
     * @param mixed        $fallback <p>This fallback will be used, if the array is empty.</p>
     *
     * @return mixed|null
     *
     * @template TFirst
     * @template TFirstFallback
     * @psalm-param TFirst[] $array
     * @psalm-param TFirstFallback $fallback
     * @psalm-return TFirst|TFirstFallback
     */
    function array_first(array $array, $fallback)
    {
        $key_first = array_key_first($array);
        if ($key_first === null) {
            return $fallback;
        }

        return $array[$key_first];
    }

    array_first([1, 2, 3], null); 

    if ($a === 'foo') { // The static-analysis-tool knows that 
                        // === between int|null and 'foo' will always evaluate to false.
	    // ...
    }

@template TFirst // we again define your typed variables

@template TFirstFallback // and one more because we have two inputs where we want to keep track of the types

@psalm-param TFirst[] $array // here we define that $array is an array of TFirst types

@psalm-param TFirstFallback $fallback // and that $fallback is some other type that comes into this function

@psalm-return TFirst|TFirstFallback // now we define the return type as an element of  the $array or the $fallback type 


 Very basic Active Record + Generics

The IDE support for generics is currently not there, :-/ so that we still need some hacks (see @method) for e.g. PhpStorm to have autocompletion.

https://phpstan.org/r/f88f5cd4-1bb9-4a09-baae-069fddb10b12

https://github.com/voku/phpstorm_issue_53352/tree/master/src/Framework/ActiveRecord

<?php

class ActiveRow
{
    /**
     * @var ManagedFactory<static>
     */
    public $factory;

    /**
     * @param Factory<ActiveRow>|ManagedFactory<static> $factory
     * @param null|array                                $row
     */
    public function __construct(Factory $factory, array $row = null) {
        $this->factory = &$factory;
    }
}

/**
 * @template T
 */
abstract class Factory
{
    /**
     * @var string
     *
     * @internal
     *
     * @psalm-var class-string<T>
     */
    protected $classname;

    /**
     * @return static
     */
    public static function create() {
        return new static();
    }
}

/**
 * @template  T
 * @extends   Factory<T>
 */
class ManagedFactory extends Factory
{
    /**
     * @param string $classname
     *
     * @return void
     *
     * @psalm-param class-string<T> $classname
     */
    protected function setClass(string $classname): void
    {
        if (\class_exists($classname) === false) {
            /** @noinspection ThrowRawExceptionInspection */
            throw new Exception('TODO');
        }

        if (\is_subclass_of($classname, ActiveRow::class) === false) {
            /** @noinspection ThrowRawExceptionInspection */
            throw new Exception('TODO');
        }

        $this->classname = $classname;
    }

    // ...
}

final class Foo extends ActiveRow {

    public int $foo_id;

    public int $user_id;

    // --------------------------------------
    // add more logic here ...
    // --------------------------------------
}

/**
 * @method Foo[] fetchAll(...)
 *
 * @see Foo
 *
 * // warning -> do not edit this comment by hand, it's auto-generated and the @method phpdocs are for IDE support       
 * //         -> https://gist.github.com/voku/3aba12eb898dfa209a787c398a331f9c
 *
 * @extends ManagedFactory<Foo>
 */
final class FooFactory extends ManagedFactory
{
    // -----------------------------------------------
    // add sql stuff here ...
    // -----------------------------------------------
}

A more complex collection example.

In the end we can extend the “AbstractCollection” and the static-analysis-tools knows the types of all the methods. 

https://github.com/voku/Arrayy/tree/master/src/Type

/**
 * @template TKey of array-key
 * @template T
 * @template-extends \ArrayObject<TKey,T>
 * @template-implements \IteratorAggregate<TKey,T>
 * @template-implements \ArrayAccess<TKey|null,T>
 */
class Arrayy extends \ArrayObject implements \IteratorAggregate, \ArrayAccess, \Serializable, \JsonSerializable, \Countable
{ ... }

/**
 * @template TKey of array-key
 * @template T
 * @template-extends \IteratorAggregate<TKey,T>
 * @template-extends \ArrayAccess<TKey|null,T>
 */
interface CollectionInterface extends \IteratorAggregate, \ArrayAccess, \Serializable, \JsonSerializable, \Countable
{ ... }

/**
 * @template   TKey of array-key
 * @template   T
 * @extends    Arrayy<TKey,T>
 * @implements CollectionInterface<TKey,T>
 */
abstract class AbstractCollection extends Arrayy implements CollectionInterface
{ ... }

/**
 * @template TKey of array-key
 * @template T
 * @extends  AbstractCollection<TKey,T>
 */
class Collection extends AbstractCollection
{ ... }

Links:

https://phpstan.org/blog/generics-in-php-using-phpdocs

https://psalm.dev/docs/annotating_code/templated_annotations/

https://stitcher.io/blog/php-generics-and-why-we-need-them

❤️ Simple PHP Code Parser

It based on code from “JetBrains/phpstorm-stubs” but instead of Php-Reflection we now use nikic/PHP-Parser, BetterReflection, phpDocumentor and PHPStan/phpdoc-parser internally. So, you can get even more information about the code. For example, psalm- / phpstan-phpdoc annotations or inheritdoc from methods.

Install:

composer require voku/simple-php-code-parser

Link:

voku/Simple-PHP-Code-Parser

More:


Example: get value from define in “\foo\bar” namespace

$code = '
  <?php
  namespace foo\bar;
  define("FOO_BAR", "Lall");
';

$phpCode = PhpCodeParser::getFromString($code);
$phpConstants = $phpCode->getConstants();

$phpConstants['\foo\bar\FOO_BAR']->value; // 'Lall'

Example: get information about @property phpdoc from a class

$code = '
  <?php
  /** 
   * @property int[] $foo 
   */
  abstract class Foo { }
';

$phpCode = PhpCodeParser::getFromString($code);
$phpClass = $phpCode->getClass('Foo');

$phpClass->properties['foo']->typeFromPhpDoc); // int

Example: get classes from a string (or from a class-name or from a file or from a directory)

$code = '
<?php
namespace voku\tests;
class SimpleClass {}
$obja = new class() {};
$objb = new class {};
class AnotherClass {}
';

$phpCode = \voku\SimplePhpParser\Parsers\PhpCodeParser::getFromString($code);
$phpClasses = $phpCode->getClasses();

var_dump($phpClasses['voku\tests\SimpleClass']); // "PHPClass"-object

Arrayy: A Quick Overview of map(), filter(), and reduce()

Arrayy: A PHP array manipulation library. Compatible with PHP 7+

The next examples are using the php array manipulation library “Arrayy” which is using generators internally for many operations.

https://github.com/voku/Arrayy

StringCollection::create(['Array', 'Array'])->unique()->append('y')->implode(); // Arrayy


map: transform all values in the collection

StringCollection::create(['foo', 'Foo'])->map('mb_strtoupper'); 

// StringCollection['FOO', 'FOO']

filter: pass all values to the truth test

$closure = function ($value) {
    return $value % 2 !== 0;
}
IntCollection::create([1, 2, 3, 4])->filter($closure); 

// IntCollection[0 => 1, 2 => 3]

reduce: transform all values into a new result

IntCollection::create([1, 2, 3, 4])->reduce(
    function ($carry, $item) {
        return $carry * $item;
    },
    1
); 

// IntCollection[24]

How to write readable code?

Why do we write unreadable code?

I think we are mostly lazy, and so we use names from underlying structures like APIs or the databases, instead of using names that describe the current situation.

For example, the method “Cart->getUser(): User“: There is a “user_id“ in the underlying database table, so the developer chooses the name “getUser()“ where the User will be returned. The problem is “getX“ and “setX“ are terrible names for methods because they did not say you anything about the context of these methods. Maybe something like “Cart->orderUser(): OrderUser“ and at some point “Cart->orderApprovalUser(): OrderApprovalUser“ are better names. But keep in mind that this depends on your use cases. And please rename also e.g. your “user_id“ in the cart database table, so that you keep the code consistent and readable.

In Eric Evans’ book Domain-Driven Design he introduces the concept of a “Ubiquitous Language“ — a shared, common vocabulary that the entire team shares when discussing or writing software. This “entire team” is made up of designers, developers, the product owner and any other domain experts that exist at the organization. And take the note that it is important that your team have a domain expert! Mostly we are not the expert in the field we are writing software for, so we need some professional input. If you build an invoice-system you will need an expert in finance-questions and -laws.


if ()


# BAD:
if ($userExists == 1) { }

# BETTER: use "==="
if ($userExists === 1) {}

# BETTER: use the correct type
if ($userExists === true) {}

# BETTER: use something readable
if ($this->userExists()) {}

# BAD:
$foo = 3;
if ($lall === 2) { $foo = 2; }

# BETTER: use if-else
if ($lall === 2) { 
    $foo = 2; 
} else {
    $foo = 3;
}

# BETTER: only for simple if-else
$foo = ($lall === 2) ? 2 : 3;

# BETTER: use something readable
$foo = $this->foo($lall);

# BAD:
if ($foo && $foo < 20 && $foo >= 10 || $foo === 100) {}

# BETTER: use separate lines
if (
    ($foo && $foo < 20 && $foo >= 10) 
    || 
    ($foo && $foo === 100)
) {}

# BETTER: avoid duplications
if (
    $foo
    &&
    (
        ($foo >= 10 && $foo < 20) 
        || 
        $foo === 100
    )
) {}

# BETTER: use something readable
if ($this->isFooCorrect($foo)) {}

loop ()


# BAD
foreach ($cartArticles as $key => $value)

# BETTER: use correct names
foreach ($cartArticles as $cartIndex => $cartArticle)

# BETTER: move the loop into a method
cart->getArticles(): Generator<int, Article>

# BAD
foreach ($cartArticles as $cartArticle) {
    if ($cartArticle->notActive) {
        $this->removeArticle($cartArticle)
    }
}

# BETTER: use continue
foreach ($cartArticles as $cartArticle) {
    if (! $cartArticle->active) {
        continue;
    }

    $this->remove_article($cartArticle)
}

# BETTER: use something readable
cart->removeNonActiveArticles(): int;

method ( )


# BAD
thisMethodNameIsTooLongOurEyesCanOnlyReadAboutFourCharsAtOnce(bool $pricePerCategory = false): float|float[]

# BETTER: use shorter names if possible (long names mostly indicates that the method does more than one thing)
cartNetPriceSum(bool $pricePerCategory = false): float|float[]

# BETTER: use less parameter and use one return type
cartNetPriceSum(): float
cartNetPriceSumPerCategory(): float[]

class ()


# BAD: 
class \shop\InvoicePdfTemplate;

class \shop\InvoicePdfTemplateNew extends \shop\InvoicePdfTemplate;

class \shop\InvoicePdfTemplateSpecial extends \shop\InvoicePdfTemplateNew;


# BETTER: use non-generic class names for non-generic classes
class \shop\InvoicePdfTemplate;

class \shop\PdfTemplateCustomerX extends \shop\InvoicePdfTemplate;

class \shop\PdfTemplateActionEaster2020 extends \shop\PdfTemplateCustomerX;


# BETTER: do not use multi-inheritance, because of side-effects
class \shop\invoice\PdfTemplateGeneric

class \shop\invoice\PdfTemplate extends \shop\invoice\PdfTemplateGeneric;

class \shop\invoice\PdfTemplateCustomerX extends \shop\invoice\PdfTemplateGeneric;

class \shop\invoice\PdfTemplateActionEaster2020 extends \shop\invoice\PdfTemplateGeneric;


# BETTER: use composition via interfaces
interface \shop\invoice\PdfTemplateInterface;

class \shop\invoice\PdfTemplateGeneric implements \shop\invoice\PdfTemplateInterface;

class \shop\invoice\PdfTemplate extends \shop\invoice\PdfTemplateGeneric;

class \shop\invoice\PdfTemplateCustomerX extends \shop\invoice\PdfTemplateGeneric;

class \shop\invoice\PdfTemplateActionEaster2020 implements \shop\invoice\PdfTemplateInterface;


# BETTER: use abstract or final
interface \shop\invoice\PdfTemplateInterface;

abstract class \shop\invoice\PdfTemplateGeneric implements \shop\invoice\PdfTemplateInterface;

final class \shop\invoice\PdfTemplate extends \shop\invoice\PdfTemplateGeneric;

final class \shop\invoice\PdfTemplateCustomerX extends \shop\invoice\PdfTemplateGeneric;

final class \shop\invoice\PdfTemplateActionEaster2020 implements \shop\invoice\PdfTemplateInterface;

Real-World-Example

https://github.com/woocommerce/woocommerce/blob/master/includes/class-wc-coupon.php#L505


// BAD:
/**
 * Set amount.
 *
 * @since 3.0.0
 * @param float $amount Amount.
 */
public function set_amount( $amount ) {
    $amount = wc_format_decimal( $amount );

    if ( ! is_numeric( $amount ) ) {
        $amount = 0;
    }

    if ( $amount < 0 ) {
        $this->error( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
    }

    if ( 'percent' === $this->get_discount_type() && $amount > 100 ) {
        $this->error( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
    }

    $this->set_prop( 'amount', $amount );
}

// BETTER: fix phpdocs
/**
 * @param float|string $amount Expects either a float or a string with a decimal separator only (no thousands).
 *
 * @since 3.0.0
 */
public function set_amount( $amount ) {
    $amount = wc_format_decimal( $amount );

    if ( ! is_numeric( $amount ) ) {
        $amount = 0;
    }

    if ( $amount < 0 ) {
        $this->error( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
    }

    if ( 'percent' === $this->get_discount_type() && $amount > 100 ) {
        $this->error( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
    }

    $this->set_prop( 'amount', $amount );
}

// BETTER: use php types (WARNING: this is a breaking change because we do not allow string as input anymore)
/**
 * @since 3.0.0
 */
public function set_amount( float $amount ) {
    if ( $amount < 0 ) {
        throw new WC_Data_Exception( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
    }

    if (
        $amount > 100
        &&
        'percent' === $this->get_discount_type()
    ) {
        throw new WC_Data_Exception( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
    }

    $this->set_prop( 'amount', $amount );
}

// BETTER: merge the logic
/**
 * @since 3.0.0
 */
public function set_amount( float $amount ) {
    if (
        $amount < 0
        ||
        (
            $amount > 100
            &&
            'percent' === $this->get_discount_type()
        )
    ) {
        throw new WC_Data_Exception( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
    }

    $this->set_prop( 'amount', $amount );
}

// BETTER: make the logic readable
/**
 * @since 3.0.0
 */
public function set_amount( float $amount ) {
    if (! is_valid_amount($amount)) {
        throw new WC_Data_Exception( 'coupon_invalid_amount', __( 'Invalid discount amount', 'woocommerce' ) );
    }

    $this->set_prop( 'amount', $amount );
}

private function is_valid_amount( float $amount ): bool {
    if ($amount < 0) {
        return false;
    }

    if (
        $amount > 100
        &&
        'percent' === $this->get_discount_type()
    ) {
        return false;
    }

    return true;
}

Should we write code that everybody can read?

I don’t think that every non-programmer need to read the code, so we do not need to write the code this way. But we should remember that we could write our code, so that every non-programmer could read it.

Summary

There are only two hard things in Computer Science: cache invalidation and naming things.

― Phil Karlton

“Indeed, the ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. …[Therefore,] making it easy to read makes it easier to write.”

― Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

Modern PHPDoc Annotations

We will start very simple with PhpStorm and default PHPDoc, then we will increase the complexity step by step until we have auto-completion for array keys directly from the database with generics, immutable and type safety support.

1.0 PhpStorm & auto-generate PHPDoc blocks

„For documentation comments, PhpStorm provides completion that is enabled by default. PhpStorm creates stubs of „PHPDoc blocks“ when you type the /** opening tag and press Enter, or press Alt+Insert and appoint the code construct (a class, a method, a function, and so on) to document. Depending on your choice, PhpStorm will create the required tags or add an empty documentation stub.“

https://www.jetbrains.com/help/phpstorm/phpdoc-comments.html

Code:

/**
 * @param array $row
 *
 * @return array
 */
abstract function formatRow(array $row): array;

1.1 Return $this|static|self

It‘s quite annoying that php itself currently only have „self“ as return type (https://wiki.php.net/rfc/static_return_type) for the current class. Because of „late static binding“ you can use „static“ in your code to refer to the class a method was actually called on, even if the method is inherited. But in PHPDoc you can already use:

  • @return $this: if you really return $this (e.g. for fluent interface)
  • @return static: refer to the class a method was actually called on
  • @return self: refer to the class a method was written in

Code:

/**
 * @return static
 */
abstract function getFoo(): self;

1.2 New (and not that new) Array Syntax

PhpStorm and (PHPStan & Psalm) are supporting some new (and some not that new) array syntax for PHPDoc types, but for now PhpStorm will not auto-generate this types. 

Examples:

  • int[]: an array with only INT values – [1, 4, 6, 8, 9, …]
  • array<int, int>: an array with only INT values – [4 => 1, 8 => 4, 12 => 6, …]
  • string[]: an array with only STRING values – [„foo“, „bar“, …]
  • array<int, string>: an array with only STRING values – [4 => „foo“, 8 => „bar“, …]
  • Order[]: an array with only „Order“-Object values – [Order, Order, …]
  • array<int|string, Order>: an array with INT or STRING as key and „Order“-Object values – [4 => Order, ‘foo‘ => Order, …]
  • array<int|string, mixed>: an array with INT or STRING as key and mixed as values – [1 => 1, 4 => „foo“, 6 => \stdClass, …]
  • array<int, array<int, string>>: an array with INT as key and and an array (with INT as key and string as value) as values – [1 => [1 => „foo“], 4 => [1 => 4], …]
  • array<int, string[]>: an array with INT as key and and an array (with INT as key and string as value) as values – [1 => [„foo“, „lall“], 4 => [„öäü“, „bar“], …]
  • array{output: string, debug: string}: an array with the key “output” and “debug”, the values are STRING values – [‘output’ => ‘foo’, ‘debug’ => ‘bar’]
  • array<int, array{output: string, debug: string}>: an array with the key “output” and “debug”, the values are STRING values – [1 => [‘output’ => ‘foo’, ‘debug’ => ‘bar’], 3 => [‘output’ => ‘foo’, ‘debug’ => ‘bar’], …]

Examples (@psalm-* || @phpstan-*): PHPStan can also use “psalm-*” prefixed annotations and Psalm understands “phpstan-*” annotations.

  • list<array{output: string, debug: string}>: an array with the key “output” and “debug”, the values are STRING values – [0 => [‘output’ => ‘foo’, ‘debug’ => ‘bar’], 1 => [‘output’ => ‘foo’, ‘debug’ => ‘bar’], …]

list: represents continuous, integer-indexed arrays (always start from index zero) like: [“red”, “yellow”, “blue”] 

Live-Examples:

Psalm: https://psalm.dev/r/922d4ba5b1

PHPStan: https://phpstan.org/r/ce657ef4-9f18-46a1-b21a-e51e3a0e6d2d

Code:

/**
 * @param array<int|string, mixed> $row
 *
 * @return array<int|string, mixed>
 */
abstract function formatRow(array $row): array;

PhpStorm support?: Sadly PhpStorm did not have good support for these types, so that you often have to add „@psalm-*“ PHPDoc comments. For example PhpStorm will accept “array<int, Order>” but PhpStorm will not understand the PHPDoc, so that you need to add e.g. “@param Order[] $order” and “@psalm-param array<int, Order> $order”. 

Examples for PhoStorm + PHPStan || Psalm: 

/**
* @param Order[] $order
* @psalm-param array<int, Order> $order
*
* @return void
*/
public function fooOrder($order): void { ... }

// you could also use "..." here

/**
* @param Order ...$order
*
* @return void
*/
public function fooOrder(Order ...$order): void { ... }
/**
* @param int $foo_id
*
* @return Foo[]|Generator
* @psalm-return Generator&iterable<Foo>
*/
abstract function fetchYieldByFoo($foo_id): Generator;

1.3 Dynamic Autocompletion (+ data from your database) via deep-assoc-completion

If you have a method e.g. “formatRow($row)” you can use “getFieldArray()[0]” (data from the database – you have to connection the IDE with your database and your queries need to be analyzable by PhpStorm (take a look at the next screenshot) and combine static data from “getHeaderFieldArray()”, so that you have auto-completion from different sources.

Code:

/**
 * @param array<int|string, mixed> $row = $this->getFieldArray()[0] + $this->getHeaderFieldArray()
 *
 * @return array<int|string, mixed>
 */
abstract function formatRow(array $row): array;

more information + examples: https://github.com/klesun/deep-assoc-completion

1.4 Immutability Check via Static Code Analyses (via psalm)

And there is even more. :) You can add PHPDoc annotation that will check if you really use immutable classes or at least methods. Please read more here: https://psalm.dev/articles/immutability-and-beyond

Code:

/**
 * @param array<int|string, mixed> $row = $this->getFieldArray()[0] + $this->getHeaderFieldArray()
 *
 * @return array<int|string, mixed>
 *
 * @psalm-mutation-free
 */
abstract function formatRow(array $row): array;

Live-Example:

– Psalm: https://psalm.dev/r/5bac0a9a07

1.5 Generics in PHP via Static Code Analyses

We can also use Generics via code annotations. PHPStan & Psalm both support it, but Psalm’s support is more feature complete and both tools can use the „@psalm-“-syntax. Here comes some simple examples.

array_last: Will return the last array element from the $array (type: TLast) or the $fallback (type: TLastFallback). We tell the function that the types comes from the input parameters and that the input is an array of TLast or TLastFallback from the fallback.

/**
 * @param array<mixed> $array
 * @param mixed             $fallback <p>This fallback will be used, if the array is empty.</p>
 *
 * @return mixed|null
 *
 * @template TLast
 * @template TLastFallback
 * @psalm-param TLast[] $array
 * @psalm-param TLastFallback $fallback
 * @psalm-return TLast|TLastFallback
 */
function array_last(array $array, $fallback = null)
{
    $key_last = \array_key_last($array);
    if ($key_last === null) {
        return $fallback;
    }
return $array[$key_last]; }

array_first: Will return the first array element from the $array (type: TFirst) or the $fallback (type: TFirstFallback). We tell the function that the types comes from the input params and that the input is an array of TFirst or TFirstFallback from the fallback.

/**
 * @param array<mixed> $array
 * @param mixed             $fallback <p>This fallback will be used, if the array is empty.</p>
 *
 * @return mixed|null
 *
 * @template TFirst
 * @template TFirstFallback
 * @psalm-param TFirst[] $array
 * @psalm-param TFirstFallback $fallback
 * @psalm-return TFirst|TFirstFallback
 */
function array_first(array $array, $fallback = null)
{
    $key_first = array_key_first($array);
    if ($key_first === null) {
        return $fallback;
    }
return $array[$key_first]; }

So we can define „Templates“ and map input arguments on that types, this can be even more complex if you use it in a class context and you map the „Templates“ on class properties. But the logic will be the same.

Here is a more complex example: https://github.com/voku/Arrayy/blob/master/src/Collection/CollectionInterface.php

PhpStorm support?: Noop, sadly we need to hack this via „PHPSTORM_META“, so here is an example:

  • override(\array_filter(0), type(0)); // suppose first parameter type is MyClass[] then return type of array_filter will be MyClass[]
  • override(\array_reduce(0), elementType(0)); // suppose first parameter type is MyClass[] then return type of array_reduce will be MyClass

Read more here:

2.0 Resume

It‘s not perfect, and type check and auto-completion only with PHPDoc is not really what I expected for the year 2020. But it‘s working and I hope PhpStorm will bring more support for the new types annotations in the future.

More Links:

 

Do Not Fear The White Space in your Code

“White space is to be regarded as an active element, not a passive background.”
– Jan Tschichold

At least since Apple is using massive white space for their product pages, every web-designer knows that the visitors prefer simple over the complex designs. And I think we can use some of this design principles to make our code more readable. 

What is white space?

White space is created by pressing the Return key [\n|\r|\r\n], Spacebar key [ ], or the Tab key [\t]. White space is essentially any bit of space in your code that is not taken up by „physical“ characters.

[\n] = LF (Line Feed) → Used as a new line character in Unix/Mac OS X

[\r] = CR (Carriage Return) → Used as a new line character in Mac OS before X

[\r\n] = CR + LF → Used as a new line character in Windows

https://stackoverflow.com/a/15433225/1155858

White space is critical for organizing our code, because we tend to find some relations between “things” to instinctively find a meaning in the composition. Whatever you see, your mind will filter, add and remove the given input and because of that we should prepare the input. So that we can easily consume the code.

  • Vertical Whitespace:
    • A single blank line.
  • Horizontal Whitespace:
    • Leading White Space (at the start of a line): Leading white space (i.e., indentation) is addressed elsewhere. (Indent with i.e. 4 spaces is a good default.)
    • Internal White Space (in the code itself): The code is more readable with some white space between the different operations in a single line.
    • Trailing White Space (at the end of a line): This kind of white spaces are unnecessary and can complicate diffs.

Horizontal alignment: Alignment can aid readability, but it creates problems for future maintenance, if you do not automate your code style.

“White space also makes content more readable. A study (Lin, 2004) found that good use of white space between paragraphs and in the left and right margins increases comprehension by almost 20%. Readers find it easier to focus on and process generously spaced content.”[Lin, 2004] Evaluating older adults’ retention in hypertext perusal: impacts of presentation media as a function of text topology by Dyi-Yih Michael Lin in “Computers in Human Behavior”, Volume 20, Issue 4, July 2004, Pages 491-503

Where do we use white space?

We mostly add white space after a parameter, function, method, if-, else-, while-, and other calls so that it’s easier to identify where code that belongs together starts and ends. Unlike a book in which you read from top to bottom, we mostly jump into the code and only read parts of the code. It’s more like a newspaper where you read the headline (i.e. method name) and then jump to the next article (i.e. method).

Whitespace changes are tricky for “diff”

You can use something like “git diff -w” …

  • –ignore-space-at-eol: Ignore changes in whitespace at EOL.
  • -b, –ignore-space-change: Ignore changes in amount of whitespace. This ignores whitespace at line end, and considers all other sequences of one or more whitespace characters to be equivalent.
  • -w, –ignore-all-space: Ignore whitespace when comparing lines. This ignores differences even if one line has whitespace where the other line has none.
  • –ignore-blank-lines: Ignore changes whose lines are all blank.

… but the problems is still there, that white space changes hurts readability from default diffs in i.e. gitlab / github / etc. so that you should try to commit white space changes in a separated commit.

Be consistent!

The most important and the only things that everybody will agree on is: “Be consistent [and try to automate this process, please]!For example, don’t mix tabs and spaces or playing different braces for the same type of code. If the team (or project) didn’t use any code style at all, then it’s quite simple, take an existing coding standard (try to follow the conventions already in place for a language, if there are any) where automation is already implemented for and stick to it in the first run.

Design principles for your code

Proximity: Things closer together will be seen as belonging together.

Code: Put code together that belongs together.

bad:

.example_1,.example_2{background: url(images/example.png) center center no-repeat, url(images/example-2.png) top left repeat, url(images/example-3.png) top right no-repeat;}

not that bad:

.example_1, 
.example_2 {
background: url(images/example.png) center center no-repeat,
url(images/example-2.png) top left repeat,
url(images/example-3.png) top right no-repeat;
}

I also like to have a single blank line before ‘break’, ‘continue’, ‘declare’, ‘return’, ‘throw’, ‘try’, so that you see that this is some kind of important code. But also the readability of simple “if”-statements can be improved.

bad:

if (($loggedInKunde->bestellungen_genehmigen || $loggedInKunde->manuelle_bestellgenehmigung) && ($wareneingang != 5)) {

not that bad:

if (
(
$loggedInKunde->bestellungen_genehmigen
||
$loggedInKunde->manuelle_bestellgenehmigung
)
&&
$wareneingang != 5
) {

better:

if ($this->useOrderApproval()) { 

And please do not mix different things like variable assinment and “if”-statments in one blob of code.

bad:

if (null !== $token = $this->tokenStorage->getToken()) {

not that bad:

$token = $this->tokenStorage->getToken();
if ($token !== null) {

better:

$token = $this->tokenStorage->getToken();
if ($token->exists()) {

 

Similarity: Things with the same characteristics (shape, color, shading, orientation, …) will be seen as belonging together.

Code: Write code that do similar things in the same code style.

bad:

$valueArray = Bar::searchSplitButKeepQuotes($value);
if (\count($valueArray) <= 0) { return ''; }

$valueFoo = Foo::searchSplit($value);
if (\count($valueFoo) === 0) {
return '';

}

not that bad:

$bars = Bar::searchSplitButKeepQuotes($value);
if (\count($bars) === 0) {
return '';
}

$foos = Foo::searchSplit($value);
if (\count($foos) === 0) {
return '';
}

 

Symmetry: Our mind tends to perceive objects by finding a starting point and it’s pleased when it can find uniformity structures. Sometimes our mind will see such structures also if they are not really there.

Code: Try to use uniformity code structures.

bad:

if (widget.IsRegular) {
    service.Process(widget);
} else {
    var result = widget.Calculation();
    widget.ApplyResult(result);
    service.ProcessSpecial(widget);
}

not that bad:

if (widget.IsRegular) {
    service.Process(widget);
} else {
    service.ProcessSpecial(widget);
}

 

Links:

Ubuntu + Windows + .dotfiles

1. Open “Update & Security” > “For Developers” > activate the “Developer Mode”.

2. Under “Programs” > “Programs and Features”, click “Turn Windows Features On or Off”.

3. Enable the “Windows Subsystem for Linux (Beta)” option in the list and click “OK”.

4. After an “reboot”, you open the command line (cmd) and type “bash” and press [Enter].

5. Enter a “Username” and a “Password. (you need the password for “sudo”-commands)

6. Now you can open “Bash on Ubuntu on Windows”.

7. Install “git” via:

sudo apt-get install git

8. Get the “.dotfiles” via:

cd ~ ; git clone https://github.com/voku/dotfiles.git; cd dotfiles

9. Install dependencies via:

./firstInstallDebianBased.sh

10. Install zsh (optional, but recommended).

11. Use the “.dotfiles” via:

./bootstrap.sh

12. Download, install and configure “ConEmu” …

… a alternative “Command Line GUI” for Windows. Instead of the default GUI we have UTF-8 support with “ConEmu”, so we can use chars like “✓”.

-> http://conemu.github.io

After you have installed it, we can force “bash” to “zsh” in the configuration.

13. Add some “symlinks” for easier navigation via: e.g.

sudo ln -s /mnt/c /c
sudo ln -s /mnt/d /d

14. Add the current “hostname” into the “/etc/hosts”-file.

sudo su
echo "127.0.0.1 $(hostname)" >> /etc/hosts
exit

Now you can edit your “.dotfiles”-settings and tweak your configuration.

Here is my example-config: https://github.com/voku/dotfiles#add-custom-commands-without-creating-a-new-fork

 

PHP: Arrays zusammenzuführen

In den folgenden Beispielen werden Standard-Funktionen von PHP gezeigt, um .

Ausgangswerte:

$array1 = [1 => 'one', 2 => 'two', 'foo' => 'bar1'];

$array2 = [3 => 'three', 4 => 'four', 6 => 'six', 'foo' => 'bar2'];

Wenn man Arrays mit “+” verbindet, werden die Werte in der Reihenfolge in welcher man diese Verbindet in das neue Array übernommen. Dabei sollte man jedoch beachten, dass gleiche Schlüssel (keys) nicht überschrieben werden.

$array2 + $array1;

—>

array
(
    [3] => three    // array2
    [4] => four     // array2
    [6] => six      // array2
    [foo] => bar2   // array2
    [1] => one      // array1
    [2] => two      // array1
)

Hier ein zweites Beispiel mit ‘+’, nun haben wir die ensprechenden Arrays jedoch umgekehrt, so dass der ‘foo’-Wert vom ersten Array behalten wird.

$array1 + $array2;

—>

array
(
    [1] => one      // array1
    [2] => two      // array1
    [foo] => bar1   // array1
    [3] => three    // array2
    [4] => four     // array2
    [6] => six      // array2
)

Diese kleine “foreach”-Schleife ist ein nachbau der ‘+’-Methode. Auch hier werden neue Werte angefügt, alte Werte jedoch behalten.

$union = $array1;
foreach ($array2 as $key => $value) {
  if (array_key_exists($key, $union) === false) {
    $union[$key] = $value;
  }
}

—>

array
(
    [1] => one      // array1
    [2] => two      // array1
    [foo] => bar1   // array1
    [3] => three    // array2
    [4] => four     // array2
    [6] => six      // array2
)


Und auch diese “foreach”-Schleife werden die Arrays vertauscht, so dass die Werte von ‘array2’ bestehen bleiben.

$union = $array2;
foreach ($array1 as $key => $value) {
  if (array_key_exists($key, $union) === false) {
    $union[$key] = $value;
  }
}

—>

array
(
    [3] => three    // array2
    [4] => four     // array2
    [6] => six      // array2
    [foo] => bar2   // array2
    [1] => one      // array1
    [2] => two      // array1
)

Die folgende PHP-Funktion (array_replace()) behält die Reihenfolge von ‘array1’, ersetzt jedoch Schlüssel (keys) welche ebenfalls in ‘array2’ vorkommen mit dessen Werten.

array_replace($array1, $array2);

—>

array
(
    [1] => one      // array1
    [2] => two      // array1
    [foo] => bar2   // array2 (position from array1)
    [3] => three    // array2
    [4] => four     // array2
    [6] => six      // array2
)

Hier wird ebenfalls ‘array_replace()’ verwendent, jedoch sind ‘array1’ und ‘array2’ wieder vertauscht und auch hier werden die Schlüssel (keys) beibehalten und die Position von ‘foo’ wird beibehalten.

array_replace($array2, $array1);

—>

array
(
    [3] => three    // array2
    [4] => four     // array2
    [6] => six      // array2
    [foo] => bar1   // array1 (position from array2)
    [1] => one      // array1
    [2] => two      // array1
)

Die Funktion ‘array_merge()’ vergibt neue Schlüssel (keys) für indizierte Arrays (0,1,2,3…). Somit fängt das neue Array nun mit dem Schlüssel “0” an.

array_merge($array1, $array2);

—>

array
(
    [0] => one      // array1 (with a new key)
    [1] => two      // array1 (with a new key)
    [foo] => bar2   // array2 (position from array1)
    [2] => three    // array2 (with a new key)
    [3] => four     // array2 (with a new key)
    [4] => six      // array2 (with a new key)
)

Auch hier wurden die Arrays wieder vertauscht und auch hier wurden die numerischen Schlüssel durch neue ersetzt.

array_merge($array2, $array1);

—>

array
(
    [0] => three    // array2 (with a new key)
    [1] => four     // array2 (with a new key)
    [2] => six      // array2 (with a new key)
    [foo] => bar1   // array1 (position from array2)
    [3] => one      // array1 (with a new key)
    [4] => two      // array1 (with a new key)
)

Die PHP-Library “Arrayy” (https://github.com/voku/Arrayy) …

Arrayy::create($array1)->mergePrependKeepIndex($array2);

—>

Arrayy {
  ["array":protected]=>
  array(6) {
    [1]=> one
    [2]=> two
    [foo]=> bar2
    [3]=> three
    [4]=> four
    [6]=> six
  }
}

Arrayy::create($array1)->mergePrependNewIndex($array2);

—>

Arrayy {
  ["array":protected]=>
  array(6) {
    [0]=> three
    [1]=> four
    [2]=> six
    [foo]=> bar1
    [3]=> one
    [4]=> two
  }
}

Links:

Was habe ich als Fachinformatiker bisher gelernt?

Nachdem ich nun bereits seit einigen Jahren lerne zu deklarieren und zu programmieren, werde ich im folgenden beschreiben was ich bisher gelernt habe.

Kurz zu mir: Lars Moelleken |
> Assistent für Betriebsinformatik
> Fachinformatiker Systemintegration
> Informatik Studium (Abgebrochen)
> Fachinformatiker Anwendungsentwicklung

Was habe ich in meiner Ausbildung zum Fachinformatiker Systemintegration gelernt?

– Du lernst nur soviel, wie du lernen willst.

Im Gegensatz zur Schule / Fachhochschule konnte und musste ich mir vieles in der Ausbildung selber beibringen und erarbeiten. Und man erkennt schnell, dass man nur soviel lernt wie man möchte. Wenn man dies einmal verstanden hat, dann setzt man sich auch gerne hin und lernt z.B. Linux- / Shell-Befehle auswendig, besucht weitere Kurse (z.B. bei der VHS), geht zu Meetups oder Konferenzen. Krümmer dich um dein Können, denn wenn man etwas macht, sollte man es auch richtig machen.

“Eine Investition in Wissen bringt noch immer die besten Zinsen.” – Benjamin Franklin

– Keine Panik!

Was man ziemlich schnell als Sysadmin lernt ist „Ruhe bewahren“ and think first – then act. Überstürzter Aktionismus hilft nicht weiter und schadet meistens sogar. Bevor man handelt, sollte man zuvor selber einige Informationen einholen (Infos von Logfiles, Hardware Status, System Status, …) so dass man auch wirklich weiß was wie man den Fehler behene kann.

– Linux && Kommandozeile <3

Wer bisher noch nicht mit einem Unix Betriebssystem gearbeitet hat, weiß leider nicht was er verpasst. Wer aus welchen Gründen auch immer Windows nutzen möchten oder muss, kann trotzdem mit der Git-Bash [+ .dotfiles] einige Vorteile von Linux nutzen. Und seine eigene Komfortzone zu verlassen und neue Betriebssysteme auszuprobieren hilft dabei den Computer insgesamt besser zu verstehen. An dieser Stelle empfehle ich mal wieder “Arch Linux”.

Man sollte sich außerdem mit der Kommandozeile vertraut machen, wenn du deine Produktivität rapide steigern möchtest. Zum Beispiel sollte man folgende Befehle man genauer ansehen: find / grep / lsof / strace

find – Linux

– Lies in der offizielle Dokumentation (und erst dann bei “Stack Overflow”).

Ob Cisco oder Manpages man liest am besten die offizielle Dokumentation zu der Programm-Version / Hardware-Version welche man gerade einsetzt. Beim programmieren nutzt man jedoch häufig stackoverflow.com und findet schnell Antworteten und Code-Beispiele, aber das “Warum” und “Wie” kommt dabei meistens zu kurz. Wenn man als erstes in die Spezifikation / Dokumentation schaut löst man nicht nur dieses Problem, sondern man versteht ggf. das Problem und weiß auch wie man ähnliche Probleme lösen kann.

– Mach immer ein Backup (benutze nieeemals betrunken “sudo”).

Manche Sachen muss man auf die hart Tour lernen, apt-get install safe-rm zu installieren gehörte für mich anscheinend dazu!

“Der Mensch hat dreierlei Wege klug zu handeln; erstens durch Nachdenken, das ist der edelste; zweitens durch Nachahmen, das ist der leichteste; und drittens durch Erfahrung, das ist der bitterest.” – (Konfuzius)

– Sei ehrlich zu Kunden, Mitarbeitern und dir selbst.

Sei ehrlich zu Kunden, insbesondere wenn etwas schief geht. Wenn das Produkt (z.B. Server) vom Kunden ausfällt, dann biete Lösungsvorschläge an und keine Ausreden und löse das Problem nicht die Schuldfrage. Niemandem ist damit geholfen, wenn man mit dem Finger auf den Kollegen oder Kunden zeigt, dem Server nicht, dem Kunden nicht und letztlich einem selbst nicht.

– Stelle Fragen, wenn du etwas nicht verstanden hast.

Rede mit Kunden nicht über etwas was du nicht verstehst, etwas nicht zu wissen (insbesondere in der Ausbildung) ist nicht schlimm, aber dann frage einen Kollegen bevor du mit einem Kunden darüber sprichst!

– Denke über dein Tun nach (nicht nur auf der Arbeit).

Wenn man Dinge hinterfragt und über seine Arbeit und sein Tun nachdenkt, dann kann man sich persönlich weiterentwickeln. Hinterfrag kritische zum Beispiel ob man wirklich bei Amazon bestellen sollte oder sollte man das Buch lieber direkt beim Verlag bestellt? Welchen Vorteil hat der Autor davon und habe ich Nachteile? Sollten wir Nagios3 oder besser direkt Icinga einsetzen? Hinterfrage deine Arbeit und bewerte kritisch ob dies wirklich eine gute / sichere / zukunftsorientierte Lösung ist.

Falls man sich selber nicht sicher ist oder eine zu eingeschränkte Sichtweise hat (weil man z.B. nur diese eine Lösung kennt), dann sollte man sich neues Wissen aneignen, ggf. andere Lösungen oder „best practices“ recherchieren und mit anderen über die Thematik diskutieren.

– Google korrekt nutzen …

Wenn man ein Problem recherchiert, dann sucht man nach der Fehlermeldung in Anführungszeichen. Und wenn man keine Fehlermeldung hat sucht man zusätzlich nach der Versionsnummer oder / und einer entsprechenden Jahreszahl.

https://suckup.de/2010/02/google-hacks-und-tricks/

Was habe ich in meiner Ausbildung zum Fachinformatiker Anwendungsentwicklung gelernt?

– Du lernst niemals aus …

Wenn man anfängt sich mit der Web-Programmierung (HTML, CSS, JS, Node.js, PHP, …) zu beschäftigen, weiß man zunächst gar nicht wo man anfangen soll! Es gibt so vieles zu lernen und dieses Gefühl begleitet einen einige Zeit (Jahre), bis man wiederkehrende Konzepte erkennt. Der Vorteil in der Web-Programmierung ist jedoch, dass viele unterschiedliche Dinge gemeinsame APIs haben oder zumindest kombiniert werden können. Ich kann in PHP eine Klasse schreiben, welche mir Data-Attribute für mein HTML erstellt, welche ich wiederum mit JavaScript auslesen kann, um diese entsprechend über CSS-Klassen zu gestalten. Aber es bleibt dabei, man lernt niemals aus und genau das ist das Spannende an diesem Job!

– Programmiere jeden Tag. (aber setzte dir selber ein LIMIT 1, x)

Auch wenn der Chef heute nicht im Büro ist und man als Azubi gerade keine Aufgabe hat, dann programmiere, wenn du Zuhause auf der Couch sitzt (und keine neue Serie auf Netflix läuft), dann programmiere und wenn du Urlaub hast, hast du Urlaub!

Hier ein interessanter Link von „John Resig“ (jQuery):
http://ejohn.org/blog/write-code-every-day/

– Denke in Modulen und Packages …

Wenn man jeden Tag Software schreibt, möchte man die selbe Funktionalität (z.B. Datenbankverbindung, E-Mail senden oder Error-Logging …) nicht mehrfach für verschiedene Projekte programmieren (und eigentlich will man Standard-Funktionen gar nicht selber programmieren). Daher sollte jeder Programmierer in Modulen denken und eine Applikation in verschiedenen, am besten austauschbaren Teilen konzipieren. In vielen Fällen kann man das System dann auch besser erweitern, da es bereits eine Schnittstelle für Module gibt, welche man nutzen kann. Die Unabhängigkeit und Entkopplung von Programmteilen hat auch den Vorteil, dass Seiteneffekte von unterschiedlichen Stellen im Quelltext weiter vermieden werden. Außerdem sollte man die Kopplung von den entsprechenden Modulen minimieren, da man ansonsten nichts durch Modulen gewinnt.

Für fast alle Dinge in der Web-Entwicklung gibt es bereits Paket-Manager:
– Frontend (css, js): bower (npm)
– Backend (Node.js): npm
– Backend (php): composer
– Backend (ruby): gem

– Open Source <3

Wenn man bereits mit Modulen und Packages arbeitet, kann man diese auch gleich als Open Source Projekt veröffentlichen + Tests via Travis-CI + Code-Coverage anzeigen + zumindest einer kleinen Dokumentation und schon allein aus diesen Gründen lohnt sich die Veröffentlichung als Open Source. Auch die Code-Qualität steigt (nach meiner Erfahrung), da man den Quelltext der Öffentlichkeit freigibt und sich daher mehr oder weniger Bewusst auf die Code-Qualität achtet.

Der Moment, wenn man seinen ersten Pull-Request für seine Software erhält oder mit jemanden aus Israel, der Türkei und der USA zusammen programmiert, unbezahlbar.

– Git und Gut!

Ich weiß nicht wie Menschen ohne eine Versionskontrolle mit dem PC arbeiten können. Selbst bei privaten kleineren Projekten oder für Konfigurations-Dateien setze ich “git” ein. Es folgende einige Vorteile, aber die Liste kann man bestimmt noch um einige Punkte erweitern …

Vorteile:
– Änderungen können nachvollzogen werden (git log, git blame)
– Änderungen können rückgängig gemacht werden (git revert, git reset)
– Änderungen können von anderen Mitarbeitern gereviewed werden
– Mitarbeiter können gleichzeitig an einem Projekt arbeiten (git commit, git pull, git push)
– Entwicklungszweige (Forks) können gleichzeitig entwickelt werden (git checkout -b , git branches)

– Nutze “github.com” und lerne von den besten.

Github ist noch einmal was ganz anders als zum Beispiel ein privater (kostenloses / freies) „gitlab“-Server, da man sich indirekt darauf geeinigt hat, dass man die Plattform für Open Source Projekte verwendet, obwohl github selbst nicht Open Source ist. Man findet daher wirklich viele gute Entwickler und Projekte auf github.com und man kann bereits durch das lesen von Quelltext / Quelltextänderungen vieles lernen. Insbesondere weil man die Entwicklung der Projekte nachvollziehen und verfolgen kann: Wie strukturieren andere Ihren Code? Wie schreiben andere Ihre “commit”-messages? Wie viel Code sollte eine Methoden beinhalten? Wie viel Code sollte eine Klasse beinhalten? Welche Variablen-Namen sollte man besser vermeiden? …

https://github.com/trending
https://twitter.com/trendinggithub

– Tests ausprobieren.

Gerade wenn man Tests für seine eigenen Funktionen und Klassen schreibt, erwischt man sich dabei genau die Fällte zu testen, welche man bereits bedacht hat, daher sollte man die entsprechende Funktionalität mit unterschiedlichen (noch nicht bedachten) Daten testen. Außerdem ist es manchmal hilfreich den Quelltext absichtlich (temporär) zu sabotieren, so dass man seine Tests ebenfalls einmal testen kann. Hier einige Listen mit Eingaben welche man testen könnte: https://github.com/minimaxir/big-list-of-naughty-strings

PS: außerdem sollte man einen zusätzlichen Test hinzufügen, wenn ein Fehler bereits aufgetreten ist, so dass man Fehler nur einmal finden muss. Hier ein Beispiel: https://github.com/swiftmailer/swiftmailer/tree/5.x/tests/bug/Swift

– Automatisiere deine Tests.

Unit-Tests, Integrationstest und Frontend-Tests helfen nur, wenn diese auch ausgeführt werden, daher sollte man sich frühzeitig mit automatisierten Tests beschäftigen und diese bei Quelltextänderungen auch automatisch ausführen. Wo und wann diese Tests ausgeführt werden entscheidet gleichermaßen darüber, wie effektiv diese Tests letztendlich sind. Sobald man einige Tests geschrieben hat, versteht man auch warum man auf zusätzliche Parameter bei Methoden und Funktionen besser verzichten sollte, da die Anzahl der Tests exponentiell ansteigt.

https://en.wikipedia.org/wiki/Software_testing

– Deployment ist wichtig …

Sobald man mit mehr als einem Entwickler an einem Projekte arbeitet, möchte man irgendeine Art von Deployment einsetzten, weil man seine Änderungen sonst gegenseitig überschreibt. Außerdem möchte man, dass der Quelltext aus dem Versionskontroll-System auf dem Server liegt, ansonsten kann man eine Applikation nicht warten bzw. erweitern!

– Konzepte zu verstehen ist wichtiger als dessen Implementierung.

Design-Patterns (Programmier-Konzepte) zu verstehen hilft einem nicht nur in der aktuellen Programmiersprache, sondern kann meistens auch auf andere Programmiersprachen angewendet werden.

Grundlegende Konzepte (Klassen, Objekte, OOP, Funktionen, ORM, MVC, DDD, Unit-Tests, Data-Binding, Hooks, Template-Engine, …) findet man in vielen Frameworks / Programmiersprechen und sobald man die Begriffe und Konzepte einmal verstanden hat, ist es gar nicht mehr so schwer neue / unterschiedliche Frameworks einzusetzen. Und man erkennt unterschiedliche stärken und schwächen von diesen Frameworks / Werkzeugen: „Wer als Werkzeug nur einen Hammer hat, sieht in jedem Problem einen Nagel.“

– Probleme lösen heißt auch Kunden verstehen.

Design-Patterns gehören zur Grundausstattung, aber man sollte sich auch immer wieder die Frage stellen: Welches Problem mit der gegebenen Lösung eigentlich gelöst werden soll? Ggf. findet man eine noch eleganterer / einfacherer Lösung. Und manchmal will der Kunde eigentlich auch was ganz anders, er weiß es nur noch nicht oder jemand hat den Kunden falsch verstanden.

– Probleme lösen heißt auch Prozesse verstehen.

Es ist aber ebenso wichtig zu verstehen warum ein bestimmtes Feature implementiert wird, da man ansonsten etwas programmiert was entweder gar nicht gebraucht bzw. genutzt wird. Man sollte daher die entsprechende Aufgabenstellung vor der Implementierung und noch vor der Planung im Gesamtkontext verstehen.

projekt-schaukel-baum

– Verteile auf mehreren Dateien.

Nutze eine Datei für eine Klasse, nutze eine Datei für CSS-Eigenschaften eines Modules oder einer speziellen Seite, nutze eine neue Datei für jeden neuen View. Den Quelltext auf verschiedene Dateien / Verzeichnisse aufzuteilen bietet viele Vorteile, so weiß der nächste Entwickler wo neuer Quelltext abgelegt werden soll und man findet sich schneller im Quelltext zurecht. So geben viele Frameworks bereits eine vordefinierte Verzeichnisstruktur für z.B. Model / View / Controller vor.

– Lesbarkeit geht vor!

Die Lesbarkeit von Quelltext sollte immer an erster Stelle stehen, da man selber oder Arbeitskollegen diesen Code in Zukunft warten bzw. erweitern müssen.

YouTube Videos zum Thema “Clean Code”: https://www.youtube.com/results?search_query=%22Clean+Code%22&search_sort=video_view_count

Best Practices: http://code.tutsplus.com/tutorials/top-15-best-practices-for-writing-super-readable-code–net-8118

– Gute Namensgebung ist eine der schwierigsten Aufgaben in der Programmierung.

Es fängt beim Domainnamen / Projektnamen an, geht über Dateinamen zu Namen von Verzeichnissen, Klassennamen, Methodennamen, Variablennamen, CSS-Klassennamen. Mache dir immer bewusst, dass andere dies lesen werden und verstehen müssen. Daher sollte man auch auf unnötige Abkürzungen verzichten und schreiben, was man beschreiben möchte.

Wir wollen beschreiben was die Funktion macht und nicht wie diese implementiert ist.

-> Falsch: farbeBlack(), nutzeFarbe(), …
-> Richtig: $page->setColor(‘black’), $page->getColor(), …

Variablen sollten beschreiben was diese beinhalten und nicht wie diese gespeichert sind.

-> Falsch: $array2use, $personenArray, …
-> Richtig: $pages, $persons, …

Zusammenfassung: Beschreibe was die Variable / Methode / Funktion / Klasse ist, nicht wie diese implementiert ist.

Weniger schlecht PHP programmieren

– An Kommentare sparen (zumindest inline) …

Gute Kommentare erklären “Warum” und nicht “Was” gemacht wird und sollten dem Leser einem Mehrwert bieten, welcher nicht bereits im Quelltext (Stichwort: Namensgebung) beschrieben ist.

Beschreibungen für Methoden, Funktionen und Klassen (javadoc, phpdoc, …) sollte meiner Meinung nach immer hinterlegt werden, so dass man seine Dokumentation im Quelltext abbildet. Moderne IDEs können zudem prüfen, ob die Parameter und Rückgaben korrekt hinterlegt wurden.

https://de.wikipedia.org/wiki/PHPDoc
https://en.wikipedia.org/wiki/JSDoc
https://de.wikipedia.org/wiki/Javadoc

Beispiele für inline Kommentare:

schlechter Code:

// Prüfen ob der User bereits einloggt ist
if (isset($_SESSION['user_loggedin']) && $_SESSION['user_loggedin'] > 1) { ... }

etwas besserer Code:

// check if the user is already logged-in
if (session('user_loggedin') > 1) { ... }

besserer Code:

if ($user->isLoggedin === true) { ... }

… und noch ein Beispiel …

schlechter Code:

// regex: email
if (!preg_match('/^(.*<?)(.*)@(.*)(>?)$/', $email) { ... }

besserer Code:

define('EMAIL_REGEX_SIMPLE', '/^(.*<?)(.*)@(.*)(>?)$/');

if (!preg_match(EMAIL_REGEX_SIMPLE, $email) { ... }

– Konsistenz in einem Projekt, ist wichtiger als persönliche Präferenzen!

Nutze den bestehenden Code und nutze gegebene Funktionen. Wenn es Vorteile bringt, dann ändern / refactor den entsprechenden Code aber refactor dann alle Stellen im Projekt welche auf diese Weise implementiert sind.

Beispiel: Wenn man bisher ohne Template-System gearbeitet hat und aus „Gründen“ eines einsetzten möchte, dann nutze dies für alle Templates im Projekt und nicht nur für deinen aktuellen Use-Case, da ansonsten inkonsistenten im Projekt entstehen. Wenn man z.B. eine neue „Email→isValid()“ Methode, dann sollte man auch alle bisherigen RegEx-Versuche im aktuellen Projekt durch die „Email“-Klasse ersetzten, weil ansonsten wieder inkonsistenten entstehen.

– Ein einheitlicher Code-Style wirkt sich sehr positiv auf die Qualität aus!

Wie im echten Leben gilt auch hier, wenn irgendwo bereits Müll liegt, sinkt die Hemmschwelle seinen einen Müll dort abzuladen extrem an. Wenn aber alles schön ordentlich aussieht, wirft man nicht einfach eine “randumInt() { return 4; } “-Funktion hinzu.

Man sollte sich im Team einen Code-Style überlegen und diesen in neuen Projekten einsetzten. In bestehenden Projekten gibt wieder Konsistent geht vor.

– Nutze Funktionale-Prinzipien && Objekt-Orientierte-Konzepte.

Eine reine Funktion (“Pure Functions”) ist nur von ihren Parametern abhängig und mit den selben Parametern liefert diese immer das selbe Ergebnis. Diese Prinzipien kann man auch in OOP berücksichtigen und sogenannte Unveränderbare Klassen erstellen (immutable class).

https://en.wikipedia.org/wiki/Pure_function
https://de.wikipedia.org/wiki/Objektorientierte_Programmierung
https://en.wikipedia.org/wiki/Immutable_object

– Bitte keine globalen Variablen verwenden!

Globale Variablen erschweren das Testen, da diese Seiteneffekte verursachen können. Außerdem kann man Quelltext mit globalen Variablen nur schwer refactoren, da man nicht weiß welche Effekte diese Änderungen auf andere Teile des Systems haben.

In einigen Programmiersprachen (z.B. JavaScript, Shell) sind alle Variablen global und werden erst durch eine bestimmtes Schlüsselwort lokal (z.B. im Scope einer Funktion oder einer Klasse).

Admin meets Frontend

– Lerne deine Werkzeuge richtig einzusetzen!

Wenn man z.B. mit Notepad Quelltext schreibt kann man auch mit einem Löffel ein Loch graben, denn dies ist ähnlich effizient. Lerne Tastatur-Shortcuts für deine Programme und dein Betriebssystem! Nutze eine IDE z.B. von JetBrains (https://www.jetbrains.com/products.html) und nutze zusätzliche Plugins und Einstellungen.

Moderne IDEs geben auch Hinweise / Vorschläge, wie man seinen Code verbessern kann. Für PHP kann man z.B. PhpStorm + Php Inspections (EA Extended) einsetzten und die globalen IDE- Inspections-Einstellungen im Team teilen.

– Performance?

In vielen Situationen muss man sich um die Performance gar nicht so viele Gedanken machen, da uns dabei moderne Programmiersprachen / Frameworks unterstützen und mit einem gesunden Menschenverstand kann man bereits vieles abschätzen, ansonsten heißt die Devise „Profiling, Profiling… Profiling“!

– Exceptions === Ausnahmen

Man sollte Ausnahmebehandlungen niemals zur Behandlung normaler (d.h. häufig auftretender) Fehler einsetzen. Ausnahmen sind Ausnahmen und sollten aus Ausnahmen bleiben. Regulärer Code behandelt die regulären Fälle! „Verwenden Sie Ausnahmen nur ausnahmsweise“ (Pragmatische Programmierer). Und auf keinen Fall sollte man Ausnahmen “abwürgen”, z.B. durch triviale Abfangen mehreren Exceptions.

– Führe deine Arbeit zu Ende

Man sollte in einer Funktion zu Ende führen, was man begonnen hat. So sollte man z.B. „fopen()“ und „fclose()“ immer in einem Code-Block (Methode || Funktion) nutzen, weil man ansonsten darauf vertrauen müsste, dass jemand anders die entsprechende Ressourcen wieder frei gibt.

– Quelltext sollte durchsuchbar sein [Strg + F] …

Der Quelltext sollte einfach zu durchsuchen sein, daher sollte man z.B. auf String-Nesting + „&” bei Sass verzichten und auch bei PHP-Funktionen wie “extract()” vermeiden. Immer wenn Variablen nicht deklariert, sondern wie von Zauberhand (z.B.: bei PHP durch Magic-Methoden) erzeugt werden, kann man den Quelltext anschließend nicht mehr so einfach ändern.

Beispiel in PHP: (schlecht)

extract(array('bar' => 'bar', 'lall' => 1));
var_dump($bar); // string 'bar' (length=3)

Beispiel in Sass: (schlecht)

.teaser {
  font-size: 12px;

  &__link {
    color: rgb(210,210,22);
  }
}

Sass Nesting (Code-Style): https://github.com/grvcoelho/css#nesting

– Programmiere für deinen Use-Case!

Ein großes Problem in der Programmierung ist, dass man versuchen muss generalisiert zu denken und zu programmieren, so dass man den Quelltext entsprechende (einfach) erweitern kann, wenn neue Anforderungen hinzukommen bzw. auch (einfach) ändern kann.

Wie sehen IT-Projekte manchmal aus? → Ein Kunde bestellt bei einem Bauernhof 10.000 grüne Äpfel, ändert seine Bestellung am morgen vor der Lieferung auf 10.000 rote Äpfel und als diese geliefert werden möchte der Kunde aber doch lieber 10.000 Birnen und möchte diese gerne erst in 6 Monaten bezahlen.

Und gerade aus diesem Grund sollte man nur den Quelltext schreiben, der wirklich für den aktuellen Use-Case benötigt ist, denn alle Eventualitäten kann man sowieso nicht abbilden und der Quelltext wird unnötig verkompliziert.

– KISS – Keep it simple, stupid.

Man sollte immer beachten, dass der Quelltext selber gar nicht so viel Wert besitzt. Der Wert entsteht erst dadurch, dass andere Entwickler diesen verstehen und für den Kunden oder sich selbst anpassen / konfigurieren / nutzen können. Dies sollte man während der Programmierung im Hinterkopf behalten, so dass man eine Lösung möglichst nachvollziehbar und „einfach“ implementiert. Und wenn ich keine neue Klasse oder schönes Design-Pattern für das aktuelle Problem verwenden muss, sollte ich dies ggf. auch nicht tun. Was jedoch auch nicht heißt, dass man alle guten Absichten über Board werfen und überall global Variablen / Singletons einsetzten sollte. Wenn jedoch eine einfache Lösung die Aufgabe bereits erfüllt, entscheide dich für diese.

Ein gutes Beispiel wie man es nicht machen sollte, stellt die JavaScript Dom Selector API da. Nicht gerade schön zu lesen oder zu schreiben …

Schlecht: (DOM Selector via JS)

document.getElementsByTagName("div")
document.getElementById("foo")
document.getElementsByClassName("bar")
document.querySelector(".foo")
document.querySelectorAll("div.bar")

Besser: (DOM Selector via jQuery)

$("div")
$("#foo")
$(".bar")
$(".foo")
$("div.bar")

(Ich weiß welche Schreibweise ich bevorzugen würde!)

– DRY – Don’t Reapeat Yourself.

Wiederholungen / Redundanzen im Quelltext oder auch in wiederkehrenden Arbeiten, entsteht relativ schnell wenn die Leute z.B. nicht miteinander kommunizieren. Aber auch unbeabsichtigt durch Fehler im Software-Entwurf, weil man es gerade keine bessere Idee hat oder meint dafür keine Zeit zu haben.

improve

Um Wiederholungen zu vermeiden, sollte man den Quelltext bzw. den Task so ablegen, dass dieser einfach zu finden und einfach wiederzuverwenden ist, denn wenn es nicht einfach zu verwenden ist, werden die Leute es nicht nutzen.

– Der Wille etwas neues zu lernen und zu verstehen ist wichtiger als Vorkenntnisse.

Wenn man bereits z.B. ActionScript (Flash) programmieren kann, aber nicht willens ist etwas neues zu lernen dann bringt einem das vorherige Wissen nichts, denn „Die einzige Konstante im Universum ist die Veränderung.“ – Heraklit von Ephesus (etwa 540 – 480 v. Chr.)

– Lese gute Bücher und Zeitschriften.

Habe mir letztes Jahr das Ziel gesetzt mehr Fachbücher zu lesen. Dafür habe ich mir folgendes auferlegt: bevor ich ein “normales” Buch lesen darf, muss ich ein Fachbuch lesen und anschließend darf ich erst ein normales lesen …

Bücher die ich gelsen habe: https://www.goodreads.com/user/show/3949219-lars-moelleken
Free Books: https://github.com/vhf/free-programming-books/blob/master/free-programming-books.md
Bücher für Programmierer: http://stackoverflow.com/questions/1711/what-is-the-single-most-influential-book-every-programmer-should-read

– Infos: folge anderen Programmieren auf Twitter / Github / Google+ / Medium / Pocket …

http://programmers.stackexchange.com/questions/135/as-a-software-engineer-who-should-i-be-following-on-twitter

– Infos: höre Podcast & abonniere RSS-Feeds / Newsletter & schaue Videos z.B. von Web-Konferenzen

Um sich über neue Technologien, Techniken, Standards, Pattern etc. zu informieren nutzt man am besten verschiedene Medien, welche man in unterschiedlichen Situationen konsumieren kann. Ein interessanten Podcast zum Thema “Frontend Architektur” vor dem einschlafen oder ein Video zum Thema “DevOp” beim zubereiten vom Mittagessen, morgens in der Straßenbahn ein Buch lesen mit dem Titel “Weniger schlecht programmieren” … um nur ein paar Beispiele zu nennen.

Podcasts: https://github.com/voku/awesome-web/blob/master/README.md#-audio-podcast
Newsletter: https://github.com/Freizeitler/Awesome-WebDev-Newsletters
github is awesome: https://github.com/sindresorhus/awesome
and there is more: https://github.com/jnv/lists

– Besuche Meetup’s & Web-Konferenzen und rede mit anderen Entwicklern.

Meetup’s sind Gruppen von Leuten die sich regelmäßig treffen und über z.B. Python, Scala, PHP, etc. austauschen. Meistens hält jemand einen Vortrag zu einem vorher abgestimmten Thema.

-> http://www.meetup.com/de-DE/members/136733532/

Web-Konferenzen machen Spaß. Punkt. Und jeder Entwickler / Administrator sollte diese besuchen, da man neue Eindrücke gewinnt und wirklich gute Leute trifft. Einige Konferenzen sind wirklich teuer, aber hier sollte man sich an seinen Arbeitgeber wenden, ggf. wird dies von der Firma übernommen. Außerdem gibt es auch wirklich günstige Konferenzen.

– Schreibe Antworten bei quora.com || stackoverflow.com || in Foren || deinem Blog …

Um sich selber mit einer bestimmten Thematik auseinander zu setzten und
wirklich zu verstehen, lohnt es sich zu recherchieren und einen Text (ggf. sogar einen Vortrag) zu verfassen, welchen andere lesen und kritisieren und somit verbessern können.

– Bleib nicht jeden Tag so lange auf der Arbeit, ansonsten wartet Zuhause irgendwann keiner mehr auf dich!!!

Bei all der Begeisterung für den “Job” (auch wenn es Spaß macht) sollte man die wirklich wichtigen Dinge nicht aus den Augen verlieren. Wieder etwas was ich auf die hart Tour lernen musste. :/