Pull to refresh
1
0
Roman Levchenko @LarexSetch

Backend разработчик

Send message

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

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

Во первых, этот принцип не подходит, из определения принципа следует что он применяется к ООП.

Во вторых, у структурного программирования есть принцип "Каждую логически законченную группу инструкций следует оформить как блок" (источник)

Но если бы мы работали в парадигме ООП то мы бы шли другим путем:

  1. Сразу обозначим что у нас не только создание поста пользователя а есть некая систем (в противном случае ООП здесь не имеет никакого смысла)

  2. Выделим операцию UpdatePost это интерфейс который будет заниматься изменением поста на основе данных в data transfer object (dto) UpdateRequest

interface UpdateRequest {
  invoke(request: UpdateRequest)
}

data class UpdateRequest(
	private val title,
  private val authorId,
  private val body
)
  1. Реализуем основную логику создания поста

class CommonUpdatePost(
	private val db
) : UpdateRequest {
	fun invoke(request: UpdateRequest) {
    db.execute("...", ...): // тут сам запрос и параметры запроса
  }
}
  1. Реализуем класс отвечающий за ограничение прав доступа, стоит отметить что получение текущего пользователя лучше вынести в отдельный сервис, т.к высока вероятность что это потребуется в нескольких операциях

class AccessCheckerDecorator(
	private val currentUserProvider, 
  private val inner
) : UpdateRequest {
	fun invoke(request: UpdateRequest) {
    if(currentUserProvider.provide().id != request.authorId) {
      throw new AccessDeniedException("You cannot change post author");
    }
    inner.invoke();
  }
} 
  1. Реализуем класс который отвечает за определенные бизнес правила

class BusinessRuleCheckerDecorator(
	private val db,
  private val inner
) : UpdateRequest {
	fun invoke(request: UpdateRequest) {
    if(isPostExists(request.id)) {
      throw new NotFoundException("Post not found with id ${request.id}");
    }
    inner.invoke();
  }
  
  private fun isPostExists(Int postId): Boolean {
    // здесь собственно запрос
  }
} 
  1. Давайте разберемся с валидацией. Первое что нужно сделать понять как мы будем валидировать, например для этого есть инструменты позволящие навешать аннотации и отправить некий сервис, тем самым вообще упростив нашу логику и делать это в контроллере, но можно и тут добавить еще один декоратор. по аналогии с предыдущими проверками

  2. Рендеринг. В моем примере нет возвращаемого результата, поэтому в принципе мы можем и не возвращать его а использовать запрос т.к все данные будут идентичны, и рендеринг это ответственность контроллера.

  3. По идеи сам контроллер будет сведен к набору действий:

    1. Вытащить данные из источника дыных (будь то stdin или некие объекты хранящие данные HTTP запроса)

    2. Десериализация строки в dto объект

    3. Валидация dto объекта

    4. Вызов операции

    5. Рендеринг (или сериализация dto в некую структуру)

Что это разбиение дает?

  1. Мы изолировали ответственности и теперь расширение/изменение отдельных кусков операции мы можем делать независимо, тем самым минимизировав риски конфликтов в репозитории (например изменение логики проверки доступов)

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

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

  4. Меньше состояний и их мутации это дает нам лучше контроллировать программу

  5. Очень много файлов между которыми нужно переключаться

  6. Изменения структуры dto может привести к проблеме, связанной с необходимостью вносить изменения во можестве файлов, при этом расширение стурктуры не приводит к этой проблеме.

  7. Требует знания и навыков применения не только SRP но и других принципов, а так же знания паттернов ООП.

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

В заключении хочу сказать, SRP действительно иногда бессмысленен, но это зависит от объема проекта и задачи, в некоторых случаях действительно он не нужен да даже и ООП, но к сожалению не в том контексте в котором вы продемонстрировали (обновление некого поста, что как бы намикает на то что уже у нас есть создание/удаление/получение списка или данных по id а так же работа с авторами постов, т.е пользователями).

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

Читая пост у меня возник вопрос:

Почему автор использует структурное программирование и рассуждает на тему SRP из набора принципов SOLID?

В то время как SOLID это набор принципов предназначенных для объектно ориентированного программирования.

Как мне кажется автор перепутал парадигмы программирования и принципы разбиения программ. Ведь в структурном программировании нет принципов SOLID, но есть другие принципы посторения программ.

Не все проблемы можно решить redis memcached, что если у тебя база из 10 мин пользователей и информация о каждом занимает 10Кб. Что бы этот объем положить в память потребуется ~100Gb оперативной памяти.

Собственно нужно отталкиваться ситуации в проекте. Может для пользователя достаточно хранить и 1кб данных, а это уже 10Gb, а значит стратегия масштабирования будет другой.

Поэтому в некоторых кейсах использование бд как основной источник данных это вполне логичный подход.

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

В описание личного опыта, например. Описать свой контекст, причины выбора того или иного инструмента, описать ситуацию и доступные ресурсы.

А вот то что творится в отрасли, гораздо сложнее понять и разобраться.

Когда читал, думал что будет развита идея, об осознанном применении инструментов разработки включая версии языка...

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

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

К сожалению PHP не имеет нормального инструмента для реализации перечислений и приходилось выкручиваться, а использовать в таком виде
  public function someFunc(TheEnum $enum): void {}

куда безопасней, наглядней и понятней, чем в таком

  public function someFunc(string $enum): void {}


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

Правда мы не учитывали кейсы где мы используем serialize или unserialize т.к. мы используем JMS сериалайзер и сериализуем в JSON, с кастомными handler-ами.

Действительно, хотелось бы детальней подобную тему раскрыть. Желательно без бонального "закладывает я при разработке", т.к спустя время технические компоненты устаревают, и с течением времени копится техдолг, если его не решать.

Ну так бейте большие куски на маленькие, тестируемые unit-ы, а уж потом собирайте из них большие куски, для которых тоже сначала пишите тесты. Single Responsibility Principle очень удобная штука.

Как раз про это и сказал в последнем предложении.
Полностью согласен. Хотел бы добавить, что не малый фактор влияет на возможность тестирования декомпозиция на модули, особенно для unit тестирования. Для больших кусков с огромным числом исходов и вариаций поведения весьма сложно написать unit-тест, а уж тем более поддерживать его. Это приводит к увеличению цены unit-тестов.
Для решения подобной проблемы стоит активно использовать паттерны и принципы.

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Date of birth
Registered
Activity