Comments 9
Абсолютно согласен с автором, по-поводу важности статического анализа. Для себя могу выделить две сложности:
Наличие false positives;
Огромное количество сообщений об ошибках;
Отсутствие состояние прогресса (не понятно когда завершится проверка)
В качестве решения для JavaScript использую:
ESLint настроенный исправлять, все что он находит;
?Putout, который сообщает только о том, что может исправить сам;
Конечно, это не спасёт от ошибки вроде ParadoxicalCondition, описанной в статье, но с этим не плохо справляются тесты + coverage.
Конкретно с Psalm у нас довольно мало false-positives, а огромное количество ошибок прячется в baseline, который со временем постепенно худеет. Ведь при модификации кода практически невозможна ситуация, когда новое выражение точно попадёт в baseline. Это связано с форматом: Psalm фиксирует не только количество ошибок определённого типа в файле, но и конкретное выражение, которое их вызывает.
А вот с JS у нас пока ещё не всё хорошо, ESLint в принципе настроен, но не форсируется. Зато новый код всё больше пишется на Typescript, который сам очень помогает с контролем типов.
Очередной пруф, что лучше пыхи ничего и нет.
Сам подключал psalm на прошлом проекте. Было интересно попробовать. Но в итоге впечатление сложилось двоякое. С одной стороны круто иметь статическую типизацию на php. С другой сама экосистема к этому была не слишком приспособлена. Те же symfony / doctrine тогда не имели нужных аннотаций. Да и вообще куча кода была написана с ориентацией на динамическую природу php.
Я причем эмпирически выставил строгость проверки что-то около 3. И в основном приходилось бороться с "вопросиками". Т.е. избавляться от nullable. И я до сих пор не до конца понимаю, как это нужно правильно делать, например, для сущностей доктрины. Т.е. все не nullable свойства с точки зрения типов нужно инициализировать в конструкторе. Но это сильно выбивается из общей практики. То же самое с автоинкрементным id. При создании новой сущности он должен быть null. Но потом этот вопросик сильно мешается в коде. Приходится добавлять во все сущности getInitializedId() :int с ассертом внутри. И так много в чем. Когда с точки зрения контекста использования ты знаешь лучше анализатора, что за реальный тип у этого значения.
Я на одном небольшом проекте для себя успешно подружил Psalm с сущностями доктрины. Исходил я примерно из таких принципов:
Если какое-то поле у сущности не может быть null
, то оно должно быть проинициализировано в конструкторе. То есть логически, если у нас User
не может быть без почты и пароля, тогда конструктор будет их принимать.
Я подумал, что для $id
использовал @psalm-suppress PropertyNotSetInConstructor
, но заглянул в код и, оказывается, нет. $id
честно прописан ?int
, и даже это где-то используется для того, чтобы отличать уже записанные в БД сущности от только что сгенерированных. А там, где я точно уверен, что он не null
, писал (int)$id
.
Вероятно, это можно всё как-то сделать на магии шаблонов, чтобы $em->find()
возвращал специально шаблонизированную Entity
, у которой $id
не может быть null
, но быстро набросать у меня не получилось.
Ну и в любом случае, это немного костыльно решается плагином.
Может и можно через дженерики возвращать сущность из entityManager с не nullable id. Но при инжекте в контроллер с помощью ArgumentResolver уже придется прописывать дженерик вручную. Да и потом везде его по типам пропихивать. Так что трейт с getInitializedId с ассертом внутри кажется меньшей проблемой.
А ведь кроме id у сущности может быть куча автозаполняемых полей. CreatedAt updatedAt, createdBy… Часто они в doctrine event subscriber автоматом заполняются. Но тогда их тоже нужно делать nullable.
Я на одном небольшом проекте для себя успешно подружил Psalm с сущностями доктрины.
На моем текущем проекте у сущности может быть несколько десятков полей. Ну, пусть даже 10 из них не nullable. Как-то сомнительно их все через конструктор инициализировать. Да и формы с таким по дефолту не очень дружат. Не знаю насчет апи платформы и прочих библиотек..
На моем текущем проекте у сущности может быть несколько десятков полей. Ну, пусть даже 10 из них не nullable. Как-то сомнительно их все через конструктор инициализировать.
Вот тут как та ситуация, когда код, который никак не хочет укладыватся в правила статического анализа, имеет некоторые архитектурные проблемы. С точки зрения идеальной архитектуры объект с не-nullable свойствами не может существовать, если у них нет значений. Тут просится что-то промежуточное типа builder'а с валидатором внутри. Но это в идеальном мире, конечно... У нас, кстати, часть таких проблем с пользовательским вводом решена с помощью spatie/data-transfer-object, но это довольно специфичное решение.
Но с точки зрения реального, а не идеального кода всё совсем не так :))) Но осознавать неидеальность, конечно, тоже небесползено.
Мы на своём проекте тоже внедрили psalm около года назад. Как боролись с большим количеством ошибок - заигнорили в конфиге псалма все файлы проекта и по одному снимали из игнора, правили, вынимали из игнора следующий файл и т.д. Так постепенно вычистили все замечания псалма. На сегодня псалм выполняется на travis CI, автоматически проверяя запушеный код. Сейчас выставлен level 4, нам хватает. Какой уровень выставлен у вас?
Статический анализ и уже выросший проект: внедрять нельзя откладывать