Как стать автором
Обновить
22
0
Алексей @pankraty

Разработчик

Отправить сообщение
Если приложение Test Runner будет умирать на первом assert-e — программисты будут вынуждены починить этот ассерт для запуска остальных тестов. Нет проблемы.


Это если сразу понятно, какой из тестов обрушил остальные. Но когда внезапно падает 150 тестов, отыскивать тот единственный, из-за которого не работают все — удовольствие сомнительное. Оказывался в подобной ситуации, когда один из тестов начал выдавать StackOverflowException, чем обрушивал TestRunner.
Хм. Вот, допустим, процесс 1 хочет открыть файл file.txt, но в папке есть только FILE.txt. В соответствии с вашим алгоритмом, процесс открывает этот файл и работает с ним. В это время процесс 2 создает рядом файл File.txt. После этого процесс 1 проверяет наличие файла с именем file.txt — и не находит его! «Как же так?» — думает он, — «Я ж держу дескриптор открытым, а файл исчез!» И от отчаяния накладывает на себя руки вываливает ошибку.

Спасибо за статью.
У нас используется AppVeyor — бесплатен для OpenSource. Довольно гибко настраивается, есть веб-морда для настройки. С другими системами не сравнивал, поэтому, к сожалению, плюсы-минусы обозначить не могу.

Английское «please» в ответ на благодарность можно перевести как «ах, оставьте», «не стоит».
По Tree(2), мне кажется, у вас тоже неверно. Должно быть синий-синий, красный-красный, красный-синий. А то если деревья можно упорядочивать не по возрастанию числа вершин, последовательность легко можно сделать бесконечной.
Они своим словоблудием продвигают в массы только нужные им идеи, не заботясь ни о теории, ни о доказательствах

Полностью согласен! Взять хотя бы
В идеале должно быть всего 2 низкоуровневых языка и 3-4 высокоуровневых, построенных на их основе.

Например, для PersonService обязательно зависимостью может быть IPersonRepository, а для PersonRepository — IDbContext.


Для некоторой сущности необязательной зависимостью может быть Style, а при отсутствии значения будет использоваться Container.Style.

А впрочем, можно куда проще, и так, чтобы работало не только для целых:

return a + Math.Log10(0.1) * b;


Но так даже скучно )
int sub(int a, int b)
{
    if (a == b) return 0;
    if (b == a + 1) return 1;
    if (a > b) return (int)Math.Log10(0.1d) * sub(b, a);
    int sum = 0;
    if (a % 2 == 1 ^ b % 2 == 1) sum = 1;
    sum += 2 * sub(a, (a + b) / 2);
    return sum;
}


Варианты с распарсиванием строки в число с указанием номера символа "-" я счел чересчур читерскими )
Вариант попроще:

int sub(int a, int b)
{
    if (a == b) return 0;
    if (a > b) return sub(b, a);
    var i = 0;
    while (a < b)
    {
        i++;
        a++;
    }
    return i;
}


Вариант посложнее:
int sub(int a, int b)
{
  if (a == b) return 0;
  if (b == a + 1) return 1;
  if (a > b) return sub(b, a);
  int sum = 0;
  if (a%2 == 1 ^ b%2 == 1) sum = 1;
  sum += 2 * sub(a, (a +b) / 2);
  return sum;
}


Правда, оба варианта возвращают разность по модулю… Пойду подумаю еще.

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

Спасибо, и правда интересная статья. Про абстракцию "бесконечный итератор timestamp-ов" я лично никогда не задумывался.

нужно было бы писать

var a = new NowService.GetNowTime();
var b = new AddDaysService().Calculate(a, 1);
b = new AddMinutesService().Calculate(a, 1);

Не-не-не! Никаких new NowService! Только DI, только хардкор!


public interface IJunkService
{
    DateTime DoJunkAction();
}

public class JunkService : IJunkService
{
    private readonly ITimeProviderService _timeProviderService;
    private readonly ITimeManipulationService _timeManipulationService;

    public JunkService(ITimeProviderService timeProviderService,
                       ITimeManipulationService timeManipulationService)
    {
      _timeProviderService = timeProviderService;
      _timeManipulationService = timeManipulationService;
    }

    public DateTime DoJunkAction()
    {
      var now = _timeProviderService.GetNowTime();
      var b = _timeManipulationService.AddDays(now, b);
      b = _timeManipulationService.AddMinutes(now, b);
      return b;
    }
}

Поначалу, возможно, выглядит абсурдно. Но, если подумать, так можно а) безболезненно обойти проблемы с рассинхронизацией времени на разных серверах / в разных часовых поясах (когда ХЗ что на самом деле вернет DateTime.Now) и б) легко и непринужденно протестировать логику, завязанную на события в будущем или прошлом. ITimeManipulationService, скорее всего, перебор, но если речь будет идти не про время, а про какие-то сущности, которые по-разному могут обрабатываться в разных странах/регионах/компаниях — такой подход оказывается довольно удобным (хотя, конечно, со своими издержками).

а это logical cohesion и он является плохой практикой. Модули группируются по функционалу а не по подобию.

С этим согласен, но я имел в виду не столько группировку в одном модуле, сколько выстраивание картины мира в голове разработчика. Если он знает, как в общем устроены сущности определенной категории в его проекте (те же репозитории, к примеру), то ему не нужно помнить каждую из них в лицо, чтобы представлять, для чего, что и как она делает. С «толстыми» сущностями такая унификация труднодостижима, у каждой своя индивидуальная специфика.
Разве это не очевидно?

Нет, не очевидно. Если классов будет 5 вместо 25, но функциональность будет той же, удержать их в голове будет ничуть не легче, т.к. единицей внимания все-таки является не класс сам по себе, а единица функциональности.
Кроме того, я не привязывался бы к конкретным цифрам 5-9, поскольку «удерживать внимание на N сущностей одновременно» это не то же самое, что «помнить и понимать устройство N сущностей» (второе, на мой взгляд, имеет куда большее значение в практической деятельности).
В случае большого количества мелких сущностей помнить и понимать их устройство, как ни удивильно, проще, т.к. они естественным образом группируются по подобию. Например, «это валидаторы, по одному на каждый тип сущности, они делают то-то и то-то», «а это репозитории, они устроены примерно так-то и так-то, вызываются обычно из таких-то мест», «вот эта пачка классов соответствует различным стратегиям, используются там-то, выбор стратегии зависит от того-то» и т.п. В случае больших классов с многочисленными методами и внутренними взаимосвязями когнитивная нагрузка выше, как результат, помнить и понимать их устройство — сложнее.
Есть теория управляемости, которая говорит, что у человека в голове помещается от 5 до 9 сущностей, так вот SRP этому явно противоречит.

Интересно, каким образом?
Это как раз легко.
1. Решение могло быть принято давно и не тобой, а ты теперь разгребаешь последствия нескольких лет его правильного и неправильного применения. При этом вторые обращают на себя куда больше внимания по понятным причинам, из-за чего может казаться, что они составляют 90% общего объема.
2. Решение могло быть принято тобой, но неверно понято рядом последователей. И поскольку некоторые подходы позволяют отстрелить себе ноги легче, чем другие, — есть основания «винить» (ну, точнее, признавать недостатки) фреймворки, которые в большей мере допускают неверное их использование.
Вы, конечно, можете строить базу исходя из того, что у клиента может быть несколько имен, отчеств и фамилий. Но вряд ли это хорошая идея. :-)

Да нет, иногда это просто-таки необходимо. На Госуслугах, например, я больше чем уверен, у физ. лиц хранится не по одному имени/фамилии. Да и в налоговой. Да и в банках… Да много где. Не самый удачный пример, короче )
Похоже, вы тоже используете подход с анемичной моделью, особо не задумываясь об этом. Удивлены, что это, оказывается, «антипаттерн»?
Соглашусь. Я бы еще дополнил таким соображением: если «приложение» — не самодостаточная система, а некая библиотека, решающая прикладную задачу с более менее четко очерченными границами (например, работа с XLS-файлами), то для нее использовать богатую модель с продуманной разветвленной иерархией классов является вполне оправданным. Благодаря тому, что границы намечены заранее, вполне реально запроектировать систему достаточно гибкой, чтобы адаптироваться под новые требования, при этом возникновение таких требований, которые поломают всю структуру, довольно маловероятно.
На противоположном полюсе я бы поставил энтерпрайз системы, особенно «полукоробочного» плана, когда компании продается готовый продукт, подвергаемый серьезной модификации под нужды конкретного заказчика. Заранее предусмотреть все требования даже одного заказчика — архисложная задача. А уж сделать так, чтобы будущие требования будущих клиентов без проблем укладывались в существующий продукт (который бы при этом не был «голым» фреймворком) — еще сложнее. Рано или поздно обязательно окажется, что допущения, справедливые для 10 клиентов, не соответствуют бизнес-модели 11-го. Для таких продуктов, как мне кажется, анемичная модель подходит весьма хорошо. В коробочном продукте реализуется базовая логика, отвечающая потребностям большинства заказчиков, а там, где логику надо переопределить, сделать это легко — достаточно подменить соответствующую службу, никак не нарушая работы остального кода.

Для других приложений может быть найдено место на этой оси где-то между двумя полюсами. Они оба имеют право на существование. Но когда один называют «паттерном» (_«Так делать правильно!»_), а второй — «анти-паттерном» (_«Дети, не делайте так!»_), то, ИМХО, второй подход незаслуженно маргинализируется.

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

Информация

В рейтинге
Не участвует
Откуда
Саратов, Саратовская обл., Россия
Дата рождения
Зарегистрирован
Активность