Периодически сталкиваюсь с такой ситуацией, когда та или иная система не должна работать, но почему-то работает. Не люблю такие ситуации по двум причинам — сложно отлавливать, и коллеги смотрят косо… работает ведь, чего тебе надобно.
А как вы относитесь к подобному «искусственному интеллекту»?
С одной стороны вроде как работает ведь. И в 90% случаев оказывается, что я когда-то давным давно предусматривал что-то, что позволило ему так работать. С другой стороны — раз не ясно почему оно работает, то оно может работать неправильно, а значит быть уязвимо.
Пример — сегодня показывал коллеге тест одной библиотеки которую я сейчас пишу.
И заметил что я там сделал опечатку. Вместо:
$data = $zarplata->read('money<?',100);
написал
$data = $zarplata->read('money<',100);
т.е. пропустил плейсхолдер.
Оказалось что код отработал верно, и по результатам теста все было ок.
Разбор полетов показал, что работало оно за счет того, что:
спокойно отдает $where как начало строки, и пустую строку как окончание.
Но есть одно но — это отрабатывается в коде по обработке плейсхолдера # а значит и эскейпится и оформляется он несколько иначе. Итог — готовая sql-инъекция.
При правильном проектировании на самом деле часто бывает, что оно работает, и ты не понимаешь почему, и это нормально… но при неправильном может быть и такое…
Вот скажите мне, я параноик который случайно был вознагражден за свою паранойю или так правильно? :)
Само собой, надо чинить. Пока оно у программистов — чинить дешево. Если сломается, будучи под нагрузкой — чинить будет очень дорого, это Вам любой QA скажет.
Куда как хуже, когда от QA приходит баг, а после разбирательства, выясняется, что работать оно из-за этого бага не могло уже очень давно, но ведь работало как-то…
Для этого и нужно МОДУЛЬНОЕ тестирование, оно бы выявило недостатки парсинга where. А в скопе, дейтвительно, ошибки могут наложиться и дать правильный результат.
Поэтому я и выделил слово «модульное», а то действительно, много любителей тестить одним тестом кучу классов и методов, но это уже не те тесты. Сейчас у себя на проекте такое разгребаем, пишем независимые тесты, уменьшается виртуальное покрытие, зато растёт реальное и отлавливаем хитрые баги и дыры, которые раньше проскакивали через широкую сеть.
«Если вы не знаете, почему это работает, вероятно, оно и не работает на самом деле.» (С.Макконнелл, цитата из «Совершенный код»)
> При правильном проектировании на самом деле часто бывает, что оно работает, и ты не понимаешь почему, и это нормально…
И это ненормально и указывает на то, что что-то идёт не так. Сейчас 1 человек не понимает, как это работает, завтра — половина коллектива — через год — никто не знает. Что является прямой причиной роста числа дефектов.
Извините, но понимание ВСЕГО что работает возможно только в оооооочень маленьких проектах.
На выходных написал одну небольшую библиотеку:
5 интерфейсов с описанием (около 20кб текста_комментариев/«кода»)
9 абстрактных классов (12кб «кода» из которых реально исполнимого 1-2кб)
5 реальных классов общим весом в 30кб, на 50% это рефакторинг старого кода, 50% свежак.
1 небольшой класс для тестов + 15 кб тестов (25 тестов).
=======
Это только ОДНА библиотечка.
Таких библиотек в продакшене иногда бывает полтора десятка.
Плюс столько же кода с бизнеслогикой…
И половина из этого кода писана 3-5 лет назад.
========
Вы знаете, я в свое время тоже любил спорить на тему «шашечки или ехать»....)))
Как раз таки при правильном проектировании совмещение разных подсистем вдруг выдает тебе неожиданные возможности, и не всегда это плохо. Иногда ты продумывал какие-то дефолты по принципу «думаю так будет лучше, но пока не знаю где это пригодиться. А потом естественно забыл об этом. Ибо в голове много чего другого было… А потом десяток таких умолчаний создало отдельный функционал. Правильный)))))
========
Банальный пример: пишешь интерфейсы и абстрактные классы, „заглушки“… у самых базовых вещей прописываешь некую дефолтную логику. Просто чтобы меньше было нереализованных „типовых“ действий… Логика подразумевает, что все эти методы будут переопрделенны в будущем… но если какие-то классы будут совместимы с нашим интерфейсом лишь частично, то „заглушка“ подтянет остаток функционала. Может даже и ненужно будет бизнеслогику править, если она не все методы использует. Потом у нас набирается таких классов-заглушек десяток. Ты уже и забыл о них. Над ними еще десяток классов наросло.
И тут ты часть интерфейсов реализуешь враперами с не очень то совместимых систем (Ну скажем АПИ биллинга смс-центра не совсем похож на АПИ базы данных… но сходство есть — фактически это несколько CRUD-интерфейсов у которых не реализовано Update. Переносишь часть модулей с другой системы, согласуешь интерфейсы, пишешь враперы и собираешься дописывать недостающий функционал… а он опа — оказывается и не нужен — всё и так работает :) Нет, ты помнишь, что заглушку на count ты сделал через read. Но вот почему во view не выводится та часть интерфейса которая по логике и не должна выводиться (к примеру удаление уже отправленных смс или их редактирование)…
А оказывается ты при проектировании „на всякий случай“ функционал который реально нужен на уровне ActiveRecord реализовал еще на уровне построителя запросов, на самом низкоуровневом CRUD. А я то был уверен (и не зря), что это все делается ActivRecord.
=============
И это я еще не затронул командную работу, когда ты вообще только интерфейсы пишешь да задачи раздаешь, а кода в глаза не видел.
******************************************************
Я собственно к чему тему то начал? Я то как раз наоборот при каждой неожиданности провожу расследование.
Именно на случай таких вот „а вдруг“ неожиданный самореализовавшийся код не предусмотрит какие-то защиты…
******************************************************
То что в Вашем комментарии написано — нарушение более чем одного правила нормального общения в сети. Отсюда и непонимание окружающими того что Вы хотели сказать.
Зато верю в то, что чем строже и больше различных проверок ввести, тем больше ошибок выскочит. Для этого:
1) ставлю error_reporting в -1 (показывать абсолютно все ошибки, включая всякие deprecated и strict standarts)
2) Иногда делаю код, преобразующий любую малейшую ошибку в исключение, валящее всю программу (в не-PHP языках это часто по умолчанию так и есть, и это правильно)
3) указываю type hints — если в функцию должен придти массив или объект определенного класса, то так и пишу
4) Ставлю assert() массово. Если переданный в функцию массив не должен быть пуст, а другая переменная не может быть отрицательной, проверяю это
5) Не использую @ и не исправляю ошибки, пока не пойму причину (так как реальная ошибка может быть в вызывающем коде)
6) В штуках, которые принимают плейсхолдеры, всегда проверяю соответствие числа плейсхолдеров и переданных данных
7) Жалею, что в PHP нет (или уже есть?) type hints для скалярных типов
> При правильном проектировании на самом деле часто бывает, что оно работает, и ты не понимаешь почему, и это нормально…
Нет, не бывает. Вы просто плохо разбираетесь в коде или особенностях языка.
А, еще. Если конструктор класса, к примеру, принимает массив с опциями, и время выполнения не критично, проверяю массив на отсутствие лишних ключей (так как в 99% это опечатка).
Ну в целом да. Плохо что нет type-hints для скаляров… С тем что мне бы стоило пройтись на депрекейтед тоже согласен… @ тоже зло… С мегабайтными проверками типов и вообще данных на входе не очень согласен, это более прожориво… хотя тоже имеет право на жизнь.
====
п.6 — вообще без комментариев. Слишком уж частный случай. Я указал свою ошибку, и мне в нее макнули «а я таких ошибок не совершаю». Информативно :)
===
> Нет, не бывает. Вы просто плохо разбираетесь в коде или особенностях языка.
Один сказал «не бывает» — без объяснений (см. выше). Я на страницу ответ накатал. Теперь следующий пишет тоже самое и опять без аргументов… Я в прошлой жизни много грешил?)
Недавно выпилил старый код из программы, где вместо проверки на тип стояло приведение к типу. Код работал много лет на многих сервисах и никто не замечал. А могло однажды выйти боком. Так что да, надо сразу искать и разбираться.
А чем было страшно? Каким именно боком могло выйти? Это влияло на стабильность/безопасность или?
Тут тонкий момент — в большинстве случаев лучше умереть получив неожиданные данные, но часто их наоборот лучше привести и не морочить себе голову. Ответьте плиз, мне это важно.
Mendel, страшного-то ничего и нет. Но поскольку код находился фактически везде (контроль входных параметров), в том числе на критичных участках — это могло привести к неявным ошибкам, например потере float-значения в суммах и преобразование их в int. Неявные ошибки — самое неприятное в работе, т.к. на их поиск могут уходить часы, дни и множество тысяч строк в лог-файлах, пока появится понимание — откуда она могла прийти. Ну и, конечно же, юнит-тестирование тут бы спасло ситуацию. Но именно на этот участок не было предусмотрено негативных тестов.
Ну и да, раз уж ушли от темы «неожиданно ПРАВИЛЬНОЕ», в сторону просто ошибок, то вот еще пример:
в 2006 году, когда под пхп5 уже начинал писать, но еще так, чтобы пхп4 работал…
Писал библиотеку авторизации.
И мучали меня сомнения… но так и не придумал почему мне все-таки не использовать $_REQUEST вместо $_GET + $_COOKIE.
Не придумал. Написал.
Сейчас библиотека сдохла. Почему? Оказывается в 5.3 появилась request_order.
====
Вот здесь как раз я признаю нарушение самого главного правила разработки: «Слушай свою жопу. Если она говорит не делай — не делай». А все остальное — общие слова. :)