• Время чтения ~17 мин
  • 16.06.2023

SOLID, эта аббревиатура была придумана Майклом Фезерсом, она представляет собой пять основных принципов объектно-ориентированного программирования, разработанных дядей Бобом.

Большинство программистов, вероятно, знают эту аббревиатуру. Но мне кажется, что расшифровать его может меньшинство.

Разве это неправильно? Не совсем, я думаю, что писать чистый и простой код важнее, чем знать теорию. Но не стоит полностью игнорировать теорию. Как еще вы будете передавать кому-то знания? Как вы обосновываете свой код в обсуждении во время проверки кода? Ваш код должен быть основан на теории и общепринятых стандартах.

Важно знать основы того, как выглядит чистый и простой код. Принципы SOLID могут быть использованы в любом объектно-ориентированном языке программирования. Я ежедневно работаю на Symfony, поэтому я покажу некоторые принципы в PHP.

Итак, давайте вместе пройдемся по пяти принципам SOLID.

Принцип единой ответственности (SRP)

id}/{format}".com/blog/static/f0712fd8b3040fc50e86f004f744cf85/fd28b/single-responsibility-principle.png" sizes="(max-width: 811px) 100vw, 811px" srcset="https://acces"GET /invoice/{id}/{format}".com/blog/static/f0712fd8b3040fc50e86f004f744cf85/ff46a/single-responsibility-principle.png 325w, https://acces"GET /invoice/{id}/{format}".com/blog/static/f0712fd8b3040fc50e86f004f744cf85/a6d36/single-responsibility-principle.png 650w, https://acces"GET /invoice/{id}/{format}".com/blog/static/f0712fd8b3040fc50e86f004f744cf85/fd28b/single-responsibility-principle.png 811w" alt="Принцип единой ответственности (SRP)" loading="lazy">

Я думаю, что это самое известное правило (вероятно, потому, что оно первое, и некоторые люди не читали дальше). А если серьезно, то я считаю, что это очень важно.

Дядя Боб описывает это так: «У класса должна быть одна, и только одна, причина для изменения». Что это значит? Для меня это предложение не помогает :).

Другие объяснения говорят, что класс или функция должны делать одну вещь.

Но что это за «одно»? Можно ли считать регистрацию пользователя «чем-то одним»? Или, может быть, это больше «вещи», потому что регистрации включают в себя некоторые другие более мелкие вещи, шифрование пароля, сохранение в базе данных и отправку электронной почты.

Может быть, отправку электронного письма можно считать «чем-то одним»? В конце концов, он состоит из множества шагов, таких как подготовка содержимого и темы электронной почты, извлечение адреса электронной почты пользователя и обработка ответа. Должны ли мы создать отдельный класс для каждого из этих действий? Как далеко мы зайдем с этой «единой ответственностью»?!

Я думаю, что нам нужно использовать другой принцип объектно-ориентированного программирования, чтобы ответить на этот вопрос. В 1974 году принцип «высокой когезии и низкой связанности» был впервые описан в статье «Структурированное проектирование» в журнале IBM.

Я постараюсь изложить это на простых для понимания примерах.

Сплоченность определяет, за какой объем отвечает функция или класс. Простым примером здесь могут быть Боб и Алиса, помощники повара. Алиса готовит десерты. Ей нужно сделать бисквит, крем, глазурь, нарезать фрукты и сложить все это вместе. Каждый из этих шагов состоит из нескольких других. Это пример низкой сплоченности. Работа Боба — чистить картошку, ничего больше, это пример высокой сплоченности. Ваш метод/класс должен быть похож на Боба, сделай что-то одно.

Связь - это то, насколько легко повторно использовать данный модуль или класс. Головоломки и блоки Lego являются хорошими примерами этого. Головоломки характеризуются высокой сцепленностью. Одна головоломка умещается только в одном месте, ее нельзя совмещать с другими головоломками. В отличие от кубиков Lego, они имеют низкое сцепление, их можно свободно комбинировать, и каждый из них можно использовать где угодно. Ваш код должен быть похож на блоки Lego, удобные для использования в разных местах.

Принцип единой ответственности следует использовать вместе с принципом «высокой сплоченности и низкой связанности». Оба эти принципа, на мой взгляд, пытаются сказать одно и то же.

Теперь пример в PHP-коде. Представьте себе класс BlogPost:

class BlogPost
{
    private Author $author;
    private string $title;
    private string $content;
    private \DateTime $date;
 
    // ..
 
    public function getData(): array
    {
        return [
            'author' => $this->author->fullName(),
            'title' => $this->title,
            'content' => $this->content,
            'timestamp' => $this->date->getTimestamp(),
        ];
    }
 
    public function printJson(): string
    {
        return json_encode($this->getData());
    }
 
    public function printHtml(): string
    {
        return `<article>
                    <h1>{$this->title}</h1>
                    <article>
                        <p>{$this->date->format('Y-m-d H:i:s')}</p>
                        <p>{$this->author->fullName()}</p>
                        <p>{$this->content}</p>
                    </article>
                </article>`;
    }
}

Что здесь не так? Класс BlogPost делает слишком много вещей, и, как мы знаем, он должен делать только одну вещь. Основная проблема заключается в том, что он отвечает за печать в различные форматы, JSON, HTML и многое другое, если это необходимо. Давайте посмотрим, как это можно улучшить.

Удаляем методы печати из класса BlogPost, остальное остается без изменений. И мы добавляем новый интерфейс PrintableBlogPost. С помощью метода, который может распечатать сообщение в блоге.

interface PrintableBlogPost
{
    public function print(BlogPost $blogPost);
}

Теперь мы можем реализовать этот интерфейс любым количеством способов, которые нам нужны:Вы можете увидеть целый пример плохой и хорошей реализации id}/{format}"/solid-php" target="_blank" rel="noopener">здесь

class JsonBlogPostPrinter implements PrintableBlogPost
{
    public function print(BlogPost $blogPost) {
        return json_encode($blogPost->getData());
    }
}
 
class HtmlBlogPostPrinter implements PrintableBlogPost
{
    public function print(BlogPost $blogPost) {
        return `<article>
                    <h1>{$blogPost->getTitle()}</h1>
                    <article>
                        <p>{$blogPost->getDate()->format('Y-m-d H:i:s')}</p>
                        <p>{$blogPost->getAuthor()->fullName()}</p>
                        <p>{$blogPost->getContent()}</p>
                    </article>
                </article>`;
    }
}

Я видел проекты,

в которых классы имеют только один открытый метод с несколькими строками кода (обычно вызывает другой метод из другого класса). Совершенно неразборчиво и ужасно в обслуживании. На мой взгляд, это пример перегиба палки.

Подведем итоги. Ваши классы и методы не должны отвечать за несколько вещей. Но дело тут не в том, чтобы впадать в крайности и источать абсолютно все. Просто для того, чтобы их было легко понять, но они также должны быть последовательными. Чтобы вам не приходилось читать их от корки до корки, чтобы понять, что они делают.

Открытый/закрытый принцип (OCP)

id}/{format}".com/blog/static/b076db16e0e2b447626eebecf7b3702c/fd28b/open-closed-principle.png" sizes="(max-width: 811px) 100vw, 811px" srcset="https://acces"GET /invoice/{id}/{format}".com/blog/static/b076db16e0e2b447626eebecf7b3702c/ff46a/open-closed-principle.png 325w, https://acces"GET /invoice/{id}/{format}".com/blog/static/b076db16e0e2b447626eebecf7b3702c/a6d36/open-closed-principle.png 650w, https://acces"GET /invoice/{id}/{format}".com/blog/static/b076db16e0e2b447626eebecf7b3702c/fd28b/open-closed-principle.png 811w" alt="Открытый/закрытый принцип (OCP)" loading="lazy">

Во-вторых, из принципов SOLID. Общее объяснение состоит в том, что «код должен быть открыт для расширения, но закрыт для модификации». Для меня не очевидно, что это означает на практике. Возможно, это лучше объяснить следствием несоблюдения этого правила. Изменение объявления метода может привести к его сбоям в работе там, где он используется. Суть в том, что изменения должны быть обратно совместимы. Конечно, лучше всего писать код, который отлично работает с самого начала, и вам не нужно его менять, но мы живем не в идеальном мире.

Я постараюсь представить его на нескольких примерах:

а) открытый/закрытый API Это будет пример принципа открытия/закрытия не на одном классе, а на всем API

. Это большой SaaS, это система учета, написанная на PHP, Symfony Framework. Ваш API используется несколькими сотнями клиентов, которые используют его для выставления счетов. В вашем API есть метод получения счетов-фактур в формате PDF. Допустим, это конечная точка типа «GET /invoice/{id}/print». Все хорошо, но однажды клиенты требуют возможность скачать CSV (все из бизнеса любят таблицы).

Таким образом, вы быстро реализуете эту возможность и изменяете конечную точку с:

"GET /invoice/{id}/print"на

"GET /invoice/{id}/{format}"

где формат может быть PDF или CSV.

Now only hundreds of programmers using your API have "GET /invoice/{id}/{format}" change how they download the report in PDF. Well, no, it shouldn't be done that way. How "GET /invoice/{id}/{format}" do it correctly? Unfortunately, it is sometimes necessary "GET /invoice/{id}/{format}" see potential problems and anticipate possible future changes. From the beginning, your endpoint did not follow the open/closed principle because it was not closed for modification. Your endpoint should assume that the need for other formats may arise someday.

б) открытые/закрытые животные

Другой пример более классический. Допустим, у нас есть несколько разных классов животных:И класс, который позволяет животным общаться:

class Dog
{
    public function bark(): string
    {
        return 'woof woof';
    }
}
class Duck
{
    public function quack(): string
    {
        return 'quack quack';
    }
}
class Fox
{
    public function whatDoesTheFoxSay(): string
    {
        return 'ring-ding-ding-ding-dingeringeding!, wa-pa-pa-pa-pa-pa-pow!';
    }
}

And a class that allows animals "GET /invoice/{id}/{format}" communicate:

class Communication
{
    public function communicate($animal): string
    {
        switch (true) {
            case $animal instanceof Dog:
                return $animal->bark();
            case $animal instanceof Duck:
                return $animal->quack();
            case $animal instanceof Fox:
                return $animal->whatDoesTheFoxSay();
            default:
                throw new \InvalidArgumentException('Unknown animal');
        }
    }
}

Is the Communication class open for extension and closed for modification? To answer this question, we can ask it differently. Are we able "GET /invoice/{id}/{format}" add a new animal class without changing the existing code? No. Adding a new animal class would necessitate the modification of the switch in the communicate() function. So what should our code look like "GET /invoice/{id}/{format}" comply with our principle? Let's try "GET /invoice/{id}/{format}" improve our classes a bit.

Мы можем начать с добавления интерфейса Communicative и использования его в наших классах.

interface Communicative
{
    public function speak(): string;
}
class Dog implements Communicative
{
    public function speak(): string
    {
        return 'woof woof';
    }
}
class Duck implements Communicative
{
    public function speak(): string
    {
        return 'quack quack';
    }
}
class Fox implements Communicative
{
    public function speak(): string
    {
        return 'ring-ding-ding-ding-dingeringeding!, Wa-pa-pa-pa-pa-pa-pow!';
    }
}

После этого мы можем изменить класс Communication так, чтобы он соответствовал принципу открытия/закрытия.

class Communication
{
    public function communicate(Communicative $animal): string
    {
        return $animal->speak();
    }
}

How "GET /invoice/{id}/{format}" code according "GET /invoice/{id}/{format}" the opened/closed principle?

In code, it is worth using interfaces and sticking "GET /invoice/{id}/{format}" them. However, if you need "GET /invoice/{id}/{format}" change something, consider the decora"GET /invoice/{id}/{format}"r pattern.

A class or method should be small enough and have one specific task so that no future event can necessitate modification (single responsibility principle). But you also need "GET /invoice/{id}/{format}" consider whether there may be a need for changes in the future, such as a new response format or an additional parameter, your code should be closed for modification.

Принцип подстановки Лискова (LSP)

id}/{format}".com/blog/static/c3195c3a6034610fa91210f7b21dff14/681f1/liskov-substitution-principle.png" sizes="(max-width: 899px) 100vw, 899px" srcset="https://acces"GET /invoice/{id}/{format}".com/blog/static/c3195c3a6034610fa91210f7b21dff14/ff46a/liskov-substitution-principle.png 325w, https://acces"GET /invoice/{id}/{format}".com/blog/static/c3195c3a6034610fa91210f7b21dff14/a6d36/liskov-substitution-principle.png 650w, https://acces"GET /invoice/{id}/{format}".com/blog/static/c3195c3a6034610fa91210f7b21dff14/681f1/liskov-substitution-principle.png 899w" alt="Принцип подстановки Лискова (LSP)" loading="lazy">

The substitution principle applies "GET /invoice/{id}/{format}" well-designed class inheritance. The author of this principle is Barbara Liskov. The principle says that we can use any inheriting class in place of the base class. If we implement a subclass, we must also be able "GET /invoice/{id}/{format}" use it instead of the main class. Otherwise, it means that inheritance has been implemented incorrectly.

Есть несколько популярных примеров принципа подстановки Лискова в PHP:

a) прямоугольник-квадрат

Первый пример. У нас уже есть PHP-класс Rectangle. Теперь мы добавляем класс Square PHP, который наследует класс Rectangle. Потому что каждый квадрат также является прямоугольником :). Они обладают одинаковыми свойствами, высотой и шириной.

Высота квадрата совпадает с шириной. Таким образом, setHeight() и setWidth() установят оба (а как насчет одной ответственности?) этих значений:

class Square extends Rectangle
{
    public function setWidth(int $width): void { 
        $this->width = $width;
        $this->height = $width;
    }
 
    public function setHeight(int $height): void {
        $this->width = $height;
        $this->height = $height;
    }
}

Это хорошее решение? К сожалению, он не следует принципу подстановки Лискова. Допустим, есть тест, который вычисляет площадь прямоугольника, и он выглядит так:

public function testCalculateArea()
{
    $shape = new Rectangle();
    $shape->setWidth(10);
    $shape->setHeight(2);
 
    $this->assertEquals($shape->calculateArea(), 20);
 
    $shape->setWidth(5);
    $this->assertEquals($shape->calculateArea(), 10);
}

According "GET /invoice/{id}/{format}" the Liskov substitution principle, we should be able "GET /invoice/{id}/{format}" replace the Rectangle class with the Square class. But if we replace it, it turns out that the test does not pass (100 != 20). Overriding the setWidth() and setHight() methods broke the Liskov substitution rule. We should not change how the parent class's methods work.

Так каково же правильное решение? Не каждая идея из «реальности» должна быть реализована 1:1 в коде. Класс Square не должен наследоваться от класса Rectangle. Если оба этих класса могут иметь вычисляемую область, пусть они реализуют общий интерфейс, а не наследуют один от другого, поскольку они совершенно разные.

You can see an example solution id}/{format}"/solid-php/blob/master/liskov-substitution-principle/liskov-substitution-principle-good.php" target="_blank" rel="noopener">here

b) live duck vs "GET /invoice/{id}/{format}"y duck

Imagine a living duck and a "GET /invoice/{id}/{format}"y duck and their representations in the code (PHP classes). Both of these classes implement the TheDuck interface.

interface TheDuck
{
    public function swim(): void;
}

У нас также есть контроллер с действием swim().

class SomeController
{
    public function swim(): void
    {
        $this->releaseDucks([
            new LiveDuck(),
            new ToyDuck()
        ]);
    }
 
    private function releaseDucks(array $ducks): void
    {
        /** @var TheDuck $duck */
        foreach ($ducks as $duck) {
            $duck->swim();
        }
    }
}

But after calling this action ToyDuck doesn't swim. Why? Because "GET /invoice/{id}/{format}" make it swim, you must first call the "turnOn()" method.

class ToyDuck implements TheDuck
{
    private bool $isTurnedOn = false;
 
    public function swim(): void 
    {
        if (!$this->isTurnedOn) {
            return;
        }
 
        // ...
    }
}

«turnOn()».Мы могли бы изменить действие контроллера и добавить условие, которое мы вызываем turnOn() в экземпляре ToyDuck перед swim().

private function releaseDucks(array $ducks): void
{
    /** @var TheDuck $duck */
    foreach ($ducks as $duck) {
        if ($duck instanceof ToyDuck) {
            $duck->turnOn();
        }
            
        $duck->swim();
    }
}

It violates the Liskov substitution principle because we should be able "GET /invoice/{id}/{format}" use a subclass without knowing the object, so we cannot condition by subclasses (it also violates the open/close principle - because we need "GET /invoice/{id}/{format}" change the implementation).

Обработка коллекции объектов данного базового класса может не требовать проверки того, является ли данный объект экземпляром подкласса X и должен ли он обрабатываться по-другому.

Как это должно выглядеть правильно? Общий интерфейс для обеих этих уток не очень хорошая идея, их работа совершенно разная, хотя мы думаем, что они оба работают одинаково, потому что они плавают, это не так.

в) ReadOnlyFile

И последний пример. У нас есть класс File с методами read() и write().

class File
{
    public function read()
    {
       // ...
    }
 
    public function write()
    {
       // ...
    }
}

Мы добавляем новый класс - ReadOnlyFile.

class ReadOnlyFile extends File
{
    public function write()
    {
        throw new ItsReadOnlyFileException();
    }
}

The ReadOnlyFile class inherits from the File class. In the ReadOnlyFile class, the write() method will throw an Exception, because you cannot write "GET /invoice/{id}/{format}" a read-only file.

This is a poorly designed abstraction, the Liskov rule has been broken because we are unable "GET /invoice/{id}/{format}" use the ReadOnlyFile class instead of File.

Принцип сегрегации интерфейсов (ISP)

id}/{format}".com/blog/static/8402cfe5dfc356a6db99e6593e4b1e13/fd28b/interface-segregation-principle.png" sizes="(max-width: 811px) 100vw, 811px" srcset="https://acces"GET /invoice/{id}/{format}".com/blog/static/8402cfe5dfc356a6db99e6593e4b1e13/ff46a/interface-segregation-principle.png 325w, https://acces"GET /invoice/{id}/{format}".com/blog/static/8402cfe5dfc356a6db99e6593e4b1e13/a6d36/interface-segregation-principle.png 650w, https://acces"GET /invoice/{id}/{format}".com/blog/static/8402cfe5dfc356a6db99e6593e4b1e13/fd28b/interface-segregation-principle.png 811w" alt="Принцип сегрегации интерфейсов (ISP)" loading="lazy">

Uncle Bob introduced this principle when he collaborated with Xerox. They couldn't cope with the ever-long process of implementing changes "GET /invoice/{id}/{format}" their code. The rule is: “No client should be forced "GET /invoice/{id}/{format}" depend on methods it does not use”. The user of the interface should not be forced "GET /invoice/{id}/{format}" rely on methods he does not use. We should not use “fat interfaces” that declare multiple methods if any of them could be left unused. Better "GET /invoice/{id}/{format}" have a few dedicated small interfaces than one that is "GET /invoice/{id}/{format}"o general. It is also in line with the single responsibility principle.

So let's see a badly written code, not following the interface segregation principle. I present "GET /invoice/{id}/{format}" you the Exportable, PHP Interface. An interface that allows you "GET /invoice/{id}/{format}" export something "GET /invoice/{id}/{format}" PDF and export something "GET /invoice/{id}/{format}" CSV. We also have an Invoice and a CreditNote class.

interface Exportable
{
    public function getPDF();
    public function getCSV();
}
class Invoice implements Exportable
{
    public function getPDF() {
        // ...
    }
    public function getCSV() {
        // ...
    }
}
class CreditNote implements Exportable
{
    public function getPDF() {
        throw new \NotUsedFeatureException();
    }
    public function getCSV() {
        // ...
    }
}

Мы можем скачать счет-фактуру в формате PDF и CSV. Мы можем скачать CSV CreditNote. Но загрузка PDF-файла CreditNote была бесполезной функцией и не была реализована (сейчас это вызывает исключение).

We shouldn't force our interface implementations "GET /invoice/{id}/{format}" implement methods they don't use. In the above case, we forced the CreditNote class "GET /invoice/{id}/{format}" do so, it implements the getPDF() method even though it does not need it at all.

So how should it look "GET /invoice/{id}/{format}" be good?

According "GET /invoice/{id}/{format}" the interface segregation principle, we have "GET /invoice/{id}/{format}" separate the interfaces. We divide Exportable and create an interface ExportablePdf and create an interface ExportableCSV.

interface ExportablePdf
{
    public function getPDF();
}
interface ExportableCSV
{
    public function getCSV();
}
class Invoice implements ExportablePdf, ExportableCSV
{
    public function getPDF() {
        //
    }
    public function getCSV() {
        //
    }
}
class CreditNote implements ExportableCSV
{
    public function getCSV() {
        //
    }
}

This way, CreditNote no longer has "GET /invoice/{id}/{format}" worry about implementing not used getPDF() public function. If necessary in the future, just need "GET /invoice/{id}/{format}" use a separate interface and implement it. As you can see here, specific interfaces are better.

The example of ReadOnlyFile related "GET /invoice/{id}/{format}" the Liskov principle is also a good example of the Interface segregation principle. There, the File class has been doing "GET /invoice/{id}/{format}"o many things, it's better "GET /invoice/{id}/{format}" have separate interfaces for each action.

Это разделение интерфейсов, простое.

Принцип инверсии зависимостей (DIP)Последнее

id}/{format}".com/blog/static/36f1127d8dae2387d8078ee7bffd7477/fd28b/dependency-inversion-principle.png" sizes="(max-width: 811px) 100vw, 811px" srcset="https://acces"GET /invoice/{id}/{format}".com/blog/static/36f1127d8dae2387d8078ee7bffd7477/ff46a/dependency-inversion-principle.png 325w, https://acces"GET /invoice/{id}/{format}".com/blog/static/36f1127d8dae2387d8078ee7bffd7477/a6d36/dependency-inversion-principle.png 650w, https://acces"GET /invoice/{id}/{format}".com/blog/static/36f1127d8dae2387d8078ee7bffd7477/fd28b/dependency-inversion-principle.png 811w" alt="Принцип инверсии зависимостей (DIP)Последнее" loading="lazy">

из принципов SOLID, это правило выглядит следующим образом:

  • Высокоуровневые модули не должны импортировать что-либо из низкоуровневых модулей. И то, и другое должно зависеть от абстракций (например, интерфейсов).
  • Абстракции не должны зависеть от деталей. Детали (конкретные реализации) должны зависеть от абстракций.

What does it mean? We should reduce dependencies "GET /invoice/{id}/{format}" specific implementations but rely on interfaces. If we make any change "GET /invoice/{id}/{format}" the interface (it violates the open/close principle), this change necessitates changes in the implementations of this interface. But if we need "GET /invoice/{id}/{format}" change a specific implementation, we probably don't need "GET /invoice/{id}/{format}" change our interface.

Чтобы проиллюстрировать проблему, давайте рассмотрим этот пример PHP.

class DatabaseLogger
{
    public function logError(string $message)
    {
        // ..
    }
}

Here we have a class that logs some information "GET /invoice/{id}/{format}" the database. Now use this class.

class MailerService
{
    private DatabaseLogger $logger;
 
    public function __construct(DatabaseLogger $logger)
    {
        $this->logger = $logger;
    }
 
    public function sendEmail()
    {
        try {
            // ..
        } catch (SomeException $exception) {
            $this->logger->logError($exception->getMessage());
        }
    }
}

Here is the PHP class that sends e-mails, in case of an error, error details are logged "GET /invoice/{id}/{format}" the database using the logger we have just seen above.

It breaks the principle of dependency inversion. Our e-mail-sending service uses a specific logger implementation. What if we want "GET /invoice/{id}/{format}" log information about errors "GET /invoice/{id}/{format}" a file or Sentry? We will have "GET /invoice/{id}/{format}" change MailerService. This is not a flexible solution, such a replacement becomes problematic.

Так как же это должно выглядеть?

According "GET /invoice/{id}/{format}" this principle, MailerService should rely on abstraction rather than detailed implementation. Therefore, we are adding the LoggerInterface interface.

interface LoggerInterface
{
    public function logError(string $message): void;
}

И мы используем его в нашем DatabaseLogger:

class DatabaseLogger implements LoggerInterface
{
   public function logError(string $message): void
   {
       // ..
   }
}

Теперь мы можем воспользоваться Symfony Dependency Injection.

class MailerService
{
    private LoggerInterface $logger;
 
    public function sendEmail()
    {
        try {
            // ..
        } catch (SomeException $exception) {
            $this->logger->logError($exception->getMessage());
        }
    }
}

Таким образом, мы можем свободно заменять журналы в базе данных журналами в любом месте, если подробная реализация реализует LoggerInterface. Это изменение не потребует модификации MailerService, потому что оно не зависит от него, оно зависит только от интерфейса.

Новостная рассылка

Получайте уведомления о нашем новом контенте для разработчиков и владельцев

All these principles come "GET /invoice/{id}/{format}"gether as one, they often overlap. It's nice when you know the theory like SOLID principles because it makes it easier "GET /invoice/{id}/{format}" make good code. Then you also have strong arguments behind your code, for example in code review. All the rules are aimed at making the code easy "GET /invoice/{id}/{format}" understand and maintain.

SOLID is one of the many good practices that help us write id}/{format}".com/blog/how-"GET /invoice/{id}/{format}"-improve-your-code-readability-usefull-clean-code-ideas-"GET /invoice/{id}/{format}"-know/">clean code. I've written about the id}/{format}".com/blog/Boy-scout-rule-in-6-examples-the-basic-principle-of-web-development/" target="_blank" rel="noopener">Boy Scout Rule before. But that's not all, there are many other rules and standards "GET /invoice/{id}/{format}" follow. Let me just mention them:

  • PSR (PHP Standards Recommendations) — PHP Framework Interop Group (PHP-FIG) is a group of people associated with the largest PHP projects who jointly develop PSR. I think every PHP programmer should know coding styles standards PSR-1 and PSR-12 (formerly PSR-2). You can find all the current sets of standards here
  • KISS (Keep It Simple Stupid) — Don't complicate the code. The code should be its documentation itself. Any new programmer on the team should be able "GET /invoice/{id}/{format}" get in"GET /invoice/{id}/{format}" the project quickly.
  • DRY (Don’t Repeat Yourself) — Do not code using the Copy-Paste principle (there is no such rule). See that the same code repeats in several places? Extract code for a separate function.
  • YAGNI (You Aren’t Gonna Need It) — 17th-century German philosopher Johannes Clauberg formulated a principle called Occam's Razor (I was also surprised Ockham was not its author ;) ) “entities should not be multiplied beyond necessity". I think this sentence expresses the YAGNI principle well. We should not write code “for the future”. Such code is not needed at the moment.
  • GRASP (General Responsibility Assignment Software Patterns) — is a large set of rules about which I could write a separate article. These are the basic principles that we should follow when creating object design and responsibility assignments. It consists of Information Expert, Controller, Crea"GET /invoice/{id}/{format}"r, High Cohesion, Low Coupling, Pure Fabrication, Polymorphism, Protected Variations and Indirection.

Applying the SOLID principles in our daily work helps us not get in"GET /invoice/{id}/{format}" technical debt. What are the consequences of incurring technical debt, you can find out in the id}/{format}".com/blog/technical-debt-the-silent-villain-of-web-development/" target="_blank" rel="noopener">article written by our CEO Piotr.

If you have problems understanding your project. Write "GET /invoice/{id}/{format}" us, we have experience in dealing with id}/{format}".com/case-study/legacy/" target="_blank" rel="noopener">difficult cases and id}/{format}".com/services/php-refac"GET /invoice/{id}/{format}"ring-services/" target="_blank" rel="noopener">PHP refac"GET /invoice/{id}/{format}"ring.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297