Pull to refresh
1
0
TimTowdy @TimTowdy

User

Send message

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

Опять таки, "If it hurts, do it more frequently, and bring the pain forward".

Если у вас в культуре релизы десятки раз в день, вы набьете шишки на раннем этапе.

когда фикс одного бага, добавляет три новых

Очередной strawman. Но даже если я его приму, что лучше: изредка пофиксить один баг и добавить три новых, но при этом фиксить остальные баги за 24 часа. Или фиксить баги пачками раз в месяц?

когда у вас тысяча сервисов и тысяча команд

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

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

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

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

Мне нужно интервью с разработчиками записать чтоб вы поверили?

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

Вот вам еще пару ссылок: Asana, Intercom. Впрочем у вас мышление конспиролога, все вокруг врут. На самом деле все живут в болоте, просто вы самый честный. Что-то мне это напоминает... Ах да, "загнивающий запад".

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

Спасибо, посмеялся. Удивительно, что работая в Trivago вы вообще не понимаете как работает hospitality индустрия.

Booking.com - это как раз идеальный пример опровергающий вашу позицию. Ваша бронь на букинге вообще никак не гарантирует вам комнату. Она может не дойти до отеля, отель может быть overbooked, вашу комнату могут продать дважды, PMS может прилечь (как локальная так и cloud), CRS может тормозить, и т.д.. Это происходит тысячи, если не десятки тысяч раз за день. И букингу пофиг. И отелям пофиг. И вендорам пофиг. Это плохо, но это реальность.

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

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

Прям вижу как вы кулаком себя в грудь бьете.

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

Более того, есть проблемы которые вы никогда не исправите. Если юзер идет на ваш сервис с мобильного интернета, у него в любой момент может пропасть связь и винить он будет вас. Можете сколько угодно обмазываться юнит-тестами, но юзеру это не поможет. Если у меня есть 0.5% юзеров у которых возникают проблемы, и я могу ускорить разработку в разы заплатив за это поднятием этого показателя до 0.6%, то я не задумываюсь это сделаю.

Компании которые стоят миллиарды долларов каким-то образом умудряются деплоить десятки-сотни раз в день: Etsy, Netflix, Uber, Airbnb, Github. И, открою вам секрет, они столько стоят не вопреки их культуре деплоя, а благодаря ей.

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

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

Регрессионное тестирование вполне себе может быть автоматизировано и не требует новых тестов на каждый коммит.

К слову, отсутствие конкретики наглядно демонстрирует ваш уровень: "нормальное" тестирование и "определенные" этапы проверок.

Но вредно и обратное — пофигиское отношение к качеству того, что ты выдаешь на прод

А вот и ожидаемый strawman argument подъехал. Я ведь ни слова не сказал о пофигистском отношении, вы его сами придумали. Потому что если вы неспособны построить эффективный цикл релиза, значит его не существует, верно?

Вы серьезно пишите интеграционные + юнит тесты на каждый коммит?

У вас локальное окружение не поднимается одной командой?

Нет CI/CD?

Ревью десяти строк занимают несколько дней/недель потому что всем некогда?

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

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

Вы по ссылке хоть сходили?

Ох уж эта боязнь релизить в продакшен.

if it hurts, do it more often.

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

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

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

Вот так и работает пропаганда. Публикация какого-то журналиста в 45,000-тиражном журнале в колонке «OPINIONS» благодаря госСМИ откладывается голове как «кто-то из влиятельных в США призвал сбросить бомбу на Россию». Грустно видеть такое на ИТ-ресурсе.
То, что о ней пишут с 70-х это не означает, что эти аргументы устарели.
В 70-з не было аргумента «если мои советы вам не подходят, то у вас говнокод». Это ваше новшество.

Но моё мнение основано на анализе ошибок, в том числе и приведшим к уязвимостям, я не делаю упор на «я так вижу».
Пустые слова, к сожалению. Никакой аналитики вы не привели. А по ссылке которую я привел сходите, там как раз аналитика по MISRA.

Ну и напоследок, как Вы думаете, всё-таки есть люди, для которых структурный поток более читаемый, или это непременно фанатики из 70-х?
Зависит то того, что вы здесь подразумеваете под структурным потоком. Если речь только о single return в функциях, то конечно есть, это во многом дело вкуса. Если же полный отказ от break/continue в циклах и try/catch/finally — то это, безусловно, фанатизм.
А Вы попробуйте
Попробовал, получилось довольно легко, т.к. код выглядит валидно. В обоих вариантах это выглядит не как exception, а как warning, вот и все.

Или Вас удивляет, что наличие «потребности» в большом количестве неструктурных переходов свидетельствует о проблемах проектирования? Об этом пишут ещё с 70-х. Вы знакомы с классикой Дейкстры?
Представьте себе, знаком. Вот только сейчас не 70-е. При фанатичном следовании «классике» Дейкстры придется отказаться не только от multiple return, но и от break/continue в циклах, и от try/catch/finally в языках с исключениями. К счастью, фанатиков нынче не много, да и сам Дейкстра сегодня вряд ли бы поддержал фанатиков.

Знакомы с рекомендациями MISRA C для критического к корректности ПО и им подобным?
А вас не смущает что 'эти рекомендации официально допускают отклонения? И что они написаны для конкретной индустрии (embedded), конкретно для С (где памятью приходится управлять вручную), и вовсе не претендуют применение в других сферах? А может даже вообще ни на что не претендуют.

Обсуждению на самом деле сто лет в обед, люди давно пришли к консенсусу:
stackoverflow.com/questions/36707/should-a-function-have-only-one-return-statement
Отчего же, если в одном случае читаемость потока исполнения выше настолько, что случайная опечатка не сможет пройти незамеченной?
Стоп, а почему вы решили что не может? На мой взгляд все ровно наоборот: в вашем примере заметить опечатку гораздо сложнее. Пропущенный return резко выбивается из общего стиля, а вот отсутствие else выглядит вполне органично.

Если в коде встречается виртуальная лесенка из 10 return (а не switch), то скорее всего, там проблема на архитектурном уровне
То же самое можно сказать о двух goto в коде Apple, да и в принципе о любой проблеме. У вас получается нефальсифицируемая теория: любой недостаток вы списываете на внешние причины. Моя теория работает везде, а там где она не работает — это у вас плохая архитектура. Неконструктивно.
Напоминаю, что речь в статье о читабельности.
Вот именно. А не о случайных опечатках, они из другой оперы.

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

Если я неправильно понял класс опечаток, от которых вы пытаетесь защититься — тогда поясните о каком именно классе идет речь.

И вы до сих пор не ответили: вместо 10 return вы тоже предпочтете сделать 10 уровней вложенных if/else?
Попробуйте отследить пропущенный return.
Весьма странный аргумент.

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

def func1():
    results = None
    user = get_user()
    if not user:
        print('no user')
    else    
        orders = get_orders(user)
        if not user:
            print('no orders')
        else
            transactions = get_transactions(orders):
            if not transactions:
                print('no transactions')
            else:
                results = [execute(t) for t in transactions]
    return results
Если не стоит запрет на преждевременный выход и я привык его применять, это не увеличит нагрузку на распознавание ситуации, что дальше есть необходимые действия и так поступать нельзя?
Безусловно увеличит. Но, как я уже сказал, проблема возникает лишь в тех средах, где нет возможности использовать try-finally или его аналоги, т.е. ничтожно редко.

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

И, кстати, почему 1-й вариант Вы записали не так?
Можно и так. Читается лучше, но все равно плохо — каждый вложенный if грузит мой ментальный стек. Если у вас десяток таких проверок будет — вы десять вложенных if-блоков будете делать?

Плюс, в таком стиле пишут крайне редко, т.к. он противоречит естественному (позитивному) ходу мыслей. В if обычно пишут позитивное условие, в else — негативное, а не наоборот. Начинать if с отрицания, при наличии else, часто считается code smell.

т.е. вместо
if not condition:
    # something bad
else:
    # something good

практически всегда будет
if condition:
    # something good
else:
    # something bad
Вот я и постарался придумать в том контексте: early return в качестве исключения. Придумать ситуацию когда один и тот же код должен выполняться в случае исключения и в случае его отсутствия мне сложно.

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

Так или иначе, вот на мой взгляд более наглядный пример, демонстрирующий преимущество early return:
def func1():
    results = None
    user = get_user()
    if user:
        orders = get_orders(user)
        if orders:
            transactions = get_transactions(orders):
            if transactions:
                results = [execute(t) for t in transactions]
            else:
                print('no transactions')
        else:
            print('no orders')
    else:
        print('no user')
    return results

def func2():
    user = get_user()
    if not user:
        print('no user')
        return None

    orders = get_orders(user)
    if not orders:
        print('no orders')
        return None

    transactions = get_transactions(orders)
    if not transactions:
        print('no transactions')
        return None
    return [execute(t) for t in transactions]


Я думаю большинство согласится, что ментальная нагрузка в func2 гораздо ниже. Ментальный стек не переполняется, взгляду не нужно прыгать по if/else блокам, читать и поддерживать такой код гораздо проще.

Естественно, если изначально стоит задача делать нечто перед выходом из функции, и язык не поддерживает try-finally либо RAII/contextmanager, то early return будет неуместен.
Попробуйте привести контрпример, где нужно именно «добавить код выполняющийся в конце функции», особенно с учетом что early return здесь используется в качестве исключения. Я попытался привести два примера, где у, очевидного на первый взгляд, подхода «добавить код в конце функции» есть более удачные альтернативы.

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

Если следовать правилу, которое я привел раньше:
source_code = F(mental_model, rules_and_restrictions)
то есть всего три причины для изменения кода:

1. Изменилась F (другими словами код не соответствует ментальной модели, т.е. в коде найден баг)
2. Изменилась ментальная модель (например добавляем новую фичу)
3. Изменились правила/ограничения (меняем ЯП, разбиваем монолит на (микро)сервисы, меняем фреймворк, применяем style guidelines, etc)

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

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

Либо же возникнет задача сделать что-то после вызова SomeFunction — но и делаться в таком случае это будет делаться отдельной функцией. И если последовательность вызовов этих двух функций несет смысловую нагрузку, тогда их можно объединить в третью функцию:
public void SomeComplexAction(age){
    SomeFunction(age)
    SomeOtherFunction()
}
Очень годно, думаю к аналогичным выводам приходят многие программисты спустя N лет опыта.

Вставлю свои пять копеек:
Преобразование GetUser(string) в GetUser(CustomerID) + GetUserByName(string) тоже зависит от контекста. Пока ваша условная библиотека невелика по размерам, GetUserByName по сравнению с GetUser будет казаться тем самым шумом, который вы описали раньше. По мере же роста кодовой базы будет возрастать вероятность неоднозначного восприятия, и, следовательно, потребность в более «явной типизации».

Хотя, на мой взгляд, энтерпрайзное тяготение к излишней типизации и длиннющим именам методов является попыткой решить проблемы некачественной ментальной модели средствами языка. Следует стремиться строить такие модели, в которых
users.find(first_name, last_name) несет тот же смысл что и UserDAOSingleton().FindUserByFirstNameAndLastName(String FirstName, String LastName). Неоднозначность должна устраняться самой моделью, а не более длинным специализированным именем метода. В GetUserByName(string) до сих пор присутствует много неоднозначности, например возникает ли исключение если юзер не найден? А если найдено более одного юзера? Возвращает ли метод одного юзера, или их список, или итерируемый курсор? Ответы на такие вопросы должны быть в самой модели, а не в имени метода/комментариях.

К слову, построение «правильной» (легкой, непротиворечивой, расширяемой, etc), ментальной модели это и есть основная сложность в программировании. Имея ментальную модель, написание кода становится просто рутиной. А код, написанный без четкой ментальной модели неизбежно превращается в набор костылей и говнокода. Грубо говоря, код это функция от ментальной модели и набора правил (языка программирования, стандартов, ограничений). При развитии почти любого проекта обычно меняется как ментальная модель, так и некоторые стандарты/ограничения. И когда этих изменений накапливается достаточно много, у программиста возникает желание «все переписать с нуля».

Как следствие, при передаче кода, написании документации, или onboarding, в первую очередь нужно описывать именно ментальную модель, а не структуры данных и всякие API.
Если приложение установили и забыли, значит приложение пользователю не нужно
С такой логикой реклама вообще не нужна. Ведь если ваш товар не купили — значит он пользователю не нужен. Рекламируются только дураки, верно?
с огромной вероятностью сразу же успешно и блокируется
Это ваша эмоциональная (а потому абсолютно бесполезная) оценка. Бизнес ориентируется не на эмоции, а на реальные метрики. Вы когда-нибудь считали эту вашу «огромную вероятность»? Попробуйте посчитать, будете удивлены.

Если у вас подписываются на уведомления 40-50% при запросе на старте приложения, и 90% при запросе в «нужном месте», но при этом до начала приложения доходят 100 из 100, а до «нужного места» 10 из 100, то бизнес вполне может решить что 40-50 «грязных» конверсий выгоднее чем 9 «чистых». А может и не решить — все зависит от бизнеса и конкретных значений. Именно потому я и говорю, что ваше утверждение спорное.

Банальнейший пример — доставка еды. Юзер хочет уведомление о том, что курьер прибыл — т.е. «нужное место» появляется уже после совершения заказа. А бизнес хочет периодически «спамить» юзеров всякими акциями. И бизнес может вполне посчитать что «грязные» конверсии приносят больше денег, чем «чистые». И принять решение перенести запрос на уведомления из «нужного места» на старт приложения. Потому что юзабилити для бизнеса — не цель, а средство.
Запрашивать разрешение на отправку уведомлений в нужное время и в нужном месте. Перед запросом объяснить, какую выгоду от этого получит пользователь. Если уведомления связаны напрямую с картой лояльности, то запрашивать разрешение стоит уже после авторизации в приложении.
На самом деле очень спорное утверждение. Большинство приложений устанавливаются и забываются на следующий день (вне зависимости от их полезности). Уведомления — единственный способ вернуть пользователя в приложение. Запрашивая разрешение в «нужном месте» вы, допустим, можете повысить конверсию с 30% до 90%, что на первый взгляд может показаться очень внушительным. Но если большинство ваших пользователей не достигают «нужного места» с первого раза (а значит, вероятнее всего, не достигнут его никогда), то зачастую для бизнеса выгоднее иметь низкую конверсию на всех пользователях, чем высокую на малой их части.

Information

Rating
Does not participate
Registered
Activity