Дядя Боб красавчег
От: vaa  
Дата: 04.02.22 01:40
Оценка: 2 (1) +1 :)
https://translate.yandex.ru/translate?url=https%3A%2F%2Fblog.cleancoder.com%2Funcle-bob%2F2021%2F10%2F28%2Ffunctional-duplication.html&lang=en-ru

Мне потребовалось несколько попыток, потому что проблема не была ни в одном из очевидных мест.
Поэтому в течение нескольких часов я добавлял все больше и больше звонков check-for-duplicate-base.


Сделал вывод, что писать код надо не при помощи TDD, а при помощи головы.
и еще вспомнил:

Они все фантазеры, наши шефы...
Им можно фантазировать — у них нет конкретной работы,
а давать руководящие указания умеют даже шимпанзе в цирке...

☭ ✊ В мире нет ничего, кроме движущейся материи.
Отредактировано 04.02.2022 1:49 Разраб . Предыдущая версия .
Re: Дядя Боб красавчег
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 04.02.22 07:31
Оценка: +1 :))
Здравствуйте, vaa, Вы писали:

vaa>https://translate.yandex.ru/translate?url=https%3A%2F%2Fblog.cleancoder.com%2Funcle-bob%2F2021%2F10%2F28%2Ffunctional-duplication.html&lang=en-ru


>> Пока я играл в модифицированную игру, она разбилась. Трудный.


Кривой переводчик, однако. Надо писать "трудное"

>> Почему? Потому что это чисто функциональная программа.


Грабли модифицированные, с изменяемой высотой насадки для искр.

vaa>Сделал вывод, что писать код надо не при помощи TDD, а при помощи головы.


Ну да, TDD не абсолютен. Но баг достаточно редкий, как видно.
The God is real, unless declared integer.
Re: Дядя Боб красавчег (нормальная ссылка на англ. источник)
От: Kernan Ниоткуда https://rsdn.ru/forum/flame.politics/
Дата: 04.02.22 08:59
Оценка: 1 (1) +2
Здравствуйте, vaa, Вы писали:

https://blog.cleancoder.com/uncle-bob/2021/10/28/functional-duplication.html
Sic luceat lux!
Re: Дядя Боб красавчег
От: maxkar  
Дата: 05.02.22 15:57
Оценка: 3 (1)

The function created a list of thefts. A theft is a [thief victim] pair. It used those pairs to create lists of all the thieves and victims, and separate lists of all the innocent klingons and all the unvictimized bases.

А если бы писалась документация, могли бы возникнуть подозрения, что thief и victim — это не часть состояния мира! Невинные клингоны — часть. Неповрежденные базы — тоже часть. А вот thief+victim — не часть. Причем, я так подозреваю, что и thief, и victim — это обновленные клингон и база а не просто описание кражи. Цельный мир (объект с инвариантами) разваливается на слабо связные (uncohesive) кусочки. Вполне себе architecture smell.

Changing the algorithm was actually quite challenging, and required me to put all the klingons and bases into hashmaps keyed by their positions.

А зачем так сложно?

case class Theft(baseLocation: Point3D, klingonLocation: Point3D, amount: Int)
case class Base(location: Point3D, funds: Int)
case class Klingon(location: Point3D, funds: Int)
case class World(bases: Seq[Base], klingons: Seq[Klingon])

val world: World = ???
val thefts: Seq[Theft] = getThefts(world)
val newWorld = thefts.foldRight(world, applyTheft)

private def applyTheft(world: World, theft: Theft): World =
  world.copy(
    bases = world.bases.map { base =>
      if (base.location == theft.baseLocation)
        base.copy(funds = base.funds - theft.amount)
      else
        base
    },
    klingons = world.klingons.map { klingon =>
      if (klingon.location == theft.klingonLocation)
        klingon.copy(amount = klingon.amount + theft.amount)
      else
        klingon
    }
  )


Всё. Это совершенно идеоматический и стандартный код по обработке глобального состояния. Ну да, foldRight — не tailrec. Можно заменить на reverse+foldLeft. Чисто и красиво. Можно отдельно тестировать getThefts. Если мир большой — можно передавать в getThefts базы и клингонов, без остальных частей, так будет проще тестировать. Данный подход легко обобщается. Вместо thefts у нас получаются effects. И будет общий метод, где все эффекты применяются последовательно. Можно отдельно будет протестировать эффекты, отдельно — их применение.

Свертка (fold) — это одна из базовых операций над списками. Во всех функциональных языках есть. Не самая частая, но достаточно употребимая. Но, видимо, Боб про нее не знает. Потому что иначе вместо борьбы с несколькими списками сделал бы сразу:

val (newBases, newKlingons) = 
  oldBases.foldRight((oldKlingons, Seq.empty))(aggregateBaseTheft)

/**
 * Processes thefts from one base and adds it into the accumulator.
 * @param state pair of (all klingons, accumulator)
 * @param base base to steal from.
 * @return par of (updated klingon state, newBase ++ accumulator). Updated clingon state contains
 *   all original klingons but some of them may have increased amount of money.
 */
def aggregateBaseTheft(state: (Seq[Klingon], Seq[Base]), base: Base): (Seq[Klingon], Seq[Base]) = {
  val (allKlingons, baseAccumulator) = state
  val (newBaseState, newClingons) = applyBaseTheft(base, allKlingons)
  (newClingons, newBaseState :: baseAccumulator)
}

/**
 * Processes theft by klingon fleet from the single base.
 * @param base base to steal from.
 * @param klingons all klingons (that could steal).
 * @return new base state (with the amount after theft) and new klingon states (with the stolen amount).
 */
def applyBaseTheft(base: Base, klingons: Seq[Klingon]): (Base, Seq[Klingon]) =
  klingons.foldRight((base, Seq.empty))(aggregateBaseKlingonTheft)


/**
 * Aggregates a single theft from the base by a given clingon into the klingon acculumator.
 * @param state pair of (current base state, accumulator)
 * @param klingon klingon performing the theft.
 * @return pair of (newBaseState, newClingonState ++ accumulator)
 */
def aggregateBaseKlingonTheft(state: (Base, Seq[Klingon]), klingon: Klingon): (Base, Klingon) = {
  val (oldBase, accumulator) = state
  val (newBase, newKlingon) = applySingleTheft(base, klingon)
  (newBase, newKlingon :: accumulator)
}

/** 
 * Performs a theft attempt from the base by a single klingon. 
 * @return updated state of base and klingon.
 */
def applySingleTheft(base: Base, klingon: Klingon): (Base, Klingon) =
  if (base.location.distanceTo(klingon.location) < epsilon)
    (base.copy(amount = base.amount - smallValue), klingon.copy(amount = klingon.amount + smallValue))
  else
    (base, klingon)

Пишется легко и просто. Я "аггрегаторных" функций завел, в реальном приложении вместо них были бы лямбды. Преимущество перед первым решением — у нас здесь сложность bases*klingons (судя по багу, такая же была и в начале). А в предыдущем решении в худшем случае получается (bases+klingons)*bases*klingons. В случае, если клингонцев может быть существенно больше баз, будет большая разница.

В общем, все стандартно и прямолинейно. А вот идея "разделить на два списка, чтобы обновить" — это как раз дурная затея. Более того, вредная. Это императивное направление мышления. Нам всего-лишь нужно для каждой базы простроить новое состояние. Для некоторых баз новое состояние совпадет со старым, для некоторых — нет.
Re[2]: Дядя Боб красавчег
От: maxkar  
Дата: 05.02.22 16:16
Оценка:
Здравствуйте, netch80, Вы писали:

>>> Почему? Потому что это чисто функциональная программа.

N>Грабли модифицированные, с изменяемой высотой насадки для искр.
Какие же это грабли? Дядя не знает совершенно стандартных техник работы с состоянием в функциональных программах. В простейшем случае это fold для состояния и эффектов. Если очень-очень надо, есть монада State. Но дядя Боб решил, что усердие в написании тестов может заменить базовую подготовку. Не прокатило.

vaa>>Сделал вывод, что писать код надо не при помощи TDD, а при помощи головы.

N>Ну да, TDD не абсолютен. Но баг достаточно редкий, как видно.
Это же нарушение внутреннего инварианта, выстрелившее совершенно в другом месте. Прострелы памяти в C, порча файловой системы — представители этого же класса ошибок. Для него редкость является отягчающим обстоятельством. Ну вот представьте, с вашей зарплаты подоходный налог вычитался бы дважды. Сильно ли вас утешило бы знание о том, что баг проявляется только у одного из 50000 работников, обслуживаемых программой? Если вовремя не заметить такое, потом можно очень долго бороться с последствиями.

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

Отсюда и вопрос — а какие классы ошибок позволяет поймать TDD? Нужно ли заботиться о тех классах? Есть ли другие способы разработки, которые покроют интересные нам типы отказов? Может, стоит тратить больше времени на ревью? На формальную верификацию? На изучение базовых приемов и техник? Или, наоборот, продвинутых приемов и техник? А может быть, у нас проблемы в команде или компании? Как так получилось, что в разработку взяли неоднозначные требования? Никто ведь не спросил, какое должно быть поведение в случае, если несколько клингонов воруют с одной базы, а у нее недостаточно денег.
Re[2]: Дядя Боб красавчег
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 06.02.22 14:36
Оценка:
Здравствуйте, maxkar, Вы писали:

M> А зачем так сложно?


Чтобы не было (bases+klingons)*bases*klingons, видимо. Твой вариант простой но не быстрый.

M>Всё. Это совершенно идеоматический и стандартный код


Идиома через "и". https://ru.wiktionary.org/wiki/%D0%B8%D0%B4%D0%B8%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%BD%D0%BE

Если списки связные, то обновлять один элемент списка через map это странное решение. Не знаю, где оно считается стандартным, я бы таких не брал.

M> Но дядя Боб решил, что усердие в написании тестов может заменить базовую подготовку. Не прокатило.


Это да, причем это далеко не первый раз у него.
Re[2]: Дядя Боб красавчег
От: vaa  
Дата: 07.02.22 02:09
Оценка: :)
Здравствуйте, maxkar, Вы писали:
M>

M>Changing the algorithm was actually quite challenging, and required me to put all the klingons and bases into hashmaps keyed by their positions.

M> А зачем так сложно?

По моему скромному мнению, проблема кроется в слабой системе типов, в clojure она фактически отсутствует.
В результате Боб сразу мыслит о процессах.
Вы же воспользовались scala и начали с того что определили возможные типы данных.
Уже имея описание типов проще понять какие операции над ними можно совершать.
Это мне напоминает ситуацию в физике, элементарные частицы лишили структуры(как аналог типа данных) представляя их то материальными точками, то струнами,
в результате определить их свойства стало невозможным и все скатилось в квантовую механику которую никто не понимает.

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

Кстати, Боб программирует на кложе уже несколько лет, но почему-то не воспользовался конкуретными примитивами типа atom для хранения состояния.
или хотя бы нормальной базой для его хранения. вот так не желание использовать проверенные технологии(напишем свой велосипед) приводят к багам.
☭ ✊ В мире нет ничего, кроме движущейся материи.
Отредактировано 07.02.2022 2:39 Разраб . Предыдущая версия . Еще …
Отредактировано 07.02.2022 2:38 Разраб . Предыдущая версия .
Re[3]: Дядя Боб красавчег
От: D. Mon Великобритания http://thedeemon.livejournal.com
Дата: 07.02.22 13:33
Оценка: 1 (1)
Здравствуйте, vaa, Вы писали:

vaa>или хотя бы нормальной базой для его хранения. вот так не желание использовать проверенные технологии(напишем свой велосипед) приводят к багам.


Интересно, много ли (однопользовательских) игр хранят текущее состояние в базе.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.