понедельник, 20 апреля 2009 г.

String vs StringBuilder

Сегодня в microsoft study-group обсуждали стринг. Собственно эта тема всех интервью - что нужно использовать для "оптимального" хранения строчек? И все хором говорят - используем StringBuilder. А на вопрос, что использовать для кокатации строк - String.Format. И все напрочь забывают, что мы программируем не для оптимизации, а для того, чтобы поддерживать код и развивать. То есть делать код понятным даже первокласснику, что не скажешь про "профессионально" оптимизированные без надобности куски творчества.

Так вот про вопросы интервью. Я с ними полностью не согласен. Люди! Не то чтобы на пороге 21 век, он уже дома! Преждевременная оптимизация - зло и порождает ДОРОГОСТОЯЩИЙ код! Качественный код приравнивается к самодокументируемому коду. Оптимизация здесь не рассматривается - она здесь не обсуждается. Так как мы бизнес-разработчики, а не любители оптимизнуть что под руку попадёт. Бизнес-разработчик пишет код, где каждая строчка отражает НАМЕРЕНИЕ, а не хитро-вычитанные фичи платформы :)

Начну развеивать заблуждение с конкатацией через String.Format. Цель функции String.Format форматирование строки, а не конкатация. То есть привращать 1 в 001,00 например. С точки зрения качественного программирования запись с целью конкатации String.Format("Total: {0} dollars", total); выглядит настолько неэкономично, что ужас. Конечно микроскопом можно гвозди забивать :) У некоторых это даже получается. Во-первых, чтобы понять это, нужно чтение слева-направа заменить на скачкообразное чтение с актвацией ячейки памяти в моём мозгу. Во-вторых, добавление новых переменных вызывает дополнительное напряжение - теряем время, за которое нам платят деньги. В-третьих, чтобы добавить новую переменную - мне нужно делать 2 (!!!!) изменения.

Исходя из этих соображений. А мои соображения основываются на принципах Implementation Patterns & Refactoring и трёх простых ценностях (коммуникация, простота и гибкость). В данном случае используем самую первую и самую важную ценность - коммуникация. Напомню: Коммуникация (communication) - разрабатываемый код должен явно отражать намерение создателя. Этот принцип подчёркивается и в рефакторинге. Так вот начиная читать строку String.Format - я чётко фиксирую себя на мысле "форматируем"!, а когда плюсиками - то конкатируем. Просто и разумно.

Ужасная конкатация через форматирование: String.Format("Total: {0} dollars", total);
Поэтому намного лучше и приятней: "Total: " + total + " dollars";
Хотя, перед PHP код снимаю шляпу: echo "Total: $total dollars";

Все сразу начинают вспоминать эффективность и оптимизацию. Не знаю, что пишут в современных книжках. Но я читал книги 1970-х годов, ещё в то время писали - "народ у нас мощные компы, бросьте оптимизаций заниматься заранее, делаете более читаемый и хьюмэн ориентед код". И это писал практик, а чуть ранее теоретик программирования Кнут писал тоже самое :)

К вопросу о StringBuilder - это ещё та тема. Все вертится вокруг сборки результата, результирующего значения. Объект выполняет роль сборщика. В одном месте мы собираем данные, а в другом потребляем. Это не форматирование и не простая конкатация. А целый алгоритм конкатации. Поэтому использование предыдущих методов, когда нужен StringBuilder, обречёт наследников вашего кода вспоминать вас недобрым словом :)

Итак с точки зрения качественного кода (а это код, которых легко читать):
StringBuilder - вещь хорошая, но его стоит применять, когда мы подходим к объекту как механизму с накапливанием значения, а результат будет снят как сливки намного дальше от кода наполнения. То есть в одном месте явно накапливаем (сложный код... преимущественно бывает в циклах), а в другом вы забираете результат (особенно часто встречается со словом return ;).
String.Format - для форматирования кода, но не конкатации. Причем в отличие от String.Builder результат забирается в месте форматирования.
+ (любимый плюсик для строк) - приятный и дешевый способ конкатации

PS. "Экономично", "дорогостоящий" и "дешевый" читать с точки зрения разработки и поддержки. Меньше тратим время, чтобы "врубиться", значить дешевле. Время - деньги.
PS2. А оптимизация делается на заключительной стадии. Ключевое слово: профайлер.

15 комментариев:

  1. String.Format("Total: {0} dollars", total) куда понятнее и легкомодифицируемо, чем "Total: " + total + " dollars"
    Я не соглачен, что второй вариант 'намного лучше и приятней'. Это неудачный пример на мой взгляд.

    ОтветитьУдалить
  2. x = String.Format("Total: {0} dollars", total);

    x = "Total: " + total + " dollars";

    Форматирование и даже здесь проявляется. Два пробела - по мне смотрятся как часть форматирования. Поэтому прийдётся пересматривать концепт :D

    Но расположив две строчки вместе я понимаю, читать слева направа без лишних вещей (String.Format, {0} и ещё скобочки) намного проще. Хотя есть другие значки. Да ещё и букв меньше. Не находишь?

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

    >> Хотя, перед PHP код снимаю шляпу
    Кстати, В Boo есть интерполяция строк :) Кстати, весьма agile-истый язык, нет личшних '{' и '}'.

    -- meowth

    ОтветитьУдалить
  4. А дай пример из Boo.

    Как только форматинг, а особенно продвинутые вещи, то я уже для этого делаю спец.классы или атрибуты начинаю добавлять. Но это тема другого разговора. Кстати, можно объединить усилия и написать статью на эту тему. Я тут чуток слабоват в форматингах, но зато могу оценить с точки зрения качества :)

    Под "точка зрения качества" я понимаю применение ряда ценностей и принципов написания кода. Должно выйти неплохо.

    ОтветитьУдалить
  5. Не вопрос :) По примеру Выше -- интерполяция "долларов"

    [code]

    dollars = 100M
    result = "Total ${i} dollars"

    x as string = "hmm?"
    x = "ok, it's really 100" if result = "Total 100 dollars"

    print x

    [/code]

    -- meowth

    ОтветитьУдалить
  6. Битва за употребление String и StringBuilder - это битва за производительность. Попробуйте замерить скорость исполнения for(int i = 0; i < 10000; i++) (1)s += "Hello"; (2)s.Append("Hello"); И будет сразу понятно когда употреблять String а когда StringBuilder.

    ОтветитьУдалить
  7. Преждевременное поднятие вопроса оптимизации - зло. Согласен?

    ОтветитьУдалить
  8. Преждевременное ... - зло. Согласен.
    Но если в коде фигурирует конструкция i<10000, то можно и преждевременно задуматься об оптимизации.

    ОтветитьУдалить
  9. Профайлер должен думать, а не я.

    Я должен думать о воплащение моей бизнес-мысли в коде. Именно воплащенная бизнес-мысль в коде ведёт к качественному коду. А не мысль-оптимизация.

    Кстати в твоём примере StringBuilder нужно использовать не по причине оптимизации, а по той, которая упомянута у меня в исходном сообщение.

    ОтветитьУдалить
  10. Кстати, судя по твоим комментариям, ты предполагаешь, что я не знаю твоих аргументов? :) Твои аргументы стандартные фразы в любом учебнике по C#. Мои мысли уникальны. Они рождены на стыке TDD + Refactoring + Implementation Patterns и рассматриваемых классов C#.

    Ты ничего нового, к сожалению не высказал. Да к тому же своими вопросами показываешь своё предположения, что я не читал стандартных учебников C# (Троелсена, Рихтера и msdn).

    Но другой вопрос - а понял ли ты мои мысли? Сможешь ли ты их использовать? Почему я предложил такие идеи? Что за ними лежит? Какие ценности и принципы они отражают?

    ОтветитьУдалить
  11. easy, easy ...
    Конечно понял, даже написал "Преждевременное ... - зло. Согласен.
    ", что ж тут не понятного. Все мои комменты - не попытка выставить в неприглядном свете.
    Тем не менее, может иногда стоит думать на шаг вперёд, если решение столь очевидно. Возможно это и есть стандартные фразы из любого учебника по С#. Более того, думаю, что MSDN - настольная книга .NET разработчика. Что ж тут плохого.
    Мысли твои не понял (не умею их читать), а что написано в статье - понял. И до этой статьи использовал принцип, что не стоит оптимизировать без надобности.
    На этом стоит закончить, иначе коммент превратится в о флуд.

    ОтветитьУдалить
  12. То есть, ты на мои вопросы не ответишь :)

    ОтветитьУдалить
  13. Многоуважаемый Денис, я тут бегло пробежался по комментариям:
    1. Скажите какой у вас был стаж работы непосредственным разработчиком, тем кто практически целый день пишет код.
    2. Судя по некоторым вашим комментариям у меня закралось подозрение что у вас мания величия, нужно как-то более уважительнее, или уж так сказать снисходительнее относитсья к людям которые заходят на ваш блог.
    3. Так как разгорелась такая дискуссия по поводу статьи, то утверждать что 'очевидно' что-то зло, а вот это 'правильно' нет никаких оснований - в противном случает получается, что либо вы плохо изложили свою идею, либо просто все тупые бараны кто не соглашается.

    ОтветитьУдалить
  14. Сергей, а можно узнать причины твоего "многоуважения" :)

    1. с 2001 года и им остаюсь
    2. это лишь твои оценочные подозрения
    3. ну по себе других не судят :)

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

    ОтветитьУдалить
  15. Кстати, дискуссия не разгорелась. Были попытки вспомнить стандарные учебники и только. К сожалению ожидаемый мною уровень обсуждений не был достигнут. Жаль.


    ЗЫ. Но оставленные комменты тоже показывают уровень, к которому нужно довести статью. За такую "косвенную" помощь и соавторство тоже спасибо.

    ОтветитьУдалить