Pull to refresh

Comments 70

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

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

100$ за 20 часов? Печальная история, конечно.
а удовольствие куда дели?
Нет удовольствия в копании чужого дерьма.
Это получается 800 долларов за 160 часов (рабочий месяц) — вполне обычная зарплата программиста не в столице.
Фриланс — это не офис. Нормальных фрилансеров с рейтом меньше 10$/h я еще не встречал.
Рейт (реальный) фрилансера зависит прежде всего от его наглости умения продавать себя.
Чудом вставили волшебное слов «нормальный» и вышли чистым из воды. Рейт фрилансера зависит от опыта работы, умения и наглости. Есть полно с рейтом меньше 10$/h.
Может, где-то их и полно (на каком-то fl.ru), но если обратиться к статистике, то в Украине, допустим, средний рейт 21-22$/h. То есть, чтобы среднее число было таким, тех, кто имеет рейт, скажем, 5$/h, должно быть довольно мало. Или тех, кто берет больше 30, много)
Можно мне ссылочку на пруф, статистику по разным местам, например? Ато Вы не очень тянете на человека которому можно доверять безоговорочно :)
Фу, ну что за переход на личности? Вот dou.ua/lenta/articles/freelance-eastern-europe/
Не похоже, что там анализируются российские биржи фриланса.
А я о чем говорил?
Может, где-то их и полно (на каком-то fl.ru)

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

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

Офигеть. Как у Вас гордости не хватило вообще их послать нафиг с тем, что у них было? Или это такая маленькая месть — преумножение их говнокода своим?
Фрилансеру почти всё равно сделать заказ на 160 часов или на 20, если он заявил свой рейт. В принципе обычная практика при любом аутсорсе предлагать заказчику хотя бы два варианта: качественно и быстро.
Расскажите про свой самый «плохой код» в карьере

Сознательное программирование я начинал с PHP (несознательное с VB6). Вам точно хочется, что бы я рассказал о том что там творилось? =)
Код называют «плохим», как правило, по двум причинам:
— если опытный разработчик видит написанный новичком код, содержащий концптуальные ошибки
— если новичёк видит код, написанный опытным разработчиком, и не понимает используемых паттернов и принципов, на которых он построен, а видит вместо этого хаос
Не буду категоричен, но я бы усомнился какая причина актуальна в вашем случае. Вынесение данных из классов с логикой в отдельную модель и одна модель вместо нескольких не кажутся такой плохой идеей.
Есть общепринятое мнение, что классы-боги это плохо. Я понял из статьи, что lavelas столкнулся как раз лицом к лицу с богом).
Осталось только определить критерии, когда класс становится классом-богом.
Вероятно, когда нарушает SRP. Тогда останется только определить, что является «обязанностью» класса, верно? ;)
Верно. Когда есть множественное нарушение SRP. Понятие обязанности класса в целях оценки удовлетворения SRP заключается в количестве причин для изменения класса, где причинами являются акторы или аспекты изменений. На самом деле, кол-во ответственностей класса можно всегда подсчитать конкретно.
Далеко не всегда. Нельзя сказать точно, сколько причин для изменения может быть у класса в общем случае.
Почти всегда можно. Другое дело, что нам не всегда следует удовлетворять SRP. Всё зависит от вероятности изменений по тем или иным аспектам, а вероятности эти обычно определяются эвристически. Невозможно везде удовлетворять SRP и собсна делать код PURE SRP.

p.s. Ну, не буду утверждать, что можно в 100% случаев подсчитать кол-во обязанностей, но хотелось бы увидеть класс, где это невозможно.
Плюс хранение важных данных в Синглтоне — тоже весьма частое заблуждение новичков.
Крайне категоричное и ничем не подтвержденное высказывание.
Что именно категоричное и не подтвержденное?
То, что хранение данных в синглтоне — плохая идея? Или то, что это частое заблуждение новичков?
То, что хранение данных в синглтоне плохая идея.
Мы довольно активно используем синглтоны, потому как они:

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

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

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

И то, что они выгружаются… И что с того? в конструкторе инициализацию прописал и всё встанет на свои места. Критические данные кешируем на ПЗУ при необходимости, откуда мы их берем при инициализации. Что не так то?
Наверное, я неправильно сделал акцент.
Я имел в виду хранение в синглтоне Критически важных данных, которые не проинициализируются в конструктору. Например, какой-нибудь sessionId, полученный после логина.
В том, что синглтоны удобны, особенно в Андроиде, где они никак не привязаны к жизненному циклу активити, я не спорю и полностью согласен. Как согласен и с тем, что удобны они и для хранения восстанавливаемых данных.
Для восстанавливаемых данных лучше использовать retainInstance фрагменты.
Не совсем понял. Ну вот есть сессия у нас, она не может быть сохранена. Окей, приложение умерло вместе с ней. Дальше при запросе данных сессии в синглтоне, мы смотри, не null ли она к примеру, и если да, то производим необходимые действия.
Вот Вам и проблема синглтона для Андроида. Что делать, если мы запустили выбор файла и пока была открыта галерея наше приложение закрылось и по возвращению у нас нет сессии, перекинуть на окно логина? Круто, я в чате шлю файл другу, но вместо этого меня выкидывает из сессии. Очень крутое поведение.
Это нужно в onSaveInstanceState сохранить id сессии и при ресторе его обратно закинуть в синглтон?
Вот Вам и проблема синглтона для Андроида. Что делать, если мы запустили выбор файла и пока была открыта галерея наше приложение закрылось и по возвращению у нас нет сессии, перекинуть на окно логина? Круто, я в чате шлю файл другу, но вместо этого меня выкидывает из сессии. Очень крутое поведение.

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

В общем день, когда программа свалилась в неопределённом месте обработки списка всё-таки свершился. Поиск точной позиции краха и восстановление информации заняли несколько часов на продакшне.
«goggle и stackowerflow ответа не дали»:
«goggle» — это оборот речи или ошибка?
Это поисковик, с помощью которого вам не удалось найти мало-мальский приемлемое решение для вашей задачи (вполне вероятно из-за формулировки)
Singleton — злое зло.

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

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

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

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

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

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

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

Это не проблема, в том контексте, о котором вы говорите, потому что вы всегда должны быть готовы, что ваше приложение выгрузится. Приложение, а не ваш объект синглтона. не путайте это. У вас может умереть процесс, что равно умиранию приложения.
Проблема синглтона в том, что их сложно поддерживать и сложно тестировать.
Мир полон костылей — это факт. Вот я представляю, что приезжаю на СТО и мастер мне что-там долго говорит, говорит, говорит и спрашивает потом — ну что будем капиталить и через месяц и 3000$ — будет как новенькая!!! Или говорит я тут, фигак, фигак за 15 минут, заплатку на 100 рублей поставлю и можно дальше ездить, хотя концептуально это не правильно. Что я выберу!!?????
Но если я поставлю заплатку, вам нужно будет постоянно следить за температурой двигателя — если она превысит 90 градусов, заплатка отвалится и весь антифриз вытечет за пол-минуты. А ещё эта заплатка цепляет рулевую тягу, поэтому нельзя поворачивать руль влево больше чем на пол-оборота.
Не совсем так: при каких-то условиях двигатель может перегреться, а рулевая тяга может зацепиться за заплатку. Но пользователь практически в 100% случаев с этим не столкнется. Зато столкнется следующий мастер, когда его попросят обслужить двигатель.
Была одна история с кодом, за который мне до сих пор стыдно:
Надо было посчитать приблизительные сроки доставки товара в почтовой службе. Но так как исходников не было, то пришлось декомпилировать DLL'ку с помощью dotPeek (очень рекомендую). В процессе полетели bool пеерменные, которые преобразовались в int, а обратно при компиляции не сконвертировались в bool. Но так как многие проблемные места были обёрнуты try'ями без логирования, то ошибка нашлась не сразу. Но это всё последующие умозаключения. Сама проблема заключалась вот в чём: было несколько файликов с пунтами доставки и временем/стоимостью доставки в каждом регионе. Для новосозданных регионов (в частности Крым, Симферополь, Севастополь, etc.) этой информации не было, а для некоторых старых информация в рантайме терялась (детали не помню, просто что-то не заработало). Поэтому для таких случаев, когда невозможно посчитать сроки доставки всё равно необходимо было выдавать хоть какие нибудь цифры. Первая мысль была про рандом, но сроки доставки должны совпадать для каждых двух пунктов соответственно, а не разниться при каждом обновлении страницы. Поэтому было решено сложить названия пунктов, взять от них длину, делённую по модулю на определённое значение и прибавить минимальный коэффициент. Все данные подбирались опытным путём.
На Почте России ваш код до сих пор работает!!!
Вот допустил ли я ошибку при проектировании, когда заложил int в качестве ключа для записей (состояние датчиков авто) или другой, ответственный, когда пропустил приближение трагедии.
Но буквально в этот вторник записей стало больше 2147483647.
Беда не пришла одна: при обновлении БД кончилось место на дисках.
В итоге — 9 часов даунтайма (в рабочее время) и, после, DDOS от авто с архивными данными за это время.
Частичная потеря данных за 1,5 часа с момента как закончился int, до момента обнаружения и осознания с остановкой всего.

P.S.: в течение месяца ожидается окончание int и для другого типа данных, надеемся хоть тут не облажаться
Хорошо хоть не float :)
Зависит от ТЗ. Если там была корректно указана скорость добавления записей в таблицу, то это однозначно факап.
UFO just landed and posted this here
Покаюсь и я.
В одном крупном продуктовом проекте для бизнеса, который я начинал как тимлид-руководитель, кто-то кинул идею о важности добавления новых таблиц чуть ли не на лету под новые хотелки каждого клиента. И я предложил то что мне казалось тогда гениальным решением — засунуть все метаданные в гигантский XML, по которому будут строиться SQL-запросы, в том числе на генерацию БД. И в довесок еще и весь интефейс в XML описали, чтоб значит все таблички и формочки генерились единообразно «на лету».
А потом заказчик очень захотел чтоб все это работало под Firebird и MSSQL. Умный гетерогенный генератор запросов оброс кучей фишечек костыликов для всевозможных ситуаций и разных СУБД. Все это осложнялось неоптимальной структурой данных, доставшихся от смежного проекта. Генератор подчас выдавал запросы длиной в несколько страниц, на отладку и разбор которых уходили долгие часы.
Напоследок ко всему этому прикрутили синхронизатор распределенных баз данных со своими запросами, очередями, транзакциями и оптимизацией запросов на лету. И конечно же все это было завернуто в многопоточную логику с сетевым взаимодействием. И оно даже работало, хотя и нестабильно. И это было крайне тяжело отлаживать и исправлять. Но я таки дотянул проект до пилотного внедрения.
Проект тянулся очень долго, кто-то из разработчиков уволился, кто-то вырос в тимлиды, команда сильно обновилась. И новые ребята просто не смогли до конца понять и принять всю эту сложность. А после моего увольнения продавили идею переписать почти всё с нуля. Наверное, вспоминают меня недобрым словом.
Ждем историю этих ребят через пару лет :).
Да, суперклассы это ужас. Кстати, в качестве альтернативного решения foreground-мусоровозу существует ещё такой хак: создание иконки приложения в трее, такие приложения тоже в фоне не закрываются.

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

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

Articles