Как стать автором
Обновить

Комментарии 77

Кажется, нужно прост отличать перечисления (это бизнес-логика, расширение требует перекомпиляции ПО) от словарей (это пользовательские данные, расширение требует добавление новой записи в БД, бизнес-логика универсальна и не требует при этом перекомпиляции ПО). Вне зависимсоти от языка программирования.

безусловно. но тут в статье другое противопоставление: не enum против каких-то конкретных экземпляров (значений свойств объекта из хранилища); а enum против сущности (объекта, класса, структуры), на которую накладывается контракт для поддержки работы в каком-то общем механизме (в статье это новостная лента)

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

в статье приведены примеры, когда одним словарём под один `enum` не обойдёшься. Здесь специально для примера взяты свифтовые перечисления с ассоциированными значениями. При этом у разных case разнотипные значения. У Post это Text, у блока клипов — это массив клипов. То есть, по сути, это абсолютно разные сущности, жёстко собранные в один enum. Эти сущности объединяет только то, что они должны быть показаны в ленте (а в ведь в реальном проекте они могут потребоваться не только в ленте). Если бы в качестве enum в таком примере был бы использован только FeedType с перечислением строковых констант типов ленты, и перебор значений был только в парсинге — это было бы лучше, и такой сценарий с перебором типов элементов гетерогенного списка, полученного из другого слоя, я в статье описываю, как допустимый. Но и тут может хватить просто констант, даже не обязательно жёстко зафиксированных одним списком, особенно если есть желание или необходимость не перекомпилировать модуль ленты при добавлении новых типов элементов ленты, если при этом сама логика ленты не меняется.

Всё, понял, прошу прощения за невнимательность (Swift-ом только со стороны интересуюсь, пока ещё не практикую), вы в своей статье показали реализацию std::variant из C++ на Swift :).

Но заголовок плохой совершенно точно, как уже отметили ниже, выглядит так, будто речь идёт о бесполезности enum, а не об ошибке программиста, неправильно декомпозирующего бизнеслогику. Энумы полезны в нужных местах, как и все остальные конструкции языка :).

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

про std::variant из C++ — да, кажется, это похоже. Мне на ум из аналогий ещё приходит конструкция Sealed classes

но, кажется, std::variant является более честным и гибким инструментом, так как принадлежность какому-то набору вариантов не является частью объявления класса и почти что его namespace, а определяется по месту реального использования.

Да он там наколенный, без статических проверок и присущеми этому способы падениями в рантайме с bad_variant_access. match (хотя его вообще в с++ нет) по нему тоже не сделаешь.

Проще свой, наколенный сделать (еще и в яндекс пригласят).

Короче, лучше бы std::variant вообще не было в с++.

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

про бесполезность enum — я считаю этот инстурмент скорее ненужным и бесполезным, чем полезным. поэтому в конце статьи и написал, что по умолчанию против него :) во всяком случае, теперь сам стараюсь не использовать этот инструмент никогда.

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

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

Не понимаю за что минуса.

В комментарии выводы/размышления, которые уже как больше полусотни лет считаются классическими аксиомами.

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

Грандиозный заголовок, микроскопический текст. Быть "против энумов" это всё равно, что "быть против унарного типа" или "протестовать против bottom-type".

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

Любое появление любых конструкций языка в коде - это всегда повод задуматься, уместно ли оно там.

Дочитал до места, где сказано, что название статьи для привлечения внимания, а сама статья о другом, поставил минус.

"Любое появление любых конструкций языка в коде - это всегда повод задуматься, уместно ли оно там." — согласен

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

Это значит, что вы просто хороших языков программирования не видели. 50% прелести Rust'а в том, что происходит с enum'ами.

Статья не про язык, а про организацию кода без переборов и переплетения сценариев.

Не буду скрывать, действительно rust не изучал и не имел с ним дела. Мне стоит восполнить этот пробел. Я иногда слышу положительные отзывы о его использовании

Я, в свою очередь, не видел swift, а зашёл по кликбейтному заголовку. В Rust описанная проблема решается с помощью типажей (trait). Каждый тип реализует один и тот же трейт, дальше идёт либо статическая мономорфизация, либо динамическая диспетчеризация (как выберете).

А enum'ы используются для сохранения инвариантов - Result (который либо Ok, либо Err), либо Option, который Some или None, и для подобных же конструкций.

у меня складывается ощущение из такого описания trait, что это аналог протокола или интерфейса, то есть как раз реализация того дизайна, что я пропагандирую

А вот про Result Ok/Error мне ближе описанный в части про сигналы подход с разделением обработки onOk onError. И мой аргумент тут такой: генератор результат уже выполнил ветвление, он выбрал, в какую из ветвей направить исполнение, почему после этого все слушатели (пользователи API) должны выполнять снова это ветвление, если они могут просто зарегистрировать обработчики на ветвление источника?

Но, конечно, такой код возможен только там, где есть возможность зарегистрировать и вызвать конкретный обработчик. При получении ответа из сети или при десериализации хранящихся на диске данных, когда у нас есть только бинарные данные, которые могут либо OK либо Error содержать — в таком случае от ветвления на стороне получателя уже не избавиться, кажется. Но этот случай в статье я рассматриваю как допустимый для какой-то реализации перебора

Трейты похожи на интерфейсы, да.

Насчёт Result... Основная проблема "ветвления" (читай, exception'ов) состоит в том, что по типам перестаёт сходиться. Если у вас функция возвращает Result, то получатель не сможет проигнорировать ошибку. Он от неё может отмахнуться (unwrap), передать выше ( оператор '?'), но он не может её проигнорировать.

Если вы полагаетесь на обработчики выше, то через несколько слоёв (и несколько раздельных команд) у вас полностью потеряно соглашение кто какие ошибки как обрабатывает. А тут у вас в коде ясно написано, и к этому можно задать вопрос "зачем?" или "а почему так?".

Более того, благодаря #[must_use] можно сделать так, чтобы нельзя было даже выкинуть результат (не присвоив его никуда).

Я тут с Rust'ом, конечно, но мне кажется, что вопрос жизни с enum'ами (так же как и техника их правильной готовки) совершенно language-indepndent, и к языкам оно относится только в вопросе "насколько удобно" в том или ином языке с таким жить.

"получатель не сможет проигнорировать ошибку" — можно сделать обязательным наличием onError обработчика.

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

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

Или я ошибочно понял сценарий?

НЛО прилетело и опубликовало эту надпись здесь

если код аккуратно разделён на слои, когда в одном месте не пытаемся на несколько уровней вниз реализовать асинхронные операции, callback hell не должно возникнуть.

про сценарий с Either не понял:( речь про список каких-то Future идёт? можно реализовать callback, где будем записывать значение в future. Или о каком сценарии речь?

НЛО прилетело и опубликовало эту надпись здесь
Если я вас правильно понял, вы просто предлагаете всегда заменить Result/Either/etc двумя continuation'ами.

С Church encoding так-то разницы и нету...

НЛО прилетело и опубликовало эту надпись здесь

Вы просто не разобрались в причинах появления enum как способа выражения возможных состояний. Ваш сфокусированный императиный подход просто не дает возможности раскрыть всю красоту и лаконичность enum. Чтобы понять этот инструмент и как его эргономично использовать, стоит вникнуть в алгебраические типы данных, функциональную композицию и научиться извлекать ассоциированные значения не привнося шаблоны паттерн матчинга непосредственно в бизнес логику. Для проникновения в глубины этого чудесного инструмента вам поможет сайт ребят из point free. Возможно вы о нем слышали. Успехов!

Почитал про алгебраические типы данных. Не буду говорить, что много, возможно, не достаточно информации нашёл, но:

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

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

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

Если вы имеете ввиду под "научиться извлекать ассоциированные значения не привнося шаблоны паттерн матчинга" оперирование сущностями через абстракции (интерфейсы), тогда это как раз то, что я пытаюсь донести)

НЛО прилетело и опубликовало эту надпись здесь

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

Действительно, Swift оставил enum немного "ущербными" из-за чего можно сделать выводы наподобие ваших. Есть пара элементарных трюков которые возвращают в enum недостающей эргономики.
Вот микропример, что имею в в иду.

struct Post: Equatable {}
struct Clip: Equatable {}

enum FeedItem {
  case post(Post)
  case clip(Clip)
  
  // "секретный" ингредиент в виде небольшого шаблонного кода
  var post: Post? {
  	guard case let .post(post) = self else { return nil }
    return post
  }
  
  var clip: Clip? {
  	guard case let .clip(clip) = self else { return nil }
    return clip
  }
}

// профит

[<<FeedItem>>].compactMap(\.post) // -> [Post]
[<<FeedItem>>].compactMap(\.clip) // -> [Clip] 

Там где вы описываете Equatable к enum Content - это излишне. достаточно чтобы ассорциированный значения реализовывали этот протокол, "ручная" реализация будет не нужна. Счетчики enum-ов и кастинга декрементируют ))

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

// ваш пример из статьи

extension FeedItem {
    enum Content {
        case post(Post)
        case clips(Clips)
    }
}

extension FeedItem.Content {
    struct Post: Decodable {
        static var feedType: String { return "post" }
        let text: String
    }
}

extension FeedItem.Content {
    struct Clips: Decodable {
        static var feedType: String { return "clips" }
        struct Clip: Decodable {
        }
        let clips: [Clip]
    }
}

В этой части где появляется FeedItem.Content.Post/Clip - ваша абстракция уже поплыла. Уберите Content и всё что вложено. Кажется в них нет никакой необходимости.

И все дальнейшие проблемы, снова решаем проблему тем же образом:

// Ваш вариант

enum FeedItemAction {
    case notInterested(FeedItem)
    case banPost(FeedItem.Content.Post)
}

struct FeedItemActionHandler {
    func handle(_ action: FeedItemAction, items: [FeedItem]) -> [FeedItem] {
        return items.compactMap { item in
            switch action {
            case .notInterested(let feedItem):
                return feedItem == item ? nil : item
            case .banPost(let post):
                return item.content == .post(post) ? nil : item
            }
        }
    }
}

// Как это могло бы выглядеть в жизни

enum FeedItemAction {
  case notInterested(FeedItem)
  case banPost(FeedItem) // FeedItem.Content.Post - главный костыль
												 // из-за которого всё шло наперекосяк
  // и снова вычислимое свойство сильно упрощающее остальной код.
  var feedItem: FeedItem? {
    switch self {
      case let .notInterested(item): return item
      case let .banPost(item): return item
    }
  }
}

struct FeedItemActionHandler {
    func handle(_ action: FeedItemAction, items: [FeedItem]) -> [FeedItem] {
        return items.filter { $0 == action.feedItem }
    }
}


Генерацию вычислимых свойств можно вынести Sourcery и не вспоминать об этих шаблонах и о том что надо "во всех местах" пойти и добавить новый case , думайте об этом также как о генерации Codable методов и прочей кодогенерации компилятором.

Держите enum простыми, без nested структур или других enum. Прячте паттерн матчинги в кодогенерацию (если религия позволяет, встречал массу ребят на дух не переносящих кодген). Это малая толика того что можно еще сделать для удобства. Надеюсь это поможет посмотреть иначе на enum-ы ))) Удачи!

кажется, вы просто предлагете запрятывание переборов. а в чём плюс? чтобы в коде был соблазн сделать больше переборов списков элементов как в

// профит

[<<FeedItem>>].compactMap(\.post) // -> [Post]
[<<FeedItem>>].compactMap(\.clip) // -> [Clip] 

?

для меня это всё выглядит как "костыли", так как везде распрораняется знание о том, а какие элементы ленты бывают, и какие их детали. из-за того, что везде по коду лезет это знание, отсюда возникают места, которые как раз вычленяют необходимые типы, как вы предложили:

// "секретный" ингредиент в виде небольшого шаблонного кода
 
var post: Post? {
  	guard case let .post(post) = self else { return nil }
    return post
}
  
var clip: Clip? {
  	guard case let .clip(clip) = self else { return nil }
    return clip
}

Для меня это всё кажется, как фиксы или костыли, чтобы прикрыть симптомы неуместного использования enum

И ещё вы, кажется, предлагаете код, который я в статье везде помечаю, как нелогичный:

case banPost(FeedItem) // почему здесь закладывается хранение всех элементов, а не постов?

Вот только я действительно не понимаю ценности этих финтов, которые, как по мне, только упрощают написание неэффективного негибкого переплетённого между сценариями кода. И статья как раз о том, как можно избавиться от этого кода и оперировать только абстракциями, а знание деталей инкапсулировать в код детелаей, не размазывая по проекту

Вы очень буквально восприняли мои примеры. Не понимая как правильно развивать этот подход, он естественно вам кажется полумерой и вы не видите что с ним делать дальшей. Не удивительно, почему вам не кажутся костылями протоколы, рендеры и контексты из тех примеров, которые вы сочли "отличным" решением. Лично я не вижу профита в том, что теперь у вас десяток новых сущностей. PostUpdateActionBus ставит точку в дальшейм обсуждении где тут костыли. В масштабе большого приложения - вы получите на порядок больше бойлерплейта, который невозможно автоматизировать. Ваш подход не плох, но и не настолько хорош, чтобы клеймить перечисления и людей которые умеют их использовать. За сим, прошу меня извинить.

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

Простите, если мой ответ показался грубым, но я действительно хотел бы увидеть те плюсы, которые enum и другие перечисления могут принести в систему, поэтому и спрашиваю так, в чем их ценности. Да и во всём докладе я делаю упор именно на избавлении от переборов и проверок, а не просто на борьбе против enum. Если в использовании enum есть преимущества, я хотел о них узнать. А пока enum, к сожалению, мне кажется инструментом, который легко ведёт к коду, как к перебору вариантов, и этой статьёй я пытаюсь подтолкнуть коллег к более осмысленному использованию перечислений и переборов:(

везде распрораняется знание о том, а какие элементы ленты бывают, и какие их детали

Ну, собственно, основное назначение enum — распространить везде знание.


Если задача прямо противоположная — enum не подходит, было бы тут про что статью писать...

Вы правы, хотя скорее автор выразил мнение о причинах нежелания использовать в коде. При этом, это ведь не единственный условные "самоограничения", которые принимаются сперва группой лиц, а потом и всем сообществом. Очень кстати то, что в статье приведены конкретные кейсы использоапния, хотя конечно далеко не факт, что у вас на практике встречается аналогичная задача и даже если встречается, не факт что enum так уж и вредно (='неудобно') было бы использовать.

ну именно с лентой Вконтакте в iOS приложении реализация описанного механизма с протоколами и контекстом элементов помогла начать писать изолированный код новых элементов ленты, при этом даже в разных модулях, и на Swift, хотя основной код старый код ленты остаётся на objc и его логика не меняется. Так что в основном модуле клиента (в котором исторически находится реализация основной логики ленты) даже нет смешения языков ("Mix and Match" Swift и Objc)

Энумы всяко лучше 1,2,3 в базе и где то в коде куски коментов вырастающие в тхт типа

```

  • 1 => Заказ,

  • 2 => Оплачено,

  • 3 => Возврат,

    ...

  • 14 - ошибка

```

Ага, живем не в идеальном мире.

для экспортирования API, каких-то констант, вполне возможно. но если речь о конкретной системе, в которой это не просто константы, а реальные состояния системы, возможно, стоит задуматься о введении сущностей под эти состояния. надо смотреть каждый конкретный случай

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


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


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


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

я скорее вот про что:

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

если при этом есть необходимость передавать информацию о состоянии наружу — тут два варианта, которые я тоже рассмотрел в части статьи про сигналы:

1) можно сделать интерфейс с передачей констант (1=>Заказ и т д)

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

И вот про выбор между этими двумя вариантыми как раз мой открытй вопрос про реактивные библиотеки

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

если для какого-то проекта проще/безопаснее/нагляднее явно перебирать состояния как значения — тогда, возможно, не стоит ничего трогать. но при росте системы стоит всё же учитывать альтернативные варианты реализации

Enum c switch идут в паре не просто так. Конструкция switch реализуется большинством компиляторов на низком уровне с помощью механизма Jump Table. Это позволяет в хорошем случае (когда все enum пронумерованы маленькими числами) все ветвление выполнить за три инструкции процессора как на x86 так и на ARM архитектуре.
Языки более высокого уровня также используют это "под капотом" у себя. Например виртуальная машина .NET имеет специальный опкод(!) для инструкции switch чтобы в дальнейшем оптимизировать подобное, а все новомодные switch по строкам, pattern matching и прочее используют примитивы вроде словарей, чтобы в конечном итоге прийти к такому свитчу

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

На фронте вообще без разницы как оно будет реализовано. Главное чтобы не было нигде квадратичной сложности. Разница несущественна.

У вас выглядит так, будто объектной модели не хватает базового класса, в производных же должно быть поведение. Зачем там вообще изначально enum непонятно - может это какие-то фичи Apple

Вы думаете, что при вызове интерфейсного метода, нигде никакого ветвления не будет? Как вы думаете, как такие методы резолвятся?

конечно, могут быть ветвеления, ведь тут всё зависит от реализации диспетчиризации

НЛО прилетело и опубликовало эту надпись здесь

к теме статьи не относится, но:

мы следим за производительностью, постоянно стараемся её улучшить. к сожалению, не все проблемы пока удалось исправить.

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

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

В итоге просто перестал заходить в данную соцсеть.

НЛО прилетело и опубликовало эту надпись здесь

Когда у вас изменяется enum, то вы сразу видите, в каких местах нужно поправить код, работающий с enum. 

Этот аргумент я слышал довольно часто, но у меня есть ответ, что в случае с выбором протокола IDE тоже будет ругаться на отстувие определния какого-то метода в какой-то из реализаций протокола

Про множество абстракций — в общем случае я предлагаю лишь одну под один enum: протокол, который описывает, что мы хотим в общем от каждого элемента

Я против сложных enum — когда в перечислениях появляются параметры. Кроме swift в других языках таких конструкций наверное и нет.

Когда в перечислениях появляются параметры, перечисление перестаёт быть перечислимым типом и становится типом-суммой. Эта штука существует в куче языков, включая Visual Prolog, Haskell, Scala и Rust.


И да, почему вы против таких enum?

НЛО прилетело и опубликовало эту надпись здесь

Если вас интересует лишь один вариант из всех — то пожалуйста:


if let Foo(Bar(Baz(i))) = x

Если же нужно разобрать несколько вариантов — не вижу причин отказываться от match/switch.

Очень хорошая статья. Последний год в голове как раз крутилось все что здесь описано. Сам тоже не перевариваю enum'ы и перечисления когда для добавления нужного значения нужно отсмотреть/поправить еще десяток мест в коде где оно используется. Особенно не нравится, когда пытаются использовать enum для задания стиля компонента и во всех методах перечисляют все значения.

Все описаное также укладывается под O из SOLID (open-closed principle) - когда для внесения изменений нужно создать новый код (например новый класс которые будет реализововать протокол) а не изменять существуюущий код (те все swift перечисления)

Спасибо)

Тут вопрос в том, какие изменения ожидаются чаще — появление новых разновидностей или появление новых операций. В первом случае надо использовать классы и наследование (либо интерфейсы и реализацию, трейты и реализацию и т.п.), во втором случае лучше использовать enum.

Справедливое замечание. У меня как-то возник даже такой пример: в компании есть анкеты. И можно сделать так, что по отдельным вопросам будут журналы для подписей или какой-то другой инфы от команд (для меня это аналог перебора вариантов enum в функции), а можно так, что будут анкеты со всеми вопросами для разных команд. Тут уже ближе к реализации интерфейса/протокола/trait.

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

Также кажется, что для добавления вопросов проще использовать первое. Но! Это невозможно легко распараллелить без конфликтов, так как есть необходимость изменения общего журнала разными командами

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

Как всегда, у любой вещи, есть 2 стороны и всегда можно микроскопом гвозди забивать - все зависит от опыта и знаний.

Собственно вся соль статьи написана в последних нескольких абзацах (с ними я согласен), а остальное это просто примеры - наверно стоило построить все наоборот - сначала тезисы, а потом примеры

Спасибо) хотелось идти от примеров, чтобы проиллюстрировать то, что я считаю проблемами

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

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

Я ожидал чего-то подобного, публикуя доклад на тему про проектирование с громким заголовком и мыслями и болями, которые в голове крутились давно :)

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

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

Да, в цикле статей мы пока еще не описали, как аналогичный механизм применяется и для обработки действий пользователя. Но статья уже в процессе подготовки и скоро появится. А пока в качестве домашнего задания :) Вы вполне можете подумать, как применить данный подход и к обработке действий пользователя по ячейкам. Ваш код еще сильнее похудеет.

Пример с Reuse Pool я привёл, чтобы показать, что можно по-другому реализовывать dequeue и не приводить никакие типы)

Не обязательно прокидывать через парсер - можно сделать аргументом render (такой пример есть в статье)

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

Александр, пожалуйста, прочитайте про expression problem. А то вы описали одну из её половин, только очень путано и многословно.

Спасибо! Почитаю

Старался идти по примерам. Досадно, что кажется путано:(

Почему то в конце статьи в паттернах не упомянуты другие паттерны двойной (динамической) диспетчеризации помимо паттерна "состояние". А мне, когда я увидел код, сразу вспомнился " Посетитель (visitor) ". Очень часто проблема "убрать enum" требует мульти-диспетчеризации вызовов. И тут без посетиля часто не обойтись. Однако это сложный для понимания и реализации паттерн. И использовать его везде - плохая идея. Таким образом не нужно торопиться избавляться от enum, т к можно сильно усложнить код на пустом месте. Если в проекте в это перечисление значения добавляются редко и не на систематической основе, то я бы 10 раз подумал. Особенно если есть вероятность, что завтра требования изменятся и этот enum вообще нужно выпилить будет вместе с кодом, который его использует.

Спасибо за статью. Интересный материал. Хорошо прям проработан подход, но все-таки думается, что могут существовать ситуации, в которых для реализации бизнес-требований будет выгоднее использовать enum.
Предлагаю рассмотреть такой случай:
пусть в ленте 100500 итемов (ячеек) 15 разных типов (посты, клипы и т.д.) и нужно дать пользователю возможность выбирать из них два итема, но с обязательным условием, что итемы должны быть разных типов.
Например, если первой выбрана ячейка постов, то вторую можно выбрать "только-не-посты" (а например клипы) и наоборот.
Кажется, что тут будет выгоднее оставить пришедший из сети enum и паттерн-матчить по нему.

Спасибо:)

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

Но и паттерн-матчинг множества типов тоже может подойти. Тут, как и везде, уже надо плясать от задачи и проекта

Enum и switch норм. Пока не пришло время передавать их в другую систему 😂

Зарегистрируйтесь на Хабре, чтобы оставить комментарий