diff --git a/manuscript/2-di.md b/manuscript/2-di.md index 6adc0d6..5f0b0f8 100644 --- a/manuscript/2-di.md +++ b/manuscript/2-di.md @@ -3,7 +3,7 @@ ## Принцип Единственной Ответственности Вы, возможно, слышали о Принципе Единственной Ответственности (Single Responsibility Principle, SRP). -Одно из его определений: Каждый модуль, класс или функция должны иметь ответсвенность над единой частью функционала. +Одно из его определений: Каждый модуль, класс или функция должны иметь ответственность над единой частью функционала. Много разработчиков упрощают его до "класс должно делать что-то одно", но это определение не самое удачное. Роберт Мартин предложил другое определение, где заменил слово "ответственность" на "причину для изменения": "Класс должен иметь лишь одну причину для изменения". "Причина для изменения" более удобный термин и мы можем начать рассуждать об архитектуре используя его. @@ -1635,7 +1635,7 @@ class WantsToDispatchJobs(private val dispatcher: Dispatcher) } ``` -Синтаксис PHP является некоторым барьером для использования DI, я надеюсь скоро это будет нивелировано либо изменениями в синтаксисе, либо иструментами в средах разработки. +Синтаксис PHP является некоторым барьером для использования DI, я надеюсь скоро это будет нивелировано либо изменениями в синтаксисе, либо инструментами в средах разработки. После нескольких лет использования и неиспользования трейтов я могу сказать, что разработчики используют трейты по двум причинам: diff --git a/manuscript/4-application-layer.md b/manuscript/4-application-layer.md index 174c9f0..777915d 100644 --- a/manuscript/4-application-layer.md +++ b/manuscript/4-application-layer.md @@ -468,7 +468,7 @@ final class PublishPostCommand } } ``` -В шаблоне **Command Bus** суффикс**Command** используется для DTO-классов, а классы испольняющие команды называются **CommandHandler**. +В шаблоне **Command Bus** суффикс**Command** используется для DTO-классов, а классы исполняющие команды называются **CommandHandler**. ```php final class ChangeUserPasswordCommand diff --git a/manuscript/5-error-handling.md b/manuscript/5-error-handling.md index 7aa7eea..5735f97 100644 --- a/manuscript/5-error-handling.md +++ b/manuscript/5-error-handling.md @@ -362,7 +362,7 @@ class Handler extends ExceptionHandler { if ($this->shouldReport($e)) { - // Это отличнео место для + // Это отличное место для // интеграции сторонних сервисов // для мониторинга ошибок } @@ -459,7 +459,7 @@ public class Foo } } ``` -Этот код даже не скопилируется. +Этот код даже не скомпилируется. Сообщение компилятора: "Error:(5, 9) java: unreported exception java.lang.Exception; must be caught or declared to be thrown" Есть два способа исправить это. Поймать его: @@ -508,7 +508,7 @@ public class FooCaller } ``` Разумеется, работать так с **каждым** исключение будет той еще пыткой. -В Java есть **проверяемые** исключения, которые обязаны быть пойманы или обьявлены в сигнатуре, и **непроверяемые**, которые могут быть выброшены без всяких дополнительных условий. +В Java есть **проверяемые** исключения, которые обязаны быть пойманы или объявлены в сигнатуре, и **непроверяемые**, которые могут быть выброшены без всяких дополнительных условий. Взглянем на корень дерева классов исключений в Java (PHP, начиная с седьмой версии, имеет абсолютно такое же): ``` @@ -531,7 +531,7 @@ public class File ``` Что сигнатура метода **getCanonicalPath** говорит разработчику? Там нет никаких параметров, возвращает строку, может выбросить исключение **IOException** а также любое непроверяемое исключение. -Возвращаесь к двум типам ошибок: +Возвращаясь к двум типам ошибок: 1. Ошибки, которые понятны вызывающему коду и могут быть эффективно обработаны там 2. Другие ошибки @@ -542,7 +542,7 @@ public class File Все это приводит к более корректной обработке ошибок. Хорошо, в Java это есть, в PHP - нет. -Почему я все ещё об этом гооврю? +Почему я все ещё об этом говорю? IDE, которое я использую, PhpStorm, имитирует поведения Java. ```php @@ -555,7 +555,7 @@ class Foo } ``` PhpStorm подсветит 'throw new Exception();' с предупреждением: 'Unhandled Exception'. -И есть два пути исбавиться от этого: +И есть два пути избавиться от этого: 1. Поймать исключение 2. Описать его в тэге @throws phpDoc-комментария метода: @@ -573,7 +573,7 @@ class Foo } ``` -Список непроверямых классов конфигурируется. +Список непроверяемых классов конфигурируется. По умолчанию он выглядит так: **\Error**, **\RuntimeException** and **\LogicException**. Их можно выбрасывать не опасаясь предупреждений. @@ -620,13 +620,13 @@ final class UserController { $service->changePassword($request->getDto()); - // возаращем успешный ответ + // возвращаем успешный ответ } } ``` Не очень удобно. Даже если учесть, что PhpStorm умеет генерировать все эти тэги автоматически. -Возвращаясь к нашему неидеальнмоу миру: Класс **ModelNotFoundException** в Laravel уже отнаследован от **\RuntimeException**. -Соответственно, он непроверямый по умолчанию. +Возвращаясь к нашему неидеальному миру: Класс **ModelNotFoundException** в Laravel уже отнаследован от **\RuntimeException**. +Соответственно, он непроверяемый по умолчанию. Это имеет смысл, поскольку глубоко внутри собственного обработчика ошибок Laravel обрабатывает эти исключения сам. Поэтому, в нашем текущем положении, стоит тоже пойти на такую сделку с совестью: @@ -642,7 +642,7 @@ class BusinessException extends \RuntimeException "throws Exception" вообще не дает никакой полезной информации. Это просто заставляет клиентский код повторять этот бесполезный "throws Exception" в своей сигнатуре. -Я вернусь к исключениям в главе про Доменный слой, когда этот подход непроверямыми исключениями станет не очень удобным. +Я вернусь к исключениям в главе про Доменный слой, когда этот подход непроверяемыми исключениями станет не очень удобным. ## Пара слов в конце главы diff --git a/manuscript/7-events.md b/manuscript/7-events.md index 882dff2..9d800db 100644 --- a/manuscript/7-events.md +++ b/manuscript/7-events.md @@ -47,7 +47,7 @@ final class PollService Объект опроса непростой. Он абсолютно бесполезен без опций ответа. Мы должны позаботиться о его консистентности. -Этот маленький промежутор времени, когда мы уже создали объект опроса и добавили его в базу данных, но еще не создали его опции ответа, очень опасен! +Этот маленький промежуток времени, когда мы уже создали объект опроса и добавили его в базу данных, но еще не создали его опции ответа, очень опасен! ## Database transactions @@ -270,7 +270,7 @@ final class EventServiceProvider extends ServiceProvider Теперь Слой Приложения просто сообщает, что был создан новый опрос (событие **PollCreated**). Приложение имеет конфигурацию для реагирования на события. Классы слушателей событий (**Listener**) содержат реакцию на события. -Интерфейс **ShouldQueue** работает точно также, сообщая о том, когда должно быть запущено выполнение этого слушателя: сразу же или в отложенно. +Интерфейс **ShouldQueue** работает точно также, сообщая о том, когда должно быть запущено выполнение этого слушателя: сразу же или в отложено. События - очень мощная вещь, но здесь тоже есть ловушки. ## Использование событий Eloquent @@ -345,7 +345,7 @@ public function create(PollCreateDto $request) А самое страшное, что событие вроде **PollCreated**, может быть вызвано, но дальше в транзакции будет ошибка и вся она будет откачена назад. Письмо же пользователю об опросе, который даже не создался, все равно будет отправлено. Я нашел пару пакетов для Laravel, которые ловят все эти события, хранят их временно, и выполняют только после того, как транзакция будет успешно завершена (гуглите "Laravel transactional events"). -Да, они решают множество из этих проблем, но всё это выглядит так нестественно! +Да, они решают множество из этих проблем, но всё это выглядит так неестественно! Простая идея генерировать нормальное бизнес-событие **PollCreated** после успешной транзакции намного лучше. ## Сущности как поля классов-событий diff --git a/manuscript/8-unit-test.md b/manuscript/8-unit-test.md index ef2f586..7a775b6 100644 --- a/manuscript/8-unit-test.md +++ b/manuscript/8-unit-test.md @@ -371,7 +371,7 @@ class OrderService Но если мы взглянем на класс **OrderService**, то увидим, что **TaxCalculator** не выглядит его зависимостью. Он не выглядит как что-то внешнее, нужное **OrderService** для работы. -Оне выглядит как часть класса **OrderService**. +Он выглядит как часть класса **OrderService**. ![](images/unit.png) @@ -402,7 +402,7 @@ Unit-тесты могут тестировать класс **OrderService** н Модуль - весьма широкое понятие. В начале этой статьи модулем была маленькая функция, а иногда в модуле может содержаться несколько классов. Программные объекты внутри модуля должны быть сфокусированы на одной ответственности, другими словами, иметь сильную связность. -Когда методы класса полностью независмы друг от друга, класс не является модулем. +Когда методы класса полностью независимы друг от друга, класс не является модулем. Каждый метод класса - это модуль в данном случае. Возможно, стоит вынести эти методы в отдельные классы, чтобы разработчики не просматривали кучу лишнего кода каждый раз? Помните я говорил, что часто предпочитаю классы одной команды, такие как **PublishPostCommand**, а не **PostService** классы? @@ -412,7 +412,7 @@ Unit-тесты могут тестировать класс **OrderService** н Обычно зависимость - это интерфейс, который имеет несколько реализаций. Использование реальных реализаций этого интерфейса во время unit-тестирования - плохая идея, поскольку там могут проводиться те самые операции ввода-вывода, замедляющие тестирование и не дающие провести тестирование этого модуля в изоляции. -Прогон unit-тестов должден быть быстр как молния, поскольку запускаться они будут часто и важно, чтобы разработчик запустив их не потерял фокус над кодом. +Прогон unit-тестов должен быть быстр как молния, поскольку запускаться они будут часто и важно, чтобы разработчик запустив их не потерял фокус над кодом. Написал код - прогнал тесты, еще написал код - прогнал тесты. Быстрые тесты позволят ему оставаться более продуктивным, не позволяя отвлекаться. Решение в лоб задачи изоляции класса от зависимостей - создание отдельной реализации этого интерфейса, предназначенного просто для тестирования. @@ -626,7 +626,7 @@ class PostsTest extends TestCase Просто добавив трейт **SoftDeletes** в класс **Post**, мы уроним этот тест. Однако, приложение работает абсолютно также, выполняет те же самые требования и пользователи этой разницы не заметят. Функциональные тесты не должны падать в таких условиях. -Тест, который делает запрос в приожение, а потом лезет в базу данных проверять результат, не является настоящий функциональным тестом. +Тест, который делает запрос в приложение, а потом лезет в базу данных проверять результат, не является настоящий функциональным тестом. Он знает слишком многое про то, как работает приложение, как оно хранит данные и какие таблицы для этого использует. Это еще один пример тестирования методом белого ящика. @@ -661,7 +661,7 @@ class PostsTest extends TestCase public function testDelete() { - // Здесь некоторая иницизация, чтобы создать + // Здесь некоторая инициализация, чтобы создать // объект Post с id = $postId // Удостоверяемся, что этот объект есть @@ -679,7 +679,7 @@ class PostsTest extends TestCase } ``` -Этому тесту абсолютно все равно как удлаен объект, с помощью 'delete' SQL запроса или с помощью Soft delete шаблона. +Этому тесту абсолютно все равно как удален объект, с помощью 'delete' SQL запроса или с помощью Soft delete шаблона. Функциональный тест проверяет поведение приложения в целом. Ожидаемое поведение, если объект удален - он не возвращается по своему id и тест проверяет именно это. @@ -1088,7 +1088,7 @@ class PollServiceTest extends \PHPUnit\Framework\TestCase Время от времени, менеджер или разработчики открывают приложение, выполняют в нём некоторые действия, проверяя корректность его работы и насколько красив его интерфейс. Это неавтоматическое тестирование без какой-либо стратегии. -Дальше (обычно после каких-нибудь болезенных ошибок на продакшене) команда решает что-то изменить. +Дальше (обычно после каких-нибудь болезненных ошибок на продакшене) команда решает что-то изменить. Первое, очевидное, решение - нанять ручного тестировщика. Он может описать главные сценарии работы с приложением и после каждого обновления проходить по этим сценариям, проверяя, что приложение работает как требуется (это называется регрессионное тестирование).