Статьи

Java-код корпоративного класса

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

Код корпоративного класса

Существует общий способ написания кода, возможно, особенно распространенный в Java, но также и в C #, который побуждает разработчика писать как можно больше классов. На крупных предприятиях такой способ построения кодекса является эндемичным. Перефразируя: каждая проблема в базе кода предприятия может быть решена путем добавления другого класса, за исключением слишком большого количества классов.

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

Тем не менее, это идет глубже, чем это. Многие люди (включая меня до недавнего времени) думают, что лучший способ написать хорошо разработанный, поддерживаемый код — это написать множество небольших классов. В конце концов, простой класс легко объяснить и, скорее всего, он будет нести единоличную ответственность . Но как вы объясните систему, состоящую из ста классов, новому разработчику? Бьюсь об заклад, вы в конечном итоге строчить на доске с коробки и линии везде. Я уверен, что ваш дизайн прост и элегантен, но когда мне нужно разобраться со 100 уроками, чтобы просто знать, как выглядит пейзаж — мне потребуется некоторое время, чтобы понять его. Масштабируйте это до предприятия с тысячами и тысячами классов, и это становится действительно сложным: ваш средний разработчик может никогда этого не понять.

Пример

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

TradeMessage

1
2
3
4
5
6
7
8
9
private long id;
 
private Date timestamp;
 
private BigDecimal amount;
 
private TradeType type;
 
private Asset asset;

Я хороший маленький функциональный разработчик, поэтому этот класс неизменен. Теперь у меня есть два варианта: i) я пишу большой конструктор, который принимает сто параметров, или ii) я создаю конструктор, чтобы придать упражнениям здравый смысл. Я иду на вариант II).

TradeMessageBuilder

1
2
3
4
5
6
7
8
9
public TradeMessageBuilder onDate(Date timestamp)
 
public TradeMessageBuilder forAmount(BigDecimal amount)
 
public TradeMessageBuilder ofType(TradeType type)
 
public TradeMessageBuilder inAsset(Asset asset)
 
public TradeMessage build()

Теперь у меня есть конструктор, из которого я могу создавать классы TradeMessage. Тем не менее, сборщик требует, чтобы строки были проанализированы по датам, десятичным числам и т. Д. Мне также нужно беспокоиться о поиске активов, поскольку TradeMessage использует класс Asset, но входящее сообщение имеет только имя актива.

Сейчас мы тестируем снаружи, как добрые маленькие разработчики GOOS . Мы начинаем с CSVTradeMessageParser (я игнорирую сеть или что-то еще, что передает наш парсер).

Нам нужно проанализировать одну строку CSV, разбить ее на составные части, из которых мы будем строить TradeMessage. Теперь у нас есть несколько вещей, которые мы должны сделать в первую очередь:

  • Разобрать метку времени
  • Разобрать сумму
  • Разобрать тип сделки
  • Поиск актива в базе данных

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

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

CSVTradeMessageParser

1
2
3
4
5
6
7
public TradeMessage parse(String csvline)
 
private Date parseTimestamp(String timestamp)
 
private BigDecimal parseAmount(String amount)
 
private TradeType parseType(String amount)

Теперь — есть проблема с вождением в этом классе — как мне проверить все эти частные методы? Я мог бы сделать их видимыми и поместить мои тесты в один пакет, но это неприятно. Или я вынужден протестировать открытый метод, смоделировать компоновщик и убедиться, что правильно проанализированные значения передаются компоновщику. Это не идеально, так как я не могу протестировать каждый метод разбора отдельно. Внезапно сделать их отдельными классами кажется лучшей идеей …

Наконец, мне нужно создать AssetRepository:

AssetRepository

1
public Asset lookupByName(String name)

Парсер использует это и передает полученный Актив в TradeMessageBuilder.

И мы сделали! Просто нет? Итак, если я проверил это с интерфейсами для моих смоделированных зависимостей, сколько классов мне пришлось написать?

  • TradeMessage
  • TradeType
  • TradeMessageBuilder
  • ITradeMessageBuilder
  • CSVTradeMessageParser
  • Актив
  • AssetRepository
  • IAssetRepository
  • TradeMessageBuilderTest
  • CSVTradeMessageParserTest
  • AssetRepositoryTest

Да, и так как это только юнит-тесты, мне, вероятно, понадобятся комплексные тесты, чтобы проверить, работает ли весь матч по стрельбе:

  • CSVTradeMessageParserIntegrationTest

12 классов! Ммм предприятие-у. Это просто игрушечный пример. В реальном мире у нас есть FactoryFactories и BuilderVisitors, которые действительно добавляют беспорядка.

Другой путь

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

TradeMessage

1
2
3
4
5
6
7
8
9
public Date getTimestamp()
 
public BigDecimal getAmount()
 
public TradeType getType()
 
public Asset getAsset()
 
public void parse(String csvline)

Это действительно все, что беспокоит звонящих — получение значений и разбор из CSV. Этого мне достаточно, чтобы использовать в тестах и ​​рабочем коде. Здесь мы создали красивый, чистый, простой API, который очень легко объяснить. Нет необходимости в доске, коробках, строках и длинных затяжных объяснениях.

Но как насчет нашего метода parse ()? Разве это не стало слишком сложным? В конце концов, он должен разложить строку, даты разбора, суммы и типы сделок. Это много обязанностей для одного метода. Но как плохо это на самом деле выглядит? Вот мой, в полном объеме:

1
2
3
4
5
6
7
8
9
public void parse(String csvline) throws ParseException
{
    String[] parts = csvline.split(',');
 
    setTimestamp(fmt.parse(parts[0]));
    setTradeType(TradeType.valueOf(parts[1]));
    setAmount(new BigDecimal(parts[2]));
    setAsset(Asset.withName(parts[3]));
}

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

Но позвольте мне спросить вас, что бы вы предпочли: 12 маленьких классов или 4 маленьких класса? Лучше ли, чтобы сложные операции были разложены на десятки классов или были аккуратно объединены в один метод?

Ссылка: код корпоративного класса от нашего партнера JCG Дэвида Грина в блоге Actively Lazy .