Привет %username%!

Сегодня я хотел бы рассказать о том, как мне помог в проекте плохой код.

Кому интересно, прошу под кат.

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

Все вроде просто, но были постоянные жалобы сотрудников на падение приложения при фотографировании. Анализ кода показал, что при вызове intent приложения камеры, Android убивал приложение из-за нехватки ресурсов и по возвращению результата в активити, последняя пересоздавалась и не содержала нужных для работы данных. Пересоздание активити просто не обрабатывалось. Ну да, есть же configChanges *sarcasm*

Шаг 1. Обработка пересоздания


Помучавшись 2 дня, поправив код, выкинув повторяющиеся фрагменты я принялся тестировать. Закинул в эмулятор taskkiller, запустил камеру, убил процесс. Фоткаю — красота!!! Все красиво, фотка есть. Нажимаю сохранить и… бабах) Приложение упало.

Шаг 2. Singleton — злое зло


Причина падения — нет данных о текущем отчете. Оказалось, что в приложении был чудо-синглтон, который хранил в себе ВСЕ! Абсолютно все критические данные, необходимые для работы приложения и даже больше. Это и токен авторизации, и 40 полей состояния отчета и ссылки на классы, описывающие отчет и геоданные юзера и еще пару коллекций Bitmap. В одно мгновение мы лишились всего, чего только можно было лишиться.

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

Шаг 3. Меняем Singleton


Все, что пришло в голову — исправить синглтон так, чтобы он стал надежным и при этом абсолютно не поменялся его интерфейс. Придумано было следующее — все что только можно запихнуть в SharedPreferenses сохранялось туда.
public String getAuthToken() {
        return getStingFromPref(AUTH_TOKEN);
}
.....
// и так далее.


Грубо, тупо, зато не нужно менять код во всем проекте и наш Singleton стал более устойчив (остались коллекции Bitmap, с которыми пока ничего не сделал). Все объекты, которые только можно сериализовать были сериализованы и тоже запихнулись в SharedPreferences.

Приложение ушло на тест, в прод и падения прекратились на 70% устройств. На остальных после отправки результата с камеры, пересоздавалась не текущая, а предыдущая активити в стеке. Для меня это пока загадка. Гуру — объясните в комментах.
Суть такая — Активити А стартует Активити Б с получением результата. Активити Б запускает intent камеры с получением результата.
Когда в процессе работы камеры процесс убивался, по созданию фотки пересоздавалась активити А. У активити Б не вызывался ни onCreate, ни onActivityResult. (P.S. Android 4.4)

goоgle и stackowerflow ответа не дали, пришлось идти другим путем и писать больше плохого кода.

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

Шаг 4. Финальный. Foreground-мусоровоз


Как же заставить Android не убивать процесс? Мои мысли были такими: «Если сказать операционке, что у нас есть важный foreground сервис, выполняющий важные нам вычисления ровно столько времени, сколько мы фоткам товар. Это определенно может сработать».

Так родился в проекте foreground-мусоровоз. Он гонял цикл от 0 до 60 и в теле засыпал на секунду. Как только приходил результат с камеры — сервис убивался. Приложение было собрано и отдано на тестирование.
Результат — KitKat оказался умнее и прибивал процесс. То-есть не изменилось ничего.

Я уже было отчаялся, когда мне в голову пришла еще одна мысль: «А если тупо сохранить ссылку на активити в синглтоне и вызвать startActivityForResult прямо из foreground-мусоровоза?». Я никогда так не делал и, надеюсь, больше не придется.

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

Вместо заключения. Заказчик предупрежден, что у него плохое приложение и я сделал его еще хуже, но оно работает так как этого ждут сотрудники компании.

Напоследок. Расскажите про свой самый «плохой код» в карьере. Давайте поднимем друг другу настроение.

Комментарии (70)


  1. lavelas
    25.11.2015 23:56
    +9

    Не ожидал, что народ начнет минусовать мою историю :(

    Всем захабренным поясняю, если вам как фрилансеру говорят почини (что равносильно перепиши с нуля), но мы тебе заплатим $100 от силы то ты:
    1. Как фрилансер — пошлешь (работы часов на 200)
    2. Как человек пожалеешь и постараешься сделать за 20 часов франкинштейна (поверьте, там хуже некуда).


    1. Bringoff
      25.11.2015 23:58
      +31

      100$ за 20 часов? Печальная история, конечно.


      1. Alexufo
        26.11.2015 00:21

        а удовольствие куда дели?


        1. crmMaster
          26.11.2015 10:24
          +4

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


      1. masterL
        26.11.2015 12:09

        Это получается 800 долларов за 160 часов (рабочий месяц) — вполне обычная зарплата программиста не в столице.


        1. Bringoff
          26.11.2015 13:32
          +1

          Фриланс — это не офис. Нормальных фрилансеров с рейтом меньше 10$/h я еще не встречал.


          1. VolCh
            26.11.2015 22:07

            Рейт (реальный) фрилансера зависит прежде всего от его наглости умения продавать себя.


          1. taliban
            27.11.2015 13:35
            +1

            Чудом вставили волшебное слов «нормальный» и вышли чистым из воды. Рейт фрилансера зависит от опыта работы, умения и наглости. Есть полно с рейтом меньше 10$/h.


            1. Bringoff
              28.11.2015 09:14

              Может, где-то их и полно (на каком-то fl.ru), но если обратиться к статистике, то в Украине, допустим, средний рейт 21-22$/h. То есть, чтобы среднее число было таким, тех, кто имеет рейт, скажем, 5$/h, должно быть довольно мало. Или тех, кто берет больше 30, много)


              1. taliban
                02.12.2015 23:50

                Можно мне ссылочку на пруф, статистику по разным местам, например? Ато Вы не очень тянете на человека которому можно доверять безоговорочно :)


                1. Bringoff
                  03.12.2015 00:37

                  Фу, ну что за переход на личности? Вот dou.ua/lenta/articles/freelance-eastern-europe/


                  1. VolCh
                    03.12.2015 07:48

                    Не похоже, что там анализируются российские биржи фриланса.


                    1. Bringoff
                      03.12.2015 08:53

                      А я о чем говорил?

                      Может, где-то их и полно (на каком-то fl.ru)

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


                  1. taliban
                    03.12.2015 23:20

                    Спасибо, отличная статистика, ребята хорошо поработали :)


    1. Lure_of_Chaos
      26.11.2015 10:21
      +10

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

      Мы не хотим Вам заплатить за огромную работу, пожалуйста, поработайте еще столько же «за еду»!

      Офигеть. Как у Вас гордости не хватило вообще их послать нафиг с тем, что у них было? Или это такая маленькая месть — преумножение их говнокода своим?


      1. VolCh
        26.11.2015 12:30
        +3

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


  1. LionAlex
    26.11.2015 00:11
    +1

    Не просто Singleton, а Registry, судя по описанию.


  1. SerafimArts
    26.11.2015 02:52
    +1

    Расскажите про свой самый «плохой код» в карьере

    Сознательное программирование я начинал с PHP (несознательное с VB6). Вам точно хочется, что бы я рассказал о том что там творилось? =)


  1. rboots
    26.11.2015 04:06
    +1

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


    1. kolipass
      26.11.2015 06:39
      +4

      Есть общепринятое мнение, что классы-боги это плохо. Я понял из статьи, что lavelas столкнулся как раз лицом к лицу с богом).


      1. Suvitruf
        26.11.2015 07:20
        +2

        Осталось только определить критерии, когда класс становится классом-богом.


        1. FractalizeR
          26.11.2015 10:42
          +3

          Вероятно, когда нарушает SRP. Тогда останется только определить, что является «обязанностью» класса, верно? ;)


          1. EngineerSpock
            26.11.2015 15:27

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


            1. VolCh
              26.11.2015 22:09

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


              1. EngineerSpock
                27.11.2015 11:26

                Почти всегда можно. Другое дело, что нам не всегда следует удовлетворять SRP. Всё зависит от вероятности изменений по тем или иным аспектам, а вероятности эти обычно определяются эвристически. Невозможно везде удовлетворять SRP и собсна делать код PURE SRP.

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


    1. xoxol_89
      26.11.2015 07:13
      -4

      Плюс хранение важных данных в Синглтоне — тоже весьма частое заблуждение новичков.


      1. Z80A
        26.11.2015 09:52
        +5

        Крайне категоричное и ничем не подтвержденное высказывание.


        1. xoxol_89
          26.11.2015 11:03

          Что именно категоричное и не подтвержденное?
          То, что хранение данных в синглтоне — плохая идея? Или то, что это частое заблуждение новичков?


          1. Mikhail_dev
            26.11.2015 11:10

            То, что хранение данных в синглтоне плохая идея.
            Мы довольно активно используем синглтоны, потому как они:

            1. Удобны, особенно как обсерверы
            2. Редко выгружаются (ибо загрузка происходит класслодером)
            3. Являются хорошим кешем.

            И то, что они выгружаются… И что с того? в конструкторе инициализацию прописал и всё встанет на свои места. Критические данные кешируем на ПЗУ при необходимости, откуда мы их берем при инициализации. Что не так то?


          1. Mikhail_dev
            26.11.2015 11:11
            -3

            То, что хранение данных в синглтоне плохая идея.
            Мы довольно активно используем синглтоны, потому как они:

            1. Удобны, особенно как обсерверы
            2. Редко выгружаются (ибо загрузка происходит класслодером)
            3. Являются хорошим кешем.

            И то, что они выгружаются… И что с того? в конструкторе инициализацию прописал и всё встанет на свои места. Критические данные кешируем на ПЗУ при необходимости, откуда мы их берем при инициализации. Что не так то?


            1. xoxol_89
              26.11.2015 11:45

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


              1. lavelas
                26.11.2015 12:23

                Для восстанавливаемых данных лучше использовать retainInstance фрагменты.


              1. Mikhail_dev
                26.11.2015 12:37

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


                1. lavelas
                  26.11.2015 12:51

                  Вот Вам и проблема синглтона для Андроида. Что делать, если мы запустили выбор файла и пока была открыта галерея наше приложение закрылось и по возвращению у нас нет сессии, перекинуть на окно логина? Круто, я в чате шлю файл другу, но вместо этого меня выкидывает из сессии. Очень крутое поведение.
                  Это нужно в onSaveInstanceState сохранить id сессии и при ресторе его обратно закинуть в синглтон?


                  1. Mikhail_dev
                    26.11.2015 13:10

                    Вот Вам и проблема синглтона для Андроида. Что делать, если мы запустили выбор файла и пока была открыта галерея наше приложение закрылось и по возвращению у нас нет сессии, перекинуть на окно логина? Круто, я в чате шлю файл другу, но вместо этого меня выкидывает из сессии. Очень крутое поведение.

                    В чем проблема то? Всё зависит от кейса. Я сейчас же говорил про кейс к примеру тех же банковских приложений, когда сессия будет считаться невалидной, и её не надо сохранять. Но можно и сохранить, это ВООБЩЕ не проблема. Взяли и сохранили в том же синглтоне. Что не так?


                    1. i_user
                      26.11.2015 14:06

                      Чтоб вам этот синглтон и классы его использующие покрыть регрессионными тестами, чтобы оценили, почему плохо.

                      Удобно — очень удобно, конечно. Но это код, который годится только на «написать и забыть» — потому что гарантировать, что
                      а. он работает
                      б. я что-то изменил, а что-то там где-то еще не отломалось — не просто сложно, а практически невозможно.

                      То есть код для фриланса, с которым, конечно, можно мириться, но гордиться и защищать — не стоит)


                      1. Mikhail_dev
                        26.11.2015 14:40

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


                        1. i_user
                          26.11.2015 14:45
                          +3

                          Важно, что в синглтоне — потому что синглтон — это shared state протянутый через все приложение.
                          Если ты изменил этот стейт — то не факт что все потребители этого синглтона ожидают это изменение и правильно на него среагируют. И проверить, что ты ничего не сломал можно только либо «регрессионным тестированием» мануальных QA, либо верой, что «я же четкий, я ж не сломал»

                          В этом и фундаментальное отличие синглтона от несинглтона — область видимости обычного объекта позволяет локализовать потенциальные места для возникновения ошибки, а синглтона — нет.


                          1. Mikhail_dev
                            26.11.2015 15:08

                            Я полностью согласен. И я понимаю этот минус.
                            Просто в статье, в комментариях пишут что мол плохо, что антипаттерн, но всякие доводы что там несколько раз может быть инициализирован, что данные в нём теряются и подобное — смешны. К этому и вёл я.

                            Если такая уж тема пошла, то скажите, как быть с глобальными данными? Есть к примеру у нас довольно часто изменяющиеся данные, которые мы должны получать в разных частях приложения, будь то сервис или UI. Сохранять в базу — это будет оверхед, ибо данные очень часто изменяются (к примеру датчики телефона, данные с NMEA протокола и т.д.). Постоянно дёргать ПЗУ будет неправильно. Как эту задачу решить элегантно? Я использую для этого Singleton.


                            1. i_user
                              26.11.2015 15:15
                              +2

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

                              Таким образом у нас нет способа получить значение этих данных, кроме как среагировав на него и нет способа изменить его, кроме как через этот синк.

                              Хранить же эти данные можно и так как описали вы. Важно — как вы предоставляете доступ к этим данным.


                              1. Mikhail_dev
                                26.11.2015 16:09

                                Понятно, спасибо. К сожалению пока не имел дело с DI.


                            1. lavelas
                              26.11.2015 15:28

                              Вы можете мне объяснить такой момент:
                              «Если ты изменил этот стейт — то не факт что все потребители этого синглтона ожидают это изменение и правильно на него среагируют.» Вы отвечаете «Я полностью согласен. И я понимаю этот минус»

                              А если стейт изменился, потому что «несколько раз может быть инициализирован, что данные в нём теряются» — это смешной довод.

                              Как так??


                              1. Mikhail_dev
                                26.11.2015 16:06

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

                                А когда приложение работает и там синглтон работает в стиле обсервера, когда активити подписывается и отписывается к примеру в метода onResume, onPause, то тут никаких потерь нет, ибо синглтон сам по себе инициируется класслодером, и если класслодер умирает, то умирает и процесс приложения, то значит умерли и все Activity (лишь только Task может жить).


                                1. lavelas
                                  26.11.2015 18:34

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

                                  А когда приложение работает и там синглтон работает в стиле обсервера, когда активити подписывается и отписывается к примеру в метода onResume, onPause, то тут никаких потерь нет, ибо синглтон сам по себе инициируется класслодером, и если класслодер умирает, то умирает и процесс приложения, то значит умерли и все Activity (лишь только Task может жить).


                                  А пардон, понял этот момент.

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


            1. lavelas
              26.11.2015 12:22

              1. Удобны, особенно как обсерверы

              Позвольте полюбопытствовать, как? Поделитесь опытом.

              В целом не буду с Вами спорить, на вкус и цвет все фломастеры разные.


              1. Mikhail_dev
                26.11.2015 12:34

                Есть к примеру Singleton, к которому подписываются и отписываются Activity/Fragments/Service при изменении нашего местоположения. Это один из кейсов.

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


  1. EndUser
    26.11.2015 05:09

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

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


  1. Sect0R
    26.11.2015 08:58
    -10

    «goggle и stackowerflow ответа не дали»:
    «goggle» — это оборот речи или ошибка?


    1. lavelas
      26.11.2015 10:32
      +2

      О, Вы правы. Подправил)


    1. Lamaster
      26.11.2015 10:41
      +1

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


  1. Z80A
    26.11.2015 09:55
    +11

    Singleton — злое зло.

    Книжек умных перечитали?


    1. lavelas
      26.11.2015 10:35
      +1

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


      1. Mikhail_dev
        26.11.2015 12:39

        Вы же понимаете, что в приложениях для Android абсолютно нормально при очередном вызове getInstance() получить новый экземпляр класса? К этому нужно быть готовым.

        А вот с этого момента поподробнее. С чего вы решили что вы получите новый экземпляр класса?


        1. lavelas
          26.11.2015 14:10
          +1

          А вот с этого момента поподробнее. С чего вы решили что вы получите новый экземпляр класса?

          Вот Вам пару вариантов:
          1. Допустим Вы инициализировали статический Singleton в стартовой активити. Когда она будет выгружена, Вы потеряете ссылку на Singleton и получите новый инстанс.
          2. Вы инициализировали статический Singleton в унаследованном от Application классе. Ваш юзер ходит по окнам и все хорошо и прекрасно. Потом ему приходит СМС и он переключается на ее чтение, а параллельно у него запущен скайп, играет музыка и в смс ему прислали ссылку, по которой он открыл браузер. Android выгружает Ваш процесс и когда юзер возвращается в приложение Вы так же получите новый инстанс.

          P.S. Я не говорю, что этот паттерн можно или нельзя использовать в андроиде. Но я точно знаю, что его использование может сыграть злую шутку.
          Конечно, Вы можете сказать, о том, что:
          Но можно и сохранить, это ВООБЩЕ не проблема. Взяли и сохранили в том же синглтоне. Что не так?

          Это и не есть проблема. Проблема в том, что вполне реально что-то не учесть.


          1. Mikhail_dev
            26.11.2015 16:19

            1. Допустим Вы инициализировали статический Singleton в стартовой активити. Когда она будет выгружена, Вы потеряете ссылку на Singleton и получите новый инстанс.

            я слышал про то что если инициировать статическую ссылку в Activity, то это чревато выгрузкой и его. Но можно ведь и не в Activity инициировать его? Например в классе, унаследованном от Application, который живёт всегда, пока живо приложение?
            2. Вы инициализировали статический Singleton в унаследованном от Application классе. Ваш юзер ходит по окнам и все хорошо и прекрасно. Потом ему приходит СМС и он переключается на ее чтение, а параллельно у него запущен скайп, играет музыка и в смс ему прислали ссылку, по которой он открыл браузер. Android выгружает Ваш процесс и когда юзер возвращается в приложение Вы так же получите новый инстанс.

            Ну что же с того? Процесс выгрузился и всё потерялось, что не сохранилось. Это логично, и так должно быть. Ведь умер не синглтон, а умерло всё приложение (поправка: процесс). При умирании UI процесса как бы всё имирает в UI.
            Это и не есть проблема. Проблема в том, что вполне реально что-то не учесть.

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


  1. Ivan22
    26.11.2015 10:21
    +2

    Мир полон костылей — это факт. Вот я представляю, что приезжаю на СТО и мастер мне что-там долго говорит, говорит, говорит и спрашивает потом — ну что будем капиталить и через месяц и 3000$ — будет как новенькая!!! Или говорит я тут, фигак, фигак за 15 минут, заплатку на 100 рублей поставлю и можно дальше ездить, хотя концептуально это не правильно. Что я выберу!!?????


    1. vbif
      26.11.2015 10:45
      +7

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


      1. Loki3000
        26.11.2015 12:14
        +2

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


  1. Lamaster
    26.11.2015 10:49
    +3

    Была одна история с кодом, за который мне до сих пор стыдно:
    Надо было посчитать приблизительные сроки доставки товара в почтовой службе. Но так как исходников не было, то пришлось декомпилировать DLL'ку с помощью dotPeek (очень рекомендую). В процессе полетели bool пеерменные, которые преобразовались в int, а обратно при компиляции не сконвертировались в bool. Но так как многие проблемные места были обёрнуты try'ями без логирования, то ошибка нашлась не сразу. Но это всё последующие умозаключения. Сама проблема заключалась вот в чём: было несколько файликов с пунтами доставки и временем/стоимостью доставки в каждом регионе. Для новосозданных регионов (в частности Крым, Симферополь, Севастополь, etc.) этой информации не было, а для некоторых старых информация в рантайме терялась (детали не помню, просто что-то не заработало). Поэтому для таких случаев, когда невозможно посчитать сроки доставки всё равно необходимо было выдавать хоть какие нибудь цифры. Первая мысль была про рандом, но сроки доставки должны совпадать для каждых двух пунктов соответственно, а не разниться при каждом обновлении страницы. Поэтому было решено сложить названия пунктов, взять от них длину, делённую по модулю на определённое значение и прибавить минимальный коэффициент. Все данные подбирались опытным путём.


    1. Ivan22
      26.11.2015 11:02
      +20

      На Почте России ваш код до сих пор работает!!!


  1. 26.11.2015 11:15

    Вот допустил ли я ошибку при проектировании, когда заложил int в качестве ключа для записей (состояние датчиков авто) или другой, ответственный, когда пропустил приближение трагедии.
    Но буквально в этот вторник записей стало больше 2147483647.
    Беда не пришла одна: при обновлении БД кончилось место на дисках.
    В итоге — 9 часов даунтайма (в рабочее время) и, после, DDOS от авто с архивными данными за это время.
    Частичная потеря данных за 1,5 часа с момента как закончился int, до момента обнаружения и осознания с остановкой всего.

    P.S.: в течение месяца ожидается окончание int и для другого типа данных, надеемся хоть тут не облажаться


    1. vbif
      26.11.2015 11:29
      +1

      Хорошо хоть не float :)


    1. JustRoo
      26.11.2015 13:43

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


      1. 26.11.2015 14:50

        Не было писали ТЗ, делали для себя.


  1. guessss_who
    26.11.2015 14:45
    +3

    Самый плохой код в карьере? О, это очень просто. Пришлось как-то исправлять ошибку в одном PHP-шном проекте, который генерировал отчеты о работе бригад по ремонту оборудования.

    Все данные для отчетов были в БД Oracle. Готовый отчет представлял собой HTML-ную таблицу. В чем нюанс? HTML-код строк этой таблицы формировался внутри SQL-запроса, путем конкатенации строк с HTML-тегами и результатов извлеченных из оракловых табли. SQL-запрос был внутри PHP-шного кода. PHP-шный код, ошметками, был внутри HTML-я (заголовок таблицы и т.п.).

    Кстати, причина, из-за которой вообще пришлось трогать это говно: Oracle ругался когда в результате конкатенации HTML-кода и результатов из таблиц формировалась строка >4K (или даже >8K, уже не помню точно).


    1. ncix
      26.11.2015 15:22
      +2

      Мсье знает толк!


  1. ncix
    26.11.2015 15:40

    Покаюсь и я.
    В одном крупном продуктовом проекте для бизнеса, который я начинал как тимлид-руководитель, кто-то кинул идею о важности добавления новых таблиц чуть ли не на лету под новые хотелки каждого клиента. И я предложил то что мне казалось тогда гениальным решением — засунуть все метаданные в гигантский XML, по которому будут строиться SQL-запросы, в том числе на генерацию БД. И в довесок еще и весь интефейс в XML описали, чтоб значит все таблички и формочки генерились единообразно «на лету».
    А потом заказчик очень захотел чтоб все это работало под Firebird и MSSQL. Умный гетерогенный генератор запросов оброс кучей фишечек костыликов для всевозможных ситуаций и разных СУБД. Все это осложнялось неоптимальной структурой данных, доставшихся от смежного проекта. Генератор подчас выдавал запросы длиной в несколько страниц, на отладку и разбор которых уходили долгие часы.
    Напоследок ко всему этому прикрутили синхронизатор распределенных баз данных со своими запросами, очередями, транзакциями и оптимизацией запросов на лету. И конечно же все это было завернуто в многопоточную логику с сетевым взаимодействием. И оно даже работало, хотя и нестабильно. И это было крайне тяжело отлаживать и исправлять. Но я таки дотянул проект до пилотного внедрения.
    Проект тянулся очень долго, кто-то из разработчиков уволился, кто-то вырос в тимлиды, команда сильно обновилась. И новые ребята просто не смогли до конца понять и принять всю эту сложность. А после моего увольнения продавили идею переписать почти всё с нуля. Наверное, вспоминают меня недобрым словом.


    1. andreycha
      26.11.2015 19:23

      Ждем историю этих ребят через пару лет :).


  1. IRainman
    27.11.2015 00:04

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

    P.S. О своём самом-самом суровом случае расскажу если вспомню действительно выдающийся и интересный ибо пока ничего интересного в голову не приходит, а о «рядовом унынии» рассказывать не хочется.


    1. lavelas
      30.11.2015 15:09

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

      Это и есть foreground-сервис. Просто этот я назвал мусоровоз, потому что в нем был мусор :)