Pull to refresh

Comments 38

PinnedPinned comments

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

Ничего страшного, никто не заставляет. Всегда можно остаться на Promise<any> и молиться, что ничего не рассыпется

молиться, что ничего не рассыпется

Что конкретно вы имеете ввиду?) Че-то за 9 лет с момента как промисы появились ни один так и не рассыпался

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

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

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

Код курильщика:

const program = Effect.gen(function* () {
  const userService = yield* UserService

  // запускаем дочерний файбер в фоне
  yield* Effect.fork(
    Effect.gen(function* () {
      yield* Effect.sleep("10 seconds")
      yield* userService.getUser("1")
      yield* Effect.log("это не выполнится")
    })
  )

  // родительский файбер завершается
  yield* Effect.log("родитель завершился")
})

Тоже самое, только код здорового человека:

async function program(userService) {
  // запускаем дочернюю задачу в фоне (не awaiting)
  (async () => {
    await sleep(10000)
    await userService.getUser("1")
    console.log("это не выполнится")
  })()

  // родитель завершается не дожидаясь дочерней задачи
  console.log("родитель завершился")
}

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

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

class MessengerAdapter extends Context.Tag("MessageAdapter")<                                                                
    MessengerAdapter,                                                                                                          
    {                                                                                                                        
      readonly incomingMessagesDebounced: Stream.Stream<Message>                                                                     
      readonly send: (response: Response) => Effect.Effect<void, MessengerAdapterError>                                        
    }                                                                                                                        
  >() {}                                                                                                                     
                                                                                                                             
  class MessageProcessor extends Context.Tag("MessageProcessor")<
    MessageProcessor,
    {
      readonly process: (msg: Message) => Effect.Effect<void, MessageProcessorError>
      readonly responsesStream: Stream.Stream<Response, MessageProcessorError>
    }                                                                                                                        
  >() {}
                                                                                                                             
  const bot = Effect.gen(function* () {                                                                                      
    const messengerAdapter = yield* MessengerAdapter                                                                     
    const messageProcessor = yield* MessageProcessor                                                                         
    const currentJob = yield* Ref.make(Fiber.void)                                                                     

    //например пришло сообщение боту из телеграма
    yield* messengerAdapter.incomingMessagesDebounced.pipe(                                                                            
      Stream.mapEffect((msg) =>                                                                                              
        Effect.gen(function* () {                                                                                            
          const prev = yield* Ref.get(currentJob)
          yield* Fiber.interrupt(prev)
          const fiber = yield* Effect.fork(messageProcessor.process(msg))                                                    
          yield* Ref.set(currentJob, fiber)                                                                                  
        }).pipe(                                                                                                             
          Effect.catchTag("MessageProcessorError", (err) =>                                                                  
            Effect.gen(function* () {                                                                                        
              yield* Effect.logError(`Failed to process message from ${msg.userId}`, err)
              yield* messageAdapter.send({                                                                                   
                userId: msg.userId,                                                                                          
                text: "Извините, бот временно недоступен. Сейчас подключим оператора."                                       
              })                                                                                                             
            })    
          )                                                                                                                  
        )         
      ),
      Stream.runDrain,
      Effect.fork                                                                                                            
    )

    //отправляем сообщение обратно
    yield* messageProcessor.responsesStream.pipe(                                                                            
      Stream.mapEffect((msg) => messengerAdapter.send(msg).pipe(
        Effect.catchAll((e) => Effect.logError("Failed to send message", e))
      )),
      Stream.runDrain                                                                                                        
    )             
  })

ну вот, например, код для обработки сообщений чатбота. Для полноты добавлю примерную реализацию MessageProcessor, реализацию MessengerAdapter опустим

import { LanguageModel, Prompt } from "@effect/ai"                                                                         
import { Effect, Layer, Queue, Stream } from "effect"

const MessageProcessorLive = Layer.effect(                                                                                 
    MessageProcessor,                                                                                                        
    Effect.gen(function* () {                                                                                                
      const languageModel = yield* LanguageModel.LanguageModel                                                               
      const queue = yield* Queue.unbounded<Message>()                                
                                                                                                                             
      return {                                                                                                               
        process: (msg) =>                                                                                                    
          Effect.gen(function* () {                                                                                          
            const response = yield* languageModel.generateText({                                                             
              prompt: Prompt.make([                             
                Prompt.makeMessage("system", { content: "You are a helpful assistant." }),                                   
                Prompt.makeMessage("user", { content: msg.text })                                                            
              ])                                                                                                             
            }).pipe(                                                                                                               
              Effect.retry({ times: 3, schedule: Schedule.exponential("1 second") }),                                              
              Effect.mapError((e) =>                                                                                               
                new MessageProcessorError({ message: `LLM failed: ${e.message}`, cause: e })
              )                                                                                                                    
            )    
            
            //если ответ от ЛЛМ пришел, то мы обязательно его доставим, прервать не получится
            yield* Effect.forEach(response.text.split("\n\n"), (chunk) =>
              Effect.gen(function* () {                                                                                            
                yield* Queue.offer(queue, { userId: msg.userId, text: chunk })                                                     
                yield* Effect.sleep("5 seconds")                                                                                   
              })                                                                                                                   
            ).pipe(Effect.uninterruptible)                                          
          }),                                                                                                                
        responsesStream: Stream.fromQueue(queue)                                                                             
      }                                         
    })                                                                                                                       
  )

Вот тут, когда приходит новое сообщение, тред, который отвечал за обработку старого сообщения обрывается. И в реализации я использую реальный сервис для работы с ЛЛМ, который автоматически прервется. Не нужно abortController внутрь никуда прокидывать. Все легко тестируется. Реализаций MessengerAdapter может быть сколько угодно. Я пытался на промисах это написать нормально, но не вышло. Может у вас выйдет. Вот функциональные требования:

  1. Входящие сообщения — бот получает сообщения {userId, text} (дебаунс обрабатывать не надо, предполагаем, что он уже обработан)

  2. Обработка — для каждого сообщения: - Отправляем запрос в LLM API (HTTP POST) - Ответ разбиваем по \n\n на чанки - Каждый чанк отправляем клиенту с задержкой 5 секунд между ними

  3. Отмена предыдущего — если пришло новое сообщение, а предыдущее ещё обрабатывается: - Если запрос к LLM ещё в полёте — отменить HTTP-запрос - Если LLM уже ответил и идёт отправка чанков — дождаться отправки всех чанков, только потом начать обработку нового

  4. Ретраи — если LLM вернул ошибку, ретраим 3 раза с экспоненциальным backoff (1с, 2с, 4с). Ретраи тоже отменяются при новом сообщении

  5. Ошибки — если все ретраи провалились, отправить клиенту "Извините, бот временно недоступен. Сейчас подключим оператора." Бот продолжает принимать новые сообщения

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

skill issue

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

ну твои вкусы и привычки не определяют реальность.
перепиши верхний код на промисы, посмотрим где вилка, а где лопата)
а до тех пор skill issue

перепиши верхний код на промисы

Вот ИИ переписал)

class MessageProcessorError extends Error {
    constructor({ message, cause }) {
        super(message)
        this.cause = cause
    }
}

const sleep = (ms) => new Promise((res) => setTimeout(res, ms))

async function retryExponential(fn, times = 3, baseDelay = 1000) {
    for (let i = 0; i <= times; i++) {
        try {
            return await fn()
        } catch (e) {
            if (i === times) throw e
            await sleep(baseDelay * Math.pow(2, i))
        }
    }
}

function createMessageProcessor(languageModel) {
    // Queue — простой массив + подписчики
    const listeners = new Set()
    const queue = {
        offer: (msg) => {
            listeners.forEach((fn) => fn(msg))
        },
        subscribe: (fn) => {
            listeners.add(fn)
            return () => listeners.delete(fn) // unsubscribe
        },
    }

    async function process(msg) {
        // Генерация с ретраями
        let response
        try {
            response = await retryExponential(() => languageModel.generateText({
                prompt: [
                    { role: 'system', content: 'You are a helpful assistant.' },
                    { role: 'user', content: msg.text },
                ],
            }),
            3,
            1000,
            )
        } catch (e) {
            throw new MessageProcessorError({
                message: `LLM failed: ${e.message}`,
                cause: e,
            })
        }

        // Uninterruptible — доставляем все чанки без возможности отмены
        const chunks = response.text.split('\n\n')
        for (const chunk of chunks) {
            queue.offer({ userId: msg.userId, text: chunk })
            await sleep(5000)
        }
    }

    // Stream из очереди — AsyncGenerator
    async function* responsesStream() {
        const buffer = []
        let resolve = null

        const unsubscribe = queue.subscribe((msg) => {
            buffer.push(msg)
            if (resolve) {
                resolve()
                resolve = null
            }
        })

        try {
            while (true) {
                if (buffer.length > 0) {
                    yield buffer.shift()
                } else {
                    // ждём следующего сообщения
                    await new Promise((r) => resolve = r)
                }
            }
        } finally {
            unsubscribe()
        }
    }

    return { process, responsesStream }
}

// Использование
const processor = createMessageProcessor(languageModel)

await processor.process({ userId: '1', text: 'Hello' })

for await (const msg of processor.responsesStream()) {
    console.log(msg)
}

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

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

Всё нормально там с кодом, если у вас извращенная фиксация на никому не нужных отменах, это сугубо ваша проблема) Тем более реализовать отмену легко, AbortController на это есть)

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

Какой правде? Отмена нафиг не нужна, просто игнорируется ненужный ответ и всё. Ради ненужной отмены писать лютый говнокод, нет, спасибо)

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

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

я пытаюсь хоть сколько то конструктивный диалог с вами построить, сравнить код и так далее, чтобы не спорить о вкусах и какому-то объективному мнению прийти, но вы чувствуете свой skill issue и сливаетесь)

У вас плохо сформулированные бизнес требования, то нужно отменять, то не нужно и все ответы нужно показывать пользователю. Если нужно все ответы показывать пользователю, то просто ничего не отменяется и не игнорируется, показывается всё, в чем проблема) А про отмену это не отмазка, это реальный факт, я никогда не делаю отмен, ибо они не нужны, если ответ не актуальный, он игнорируется. Это стандартное решение для race condinition. А если и правда хотите конструктивно, то не надо кидать код, который работает и перейдя по ссылке сразу воспроизведется.

Например вот так игнорируется неактуальный ответ:
https://stackblitz.com/edit/typescript-bracpfqe?file=helpers.ts,index.ts
Или вот так debounce:
https://stackblitz.com/edit/typescript-ycuccy9d?file=helpers.ts,index.ts

Плюс их можно совместить вместе. А вообще я ещё в 2020 году написал плагин для Vite который сам добавляет всю разметку в коде для борьбы с race condition и debounce, я просто помечаю нужный метод декоратором и всё, весь код остается чистым, а на этапе сборки в него инжектятся хэлперы для асинхронщины. Там кстати даже и сетевые запросы отменяются с AbortController, но это я уже так, ради баловства добавил, а не ради надобности.

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

Что касается кода воспроизводимого, в терминале можете печатать: https://effect.website/play#9b6d502ad956

У вас, конечно, очень милый stillActual, но вот с exponential backoff retry утилитой вашей же он плохо уживается. Нужно какую-то другую утилиту писать(( Хорошо, что мне ничего такого городить не нужно, в Effect все из коробки и все хорошо отлажено)
Ну вы попробуйте все таки перепишите мой этот код так, как вам кажется правильным, а то никак иначе как отмазкой я вашу реакцию воспринимать не могу.



Вы так и не ответили на вопрос: А как вы отличаете гавнокод от кода, который вы просто не понимаете?
Вкусы и привычки разные бывают, они со временем меняются, но объективные метрики остаются, например: тестируемость, типобезопасность, арсенал возможностей. Или вы это все тоже отрицаете?

А как вы отличаете гавнокод от кода, который вы просто не понимаете?

Если я смотрю на код и его не понимаю, но он не выглядит противоестественным и вырви глазным, то это просто код который я не понимаю, а если этот код выглядит противоестественно, то это говнокод.

Пример не связанный с Effect, RxJs и прочей вырви глазной ересью:

// Говно код
const rating = user.role === 'admin' ? 100 : user.role === 'editor' ? 80 : 50;

// Код здорового человека
let rating = 50;
if (user.role === 'admin') {
    rating = 100;
} else if (user.role === 'editor') {
    rating = 80;
}

тестируемость, типобезопасность, арсенал возможностей

Это всё более чем доступно при подходе здорового человека, написал 1 раз хэлпер функции/классы/плагины и всё дела. А писать говнокод который вы предлагаете, увольте)

Что касается кода воспроизводимого, в терминале можете печатать: https://effect.website/play#9b6d502ad956

Ну напечатал, и что это дает? Ну напечатал он ответ, дальше то что? Где киллер фичи недоступные коду здорового человека? Более того, весь код это просто грязное месиво на которое без слез не взглянешь. Представим что он бы работал в 5 раз быстрее, жрал в 5 раз меньше памяти, и даже этих реально весомых аргументов даже близко не достаточно, чтобы писать такой отвратительный код. Он просто тупо противоестественный, это все равно что ходить всегда задом наперед, или выгибать колени в другую сторону)

Вот я повторил поведение из вашей ссылке которое я вижу глазами:

https://stackblitz.com/edit/typescript-yzz55zpm?file=helpers.ts,index.ts

Там и debaunce и отмена(в данном случае просто игнор)

И весь код при этом максимально просто и понятный для всех кто просто тупо знаком с JS.

А вам в голову не приходило, что противоестественность-это ваша субъективная оценка?) Это же буквально значит: "код выглядит не так, как я привык его видеть"))

Опять же, вместо объективной оценки вы кидаетесь слоганами, которые ничего, кроме как вашу неприспособленность читать такой код не отражают

весь код это просто грязное месиво на которое без слез не взглянешь

Киллер фичи ищете, ну вот, например, композабельность, отменяемость процессов (заметьте, ни одного abort controller, но у меня отменяется все вплоть до retry операций), тестируемость (я могу просто написать тестовые имплементации MessengerAdapter, MessageProcessorи все очень легко замокать), типобезопасность (все ошибки и зависимости у меня отслеживаются, типизируются). Сравните по этим параметрам наши с вами реализации и подубавьте снобизм, может что-то для себя новое откроете))



Я еще раз продублирую требования, которым ваш код не соответствует:

  1. Входящие сообщения — бот получает сообщения {userId, text} (дебаунс обрабатывать не надо, предполагаем, что он уже обработан)

  2. Обработка — для каждого сообщения: - Отправляем запрос в LLM API (HTTP POST) - Ответ разбиваем по \n\n на чанки - Каждый чанк отправляем клиенту с задержкой 5 секунд между ними

  3. Отмена предыдущего — если пришло новое сообщение, а предыдущее ещё обрабатывается: - Если запрос к LLM ещё в полёте — отменить HTTP-запрос - Если LLM уже ответил и идёт отправка чанков — дождаться отправки всех чанков, только потом начать обработку нового

  4. Ретраи — если LLM вернул ошибку, ретраим 3 раза с экспоненциальным backoff (1с, 2с, 4с). Ретраи тоже отменяются при новом сообщении

  5. Ошибки — если все ретраи провалились, отправить клиенту "Извините, бот временно недоступен. Сейчас подключим оператора." Бот продолжает принимать новые сообщения

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

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

Вы не привыкли читать Effect код, а я вот JS код читать очень даже привык. И вот строка непонятно, что делает. stillActualDebounce-это вообще что значит?) Пахнет плохим кодом)

if (!(await stillActualDebounce(1000)) || !stillActual()) return;

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

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

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

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

но вот в остальном:

ну, во-первых, иногда отмены все таки реально нужны и игнорировать не получится. наш случай может не самый показательный, но даже тут есть следующий кейс:
мы уперлись в RateLimit, что в принципе частая ситуация при работе со сторонними сервисами. В вашем коде начинается экспоненциальный бэкофф, и если юзер в этот момент напишет что-то еще, ретрай запросы так и будут идти, только мешая нам решить проблему с RateLimit. Я надеюсь, вы понимаете, что это не воображаемый бизнес кейс, и не начнете отмазываться)) https://stackblitz.com/edit/typescript-3yem5krj?file=index.ts (тут репродакшн кейса, напишите fail два раза)

во-вторых, у вас уж больно много внутренней имплементации протекает в бизнес логику, какие-то непонятные queue.next(), queue.abort(), await succesDebounced(1000), await queue.withForQueue() ,

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

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

вы просто напрототипировали мой пример, игнорируя то, что мой код был хорошо смоделирован (благодаря системе сервисов Effect'а), легко тестируем, максимально типизирован. Чтобы добавить новый источник источник сообщений, например, телеграм, мне достаточно изолированно написать новую реализацию TelegramMessengerAdapter (ллмка с этим очень хорошо справляется, благодаря типам и contract-first) и в одной строке подменить реализацию. я не изобретал велосипед, чтобы простые отмены реализовать. мне не нужно хелперы бесконечные поддерживать и закладывать в них гибкую имплементацию, это все уже сделали за меня. При этом всем количество строчек у нас сопоставимое.

У меня там, конечно, какие-то моменты тоже страдают:

 например вот тут протекает бизнес логика
например вот тут протекает бизнес логика

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

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

ну, во-первых, иногда отмены все таки реально нужны

Там где они вдруг реально нужны 1 из 10000, как бы так же легко это реализуется как и все остальное я реализовал.

во-вторых, у вас уж больно много внутренней имплементации протекает в бизнес логику, какие-то непонятные queue.next()queue.abort()await succesDebounced(1000), await queue.withForQueue()

Непонятные?) Во первых там есть есть комментарии, плюс их можно дополнительно расставить, во вторых с чего вдруг Effect.* понятнее стали? :D

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

Что правда?)

Заменить функцию на другую или внутри llmRequest вызвать другой источник
Заменить функцию на другую или внутри llmRequest вызвать другой источник

тестировать этот код будет очень сложно (скорее всего настолько, что никто этого делать не будет), дебажить и фиксить баги так, чтобы ничего другого не сломать тоже надо постараться.

Что правда?)) Прям вообще у меня такое месиво из кода и вообще не ничего не понятно, он же не читается сверху вниз слева направо да?)

Я надеюсь, вы понимаете, что это не воображаемый бизнес кейс, и не начнете отмазываться)) https://stackblitz.com/edit/typescript-3yem5krj?file=index.ts (тут репродакшн кейса, напишите fail два раза)

И что? И где тут проблема?

Вы так говорите как будто добавить одну строчку чтобы не делать ретраи при достижении rate limit и сделать некий cooldown в N секунд/минут прежде чем по новой начать отправлять запросы это сложно)))) Это так же элементарно как и все остальное. Но видимо не для вас, раз вы не в состоянии такие вещи решать если их за вас не решила библиотека. А если для чего-то нет библиотеки, то значит для вас эта задача невыполнима)


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

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

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

Непонятные?) Во первых там есть есть комментарии, плюс их можно дополнительно расставить, во вторых с чего вдруг Effect.* понятнее стали?

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

чтобы понять это надо вчитываться в реализацию-редфлаг

Это так же элементарно как и все остальное

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

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

Что правда?)) Прям вообще у меня такое месиво из кода и вообще не ничего не понятно, он же не читается сверху вниз слева направо да?)

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

Что правда?)

Заменить функцию на другую или внутри llmRequest вызвать другой источник
Заменить функцию на другую или внутри llmRequest вызвать другой источник

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

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

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

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

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

Вы реализуете логику используя противоестественный вырвиглазный говнокод. Прям месиво.

😂😂😂😂, ну конечно, убеждайте себя дальше, на самом то деле просто skill issue и задетое самолюбие)

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

Ну я об этом в статье писал (которую вы не читали судя по всему):

Конечно, вся функциональность эффекта не уникальна (кроме, наверное, типизированного DI). На каждый механизм, который я разбирал, можно будет найти npm пакет, который делает тоже самое. Но зачем? В Effect уже есть все, что нужно. Оно написано единообразно, все прекрасно друг с другом сочетается, активно поддерживается и позволяет писать хороший код, не будучи system design гением. С Effect хорошо работают LLM, и благодаря такой мощной типобезопасности LLM быстро понимает, где накосячила

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

В сухом остатке:

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

  • Вы написали сложнотестируемый гавнокод для такой простой задачи

  • Вы ничего про Effect не знаете, кроме того, что там используют генераторы и пытаетесь как-то о чем-то рассуждать

Ваш единственный аргумент "вырвиглаз" и "противоестественность"

Это сразу на корню перечёркивает вообще всё что связанно с effect. Поэтому как бы да, какая разница что в него встроили, если пользоваться этим безобразим максимально противно. Просто месиво из лапши и вся она кишит Effect.*

Вы написали сложнотестируемый гавнокод для такой простой задачи

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

Вы ничего про Effect не знаете, кроме того, что там используют генераторы и пытаетесь как-то о чем-то рассуждать

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

Я считаю лишней тратой времени решать то, что уже решено за меня и хорошо работает.

Ну да ну да) Я умный, просто мне лень решать эту задачу, а так я конечно же знаю как её решить. Поэтому без решебника никуда.

позволяет писать хороший код, не будучи system design гением

Хороший?)) Вы сделали много ошибок в слове плохой и лапша.

https://effect.website/play#9b6d502ad956
Ну это прям дичь:

Скрытый текст
Скрытый текст
Скрытый текст

 не будучи system design гением

Ну вот вы опять обнажаете что базовые вещи для обычного разработчика, типо debounce, race condition, отмена запроса, очередь и т.п. Для вас это system design гений)

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

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

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

@effect/cluster - распределённые системы: sharding, entity-модель (virtual actors), singleton-сервисы, message routing между нодами кластера, cron и workflows

Ну да ну да) Я умный, просто мне лень решать эту задачу, а так я конечно же знаю как её решить. Поэтому без решебника никуда.

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

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

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

Спорить о вкусах-дело неблагодарное, особенно с недалеким человеком. Для вас код на Effect вырвиглазный, для кого-то (а судя по 7 миллионам скачиваний в неделю таких много) такой код нормальный. Давайте просто сравним наш с вами код по объективным метрикам: тестируемость, типизированность, корректная работа с ошибками. Предлагайте свои варианты, даже можете предложить свою проблему. Предполагаю вы сейчас опять скажете, что у вас кровь из глаз и вам сравнивать ничего не надо, вам и так все понятно)

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

Вот тесты:
https://stackblitz.com/edit/typescript-p2ctgemi?file=helpers.test.ts,sendToBot.test.ts,vitest.config.ts

Давайте просто сравним наш с вами код по объективным метрикам

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

Более того ваш говнокод не реализовывает даже ваше ТЗ которое вы мне писали и которое реализовывает мой прототип.
Стоит только попасть в ветку где llm должен вернуть ошибку так все, у вас просто на этом всё заканчивается и никаких сообщений об ошибках никаких подключений операторов и т.п. нет. Т.е. у вас даже код специально с выкинутым функционал, чтобы казаться чуть менее ущербным. А если вы реально реализуете то же, что уже реализовано у меня в прототипе, то все станет ещё страшнее.

Сдаётся мне вы его подредактировали после того как я на него первый раз посмотрел и выкинули оттуда часть говнокода) Хотя может и изначально вы даже это всё не реализовали, зато с пеной у рта говорили что у меня не все по вашему тз))

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

Стоит только попасть в ветку где llm должен вернуть ошибку так все

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

😂😂, я прост забыл ограничение на ретраи поставить, и еще там sleep долгий был, на 1 секунду поменял

одной строчкой 😉 times добавил
одной строчкой 😉 times добавил

В очередной раз вы пургу несете, не разобравшись) Ну бывает, запутался в одной строчке))

Сдаётся мне вы его подредактировали после того как я на него первый раз посмотрел и выкинули оттуда часть говнокода)

Нет, просто вы начинаете привыкать к хорошему коду)

Ну очередной бред, у меня прототип написанный на скорую руку, в нем уже функционала больше чем в вашей лапше,

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

У вас настолько гавнокод что вы мокаете рандом функцию, вместо того, чтобы мокать llm вызов свой... Это вот то, о чем я говорил. Ваш код плохо написан, вам удобнее замокать рандомизатор. У вас нет инверсии зависимостей, вам мокать просто неудобно. Надо будет по файлам разбивать, и модули мокать. И это в принципе нормально и даже удобно, когда мало зависимостей. Когда зависимостей много, это забирает энергию и создает нежелание вообще к тестам прикасаться. С Effect все наоборот, вы знаете, что надо мокать на уровне компилятора. Вам не нужно в коде копаться, чтобы понять, что вы забыли замокать. Вам не нужно париться с моком модулей и корректностью типов, все там сразу четко работает.

Ну так что, по объективным критериям оценивать будем?)

Ну так что, по объективным критериям оценивать будем?)

Объективные критерии в реальной жизни:
1) Код работает? Да/Нет
2) Код легко читаемый, логичный и понятный? Да/Нет
3) Насколько без болезненно можно вносить в код правки? от 1 до 10 (При выполнении пункта 2, автоматически можно считать что 10)
4) Код следует принципам KISS? (Опять же всё так же прямая зависимость от пункта 2)

По сути только пункты 1 и 2 самые важные критерии.

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

А ваши нездоровые фиксации и фантазии можете оставить в своих пет проектах)

В очередной раз вы пургу несете, не разобравшись) Ну бывает, запутался в одной строчке))

И где ответы оператора после того как он должен "подключиться"? У меня есть, где обработка сообщения которое ввел пользователь(пусть фигурирует в ответах бота как у меня)? Пусть ещё при вводе fail llm фейлится. У меня всё это есть. Дополните хотя бы этим и ссылку скиньте

Тоже самое, только код здорового человека:

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

Sign up to leave a comment.

Articles