Две раздражающие частности в PHP
В ПХП немало глобальных проблем, которые многих раздражают, об этом написано много статей. Но есть две частные проблемы, два примера неинтуитивного поведения, которые рождают досадные баги на ровном месте. Они раздражают лично меня.
На текущем месте я работаю более шести лет и прежде чем у нас появилось ревью кода до пуша, в общую ветку регулярно попадали результаты недопонимания работы двух функций, которые порой рождали весьма причудливые баги.
Первый относится к функции exec. Свой второй параметр она получает по ссылке и выводит туда массив строк, которые выдаёт запускаемая команда. Беда возникает в следующем коде:
exec('1st command', $out, $ret);
// … проверяем $ret, делаем что-то полезное с $out
exec('2nd command', $out, $ret);
// … проверяем $ret, делаем что-то полезное с $outДело в том, что вторая команда exec не очистит второй параметр и добавит в него вывод второй команды. Таким образом вызов двух команд склеится, а это не то, что мы обычно ожидаем.
Такое поведение описано в руководстве и возможно это полезно для цикла с накоплением, но в общем случае контринтуитивно.
Второй раздражающей частностью является результат работы array_unique. Опять же, особенность описана в руководстве, но про неё нередко забывают: эта функция сохраняет ключи массива. Вот как это выглядит на практике:
var_dump(array_unique([1,1,2]));
/* array(2) {
[0]=>
int(1)
[2]=>
int(2)
} */В случае ассоциативного массива претензий нет, это необходимо, возможно есть случаи, когда это полезно и для обычного массива, но чаще всего пропуски в ключах мешают в самых неожиданных местах.
Самый замысловатый баг, который я помню был таким. Результирующий массив превращался в джейсон-строку и передавался микросервису на Гоу. Чаще всего значения были уникальными и всё работало как ожидается.
Но однажды звёзды сошлись так, что уникальность нарушилась и в результате работы array_unique в массиве появились пропуски. В обычный массив такое в джейсоне превратить нельзя, поэтому ПХП сделал из него объект с числовыми ключами. Гоу такое не простил — у него статическая типизация и в него нельзя пихать всякое.
Микросервис выдал ошибку в лог, данные не принял. У тестеров возникла очень странная ошибка, которая повторялась
Увы, но документацию к языкам сейчас никто подробно не читает, а если читает, то такие особенности не помнит — такова данность и ничего не изменить. Хотелось бы чтобы в языках было меньше такого поведения, либо чтобы специализированные редакторы
Комментарии 24
Я себе ввожу в привычку array_keys(array_flip(…)).
array_keys(array_flip(…)) — 1,6 секунды
array_values(array_unique(…)) — 68 секунд
$actual = array_unique([1, 1, 2]);
$expected = [1, 2];
хотя значения
$actual = array_unique([1, 1, 2]);
sort($actual);
$expected = [1, 2];
и, о чудо, тесты проходят ;)
В реальности, тест будет еще сложнее, т.к. вот такой тест тоже пройдет, но не должен в большинстве случаев:
$actual = array_unique([1, 1, 2]);
sort($actual);
$expected = ['1', '2'];
а как насчет такого теста:
$actual = array_unique([0, 2]);
sort($actual);
$expected = [null, '2'];
норм? или нет? а ведь массивы уже и не уникальны вовсе ;)
тесты надо писать правильно, иначе в них нет смысла, о чем и было сказано в моем комментарии выше.
ваш метод должен возвращать упорядоченный массив — простой assertEquals() отловит, если это не так.
вы использовали
не надо демагогии, про пишите тесты. у меня они отлавливают огромное количество вот таких вот мелочей, когда мне было лень досконально документацию по всем используемым функциям прочитать.
Кстати это только выглядит просто — ну не мог же программист не знать, что тут не уникальные значения, раз array_unique поставил. Но в жизни там модели, база и всё такое, когда значения не уникальны — редкий случай, не
точно выловит?)
верно. и как показано выше, assertEquals сработает не верно. По сути, assertEquals — это '==', что категорически не приемлимо для сравнения массовов.
не поверите — у меня тоже. именно поэтому и знаю, что протестировать метод, который возвращает уникальные значения массива (даже плоского, не говоря про несколько уровней) не так просто. именно это я сказал выше.
еще вопрос напоследок: если надо протестировать уникальные значения, почему вы тестируете еще и индексы?)
@vasa_c:
Откуда взялось это утверждение?
@thefish:
Тут нет «странного поведения». Оно задокументированное + часто используется именно в таком виде: оставить уникальные значения с ключем из первого попадания.
Ну давайте ещё мильён примеров приведём. Стопроцентную гарантию даёт только страховой полис. А тесты дают 90% выловленных глупых ошибок. От того, что никогда нельзя отловить 100, нам что, забить и не ловить эти 90?
Где надо, почему? Предполагается, что на выходе порядковый массив, это и протестировано.
Где подобное в статье? Нет, соль в другом)
Эмм… Так это вы утверждали, что тест выловить ошибку. Я дал пару примеров, что это не так.
Понятно. Вы любите подгонять задачи под себя. Не увидел ни намека в посте на порядок. Замените массив в посте на [1 => 1, 5 => 5, 6 => 5, 12 => 6]. От этого суть поста не поменяется.
такразве-ж я хоть раз высказался против тестов?))) наоборот — тесты это гуд, но в данном случае, если написать их быстро и не обдумано, они не помогут.код-ревью и подобные моменты попадаются — люди пишут тесты не подумав, что и как надо проверить. в случае с array_unique — вот сценарий, когда и с тестами норм, и с реализацией вроде норм, но упадет примерно как в посте:
5-го шага имеем поломанное апи и нормально проходящие тесты.овер-много. потому и задевает, что ты пишешь про тесты, на которых я уже ломал и руки, и ноги, и голову)
видимо, мы просто по разному смотрим на пост. для меня это ежедневная проблема, т.к. я каждый день делаю
1. имеем базу с записями [1 => 'a', '2' => 'b', 3 ='c']
2. реализовываем метод получения уникальных данных из базы, используем array_values. Пишем тесты. Все отлично, все работает, тесты проходят.
3. добавляем в проект экспорт данных из базы в json. все отлично, все работает, тесты проходят.
3. добавляем в базу данные [4 => 'a', 5 => 'b']
4. все работает, тесты проходят.
5. удаляем из базы записи с id 1 и 2. тесты поломались, хотя логика правильная, т.к. на выходе массив с уникальными значениями — поправили тесты, чтобы не проверять индексы.
и после
знаешь сколько я натыкался на подобные сценарии?
В Пиэйчпи. Будь последовательным.