Примечание переводчика: я тоже думал, что время статей "Что быстрее — двойные или одинарные кавычки?" прошло еще 10 лет назад. Но вот подобная статья ("What performance tricks actually work") недавно собрала на Реддите относительно большой рейтинг и даже попала в PHP дайджест на Хабре. Соответственно, я решил перевести статью с критическим разбором этих и подобных им "тестов".
Есть множество статей (и даже целых сайтов), посвященных запуску разнообразных тестов, сравнивающих производительность различных синтаксических конструкций, и заявляющих на этом основании что одна быстрее другой.
Главная проблема
Такие тесты являются неверными по многим причинам, начиная с постановки вопроса и заканчивая ошибками реализации. Но что важнее всего — подобные тесты бессмысленны и в то же время вредны.
- Бессмысленны потому, что никакой практической ценности не несут. Ни один реальный проект еще никогда не был ускорен с использованием методов, приводимых в таких статьях. Просто потому, что не различия в синтаксисе имеют значение для производительности, а обработка данных.
- Вредны потому, что они приводят к появлению дичайших суеверий и — что еще хуже — побуждают ничего не подозревающих читателей писать плохой код, думая при этом что "оптимизируют" его.
Этого должно быть достаточно, чтобы закрыть вопрос. Но даже если принять правила игры и притвориться, будто данные "тесты" имеют хоть какой-то смысл, то выяснится, что их результаты сводятся лишь к демонстрации необразованности тестировщика и отсутствия у него всякого опыта.
Одинарные против двойных
Взять пресловутые кавычки, "одинарные против двойных". Разумеется, никакие кавычки не быстрее. Во-первых, есть такая вещь, как opcode cache, которая сохраняет результат парсинга РНР скрипта в кэш-памяти. При этом PHP код сохраняется в формате opcode, где одинаковые строковые литералы сохраняются как абсолютно идентичные сущности, безотносительно к тому, какие кавычки были использованы в РНР скрипте. Что означает отсутствие даже теоретической разницы в производительности.
Но даже если мы не будем использовать opcode cache (хотя должны, если наша задача — реальное увеличение производительности), мы обнаружим, что разница в коде парсинга настолько мала (несколько условных переходов, сравнивающих однобайтные символы, буквально несколько инструкций процессора) что она будет абсолютно необнаружима. Это означает, что любые полученные результаты продемонстрируют лишь проблемы в тестовом окружении. Есть очень подробная статья, Disproving the Single Quotes Performance Myth от core-разработчика PHP Никиты Попова, которая детально разбирает этот вопрос. Тем не менее, почти каждый месяц появляется энергичный тестировщик чтобы раскрыть обществу воображаемую "разницу" в производительности.
Логические нестыковки
Часть тестов вообще бессмысленна, просто с точки зрения постановки вопроса: Например, тест озаглавленный "Действительно ли throw — супер-затратная операция?" это по сути вопрос "Действительно ли, что обрабатывать ошибку будет более затратно, чем не обрабатывать?". Вы это серьезно? Разумеется, добавление в код какой-либо принципиальной функциональности сделает его "медленнее". Но это же не значит, что новую функциональность не нужно добавлять вообще, под столь смехотворным предлогом. Если так рассуждать, то самая быстрая программа — та, которая не делает вообще ничего! Программа должна быть полезной и работать без ошибок в первую очередь. И только после того как это достигнуто, и только в случае, если она работает медленно, ее нужно оптимизировать. Но если сама постановка вопроса не имеет смысла — то зачем вообще тогда тестировать производительность? Забавно, что тестировщику не удалось корректно реализовать даже этот бессмысленный тест, что будет показано в следующим разделе.
Или вот другой пример, тест озаглавленный "Действительно ли $row[id]
будет медленнее, чем $row['id']
?" это по сути вопрос "Какой код быстрее — тот который работает с ошибками, или без?" (поскольку писать id
без кавычек в данном случае — это ошибка уровня E_NOTICE
, и такое написание будет объявлено устаревшим в будущих версиях РНР). WTF? Какой смысл вообще измерять производительность кода с ошибками? Ошибка должна быть исправлена просто потому, что это ошибка, а не потому что заставит код работать медленнее. Забавно, что тестировщику не удалось корректно реализовать даже этот бессмысленный тест, что будет показано в следующим разделе.
Качество тестов
И снова — даже заведомо бесполезный тест должен быть последовательным, непротиворечивым — то есть измерять сравнимые величины. Но, как правило, подобные тесты делаются левой пяткой, и в итоге полученные результаты оказываются бессмысленными и не соответствующими поставленной задаче.
Например, наш бестолковый тестировщик взялся измерять "избыточное использование оператора try..catch
". Но в актуальном тесте он измерял не только try catch
, но и throw
, выбрасывая исключение при каждой итерации цикла. Но такой тест просто некорректен, поскольку в реальной жизни ошибки возникают не при каждом исполнении скрипта.
Разумеется, тесты не должны производиться на бета-версиях РНР и не должны сравнивать мейнстримные решения с экспериментальными. И если тестировщик берется сравнивать "скорость парсинга json и xml", то он не должен использовать в тестах экспериментальную функцию.
Некоторые тесты попросту демонстрируют полное непонимание тестировщиком поставленной им же самим задачи. О подобном примере из недавно опубликованной статьи уже говорилось выше: автор теста попытался узнать, действительно ли код, вызывающий ошибку ("Use of undefined constant") будет медленнее, чем код без ошибок (в котором используется синтаксически корректный строковый литерал), но не справился даже с этим заведомо бессмысленным тестом, сравнивая производительность взятого в кавычки числа с производительностью числа, написанного без кавычек. Разумеется, писать числа без кавычек в РНР можно (в отличие от строк), и в итоге автор тестировал совершенно другую функциональность, получив неверные результаты.
Есть и другие вопросы, которые необходимо принимать во внимание, такие как тестовое окружение. Существуют расширения РНР, такие как XDebug, которые могут оказывать очень большое влияние на результаты тестов. Или уже упоминавшийся opcode cache, который должен быть обязательно включен при тестах производительности, чтобы результаты тестов могли иметь хоть какой-то смысл.
То, как производится тестирование, также имеет значение. Поскольку РНР процесс умирает целиком после каждого запроса, то имеет смысл тестировать производительность всего жизненного цикла, начиная с создания соединения с веб-сервером и заканчивая закрытием этого соединения. Есть утилиты, такие как Apache benchmark или Siege, которые позволяют это делать.
Реальное улучшение производительности
Все это хорошо, но какой вывод должен сделать читатель из данной статьи? Что тесты производительности бесполезны по определению? Разумеется, нет. Но что действительно важно — это причина, по которой они должны запускаться. Тестирование на пустом месте — это пустая трата времени. Всегда должна быть конкретная причина для запуска тестов производительности. И эта причина называется "профилирование". Когда ваше приложение начинает работать медленно, вы должны заняться профилированием, что означает замер скорости работы различных участков кода, чтобы найти самый медленный. После того как такой участок найден, мы должны определить причину. Чаще всего это или гораздо больший, чем требуется, объем обрабатываемых данных, или запрос к внешнему источнику данных. Для первого случая оптимизация будет заключаться в уменьшении количества обрабатываемых данных, а для второго — в кэшировании результатов запроса.
К примеру, с точки зрения производительности нет никакой разницы, используем ли мы явно прописанный цикл, или встроенную функцию РНР для обработки массивов (которая по сути является лишь синтаксическим сахаром). Что действительно важно — это количество данных, которые мы передаем на обработку. В случае, если оно необоснованно велико, мы должны урезать его, или переместить обработку куда-то еще (в базу данных). Это даст нам громадный прирост производительности, который будет реальным. В то время как разница между способами вызова цикла для обработки данных вряд ли будет заметна вообще.
Только после выполнения таких обязательных улучшений производительности, или в случае если мы не можем урезать количество обрабатываемых данных, мы можем приступить к тестам производительности. Но опять же, такие тесты не должны делаться на пустом месте. Для того чтобы начать сравнивать производительность явного цикла и встроенной функции, мы должны быть четко уверены, что именно цикл является причиной проблемы, а не его содержимое (спойлер: разумеется, это содержимое).
Недавний пример из моей практики: в коде был запрос с использованием Doctrine Query Builder, который должен был принимать несколько тысяч параметров. Сам запрос выполняется достаточно быстро, но Doctrine требуется довольно много времени, чтобы переварить несколько тысяч параметров. В итоге запрос был переписан на чистый SQL, а параметры передавались в метод execute() библиотеки PDO, который справляется с таким количеством параметров практически мгновенно.
Значит ли это, что я больше никгда не буду использовать Doctrine Query Builder? Разумеется, нет. Он идеально подходит для 99% задач, и я продолжу использовать его для всех запросов. И только в исключительных случаях стоит использовать менее удобный, но более производительный способ.
Запрос и параметры для этой выборки конструировались в цикле. Если бы у меня возникла дурацкая идея заняться тем, каким образом вызывается цикл, то я попросту потерял бы время без какого-либо положительного результата. И в этом-то заключается самая что ни на есть суть всех оптимизаций производительности: оптимизировать только тот код, который работает медленно в вашем конкретном случае. А не тот код, который считался медленным давным-давно, в далекой-далекой галактике, или код, который кому-то пришло в голову назвать медленным на основании бессмысленных тестов.
Комментарии (71)
FanatPHP Автор
09.08.2018 18:29Ну вот кстати я бы посмотрел на реальный проект, который был ускорен таким способом.
Причем именно на проект целиком, а не на специально написанный цикл на 100500 итераций :)SerafimArts
09.08.2018 18:47Кусок Symfony, 146%. Если погуглить, то можно найти где-то PR с апом производительности. Вроде как Fesor в одном из дайджестов скидывал эту ссылку.
Да и я у себя делал замеры, тупо через styleci менял по всему проекту туда-сюда и отличия вполне ощутимые, если это делать на регулярной основе (ну т.е. везде). Лучше всего эффект воспроизводится при множестве мелких вызовов.FanatPHP Автор
09.08.2018 18:52Ну вот опять же, "множество" — это сколько? Может, лучше уменьшить это множество, чем оптимизировать его? Сколько должно быть этих мелких вызовов, чтобы разница стала хотя бы на пределе чувствительности радара ощущаться?
SerafimArts
09.08.2018 19:06Ну иногда множество уменьшить нельзя.
P.S. Я тут нагуглил этот самый иссью (а не PR): github.com/symfony/symfony/issues/21020 Но если в среднем «по больнице», то должно выходить ~6-10% (рабочий проект на Symfony + phpcbf). Но это так…
P.P.S. А с другой стороны не пофигу ли? Тут реальные плюсики по скорости «на шару», подключаешь фиксер и вуаля, а мы ещё сопротивляемся.FanatPHP Автор
09.08.2018 22:39+1Ну опять же — никаким приростом уровня приложения тут и не пахнет. Я тут вижу стандартный вывод phpbench, тупо вызывающего по 10 тыщ раз одну и ту же функцию, не делающую ничего.
Все в точности, как говорится в статье выше — если только заставить эти несчастные подопытные функции выполнять хоть какую-то полезную работу, то все эти проценты улетучатся, как белых яблонь дым.
Fesor
10.08.2018 00:47не делающую ничего.
а как еще вы можете померять оверхэд при вызове методов/функций?
Да, для вашего проекта и вашего кода возможно профита никакого. Для фреймворков — профит будет, как и для библиотек инфраструктурных. Банально потому что там все чуть посложнее может быть.
Опять же, я не вижу никакой проблемы поскольку такие вот "оптимизации" выполняются автоматически. Возможно в 8-ой версии пыха эта проблема с фолбэком в глобальный нэймспейс уже будет не актуальна.
SerafimArts
10.08.2018 01:43Возможно в 8-ой версии пыха эта проблема с фолбэком в глобальный нэймспейс уже будет не актуальна.
А сфигали будет не актуальна? Заставим всех всегда явно прописывать неймспейс для всех функций? Ну это как-то… диковато.Fesor
10.08.2018 11:36ну вопервых такие обсуждения были, а во вторых — я больше про всякие JIT и префетчинги, которые могут это дело учитывать что бы невилировать проблемы с производительностью.
SerafimArts
10.08.2018 12:12JIT не поможет, функция может быть объявлена позже последующий вызов должен уже измениться:
namespace A; if (some_foo()) { // \some_foo function some_foo() { ... } } some_foo(); // \A\some_foo
Fesor
10.08.2018 12:41Ну как бы JIT как раз поможет, поскольку он может использовать информацию доступную в рантайме (кто-то объявил такую-то функцию) для оптимизаций. Ну это все так, пустые разговоры)
FanatPHP Автор
10.08.2018 07:17Никак не буду мерить :)
В статье ясно сказано — оптимизировать надо то, что в тормозит в реальности, а не в чьём-то в воображении — и я с этим согласен.
На вызовы функций приходится 0.000...% общего времени исполнения кода. Ускорение этого участка на 5% я при всем желании не замечу.
Если моя программа работает медленно, то самое последнее, чем я буду заниматься — это пририсовывать палочки к вызовам функций.
Fesor
10.08.2018 11:44На вызовы функций приходится 0.000...% общего времени исполнения кода.
Никак не буду мерить :)ну как-то так наша дискуссия сейчас идет.
Во первых у вас возможно 0.0001% а у меня 10% (комбинаторы парсеров, очень простые функции, их много, и они оч много выполняются). Потому что бы сделать более честный бенчмарк и меряют оверхэд от фолбэка в глобальный неймспейс а не "реальное приложение", далее ваша задача экстраполировать результаты данного бенчмарка на свой код, и прикинуть будет профит или можно забить. У некоторых например за счет этого фолбэка на глобальный неймспейс работают подмены функций в тестах)
Во вторых, если эта "оптимизация" требует от меня лишь включить еще один фиксер в php-cs-fixer — я не вижу вообще никакой причины обсуждать "бесполезность" таких вещей. И нет, "пририсовывать палочки" не обязательно — можно сделать явные
use function
, причем все это не руками а автоматически.
p.s. просто что бы вас как-то попугать. Если в системе будет эксплойт, и там будет возможность как-то подсунуть свой код на сервере — я легко могу сделать так что бы у вас в системе была подменена реализация функции
password_hash
какая-нибудь. Но это так, страшилки и пугалки. Мы ж не на вордпрессах пишем.FanatPHP Автор
10.08.2018 11:56+1Ну, я пока не видел приложения, которое показало бы стабильный измеряемый и подтверждаемый прирост производительности в 0.6% (6 процентов от 10 процентов) от добавления слешей перед вызовами функций. Когда увижу — тогда соглашусь — да, эта оптимизация стоит моего внимания.
До тех пор я буду по-старинке — сначала профилировать, потом оптимизировать. А не наоборот.
OnYourLips
10.08.2018 08:39а как еще вы можете померять оверхэд при вызове методов/функций?
JMeter на реальном приложении, а не на глупых синтетических тестах.Fesor
10.08.2018 11:39+1еще раз — на «реальном» приложении все будет зависеть от количества вызовов методов для операций. У подавляющего большинство нет таких проблем и там будет копеешный выйгрыш, а если вы делаете какой-нибудь парсер SQL-я или там graphql-я то там выйгрыш может быть существенный (за счет большого количества вызовов).
Проблема «мерять на реальном приложении» в том, что реальных приложений много разных. А померять стоимость вызова пустого метода двумя способами легко и просто, а дальше уже разработчик «реального приложения» может банально прикинуть актуальна ему эта проблема или нет (если у него за запрос всего пара сотен вызовов функций — ему не надо париться, а если у него пара миллионов вызовов на операцию — заморочаться можно).
Понятнее объясняю причины для такого способа измерений?OnYourLips
10.08.2018 13:36У подавляющего большинство нет таких проблем и там будет копеешный выйгрыш
который будет на порядки меньше погрешностей измерения.
Причины понятны, и именно поэтому я с ними не согласен: такой тест даст противоположный результат. Ввывод на реальном приложении будет такой, что оптимизировать подобное не нужно, а с синтетическими тестами вывод будет обратным.
Fesor
10.08.2018 00:49Доктрина, симфони. У меня были проекты где для бизнес логики нужно было много много вызовов функций простых делать (там рекурсивно дерево рефералов обходилось + чуть-чуть математики простой) и там такие вот "микро" оптимизации могли дать еще +10%. Были так же проекты где надо было класстеризовать точки, но это все единичные случаи конечно. А вот на инфраструктурных вещах профит может быть солидный.
FanatPHP Автор
10.08.2018 07:19Ну вот опять это лукавство :)
10% не от общего времени исполнения кода, а от времени, которое затрачивается на вызов функций. А сколько его там затрачивается?Fesor
11.08.2018 14:05+1еще раз — у меня есть маленький пет проджект — SQL парсер на комбинаторах парсеров (игрался с ними), и там доля вызовов функций в целом значительная. Еще у меня был пет проджект — re2php, типа компиляция регулярных выражений в php код (что бы за линейное время разбирать жирные строчки ну и просто прошариться в NFA->DFA). И там вызовов функций было много, очень много (всякие `ord` и т.д.). И там оверхэд от вызова метода чувствуется и можно выжать дополнительно 5-10% (банальный пример — попробуйте реализовать mb_strlen на чистом php и померяйте)) Зачем это делать? Потому что есть специфические задачи когда надо парсить много и «юникод» там в определенных местах, а в большинстве мест хочется все делать за O(1).
Вы когда говорите что «не нужно все это», вы уточняйте что помимо мизерного эффекта на реальный код, нужно еще мизерное количество усилий для того что бы эту оптимизацию выполнять.
Я не говорю что все всегда должны эту оптимизацию делать — это не так. Но обвинять в микрооптимизациях тех, кто просто в php-cs-fixer поставил опцию в том что они тратят свое время на что-то бесполезное я бы не стал.FanatPHP Автор
12.08.2018 09:03+1Извините, но я не верю.
Даже в отчаянных попытках натянуть сову на глобус, в бесстыдно синтетических тестах без какой-либо полезной нагрузки вообще, по приведенным выше ссылкам получается 8.55% в прыжке.
Любая полезная нагрузка эту цифру только уменьшит, причем катастрофически.
То есть ваши 10% на приложение — это либо ошибка, либо вы одновременно оптимизировали что-то ещё — ту самую полезную нагрузку. Что является куда более реальным сценарием.
В любом случае, я бы хотел увидеть тесты.
porn
10.08.2018 01:09У нас замена `array_key_exists()` на `isset()` дала заметный (процентов 30) прирост производительности на проекте (там был парсинг json, деталей не помню). Главное — не забывать, что isset работает не так, как array_key_exists.
VolCh
09.08.2018 19:56Я считаю, что микрооптимизации имеют право на жизнь во время выработки стайл гайда компании или проекта, формального или неформального — не суть. Грубо, если двойные кавычки таки медленее (пускай на пару тактов даже), а остальные аргументы за и против попахивают вкусовщиной и в целом уровновешены (5 человек говорит, что им одни удобнее, а 5 что другие), то почему бы и не выбрать одинарные? Хоть какой-то объективный аргумент в их пользу есть.
Понятно, что такими оптимизациями заниматься надо не тогда, когда, поступают жалобы на тормоза, а именно когда выбирается стайл гайд.
FanatPHP Автор
10.08.2018 10:34Ну написано же, что даже не на пару тактов :)
В опкод кэше все строки равны. Идентичны. Эквивалентны. Нету там пары тактов.
Аргумент этот какой угодно, но не объективный. Учитывая живучесть мифа — так даже и вредный.
На объективный тянет другой аргумент, который приводили ниже — тупо удобство работы со спецсимволами.VolCh
10.08.2018 20:20Есть пара тактов, только не в опкеше, а в парсере :)
А удобство это как раз субъективный, кому-то удобно, а кому-то нет.FanatPHP Автор
12.08.2018 09:09Ну так эти пара тактов размазываются на сотни тысяч вызовов кэшированного кода.
Разницу между "50\$" vs '50$' я бы не назвал субъективной, но если вам так хочется, то могу снять это предложение, оно не имеет для меня ни малейшей ценности. Вопрос не в том, какой ещё аргумент посчитать объективным, а в том, что "пара тактов" таковым не является.
ЗЫ. Я бы не был настолько тошнотворно-серьезен, если бы этот миф не был настолько живуч.
VolCh
12.08.2018 10:20Не факт, кстати, что размазывается. Опкеша может вообще не оказаться в рантайме (например, его нет в официальном докер-образе PHP), или он может быть отключен (в CLI например).
edogs
09.08.2018 22:12Нам почему-то кажется, что ТС слишком сильно напрягся по поводу этой статьи.
Статья прикольная, человек заморочился ради развлечения, получил какие-то результаты — чего плохого-то? Никто же на полном серьезе не собирается следовать этим гайдлайнам. А статья — так ведь нельзя же делать только серьезные вещи и только с серьезным видом, иногда можно и посчитать количество ангелов на игольном конце.
serginhold
09.08.2018 22:24Разумеется, никакие кавычки не быстрее
к вопросу о скорости:
и не лень же некоторым shift зажимать :)Fesor
10.08.2018 00:50не лень если в строке будут встречаться одинарные кавычки или нужна интерполяция строк.
AlexTest
10.08.2018 05:50У меня такая логика в организации кода: одинарные кавычки обязательно использую для строк без управляющих последовательностей и переменных. Мне так потом легче читать свой код — различать т.с. «простые» строки от «непростых», особенно в случае когда нет подсветки синтаксиса.
rjhdby
10.08.2018 11:29+1Немного не по теме статьи — небольшое наблюдение про двойные и одинарные кавычки.
В PHPStorm константные строки в двойных кавычках подсвечиваются и предлагается заменить кавычки на одинарные.
Я всегда, не задумываясь, соглашался за ради единообразия кода (ну в самом деле, почему нет?), потом задумался — вроде серьезный инструмент, не могут же его создатели быть жертвой этого заблуждения. Решил внимательно прочитать предупреждение и невольно улыбнулся :)
VolCh
10.08.2018 12:47Вроде это сторонний плагин типа EA Inspections или как-то так генерит, а не сам шторм.
newartix
10.08.2018 13:20на самом деле зачем применять двойные кавычки? они ведь реализуют другую функциональность нежели одинарные. когда вижу двойные кавычки — сразу задаю себе вопрос: а что, здесь должна быть переменная в строке? а её нет? ошибка в кавычках или в отсутствии переменной?
rjhdby
10.08.2018 13:541. Раньше была подстановка, а потом удалили
2. Строка в кавычках скопирована из стороннего источника (кода)
3. Просто работа со старым (своим/чужим) кодом, в котором использовался другой стиль
и т.д.
roscomtheend
10.08.2018 15:21Разумеется, никакие кавычки не быстрее.
Зависит от языка и версии, кое-где (Perl) кое-когда (давно) разница была. Более того — ' '.$x.' ' было быстрее, чем " $x ". В PHP особо не сталкивался, там может быть привнесённое или в 2018м могут не помнить как оно было раньше.
VolCh
11.08.2018 12:05+1Тут имеется в виду, что в современном PHP парсер анализирует строку "" на содержимое, и если нет интерполяций переменных, то приводит её к тому же опкоду, что и строку '' и на этапе исполнения разницы реально нет.
artskep
Ну, не знаток PHP, но многое, по понятным причинам, можно перенести на другие языки, и я полностью согласен, за исключением throw.
Автор пишет правильно, что обработанная ошибка лучше, чем необработанная, даже если надо пожертвовать миллисекундами, но в жизни есть ситуации, когда программист использует исключения не для обработки ошибок, а для flow control в штатной и очень часто используемой ситуации. И это может быть проблемой.
Да, даже без вопроса производительности это bad design, но многие так делают (я тоже делал), но понимание того, как такое решение влияет на код, КМК, важно
FanatPHP Автор
Да, я согласен. Сам такого не видел, но слышал, что иногда throw используется тупо как замена goto, и в итоге throw используется при штатном выполнении кода, а не только при возникновении ошибок. Но что характерно — здесь опять же, в первую очередь это bad design, а уже во вторую — производительность. То есть, плохой код надо исправлять потому что он плохой, а не потому что он на пару миллисекунд медленнее.
artskep
Мне не жалко — могу привести пример (достаточно часто повторяющийся и, чего греха таить, использующийся постоянно): есть какой-то user input, который штатно может принимать как числа, так и какую-то строку определенного формата.
Первая мысль, которая возникает у разработчика — сделать попытку привести строку к числу и, если не проканает, то считать ввод не числом, а строкой и дальше думать как ее обработать.
С точки зрения кода, как правило, проще. Да и читается лучше. Но по факту сначала идет незримый парсинг, чтобы привести к числу, потом обработка исключения, а потом уже более интересный парсинг строки (и код даже становится чуть чище за счет того, что мы точно знаем, что это не число). Но при видимой чистоте кода то, что происходит «за кулисами» многие забывают.
Не то, чтобы это совсем проблема. Скажем честно — в подавляющем большинстве случаев горлышко бутылки на I/O операциях или на зависимостях на третьи компоненты типа БД, и, если это не так, то без профилирования заниматься оптимизацией плохая идея. Но понимать проблему, конечно, стоит.
FanatPHP Автор
Честно говоря, в РНР это делается простым условием. Если, скажем, мы ожидаем int, то
или я неверно понял задачу
artskep
Это правильный дизайн (проверка, а потом разбираться). Но (не уверен в PHP, но в Джаве часто видел), что сначала тупо пытаются привести тип к численному, а уж если свалится, то работаем со строкой.
FanatPHP Автор
Ну да, логично. Поскольку в пхп типизации вообще, по сути, никакой, то можно приводить что угодно к чему угодно, и это не будет ошибкой.
akryukov
Насколько я знаю, в java "из коробки" нет функции проверки строки на предмет конвертируемости в int/double вроде "is_int(x)". Альтернатив немного — тянуть Apache Commons или использовать регулярки. А с регулярками можно поймать числа вроде "0x061", "1,602176565E-19". Плюс в разных локалях разный символ для отделения целой части от дробной.
Думаю что из за всех этих сложностей, на дизайн забивают и обрабатывают исключение при попытке преобразования.
vooft
Чем это правильнее бросания исключения?
edogs
Упрощенно
1) Проверка
Если (тут есть грабли): обойти грабли
2) Исключение
Если (наступили на грабли): кричим об этом
MikailBag
Только не кричим, а отходим назад и делаем вид, что грабель не было.
Fesor
Это как раз не очень корректно, поскольку тогда мы не можем говорить об «исключительной ситуации».
vooft
Не вижу «правильности» :)
В обоих случаях мы выполняем примерно одну и ту же работу: надо пробежаться по строке и определить, является ли каждый символ составным элементом числа.
Отдельный метод в джаве будет выглядеть ооооооооочень странным, поскольку сделает столько же работы, сколько и полноценный парсинг, но пользы принесет меньше. Я, отчасти, понимаю, зачем это нужно в php, ввиду очень своеобразной системы типов, но в языках с более строгой системой типов это будет просто мусором в пространстве имен.
А уж использование регулярных выражений для проверки на число — это прям совсем смешно :)
mayorovp
Почему бы методу не возвращать, к примеру,
Optional<Integer>
? И исключений не надо, и работы лишней не делается.Fesor
Потому что в php нет дженериков?
Можно еще вот так:
но это так, для тех кто хочет проникнуться Go
mayorovp
Я отвечал на вот это:
Fesor
В «джаве» вы обязаны объявить все исключения которые вы «прокидываете» выше и не обрабатываете. Потому там ситуация чуть-чуть отличается от ситуации в php.
Ну и «отдельный метод» — понятия не имею о чем вы.
vooft
null — не всегда показатель ошибки, зачастую это вполне себе first-class value. К примеру, что должен этот метод вернуть, если ему передали null string?
Я бы в этом случае больше предложил использовать что-то с тройным состоянием, вроде Mono из Project Reactor: оно может содержать значение, если все ок, может не содержать никакого значения, если был null, или же содержать исключение, если случилась ошибка.
Fesor
вам не предлагают null, вам предлагают `Optional`, это чуть-чуть другое, и в целом это дело позволяет вам определить вариант проверить были ли ошибки или нет.
Ну и опять же, лично мне нравится вариант go с явным разделением на «ошибки» и «исключения» (panic).
дальше основной проблемой становится невозможность в php описать ни maybe/optional ни туплы аля го. И остаются только исключения. Которые становятся проблемой если у вас control flow на них завязан.
vooft
Я говорил про null на входе :) Optional только на выходе выдается.
Ситуация примерно такая:
Optional<Integer> isInteger(String s)
В случае ошибки парсинга возвращаем
Optional.empty()
, что будет еслиs = null
? И что делать если в моем случае null — вполне себе допустимое значение, и я хочу получить назад null? Если кидатьNullPointerException
, то это как бы ломает всю идею использованияOptional
.В джаве так-то тоже есть
Exception
, который не должен являться фатальным, иError
, который даже не имеет смысла ловить.mayorovp
А в чем, собственно, проблема? null string нельзя распарсить в число, значит возвращаем
Optional.empty()
vooft
Почему нельзя? Можно.
mayorovp
Это называется не «распарсить»
vooft
Может и называется, вопрос в контракте метода.
quwy
Беда вашего примера в том, что он не работает как вы ожидаете, потому что is_int() — это отнюдь не проверка строки на содержание исключительно целочисельного литерала. Очень распространенная ошибка, обусловленная идиотским механизмом неявного преобразования типов в PHP.
FanatPHP Автор
Беда вашего комментария в том, что вы прочитали из моего примера только одну строчку, а надо было — весь пример целиком ;-)
В РНР действительно идиотский механизм неявного преобразования типов, но приведенный выше пример как раз и является успешной попыткой эту ситуацию исправить.
DeniSun
Могу привести другой пример.
Результат работы функции можно вернуть через вызов исключения или как целое число с номером ошибки.
oxidmod
Результат нужно возвращать как результат
FanatPHP Автор
Мне кажется, это зависит от предназначения функции. Если это проверка, которая и должна вернуть 0 или 1 в зависимостиот переданного параметра, то исключение не нужно.
А если функция должна как-то обработать и вернуть введенное значение, то исключение — это как раз идеальный способ сообщить об ошибке. Но это будет именно ошибка, а не результат работы.
VolCh
Ну, широко распространены две крайности:
Как по мне, то надо как-то баланс соблюдать обычно, ориентируясь на планируемые типовые сценарии использования, что скорее всего будет делать клиент нашего кода при ошибке того или иного типа. Исключения именно для исключительных ситуаций, когда ожидаемое поведение клиента — корректно освободить ресурсы и умереть, выдав пользователю "что-то пошло не так" и записав трейсы в логи. А бросать с десяток исключений по цепочке только для того, чтобы вывести пользователю "этот логин занят, попробуйте другой", как-то не комильфо.
eliduvid
В питоне так даже и принято. Например итераторы сигнализируют о конце итерации при помощи исключения.