понедельник, 4 августа 2014 г.

[Перевод] PHP: бесконечно плохой дизайн

Дисклеймер


Пару лет назад была написана эпичная статья, которая громила PHP налево и направо. Разумеется, написана она была апологетом другого языка — Python. Так получилось, что статью я прочёл сравнительно недавно, и посчитал, что просто обязан взяться за её перевод. Однако меня слегка опередили, что, в общем, было предсказуемо (как никак 2 года прошло). Всё же, по моему скромному мнению, лежащий на Хабре перевод изобилует всякими машинными формулировками, неточностями, неуклюжими оборотами и мой вариант получился как минимум не хуже.
Пару слов о самом переводе: его я делал достаточно вольно, неуклюжие выражения старался заменить более удобными русскими аналогами (как, например, сам заголовок статьи), не придерживался буквенной точности, но и не опускал некоторые вещи (у автора перевода на Хабре некоторые ремарки и пункты опущены), сленг тоже был по возможности адаптирован.
P.S. Для меня самого PHP был и остаётся одним из основных инструментов разработки. Я не разделяю целиком и полностью позицию автора, искренне считаю, что местами он перегибает, местами не понимает, а местами просто капризничает. Однако, во многом автор чертовски прав, плюс ко всему объём изложенного им материала и та скрупулёзность, с которой он его собирал вызывает уважение. Так что статья достойна того, чтобы потратить на её чтение полчаса-часик, подумать, обсудить с коллегами. Итак, поехали.


Предисловие.


Я капризный человек. Я постоянно жалуюсь на кучу различных вещей. В мире технологий есть много такого, что мне не нравится, и, в общем, это вполне ожидаемо — программирование это занятная и молодая отрасль, и никто из нас не имеет ни малейшего понятия о том, чем мы всё-таки занимаемся. Добавим к этому закон Старджона ("90 процентов чего угодно — ерунда" - прим.перев.) и вуаля — у меня есть куча всякой всячины, разборок с которой мне хватит на всю жизнь.
Но я не об этом. PHP не просто "неудобен в использовании", или "плохо подходит для того, что я хочу", или "неоптимален" или просто "против моей религии". Я могу рассказать вам массу всевозможных хороших вещей о языках, которых я избегаю, равно как и массу неприятных вещей о языках, которые мне очень нравятся. Так что не стесняйтесь, спрашивайте! Это же интересно обсудить.
Но PHP — это единственное исключение. Практически каждая фича языка сделана шиворот-навыворот. И язык, и структура, и вся экосистема — всё сделано просто очень плохо. И я не могу даже указать на конкретно плохо реализованную фичу, потому что ,разруха в языке слишком системна и всеобъемлюща. Каждый раз, когда я пытаюсь составить список своих нареканий к PHP, я намертво застреваю в его глубинах, обнаруживая всё более и более ужасные мелочи (как в бесконечном фрактале).
PHP — это недоразумение, это ожог на коже моего ремесла. Он настолько плох и нелогичен, и в то же время, настолько любим новичками, которые еще не успели выучить никакого другого языка, что это поистине ужасает. Он всё же имеет чуточку хороших качеств, которые должны были бы искупить его вину, но они настолько ничтожные, что я вообще предпочел бы забыть о их существовании.
Но раз уж я взялся писать о PHP, то вот моя последняя попытка описать всё то, что мне не нравится.

Аналогия


Как-то я привёл маленькую аналогию одной своей знакомой, чтобы объяснить ей своё разочарование в PHP, и она настояла, чтобы я повторил это тут.

"Я не могу даже чётко сказать, что же не так с этим PHP, потому что... Блин. Ну вот представь, например, ящик с инструментами. Выглядит как обычный ящик, стандартный набор инструментов.
Тебе нужна, скажем, отвёртка, ты достаёшь её из ящика, а она оказывается одной из вот этих странных треугольных отвёрток для каких-то специализированных треугольных пазов. Хорошо, не очень нужная тебе вещь, но в отдельных редких случаях она наверняка удобна и полезна. Ок.
Достаёшь молоток и, к своему удивлению, обнаруживаешь, что обе части головки молотка — гвоздодёры. В принципе, молоток-то пригоден. Я имею в виду тот случай, что ты будешь забивать им гвозди боковой частью головки.


Достаёшь плоскогубцы, а у них на губках нет насечки — они просто гладкие. Не очень удобно, но, в принципе, ими можно откручивать какие-нибудь болты. Короче, пофиг.
И так каждый раз с каждым инструментом. Всё, что лежит в этой коробке какое-то странное и неудобное, но не настолько, чтобы эти инструменты совсем никуда не годились. И, в общем-то, не ясно, что не так с этим набором в целом, вроде все инструменты в нём есть.
А теперь представь, что на свете около миллиона плотников, которые используют их и все в один голос говорят: "Эй, а что не так с этими инструментами? Я всю жизнь ими пользуюсь и они прекрасно работают!". И они любезно показывают тебе построенные ими дома, где каждая комната с пятью углами, а крыша сделана вверх тормашками. Ты просто стучишься в переднюю дверь, а она обрушивается прямо перед тобой под рёв и крики всех этих плотников, которые обвиняют тебя в порче их двери."

Позиция


Я утверждаю, что следующие качества просто необходимы для любого языка программирования, который претендует на звание продуктивного и полезного, но PHP нарушает их с каким-то особым варварством. Если вы не согласны с тем, что они важны, то я не представляю, что дальше по тексту мы с вами вообще в чём-то сойдёмся.
  • Язык должен быть предсказуемым. Это посредник, который позволяет человеку выразить свои идеи, а машине — воплотить их в жизнь, поэтому чертовски важно, чтобы человек правильно понимал и воспринимал программу на этом языке;
  • Язык должен быть согласованным. Одинаковые вещи должны выглядеть одинаково в разных местах, а различные должны различаться. Знание одной части языка должно давать правильное представление о том, как устроена его остальная часть.
  • Язык должен быть выразительным. Новые языки существуют для того, чтобы избавляться от шаблонности, которая достаётся в наследство от старых языков (Мы вообще все могли бы писать в машинных кодах). Язык также должен стараться избегать введения новых собственных шаблонов.
  • Язык должен быть надежным. Языки — это инструменты для решения проблем; поэтому язык должен их решать, а не создавать новые одним своим существованием — ничто не должно отвлекать от решения основных задач.
  • Язык должен быть отлаживаемым. Когда что-то идёт не так, программисты должны найти причину ошибки для её исправления, и всем в такие моменты нужна любая доступная помощь.

Моя позиция относительно PHP такова:
  • PHP полон сюрпризов: mysql_real_escape_string, E_ALL
  • PHP противоречив: strpos, str_rot13
  • PHP тащит за собой старые стереотипы: систему проверки ошибок как в C API, ===
  • PHP странный: ==, foreach ($foo as $bar)
  • PHP непрозрачный: никакик стек-трейсов по умолчанию или для фатальных ошибок, слишком запутанная система сообщений об ошибках.
Я не могу написать даже абзаца пояснения для каждого случая, потому что они распадаются на более и более мелкие проблемы и так до бесконечности. Я верю, что читатель — умный и думающий человек.

Пожалуйста, не пишите вот эти вещи


Я очень часто участвовал в спорах о PHP. Я слышал массу каких-то очень общих контраргументов, которые годились лишь для того, чтобы прекратить этот бессмысленный спор здесь и сейчас. Пожалуйста, не пишите мне их снова :(
  • Не нужно рассказывать мне о том, что, дескать "плохому танцору ..." и "хороший спец может писать на любом языке ...", бла-бла-бла. Это ровным счётом ничего не значит. Хороший плотник может и камнем гвозди забивать так же хорошо, как и молотком, только вот скольких плотников с камнями вы повидали в своей жизни? Одно из качеств, которое присуще хорошему разработчику — это умение выбирать инструменты, которые работают лучше.
  • Не нужно рассказывать мне, что "это обязанность разработчика-профессионала знать тысячи странных исключений и неожиданных поведений". Да, это необходимо знать в любой системе, потому что машина сама по себе отстой. Однако это не означает, что нет никакого верхнего предела на количество таких "приколов", приемлемых в конкретной системе. PHP — это вообще одни сплошные приколы, и это ненормально, когда борьба с языком требует больше усилий, чем решение проблем. Мой рабочий инструмент не должен создавать мне дополнительной работы.
  • Не стоит говорить мне что "так работает сишный API". Какая вообще причина существует на этой планете использовать язык программирования высокого уровня, который предоставляет чуточку полезных функций работы со строками и тонну функций-врапперов C? Да пишите просто на чистом C! Для этого даже есть CGI-библиотека.
  • Не надо говорить мне о том, что "ты как-то странно пишешь код, вот и получается такая ерунда". Если две функции существуют в языке, то в какой-то момент времени в каком-нибудь тридевятом царстве какой-нибудь разработчик найдёт причину использовать их вместе. И опять же, это не C; здесь нет спецификаций, здесь нет причин возникнуть "неожиданному поведению".
  • Пожалуйста, не надо твердить мне, что Википедия и Фейсбук написаны на PHP. Я в курсе! Они с таким же успехом могли быть написаны и на Brainfuck, до тех пор, пока есть достаточное количество толковых специалистов, готовых преодолевать проблемы этих платформ. Но все мы также знаем, что время разработки можно было бы как сократить, так и увеличить в два раз, если бы эти продукты были написаны на каком-либо другом языке. Так что одиночное утверждение , мол, "они написаны на PHP", также ровным счетом ничего не значит.
  • В идеале, не рассказывайте мне вообще ничего! Этот текст — одна большая и крупнокалиберная пуля в голову PHP; если даже он не убедит вас изменить мнение о PHP, то вас уже ничто не переубедит, так что заканчивайте спорить с каким-то чуваком в интернете, поди-те лучше и зафигачьте крутой веб-сайт в рекордные сроки, чтобы убедить меня, что я не прав.
Небольшое отступление: я оооочень люблю Python. Я также могу долго гундеть о том, как в нём всё плохо, если вы действительно хотите это слушать. И я не утверждаю, что он идеален; но я просто взвесил все его плюсы и минусы и убедился, что он как нельзя лучше подходит для вещей, которые я делаю.
И я никогда не встречал PHP-разработчиков, которые могут сказать то же самое о PHP. Но я видел очень многих, кто извиняется за все те вещи, которые он когда-то писал на этом языке. Такое положение вещей, мягко говоря, ужасает.

PHP.


Ядро языка.

CPAN когда-то называли "стандартной библиотекой Perl". Это не характеризует в полной мере сам Perl, но дает основания полагать, что прочное ядро может творить чудеса.

Философия.

  • PHP был изначально создан для не-программистов (читай, для создания не-программ); и он не скрывает эти корни. Вот выбранная цитата из документации к PHP 2.0, касательно + и его аналогов, которые делают приведение типов:
    "... Как только вы начинаете использовать различные операторы для каждого типа, вы тем самым усложняете язык. Пример: вы не можете использовать '==' для строк (sic!), вы должны использовать 'eq'. Я не вижу причин, особенно для таких языков как PHP, где большинство скриптов просты и написаны не-программистами, усложнять его, тем самым повышая порог входа для тех, кто только-только выучил его синтаксис..."
  • PHP создан для того, чтобы париться с ним в любой ситуации. Когда перед ним стоит выбор: сделать что-то бессмысленное или прерваться с ошибкой, он делает что-то бессмысленное. Всё лучше, чем ничего, конечно.
  • У этого языка нет своей четкой философии, идеи, дизайна. Ранние версии были вдохновлены Perl'ом; здоровенная stdlib с out-параметрами перекочевала из C; ОО-часть создана под влиянием C++ и Java.
  • PHP, черпая вдохновение от других языков, всё же умудряется оставаться непонятным для тех, кто эти языки знает. Конструкция (int) выглядит как в C, но сам int в языке не существует. Пространства имен используют \. Новый синтаксис вывода массива [key => value] вообще уникален среди всех языков, имеющих хеш-литералы.
  • Слабая типизация (то есть тихое фоновое преобразование между строками / числами / всем остальным) настолько запутана, что можете забыть все свои прежние знания о ней из других языков.
  • Новые фичи языка появляются в виде нового синтаксиса функций или чего-то, напоминающего функции. Исключение составляет только ОО-часть, которая заслужила множество новых операторов и ключевых слов;
  • Некоторые приведенные здесь проблемы, в общем-то, обретают решения, если вы готовы платить Zend за их open source язык программирования.
  • Чтобы всё работало, нужно совершить уйму лишних телодвижений. Взгляните на эту строчку кода откуда-то из документации, например:
    @fopen('http://example.com/not-existing-file', 'r');
    Что она делает?
    • Если PHP был собран с флагом --disable-url-fopen-wrapper, это не сработает. (Доки не говорят, что значит "не сработает": вернет null, выбросит исключение?). Кстати, этот флаг убрали в PHP 5.2.5.
    • Если allow_url_fopen установлен в disabled в файле php.ini, оно по-прежнему молчит. (Почему? ХЗ.)
    • Потому что перед выражением стоит @, предупреждение о несуществующем файле не будет выведено (Окау).
    • Но оно будет выведено, если scream.enabled установить вручную в php.ini.
    • Но не будет выведено, если не установлен корректный параметр в error_reporting.
    • Где оно будет выведено, зависит от значения display_errors в php.ini. Или в ini_set.

    Я не могу сказать, что эта безобидная строчка кода мне выведет без проверки флагов компиляции, конфигурации сервера и конфигов конкретно в моей программе. И здесь это нормальное встроенное поведение.
  • В языке полно глобальных и неявных состояний. mbstring использует глобальный набор символов. func_get_arg и подобные выглядят как обычные функции, но работают в контексте вызывающей функции. Обработка ошибок и исключений имеет глобальные настройки. register_tick_function устанавливает глобальную функцию, которая запускается каждый тик — что!?
  • Здесь нет поддержки многопоточности ни в каком виде (неудивительно, учитывая описанное выше). В сочетании с отсутствием встроенного fork (об этом ниже поговорим), заниматься параллельным программированием на PHP практически невозможно.
  • Некоторые части PHP как будто специально сделаны, для написания кишащего багами кода.
    • json_decode возвращает null для некорректного ввода, даже если null — это вполне валидный объект для декодирования в JSON — эта функция полностью ненадежна, даже если вы каждый раз вместе с ней используете json_last_error;
    • array_search, strpos и другие подобные функции возвращают 0 если они нашли подстроку в нулевой позициий в строке, но false если они вообще ничего не нашли.
Дайте-ка мне немного объяснить последнее замечание.
В С функции типа strpos возвращают -1 если искомый элемент не был найден. Если вы проверите этот случай и попытаетесь использовать его как индекс, то просто залезете в мусорную память и ваша программа рухнет. (Скорее всего. Но это же С. Хрен его знает, что с ней случится. Но я уверен, там есть спец.инструменты для таких случаев).
Скажем, в Python эквивалентный метод .index породит исключение, если элемент не будет найден. Если вы не проверите этот случай — ваша программа рухнет.
В PHP же эта функция вернёт false. Если вы решите использовать полученный FALSE как индекс, или сделаете ещё что-нибудь похожее (кроме строгого сравнения ===), то РНР заботливо и тихо преобразует для вас false к нулю. Ваша программа не рухнет; вместо этого она начнет выполнять неверные действия без какого-либо предупреждения, если вы вдруг забудете эту её маленькую особенность и не нагородите соответствующих обработок.
Ребята, это плохо! Языки программирования — это инструменты; они созданы для работы со мной. Здесь же, в РНР, активно понатыканы скрытые ловушки, в которые я рискую провалиться, если не буду как кэгэбэшник проверять каждую тривиальную вещь. РНР — это минное поле.
Я слышал много крутых историй об интерпретаторе РНР и его разработчиках из самых различных мест. Это люди, которые работали над ядром языка, над его отладкой. И ни одна история не была хотя бы положительной. Поэтому я хочу заявить прямо здесь, потому что это стоит повторить: сообщество РНР — это сообщество новичков и любителей. Очень малый процент людей, который его разрабатывает или пишет на нём код понимает вообще, что делает (о, уважаемый читатель, вы, конечно, редкое исключение!) Te, кто вырастает из этого подросткового периода сразу же перезжают жить на другие платформы, что снижает компетентность сообщества в целом. Это и есть самая большая проблема с РНР: это сообщество слепых, ведомых слепыми поводырями.
Но ок, вернёмся к нашим баранам.

Операторы.

  • Оператор == бесполезен
    • Он не транзитивен. "foo" == TRUE, и "foo" == 0, но, конечно, TRUE != 0.
    • Оператор == преобразует в числа, при каждом удобном случае (123 == "123foo" .. хотя "123" != "123foo"), а затем пытается преобразовать в число с плавающей точкой. Поэтому большие шестнадцатеричные числа (например, хэши паролей) могут внезапно оказаться равными, хотя на самом деле они не равны. Даже JavaSript себе такого не позволяет.
    • По этой же причине, "6" == " 6", "4.2" == "4.20", и "133" == "0133". Но в то же время 133 != 0133, потому что, внезапно, 0133 — это восьмеричное число. Но опять же "0х10" == "16" и "1е3" == "1000"!
    • Оператор === сравнивает значения с учётом типов, но... Это не работает для объектов, где === даёт true только в том случае, если оба операнда на самом деле — один и тот же объект! Для объектов, == сравнивает и значение и тип (каждого атрибута), т.е. делает то же самое, что и === для всех других типов. Шта?
  • Сравнение не намного лучше:
    • Это даже не согласуется: NULL < -1 и NULL == 0. Таким образом, сортировка здесь недетерминирована: она будет зависеть от того, в каком порядке алгоритм решит сортировать элементы.
    • Операторы сравнения стараются сортировать массивы двумя различными способами: сначала по длине, потом по элементам. Если у массивов одинаковое количество элементов, но разный набор индексов, то конечно, они несравнимы между собой.
    • Объекты в сравнении с чем угодно всегда больше ... кроме других объектов, по сравнению с которыми они не больше и не меньше;
    • В качестве более строгого и безопасного по отношению к типам == в РНР есть ===. В качестве более безопасного по отношению к типам <, в РНР есть ... Ничего нету. "123" < "0124", всегда, хоть убейся.
  • Несмотря на сумасшествия, описанные выше, и отказа от парных операторов для различных типов (как в Perl), PHP не перегружает оператор +. Оператор + это всегда сумма, а . это всегда конкатенация. Так то!
  • Доступ к элементу по индексу можно писать как [], так и {}.
  • [] можно применять к любой переменной, не только ко строкам и массивам. Просто вернётся null, безо всякого предупреждения.
  • [] не умеет слайсить (slice), он может только возвращать отдельные элементы.
  • foo()[0] — это синтаксическая ошибка. (пофиксили в PHP 5.4)
  • В отличие (буквально!) от всех других языков со схожим оператором, тернарный оператор ?: ассоциативен слева (!). Поэтому этот код:
    $arg = 'T';
    $vehicle = ( ( $arg == 'B' ) ? 'bus' :
    ( $arg == 'A' ) ? 'airplane' :
    ( $arg == 'T' ) ? 'train' :
    ( $arg == 'C' ) ? 'car' :
    ( $arg == 'H' ) ? 'horse' :
    'feet' );
    echo $vehicle;
    
    вернёт "horse".

Переменные.


  • Не существует способа объявить переменную. Несуществующие переменные создаются динамически со значением null во время первого использования.
  • Глобальные переменные требуют наличия ключевого слова global перед использованием. Это естественное следствие из вышесказанного и оно вполне разумно, кроме того, что значение глобальной переменной невозможно даже прочитать без предварительного объявления — РНР просто создаст локальную переменную с тем же именем вместо этого. Я не знаю ни одного другого языка с такими же проблемами в области видимости.
  • Здесь нет ссылок. То, что в РНР называется ссылками на самом деле — алиасы; это шаг назад, как у ссылок в Perl; нет никаких аналогов "pass-by-object" как в Python.
  • "Ссылочность" как страшная болезнь поражает все переменные в РНР. Поскольку РНР является языком с динамической типизацией, то у переменных, по большому счёту, нет определённого типа ... кроме ссылок, которые украшают определения функций, синтаксис переменных и присваивания. Как только переменная становится ссылкой (а это может произойти где угодно), она намертво застревает в этом типе. Не существует никакого очевидного пути обнаружить это, а чтобы привести её обратно к другому типу нужно её просто уничтожить и создать заново.
  • Ладно, я соврал. Тут существуют "SPL-типы", которые тоже заражают переменые. Так например $x = new SplBool(true); $x = "foo"; закончится неудачей. Типа статическая типизация, ага.
  • Можно сослаться на индекс, которого не существует, ну например в неопределённой переменной (которая тут же создастся как массив). Использование необъявленного массива в обычном случае порождает NOTICE, а тут — всё нормально.
  • Константы можно определить, передавая в вызов функции строку, до этого момента констант не существует (на самом деле это может быть копией поведения Perl при использовании use_constant).
  • Имена переменных чувствительны к регистру. Имена функций и классов — нет. Включая названия методов, в которых "верблюжья нотация" становится каким-то странным выбором наименования.

Конструкции.


  • array() и несколько похожих конструкций не являются функциями. array сам по себе не означает ничего. Такая штука: $func = "array"; $func(); не прокатит.
  • Размотка массива может быть сделана с помощью конструкции list($a, $b) = ... list() — это похожая на функцию конструкция, прямо как array. Я не знаю, почему ей не присвоили нормальное говорящее имя, и почему её имя настолько неочевидное.
  • (int) очевидно создана, чтобы выглядеть как С, но это цельная смысловая единица: в языке нет ничего с названием int. Попробуйте: не только var_dump(int) не сработает, он к тому же выбросит parse error, потому что аргумент выглядит как оператор преобразования типа.
  • А вот (integer) — это синоним для (int). Есть ещё пары синонимов: (bool) / (boolean), (float) / (double) / (real).
  • Существует (array) как оператор преобразования типа в массив и (object) для преобразования в объект. Звучит странно, но их можно использовать следующим образом: приписать (array) к аргументу функции, при этом неважно, будет ли передан один элемент или целый список — все они будут обрабатываться одинаково. Но подвох в том, что вы не сможете использовать эту фичу безопасно: как только вы передадите в функцию Объект, преобразование его в массив просто создаст массив с его атрибутами. (Преобразование в объект сделает обратное).
  • include() и его вариации — это простые сишные конструкции типа #include : они лишь подгружают внешний код в ваш файл. Здесь не существует модульной системы.
  • В РНР не существует внутренних классов и функций: они все глобальные. Подгружая код с помощью include() и подобных конструкций вы подгружаете все переменные в локальную область видимости, но сами функции и классы все глобальны.
  • Добавление элемента в конец массива можно делать так: $foo[] = $bar.
  • echo — это выражение, а не функция.
  • А вот empty($var) — это настолько НЕ функция, что любая попытка передать вместо переменной выражение, например empty($var || $var2) вызывает parse error. Какого чёрта парсеру вообще знать про какую-то особенную empty?
  • Какой-то излишний синтаксис для блоков типа: if (...): ... endif;
И куча всего ещё.

Обработка ошибок.


  • В РНР есть уникальный оператор @ (позаимствованный из DOS), который подавляет вывод ошибки.
  • Ошибки в РНР не создают стек-трейсов. Для этого вам необходимо отдельно устанавливать обработчик. (Но для фатальных ошибок все равно не поможет — см. ниже).
  • Ошибки парсера просто выплевывают состояние парсера и ни слова больше, что делает отлов какой-нибудь забытой кавычки ужасающе неудобным.
  • Парсер PHP, встречая :: в неположенном месте ссылается на T_PAAMAYIM_NEKUDOTAYIM, а встречая << — на T_SL. Сиди и думай, что тебе пытались сказать.
  • Большинство обработок ошибок представляют собой запись в лог сервера, который никто не читает и на который большинству совершенно пофиг.
  • E_STRICT как параметр error_reporting() делает совсем не то, что можно подумать из его названия. а что именно он делает в документации не указано.
  • E_ALL показывает все категории ошибок. Все, кроме E_STRICT (пофиксили в 5.4).
  • Невероятная каша в языке в утверждении того, что разрешено, а что — нет. Я не знаю, как E_STRICT здесь принимает решения, но вот эти вещи в РНР — норма:
    • Попытка получить доступ к несуществующему свойству объекта — WARNING;
    • Использование переменной как имени функции, имени переменной или класса — тишина;
    • Попытка использования необъявленной константы — NOTICE;
    • Попытка доступа к свойству чего-то, что не является объектом — NOTICE;
    • Попытка использовать несуществующую переменную — NOTICE;
    • 2 < "foo" — тишина;
    • foreach (2 as $foo) — WARNING;
    А вот эти — нет:
    • Попытка доступа к несуществующей константе класса, например $foo::x (FATAL ERROR);
    • Использование константы в качестве имени функции, переменной, класса — PARSE ERROR;
    • Попытка вызовa несуществующей функции — FATAL ERROR;
    • Точка с запятой, случайно оставленная конце блока с управляющей конструкцией или в конце файла — PARSE ERROR;
    • Использование list и других псевдо-конструкций языка в качестве имени метода — PARSE ERROR;
    • Индексация возвращаемого функцией значения, например foo()[0] — PARSE ERROR (пофиксили в 5.4, см. выше).

  • Далее по списку попадутся ещё несколько "хороших"" примеров с PARSE ERROR.
  • Метод __toString не умеет бросать исключения. Если вы попробуете это сделать, то РНР, хмм ... сам выбросит исключение. Точнее FATAL ERROR, что было бы сносно в каких-то случаях, но здесь ...
  • Ошибки РНР и РНР-исключения — это совсем два разных зверя. И не похоже, чтобы они вообще хоть как-то взаимодействовали между собой.
    • Ошибки РНР (как внутренние, так и те, что вызваны trigger_error) не отлавливаются конструкцией try/catch.
    • Точно так же, исключения не активируют обработчики ошибок, которые установлены в set_error_handler.
    • Вместо этого, существует отдельный set_exception_handler, который работает с непойманными исключениями, так как в mod_php модели невозможно обернуть точку входа программы в блок try.
    • Ошибки вида FATAL ERROR (вызовем так, например: new ClassDoesntExist()) вообще невозможно отловить. При том, что их вызывают порой совершенно безобидные вещи, грохая вашу программу по каким-то сомнительным причинам. Функции аварийного завершения работают, но не возвращают стек-трейсов (потому что вызываются на верхнем уровне) и потому не могут сообщить, рухнула ли программа из-за ошибки или проработала до завершения нормально.
    • Попытка выбросить объект, который не является экземпляром класса Exception вызовет (что бы вы думали?) — FATAL ERROR, а не выбросит исключение.
  • В РНР не существует конструкции finally, что делает написание кода обёртки (установку обработчика, исполнение кода, удаление обработчика, запуск теста, "манкипатч" и т.п.) утомительным и сложным. Несмотря на то, что сама концепция ООП в РНР была в большинстве своём скопирована с Java, finally убрали специально, потому что
    "finally не имеет смысла в контексте РНР"
    Да ну?(Пофиксили в 5.5).

Функции.


  • Вызовы функций в РНР, по всей видимости — достаточно затратная штука.
  • Некоторые встроенные функции взаимодействуют с функциями, возвращающими ссылки таким, скажем, несколько странным образом.
  • Как уже было сказано, множество вещей, которые выглядят как функции или должны быть функциями на самом деле являются конструкциями языка, поэтому всё, что вы можете проделывать с функциями с ними работать не будет.
  • Аргументы функций могут иметь "подсказки типов" и это, вообще говоря, не что иное, как статическая типизация. Но вы не можете написать ни один из базовых типов: ни string, ни object, ни int, не смотря даже на то, что всякая встроенная функция их использует. Возможно потому, что, например, int в РНР не определено (см. выше про (int)). Вы также не сможете использовать специальные псевдотипы, которые часто используются в тех же встроенных функциях: mixed, number, или callback (с версии 5.4 доступен ещё callable).
    • В результате, такой код:
      function foo(string $s) {}
      foo("hello world");
      
      породит ошибку:
      PHP Catchable fatal error: Argument 1 passed to foo() must be an instance of string, string given, called in...
      
    • Как вы могли заметить, данная в примере выше "подсказка типа" на самом деле не существует: в этой программе нет класса string. Если вы попробуете использовать ReflectionParameter::getClass() для того, чтобы выяснить тип, то РНР будет упираться, что такого класса не существует, что делает невозможной любую попытку выяснить имя этого класса.
    • Подсказку типа нельзя применить к значению, возвращаемому функцией;
    • Передачу аргументов текущей функции другой (диспетчеризация, нередкая ситуация) можно сделать с помощью call_user_func_array('other_function', func_get_args()). Но использование функции func_get_args() вызовет ошибку типа FATAL ERROR во время выполнения, жалуясь на то, что эта функция не может быть параметром у call_user_func_array(). Как это вообще может быть ошибкой? (Пофиксили в 5.3).
    • Замыкания требуют чётко и ясно указывать каждую используемую внешнюю переменную. Почему бы интерпретатору самому не сообразить? (Ладно, отвечу — потому что в РНР при использовании переменной где бы то ни было, она молча создаётся заново, если явно не было прописано иное, помните?)
    • Переменные для замыканий "передаются точно также как и обычные аргументы функций. Поэтому, массивы, объекты и т.п. можно "передать" в замыкание просто по значению. До тех пор, пока вы не используете &.
    • Потому что переменные для замыканий — это автоматически передаваемые аргументы, здесь нет вложенной области видимости, потому замыкание не может ссылаться на скрытые методы, даже если они объявлены внутри класса (Вроде пофиксили в 5.4, но вообще непонятно).
    • Никаких именованных параметров у функций. На самом деле, разработчики их полностью выпилили, потому что они "загрязняют код".
    • Параметры функции со значениями по умолчанию в объявлении функции могут указываться перед параметрами без значений, хотя в документации чётко прописано, что так делать по меньшей мере странно. Так почему бы не запретить это?
    • Дополнительные параметры функции игнорируются (кроме встроенных функций, которые порождают ошибку). Пропущенным аргументам при вызове присваивается значение NULL.
    • Функции с "переменным числом параметров" требуют возни с func_num_args, func_get_arg, и func_get_args. В синтаксисе языка просто не предусмотрена нормальная работа с такими функциями.

ООП


  • Процедурная часть РНР сделана как в С, а вот объектная часть (хо хо) сделана наподобие Java. Я даже не могу представить, насколько они диссонируют друг с другом. Система классов сделана как у более мощного и низкоуровневого языка Java, который по своей природе и намеренно намного более ограничен и строг, чем современники РНР, и я, если честно, в замешательстве.
    • Я не встречал ни одной глобальной функции, у которой была бы хоть одна заглавная буква в названии, но зато у всех важных встроенных классов имена методов написаны в "верблюжьей" нотации и имеют названия типа getFoo(), как в Java.
    • Perl, Python и Ruby имеют концепцию доступа к свойствам из внешнего кода; в PHP есть только неуклюжий __get и ряд подобных методов (документация непонятным образом называет такие специальные методы "перегруженными").
    • Классы имеют нечто наподобие объявления переменный и констант (var и const) для своих атрибутов, в то время как в процедурной части языка это полностью отсутствует.
    • Несмотря на сильное влияние C++/Java, в которых объекты довольно монолитны и непрозрачны, РНР рассматривает объекты словно какие-то особые массивы — например, нормальное поведение foreach ($obj as $key => $value) — это перебор всех доступных атрибутов объекта.
  • Классы — это не объекты. При любом метапрограммировании следует обращаться к ним по строковому имени, прямо как к функциям.
  • Встроенные типы — это тоже не объекты и (в отличие от Perl) и никоим образом их нельзя представить в виде объектов.
  • instanceof — это оператор, несмотря на то, что классы в РНР появились в поздних версиях, а большая часть языка строится на функциях и процедурном синтаксисе. Влияние Java? Классы — это не объекты первого класса? (Я не знаю, есть ли они)
    • Но есть функция is_a. И у неё есть дополнительный аргумент, указывающий, допускается ли имя класса в виде строки в качестве параметра object.
    • Это не работает со встроенными типами опять же (помните, int в РНР не определён). Для таких вещей вам нужно использовать is_int, и т.п.
    • Опять же, в левой части должна быть переменная, там не может быть выражения. Потому что оно приведёт к ... PARSE ERROR.
  • clone — это оператор!?
  • Атрибуты объекта — это $obj->foo, а атрибуты класса — Class::$foo. При попытке выполнить $obj::$foo РНР попытается привести $obj к строке и будет использовать полученную строку как имя класса. К атрибутам класса нельзя получить доступ из объекта; пространства имен совершенно разные, что делает атрибуты класса абсолютно бесполезными при полиморфизме. Методы класса, конечно, являются исключением из этого правила и могут вызываться как и любые другие методы (Я говорил, что такая система работает в С++. Но С++ не является хорошим примером ОО-языка).
  • В то же время, метод экземпляра может вызываться статично (Class::method()). Если выполнить этот трюк из другого метода, это сработает как обычный метод, вызванный текущим $this. Я так думаю.
  • new, private, public, protected, static и т.п. Попытка расположить к себе Java-разработчиков? :) Я в курсе, что это скорее дело вкуса, но я не знаю, зачем всё это нужно в языке с динамической типизацией: в С++ большинство этих плюшек — это вопросы компиляции и разрешения имен.
  • РНР имеет первоклассную поддержку "абстрактных классов" — классов, экземпляры которых не могут быть созданы. Код в подобных языках добивается этого просто выбрасывая исключение в конструкторе.
  • Дочерние классы не могут перегружать приватные методы. Дочерний класс, перегружающий публичные методы не может даже увидеть, не говоря уже о вызове, приватные методы суперкласса. Создаст проблемы, скажем, для тестовых моков.
  • Нельзя назвать метод, например, list, потому что list() — это специальная конструкция (не функция, опять же) языка и парсер растеряется. Хотя здесь нет ни единой причины для двусмыслия, а "манкипатч" для класса вообще прекрасно работает ($foo->list() не вызывает синтаксической ошибки).
  • Если исключение выброшено во время инициализации аргументов в конструкторе (например, new Foo(bar()) где bar() выбрасывает исключение), то конструктор не сработает. А вот деструктор — запросто. (Пофиксили в 5.3).
  • Исключения в __autoload и деструкторах порождают FATAL ERROR (Пофиксили в 5.3.6. Теперь деструктор может выплюнуть исключение в буквальном смысле где угодно, начиная с того момента когда счётчик ссылок вернёт ноль. Хмм.)
  • Откровенно говоря, в РНР вообще нет конструкторов и деструкторов. __construct() — это инициализатор, как питоновский __init__. Не существует метода, который вы могли бы вызвать в классе, чтобы выделить память и создать объект.
  • ООП привнесло в РНР интерфейс итератора с учётом особенностей самого языка (например, конструкция for .. as), однако в РНР нет ни одной встроенной сущности, реализующей данный интерфейс. Если вы хотите итератор для массива, вам нужно обернуть его в ArrayIterator. Не существует встроенных способов соединять, разделять или проделывать другие действия с итераторами как с объектами первого класса.
  • Интерфейсы типа Iterator зарезерировали за собой несколько хороших имен для методов. Если вы хотите, чтобы ваш класс реализовывал интерфейс итератора с нестандартным поведением (например, без итерации по все доступным атрибутам) и хотите использовать какое-нибудь общепринятое имя для своего метода вроде key или next, или current — что ж, придётся обломаться.
  • Классы могут перегружать методы своего преобразования в строку и того, как они ведут себя при вызове, но не могут перегружать методы своего преобразования в числа и другие встроенные в язык базовые типы данных.
  • Строки, числа и массивы: все они имеют определённое в языке преобразование в строку; язык во многом полагается на это. Функции и классы — это строки. Попытка преобразовать встроенный или пользовательский объект (даже Замыкание) в строку порождает ошибку, если внутри не определён метод __toString. Даже echo становится потенциально уязвимым для ошибок.
  • Не существует перегрузки для равенства или упорядочивания.
  • Статические переменные внутри методов объекта глобальны; они сохраняют своё значение для всех экземпляров класса.

Стандартная библиотека

Perl требует некоторой сборки. Python уже поставляется с "батарейками внутри". PHP — это кухонная раковина из Канады, у которой на всех барашках смесителя написано "С" (игра слов: С как название языка и как сокращение С = Cold, холодный — прим. перев.).

В целом

  • Отсутствует модульная система. Можете компилировать расширения языка, но какие из них включаются задаёт php.ini и выбор за вами: включать свои расширения в глобальное пространство имён или нет.
  • Пространства имён — относительно недавняя фича, появление которой, разумеется, никак не повлияло на разделение функций. В глобальном пространстве имён тысячи функций.
  • Разные фрагменты стандартной библиотеки жутко несогласованы друг с другом:
    • Нижнее подчеркивание vs его отсутствие: strpos/str_rot13, php_uname/phpversion, base64_encode/urlencode, gettype/get_class
    • "to" vs 2: ascii2ebcdic, bin2hex, deg2rad, strtolower, strtotime
    • Объект+глагол vs глагол+объект: base64_decode, str_shuffle, var_dump vs create_function, recode_string
    • Порядок параметров: array_filter($input, $callback) и array_map($callback, $input), strpos($haystack, $needle) и array_search($needle, $haystack)
    • Путаница с префиксами: usleep vs microtime
    • Нечувствительность к регистру функций влияет на то, где находится i в их имени
    • Половина функций работы с массивами начинается с префикса array_, а вторая половина нет
    • htmlentities и html_entity_decode делают прямо противоположные вещи, при этом соглашения об их именовании тоже прямо противоположные.
  • Итак, кухонная раковина. Стандартная библиотека также включает:
    • Привязка к ImageMagick, привязка к GraphicsMagicks (что по сути форк ImageMagick), и горстка функций для просмотра EXIF-данных (что может делать и ImageMagick);
    • Функции для парсинга bbcode, крайне специфичной разметки, которой пользовалась горстка каких-то форумных пакетов.
    • Дофига пакетов для XML. DOM (OO), DOM XML (не ОО), libxml, SimpleXML, “XML Parser”, XMLReader/XMLWriter и более полдюжины сокращений, которые я не могу идентифицировать. Там безусловно есть какая-то разница между ними всеми, не стесняйтесь поковыряться и выяснить, что из них что.
    • Привязки для двух конкретных процессоров кредитных карт: SPPLUS и MCVE... Чтоооа?
    • Три способа достучаться до MySQL: mysql, mysqli и более абстрактная PDO.

Влияние С

Это заслуживает отдельной пули в лоб, потому что этот абсурд до сих пор насквозь пронизывает язык. РНР — это язык программирования высокого уровня с динамической типизацией. Тем не менее, основная часть его стандартной библиотеки — это по-прежнему тоненькие обёрточки вокруг С API, с соответствующими последствиями.
  • Выходные (out) параметры, хотя РНР без особых услилий может вернуть и необъявленный массив, и несколько переменных.
  • Кучка функций для получения последней случившейся ошибки в какой-нибудь подсистеме (см. ниже), хотя в РНР уже больше восьми лет существуют исключения.
  • Бородавки типа mysql_real_escape_string, которая имеет ровно те же аргументы, что и канувшая в лету mysql_escape-string, но существует только потому, что является частью C API.
  • Глобальное поведение для неглобальной функциональности (как с MySQL). Использование нескольких MySQL соединений требует передачу дескриптора подключения при каждом вызове функции.
  • Обёртки действительно очень и очень тонкие. Например, вызов dba_nextkey без предварительного вызова dba_firstkey вывалится в segfault.
  • К тому же эти обёртки ещё и зависимы от платформы: fopen(directory, "r") работает в Linux, но вернёт false в Windows и покажет WARNING.
  • Есть набор ctype_* функций (например, ctype_alnum), которые позволяют проверить принадлежность символа или строки определенному классу символов в С.

Обобщения

Этого понятия в РНР нет. Если две функции должны делать чуточку разные вещи, то в РНР это будет две разных функции.
Как вы сортируете в обратном порядке? В Perl можно было сделать так: sort { $b <=> $a }. В Python так: .sort(reverse=True). В РНР для этого сделана отдельная функция — rsort().
  • Функции, возвращающие ошибку : curl_error, json_last_error, openssl_error_string, imap_errors, mysql_error, xml_get_error_code, bzerror, date_get_last_errors дальше продолжать?
  • Функции сортировки: array_multisort, arsort, asort, ksort, krsort, natsort, natcasesort, sort, rsort, uasort, uksort, usort
  • Функции поиска текста: ereg, eregi, mb_ereg, mb_eregi, preg_match, strstr, strchr, stristr, strrchr, strpos, stripos, strrpos, strripos, mb_strpos, mb_strrpos + вариации, которые делают замену.
  • Чёртова куча никому не нужных алиасов: strstr/strchr, is_int/is_integer/is_long, is_float/is_double, pos/current, sizeof/count, chop/rtrim, implode/join, die/exit, trigger_error/user_error, diskfreespace/disk_free_space
  • scandir возвращает список файлов в заданной директории. Вместо того, чтобы вернуть их в том порядке, в каком они присутствуют (сначала директории, что зачастую полезно), она их молча сортирует. Есть необязательный аргумент, который отвечает за ... сортировку в обратном алфавитном порядке. Видимо, функций сортировки им было недостаточно, раз их впихнули даже сюда. (Ура! В 5.4 добавили третий параметр, отменяющий сортировку).
  • str_split разбивает строку на куски одинаковой длины. chunk_split разбивает строку на куски одинаковой длины, а затем склеивает их с разделителем. Это уже даже не смешно.
  • Чтение из архивов требует множество различных функций, зависящих от его формата. Есть шесть различных групп таких функций, все с разным API: для bzip2, LZF, phar, rar, zip, и gzip/zlib
  • Поскольку вызов функции с массивом вместо набора параметров жутко неудобный (call_user_func_array), то существуют некоторые пары функций вроде printf/vprintf и sprintf/vsprintf. Они делают то же самое, одна функция принимает параметры, а другая — массив параметров.

Текст

  • preg_replace с флагом /e (eval) сделает замену совпадений по регекспу в строке, а затем (!) попытается выполнить её (eval).
  • Функция strtok по всей видимости была введена по образу и подобию одноимённой функции С, что было изначально плохой идеей по самым разнообразным причинам. И неважно, что РНР может запросто вернуть массив (что было бы неудобно в случае С), или что даже для самого изощрённого хака использование strtok не оправдано и не нужно.
  • Функция parse_str парсит строку запроса, хотя в её названии это нигде не отражено. Кроме того, она ведёт себя так же как и register_globals и дампит запрос в локальную области видимости, создавая в ней переменные, если не передадите в качестве второго параметра массив (в этом случае, все затолкается в массив и никаких переменных создано не будет).
  • Функция explode отказывается разбивать строку, если разделитель не указан или упущен. Все другие реализации функций разбивки строк имеют какое-то поведение по умолчанию, если разделитель не указан явно; в РНР же вместо этого есть совершенно иная функция со сбивающим с толку названием str_split , которая, как описано в документации, "переводит строку в массив".
  • Для форматирования дат, существует strftime, которая работает прямо как её С API аналог и учитывает локаль. Есть еще date, у которой синтаксис абсолютно другой и работает она только с английским.
  • "gzgetss — возвращает строку из gz-файла с удалёнными HTML-тегами". Мне до смерти интересно узнать всю логическую цепочку умозаключений, которые повлекли за собой создание этой функции.
  • mbstring
    • это всё про "многобайтные строки", хотя проблема кроется в наборах символов;
    • тем не менее, это работает и с обычными строками. Имеет единый глобальный набор символов по "умолчанию". Некоторые функции, конечно, позволят явно указать кодировку, но тогда она применится ко всем параметрам и возвращаемым значениям.
    • Предоставляет ereg_* функции, но они устарели. preg_* функции проигнорированы, хотя они и умеют понимать utf-8, если им передавать некоторые специфические PCRE-флаги.

Система.

  • В общем, тут есть множество функций, которые стирают грань между текстом и переменными. compact и extract — это лишь верхушка айсберга.
  • Есть несколько способов, чтобы привнести динамику в РНР-код, но с первого взгляда это не дает никаких преимуществ, да и разница не видна. classkit может изменять определённые пользователем классы, runkit может замещать их и изменять другие пользовательские сущности (константы, функции), Reflection* позволяет проводить реверс-инжиниринг классов, интерфейсов, функций, методов; существует также множество отдельных функций для получения отчетности о классах, функциях ... Независимы ли все эти подсистемы, связаны ли они, излишни ли?
  • get_class($obj) вернет имя класса объекта. get_class() вернет имя класса, в котором была вызвана. Оставив в стороне тот факт, что одна функция делает две совершенно разные операции, выполним: get_class(null): ... вернёт то же, что и в последний раз. Получается, что при произвольном значении вы не можете ей доверять. Сюрприз!
  • Классы stream_* позволяют релизовать пользовательские поточные объекты для работы с fopen и другими встроенными файловыми функциями. А вот с "ftell" их использовать нельзя по глубоко внутренним причинам (есть ещё множество функций, которые с этим связаны).
  • register_tick_function принимает замыкания. unregister_tick_function не примет; вместо этого он вызовет ошибку, жалуясь на то, что замыкание нельзя перевести в строку (string).
  • php_uname расскажет вам о текущей ОС. Если вдруг бедный РНР не сможет определить ОС, в которой он запущен, то он покажет вам информацию о той ОС, в которой он был собран. И если это произойдет он не считает себя обязанным вам об этом сообщать.
  • Встроенных fork и exec нет. Их можно прикрутить с помощью расширения pcntl, но по умолчанию оно не установлено. popen не поддерживает pid.
  • Возвращаемое значение stat закешировано.
  • session_decode предназначена для считывания произвольной РНР-сессии в строку, но на деле работает только в активной сессии. Кроме того, она дампит результат в $_SESSION, вместо того, чтобы просто его вернуть.

Разное.

  • curl_multi_exec не влияет на возвращаемое значение curl_errno при ошибке, но влияет на значение curl_error.
  • Параметры mktime следуют в таком порядке: часы, минуты, секунды, месяц, день, год.

Работа с данными.


Программы — это не более, чем большие механизмы, которые пережевывают одни данные и выплевывают другие, обычно в большем объёме. Было создано множество языков в зависимости от того, с какими данными им придется работать, начиная с awk и заканчивая С и Прологом. Если язык не умеет обрабатывать данные, это значит что он не умеет делать вообще ничего.

Числа

  • Целые числа Integers определены со знаком и являются 32-битными на 32-битных платформах. В отличие от всех современников, в РНР нет автоматической поддержки bigint. Таким образом, вы можете в конечном счёте столкнуться с такими сюрпризами, как отрицательный размер файла, а ваша математика может работать по-разному, в зависимости от архитектуры процессора. Единственный ваш вариант для больших целых чисел — это использовать обёртки GMP или BC. (Разработчики предложили добавить новый 64-битный тип данных, это безумие.)
  • РНР поддерживает восьмеричную систему исчисления, просто добавляя 0 перед числом, например 012 — это десять в десятичной системе. Однако, 08 становится нулем. Восьмерки, девятки и все последующие цифры в числе исчезают. 01с — это синтаксическая ошибка.
  • 0х0+2 будет 4. Парсер считает двойку и как часть шестнадцатеричного числа и как отдельное десятичное число, обрабатывая это как 0x002 + 2. 0x0+0x2 выведет то же самое. Странно, но 0x0 +2 по-прежнему 4, а вот 0x0+ 2 — это правильный ответ, 2. (Пофиксили в РНР 5.4, но опять сломали: в 5.4 ввели новый синтаксис для шестнадцатеричных чисел — префикс 0b, и снова история повторяется: 0b0+1 равно 2).
  • pi — это функция, а константа — это M_PI.
  • В РНР нет оператора экспоненты, только функция pow.

Текст.

  • Нет поддержки Юникода. Только ASCII работает надежно, я серьёзно. Есть расширение mbstring, которое упоминалось выше, но оно немножко валится.
  • Отсюда логично вытекает, что использование встроенных функций работы со строками с текстом в utf-8 потенциально может его повредить.
  • По той же причине, не существует сравнения с учётом регистра вне пределов ASCII. Несмотря на распространение нечувствительных к регистру версий функций, ни одна из них не сможет сказать, что é это то же что и É.
  • Вы не можете заключать в кавычки индексы переменных при их интерполяции в строке, например "$foo['key']" — это синтаксическая ошибка. Вы можете убрать кавычки (получите WARNING) или использовать фигурные скобки ${...} / {$...}
  • "${foo[0]}" — сработает. "${foo[0][0]}" — синтаксичская ошибка. Если же включим $ внутрь скобок то оба варианта сработают. Плохое копирование похожего синтаксиса Perl (с радикально другой семантикой)?

Массивы.

Бля, ребята.
  • В одном этом типе уживаются и списки, и упорядоченные хеши, и упорядоченные множества, и разреженные списки, и, внезапно, всякие странные комбинации всего перечисленного. Как это работает? Какая память используется при этом? Кто знает? Ну хотя у меня больше нет вариантов, чего это я.
  • => не является оператором. Это специальная конструкция, которая живёт только внутри array(...) или foreach.
  • Отрицательная индексация (с конца) не работает. Значение -1 настолько же валидно, как и 0.
  • Несмотря на то, что массивы в РНР — это, по сути, единственная присутствующая в языке структура данных, она не имеет никакого сокращенного синтаксиса. array(...) это и есть сокращенное напиание (в РНР 5.4 появились литералы, [...]).
  • Что сбивает с толку так это тот факт, что Array преобразуется в строку, порождая NOTICE.
  • Конструкция => базируется на синтаксисе Perl, который разрешал foo => 1 без заключения в кавычки (по сути, это единственная причина существования этой конструкции в Perl, в других случаях хватило бы запятой). В РНР вы не сможете проделать это, не получив WARNING; это единственный язык в своей нише, в котором нельзя создать хеш, не заключая строковые индексы в кавычки.
  • Функции работы с массивами зачастую ведут себя странно и неожиданно, так как оперируют и со списками, и с ассциативными массивами, и с комбинацией и тех и других. Рассмотрим array_diff, который "обнаруживает расхождения в массивах".
    $first = array("foo" => 123, "bar" => 456);
    $second = array("foo" => 456, "bar" => 123);
    echo var_dump(array_diff($first, $second));
    
    Что сделает этот код? Если array_diff расценит свои входные массивы как хеши, тогда очевидно, что они разные: одни и те же ключи имеют различные значения. Если расценивать их как списки, то всё равно они различны: значения идут в разном порядке.
    На самом деле, array_diff скажет, что они равны, потому что он расценивает их как множества: сравнивает только значения, игнорируя порядок.
  • По той же причине, array_rand ведёт себя странно в выборе случайных индексов, что не очень полезно, посколько самый частый случай использования подобной функции — это взять случайный элемент из определённого списка.
  • Несмотря на все сказанное, взгляните как сильно РНР иногда полагается на порядок следования индексов:
    array("foo", "bar") != array("bar", "foo")
    array("foo" => 1, "bar" => 2) == array("bar" => 2, "foo" => 1)
    
    Я оставляю за читателем право выяснить, что произойдет, если массив будет смешанным (потому что я ХЗ).
  • array_fill не умеет создавать пустые массивы; вместо этого он породит WARNING и вернёт false.
  • Все (ну или подавляющее большинство) функций сортировки делают своё дело прямо внутри сортируемого массива и ничего не возвращают. Нет встроенного способа создать отдельную отсортированную копию текущего массива; вам самим надо сначала скопировать массив, затем его отсортировать, а затем уже использовать скопированный ранее.
  • А вот array_reverse возвращает новый массив.
  • Упорядоченные списки и некоторые ассоциативные массивы кажутся хорошим способом передачи аргументов для функций, но нет.

Не массивы.

  • Стандартная библиотека включает класс Quickhash — ООП-реализацию «специфических строго-типизированных классов» для создания хэшей. И на самом деле предоставляет четыре класса, каждый для работы с различными комбинациями типов ключей и значений. Непонятно, почему встроенная реализация массивов не может быть оптимизирована для этих очень частых случаев, и какова относительная эффективность этого самого Quickhash.
  • Есть класс ArrayObject (реализующий пять различных интерфейсов), который может обернуть массив и позволяет ему "притвориться" объектом. Пользовательские классы могут реализовывать те же интерфейсы. Проблема в том, что у этого класса пригоршня методов, половина которых не похожа на встроенные функции, к тому же встроенные функции не умеют оперировать ArrayObject'ом или другим похожим на массив классом.

Функции

  • Функции — это не данные. Замыкания на самом деле являются объектами, но обычные функции — нет. Вы не можете даже сослаться на них по их прямым именам; var_dump(strstr) выкидывает WARNING и думает, что вы имели в виду строку "strstr". Нельзя отличить произвольную строку от "ссылки" на функцию.
  • create_function на самом деле обычная обёртка над eval. Она создаёт функцию с обычным именем и устанавливает её глобально (поэтому сборщик мусора никогда её не соберет, не используйте в цикле!). На деле о текущем контексте она ничего не знает, поэтому не является замыканием. Её имя содержит NUL-байт, поэтому такая функция никогда не конфликтует с обычными функциями (потому что PHP-парсер валится, как только натыкается на NUL в файле, где бы он ни находился).
  • Объявление функции с именем __lambda_func сломает create_functionна самом деле она создаёт через eval-функцию с именем __lambda_func, затем внутренними методами переименовывается. Если __lambda_func уже существует, выбросится FATAl ERROR.

Другое

  • Инкрементируя (++) NULL получаем 1. Декрементируя (--) NULL получаем NULL. Строка от декремента также не изменится.
  • Нет генераторов (Пофиксили в 5.5. Клево, они просто склонировали целиком Python generator API. Впечатляюще. Только почему-то $foo = yield $bar — это синтаксическая ошибка, надо писать $foo = (yield $bar). Пффф ...)

Веб-фреймворк.


Выполнение

  • Один файл php.ini контролирует огромную часть функционала PHP и навязывает сложные правила относительно того, что и когда перегружается. PHP-приложению, которое будет крутиться на какой-то произвольной машине, необходимо перенастраивать свой конфиг, чтобы всё работало нормально, что уничтожает всю ценность имеющегося механизма настройки через php.ini.
  • РНР ищет php.ini сразу в нескольких местах, так что вполне возможно (или невозможно) с помощью альтернативного конфига перенастроить свой хост. Но только один из таких файлов будет принят, так что вы не можете просто так взять и перенастроить ряд опций, и просто расслабиться.
  • По существу PHP выполняется как CGI. При каждой загрузке PHP-страницы всё приложение заново перекомпилируется и выполняется. Даже дев серверы для игрушечных Python-фрэймворков так себя не ведут. Это создало целый рынок «PHP-акселераторов», выполняющих компиляцию единожды, что ускоряет PHP до уровня любого другого языка. Zend, компания, стоящая за PHP, сделала это частью своей бизнес-модели.
  • Достаточно долгое время PHP-ошибки по умолчанию сыпались на клиента — наверное, для помощи разработчикам. Не думаю, что там всё до сих пор так, но всё ещё время от времени натыкаюсь на внезапные mysql-ошибки, выплевываемые на верх страницы.
  • PHP всё ещё полон странных «пасхалок» вроде вывода логотипа PHP при передаче соответствующего аргумента в запросе. Кроме того, что это никак не связано с построением вашего приложения, это ещё и позволяет определить используете ли вы PHP (и возможно прикинуть какую версию), независимости от того как много вы нагородили в ваших mod_rewrite, FastCGI, обратном проксировании и т.п.
  • Пустые строки перед <?php или после ?>, даже в библиотеках, считаются текстом и выводятся наружу (или приводят к знаменитой «headers already sent»). Вам на выбор: либо избегать лишних пустых строк в конце файла (хотя одна после ?> не считается) или просто забить и не писать в конце ?>. Всё нормально.

Развёртывание и внедрение

Внедрение часто упоминается как самое преимущество PHP: скиньте несколько файлов на сервер и всё. В самом деле это проще, чем выполнение целого процесса, как если бы вы делали это в Python, Ruby или Perl. Но PHP лишает нас многих других радостей.
Со одной стороны я за то, чтобы веб-приложения выполнялись как сервера приложений, а запросы обратно проксировались к ним. Это требует минимальных усилий для настройки и даёт множество плюшек: вы можете управлять веб-сервером и приложением раздельно друг от друга, вы может запускать сколь угодно много или мало процессов приложения на сколь угодно большом количестве машин без дополнительных веб-серверов, вы можете запускать приложение под другим пользователем без особых усилий, вы можете менять веб-серверы, вы можете остановить приложение не трогая веб-сервер и пр. Связывать ваше приложение с веб-сервером абсурдно и причин так делать больше не остаётся.
  • РНР в прямом смысле привязан к Apache. Их раздельная работа, или работа с каким-либо другим веб-сервером требует стольких же танцев с бубном, как и развёртывание приложения на другом языке (если не больше).
  • php.ini применяется ко всем РНР-приложениям, которые запускаются на этой конкретной машине. Есть только один файл php.ini, который применяется глобально; если же вы хоститесь на чужом сервере, или два ваших приложения требуют разных настроек, то вам не повезло: придётся применять набор всех нужных настроек из самого приложения через ini_set, конфигурационный файл Apache или .htaccess. Если сможете. Потому что — вау! — нужно проверить так много мест, чтобы определить как и где вот эта опция получает своё значение.
  • По этой же причине нет простого способа «отделить» PHP-приложение и его зависимости от остальной системы. Выполняете два приложения, требующие разных версий библиотеки или самого PHP? Начните со сборки второй копии Apache.
  • Метод «сбрось несколько файлов» между прочим делает routing жуткой болью в заднице, также это значит что вы должны осторожно разрешать и запрещать доступ, потому что ваша иерархия URL'ов — это всё ваше дерево кода. Конфиги и другие «запчасти» требуют защитных проверок как в C, чтобы избежать их прямой загрузки. Всякий мусор от систем контроля версий (типа .svn) тоже должен быть защищён. С mod_php всё в вашей файловой системе становится потенциальной входной дверью; с сервером приложений, у вас только одна входная точка, и только URL контролирует куда идти.
  • Вы не можете по-тихому обновить несколько файлов, выполняемых как CGI, если не хотите падений и неопределенных поведений, в то время, когда пользователи дёргают ваш наполовину обновлённый сайт.
  • Несмотря на то, как просто настроить Apache для выполнения PHP, даже здесь вас поджидает несколько скрытых ловушек. В то время, как документация PHP советует использовать SetHandler для запуска .php-файлов как PHP, AddHandler работает так же хорошо, и на самом деле Google выдаёт мне в два раза больше результатов для AddHandler. Собственно в чём проблема.
    Когда вы используете AddHandler, вы указываете Apache «выполнить это как php» — один из возможных способов обработки .php-файлов. Но! Apache вовсем не того же мнения о расширениях файлов, как и каждый человек на этой земле. Он создан распознавать, например такое название index.html.en как HTML-файл на английском. С точки зрения Apache файл может иметь сколь угодно много расширений одновременно.
    Представьте, что у вас есть форма загрузки файлов, которая выгружает файлы в какую-то общедоступную директорию. Чтобы быть уверенными, что никто не загрузит PHP-файл, вы просто проверяете, что расширение файла не .php. Всё, что атакующий должен сделать — это загрузить файл с именем foo.php.txt; загрузчик не увидит никаких проблем, но Apache распознает файл как PHP и с радостью выполнит его.
    Проблема не в том, что «надо было использовать исходное имя файла» или «надо было лучше валидировать». Проблема в том, что ваш веб-сервер, должен выполнять любой старый код, на который он может наткнуться — именно это свойство делает PHP «простым для внедрения». CGI требует +x, это хотя бы что-то, но PHP не требует даже этого. И это не теоретическая проблема; я нашёл несколько сайтов с такой дырой.

Фичи, которых не хватает

Я думаю, что нижесказанное имеет различные уровни критичности для построения веб-приложения. Было бы здраво в PHP, как в языке, который преподаёт себя как веб-ориентированный язык, реализовать что-нибудь из этого.
  • Нет системы шаблонов. Есть сам PHP, но нет ничего что бы работало как большой шаблонизатор, вместо того, чтобы работать как программа.
  • Нет XSS-фильтра. Нет, «используй htmlspecialchars» — это не XSS-фильтр. Вот это XSS-фильтр.
  • Нет CSRF-защиты. Вы должны пилить её сами.
  • Нет обобщённого стандартного API для баз данных. Штуки, типа PDO вынуждены оборачивать отдельные API для каждой СУБД для достижения абстракции.
  • Нет routing'а. Ваш сайт выглядит в точности также как ваша файловая система. Многие разработчики наивно думают, что mod_rewrite (и вообще .htaccess в целом) — это подходящая замена.
  • Нет аутентификации или авторизации.
  • Нет сервера-песочницы. (Типа "пофиксили" в 5.4. Привело к Content-Length уязвимости, указанной ниже. Кроме того, вам всё равно придётся портировать все свои rewrite-правила в какую-то обёртку РНР, routing'а ведь так и не появилось).
  • Нет интерактивной отладки.
  • Нет явного механизма развёртывания, только «скопируйте вот эти файлы на сервер».

Безопасность


Границы языка

Плохая репутация PHP в области безопасности в основном связана с тем, что он принимает произвольные данные из одного языка и выдаёт их в другой. Это плохая мысль. "<script>" ничего не значит в SQL, но точно значит в HTML.
Ещё хуже становится от вечных криков об «очистке входных данных». Это полная шняга; не существует волшебной палочки, взмахнув которой вы сделаете кусок данных «чистым». Что нужно делать так это говорить на нужном языке: использовать placeholder'ы в SQL, использовать списки аргументов при создании процессов и пр.
  • PHP открыто поощряет т.н. «очистку»: для этого даже есть целое расширение.
  • Все эти addslashes, stripslashes и прочая slash-хрень — просто бумажный щит, который ничего не даёт, кроме иллюзии защищённости.
  • Насколько я знаю, нет способа безопасно создать процесс. Можно только выполнить команду через шелл. Вы можете сидеть и экранировать всё как сумасшедший и надеяться, что шелл по умолчанию использует верное экранирование; либо вручную делать pcntl_fork и pcntl_exec.
  • Две функции escapeshellcmd и escapeshellarg имеют практически одинаковые описания. Заметьте, что для Windows, escapeshellarg не сработает (т.к. предполагает семантику Bourne shell), а escapeshellcmd просто заменяет несколько знаков на пробелы, потому что никто не может понять как работает экранирование в Windows cmd (который может тихо упасть вне зависимости от того, что вы пытаетесь сделать).
  • Оригинальные встроенные в MySQL привязки, до сих пор широко используемые, не могут создавать связываемые переменные (prepared statements).
До сих пор PHP-документация по SQL-инъекциям рекомендует сумасшедшие практики типа проверки типов, используя sprintf и is_numeric, повсеместное использование mysql_real_escape_string, или addslashes (которая «может быть полезна»!). PDO или параметризация даже не упоминаются, кроме как в пользовательских комментариях. Я пожаловался на это конкретное место разработчикам PHP минимум два года назад, они были оповещены, но эта страница не обновлена до сих пор.

Небезопасен по умолчанию

  • register_globals. Его выключили по умолчанию достаточно давно, и он пропал в PHP 5.4. Мне пофигу. Всё равно это дырка.
  • include разрешает HTTP URL'ы. Туда же.
  • Magic quotes. Так близки к безопасности по умолчанию, и всё же слишком далеки от правильного понимания самой концепции. В ту же печь.
  • Вы можете, скажем, исследовать сеть, используя поддержку XML, злоупотребляя его повсеместной поддержкой имён файлов как URL. И только libxml_disable_entity_loader() может это пофиксить, хотя проблема неоднократно всплывала во многочисленных комментариях. (В 5.5. появилась легкая в использовании функция password_hash, которая к счастью должна сократить количество самопального криптокода).

Ядро

Сам интерпретатор РНР содержал некоторые воистину очаровательные проблемы безопасности.
  • В 2007 году интерпретатор имел уязвимость переполнения разрядной сетки integer. Фикс начался с if (size > INT_MAX) return NULL; и покатился по наклонной. (Для тех, кто не в курсе C: INT_MAX самое большое целое, которое может уместится в переменную, вообще. Я думаю, остальное вы осознаёте сами).
  • Чуть позже, в PHP 5.3.7 умудрились включить функцию crypt(), которая фактически позволяла залогиниться с каким угодно паролем.
  • PHP 5.4 уязвим к отказу в обслуживании, т.к. он берёт заголовок Content-Length (который кто угодно может установить в любое значение) и пытается выделить под это столько же памяти. Это хреновая идея.
Я мог бы раскопать ещё что-нибудь вкусное, но дело не в том, что есть X эксплойтов — в программах бывают баги, это случается, так или иначе. Шокирует сама природа этих багов. Я ведь даже не ищу их специально — это то, что упало мне на порог за последние пару месяцев.

Итак, заключение.

Некоторые комментаторы справедливо указали, что у меня нет заключения. И да, у меня действительно нет заключения. Если вы дочитали до этого места, я предполагаю, вы были согласны со мной с самого начала :) Если вы знаете только PHP и вам любопытно научиться чему-то ещё, гляньте учебник по Python и попробуйте Flask для веба. (Я не большой фанат их языка шаблонов, но он делает своё дело). Он разделяет части вашего приложения, но это всё ещё те же самые исходные части и они должны быть похожи на то, что вы видели до этого. Я, возможно, напишу хороший пост об этом; но ураганное введение в целый язык и веб-стек всё же тема для другой статьи.
Позже и для бОльших проектов вы можете попробовать Pyramid (уровнем повыше) или Django — сложный и монстрообразный, хорошо подходящий для построения сайтов, похожих на сайт самого Django. Если вы не разработчик, но всё равно читаете это по какой-либо причине, я не успокоюсь, пока все не планете не прочитают Learn Python The Hard Way.
Я не пробовал Ruby on Rails и его соперников, вроде Perl ещё жив с его Catalyst'ом. Читайте хорошие вещи, учитесь хорошим вещам, создавайте их, жгите.

Ссылки

Спасибо за вдохновение:

Пожалуйста, держите меня в курсе всех ошибок, которые я (в самом деле!) совершил, написав эту статью.
Читать дальше......

суббота, 14 июня 2014 г.

[Перевод] G.Weinberg - 1.1 Чтение программ


Когда-то, во времена популярности таких языков как COBOL, очень активно обсуждался вопрос о возможности менеджеров читать программы своих программистов. Как выяснилось позже, все эти действия были направлены просто на привлечение менеджерами дополнительных средств: они просто не хотели зависеть от своих программистов. О реальной возможности чтения программ речи не шло. Ведь, действительно, зачем менеджерам читать программы? Даже сами программисты их не читают.
Но разве есть реальная необходимость читать программы? Разве программы пишутся не для ЭВМ? И да, и нет. Даже закрывая глаза на то, что программы нужно модифицировать, создавать им интерфейс для общения друг с другом и т.п., идея читать программный код не такая уж и плохая с точки зрения обучения программированию.
Программирование, среди всего прочего, это своего рода письменность. Чтобы освоить письменность, нужно, в первую очередь, писать. Но, что касается других форм письменности, чтение также способствует освоению этого навыка. А может ли программист научиться писать программу, просто почитывая программный код? Теоретически — да, практически — никогда. Что же полезного тогда можно извлечь из чтения программ? Давайте рассмотрим маленький пример на языке PL/1 (см. Пример 1).

Пример 1.
XXX: PROCEDURE OPTIONS(MAIN);
     DECLARE B(1000) FIXED (7,2),
             C FIXED (11,2),
             I,J FIXED BINARY;
     C = 0;
     DO I = 1 TO 10;
         GET LIST((B(J) DO J = 1 TO 1000));
         DO J = 1 TO 1000
             C = C + B(J);
             END;
         END;
     PUT LIST('SUM IS', C);
     END XXX;

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

Машинные ограничения
Одна из причин появления какого-либо фрагмента кода — это ограничения ЭВМ, на которой решается данная конкретная задача в сравнении с некоторой идеальной ЭВМ. В предложенном примере видно, что в общей сложности была получена сумма десяти тысяч чисел, но прочитаны они были в циклах по тысяче. Поскольку числа, очевидно, перфорированы1 в формате списков PL/1, то единственная причина такого решения — невозможность сохранить все 10 000 чисел одновременно. Другими словами, не имея 40 000 байтов в наличии, программист должен разбить свою процедуру суммирования на маленькие "секции", что привело к появлению дополнительного цикла. Если бы ЭВМ была в состоянии хранить все 10 000 чисел, то, вероятно, код программы выглядел бы так, как показано в примере 2.

Пример 2.
XXX: PROCEDURE OPTIONS(MAIN);
     DECLARE A(10000) FIXED (7,2),
             C FIXED (11,2),
             J FIXED BINARY;
     C = 0;
     GET LIST((A(J) DO J = 1 TO 10000));
     DO J = 1 TO 1000
         C = C + A(J);
         END;
     PUT LIST('SUM IS', C);
     END XXX;

Разумеется, когда программист пишет подобный фрагмент кода,предназначенный для преодоления машинных ограничений, за редким исключением это будет отражено в комментариях. Хотя это и добавляет интриги чтению программ, всё же такие действия плохо сказываются на программе, когда, например, она передается в работу на другую машину.
Ещё одна область, вынуждающая программистов писать код с учётом машинных ограничений, это потребность в промежуточном хранении. Эта потребность существует потому, что идеальный и супернадежный носитель информации ещё не создан, поэтому программисту приходится иметь дело со множеством различных устройств хранения, каждое из которых имеет свои особенности выбора времени и способов обращения, вместимости. Таким образом, значительная часть работы программиста направлена на преодоление разнородности в хранении информации.

Ограничения языка
Однако, машинные ограничения нас мало интересуют, ведь мы в вопросах психологии ищем более "человечные" причины написания того или иного кода. Шагом навстречу им будет рассмотрения роли языковых ограничений в приведенном нами Примере 2.
Вообще говоря, язык PL/I предоставляет программисту встроенную функцию под название SUM для вычисления суммы элементов множества. Изначально функция SUM была больше «математической» функцией, чем «арифметической»: она принимала входящие значения в формате с плавающей запятой или преобразовывала их к такому формату при необходимости. Однако, такое преобразование могло привести к потери точности, если бы массив состоял из десятичных дробей с фиксированной запятой (FIXED DECIMAL). Многие программисты было обеспокоены обнаружением потери пени, при попытке использования функции SUM для расчёта бухгалтерских балансов.
Возникшая ситуация привела к тому, что функцию SUM объявили как непригодную для точных расчётов, т.е. как чисто арифметическую. Если бы не это языковое ограничение,то программа из нашего примера могла бы выглядеть лаконичнее, как показано в Примере 3.

Пример 3.
XXX: PROCEDURE;
     DECLARE A(10000) FIXED (7,2),
             J FIXED BINARY;
     GET LIST((A(J) DO J = 1 TO 10000));
     PUT LIST('SUM IS', SUM(A));
     END XXX;

В примере 3, мы также убрали OPTIONS(MAIN) при объявлении PROCEDURE, иллюстрируя другую форму языкового ограничения - ограничение конкретной реализации. Этот неуклюжий атрибут требовался только в определенных операционных системах, интерфейс которых требовал, чтобы головные MAIN-программы обрабатывались иначе, чем подпрограммы.

Ограничения программиста
Чисто психологическими являются ограничения самого программиста, который по каким-то причинам недостаточно хорошо владеет языком, компьютером или самим собой. В нашем примере, программист действительно не понимал обозначения массива в PL/I - до такой степени, что даже не осознавал возможности поместить имя массива в список данных, чтобы получить все его элементы. Если бы он был знаком с этой особенностью языка (которая была, в конце концов, даже в FORTRAN) он, возможно, написал бы программу, показанную в Примере 4.

Пример 4.
XXX: PROCEDURE;
     DECLARE A(10000) FIXED (7,2);
     GET LIST(A);
     PUT LIST('SUM IS', SUM(A));
     END XXX;

Незнания полной мощности используемого языка — это так называемые лексические ограничения.

Историческое наследие (legacy-код)
Очень часто в программах можно обнаружить код, классифицируемый по любому из вышеуказанных ограничений, но доставшийся программе "в наследство". Например, как только функция SUM превратилась в арифметическую функцию, то не осталось ни одной причины использовать её для точных расчётов, как в Примере 2. Тем не менее, если эта программа уже находится в состоянии промышленной эксплуатации, то маловероятно,что кто-нибудь начнет в ней ковыряться только из-за того, что изменилось определение функции SUM.

Однажды два программиста, разбиравшиеся в коде одной программы, работавшей в управлении социальной безопасностью, обнаружили любопытный артефакт. Каждый раз, когда у входной перфокарты в определённой колонке находилась буква «А», она преобразовывалась в «1». Это было особенно любопытно ввиду того, что в этой колонке фактически не могло быть чисел и программа была написана так, чтобы гарантировать их непопадание. Отказавшись переписывать всё заново, программисты начали расследование, которое выявило следующее.
Несколькими годами ранее, на одном из клавишных перфораторов в одном из окружных офисов появилась ошибка, которая вызывала пробой «А как 1» именно в этой колонке. Так как это было перед предварительной отладкой программы, эти неправильно перфорированные карты проходили через внутреннюю программу и аварийно завершали её. К тому времени, когда проблема была обнаружена, было неизвестно, сколько таких карт в обращении. Таким образом, самым простым выходом из этой ситуации стала «временная» модификация к программе. Как только она была сделана, карта вновь заработала хорошо, и все забыли об этом (снова психология!). А баг сидел там до тех пор, пока не оказался раскопан много лет спустя двумя программистами-археологами.

К сожалению, не весь исторический код может быть так легко распознан, как это показано в наших примерах. В частности, чем больше программа, тем более разбросанными оказываются последствия исторического выбора, сделанные в начале её "жизни". Даже сама структура программы может быть определена размером и составом группы программистов, которая первоначально занималась её разработкой, так как работа должна была быть разделена между определенным числом людей, у каждого из которых были определенные достоинства и недостатки.

Требования
Рассматривая Примеры 1 и 4, можно подумать, что решением самой задачи занимается довольно малая часть всего написанного кода. Довольно справедливое замечание для приведенных примеров, однако, не исключающее наличие программ, занимающихся конкретно только решением задачи.
И всё же, даже успешно извлекая полезное "ядро" программы из приведённых примеров, не стоит впадать в иллюзию, что при разработке всё так очевидно и просто: сначала решается задача, а якобы потом достраивается код, преодолевающий всевозможные ограничения. Кроме очевидных трудностей в определении намерений программиста, таких как незнание языка или написание "преждевременно оптимизированного" кода, всегда будет оставаться фактом то, что в большинстве случаев мы не знаем, что мы получим, пока не "сядем в лужу", начав это программировать.
Требования развиваются вместе с программами и программистами. Написание программы является процессом обучения и для программиста и для заказчика. Кроме того, этот процесс обучения имеет место в контексте определенной машины, определенного языка программирования, определенного программиста или программной команды в особых производственных условия, и определенном наборе исторических событий, которые определяют не только форму кода, но также и то, что делает код. В некотором смысле, самая важная причина изучения процесса программирования заключается не в том, чтобы сделать программы более эффективными, компактными, дешевыми или более понятными. Самая важная выгода - это перспектива получения от наших программ того, что мы действительно от них хотим, а не того, что нам удастся выжать из них всеми силами.

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



1 - речь идёт о программах на перфокартах.
Читать дальше......

понедельник, 9 июня 2014 г.

[Перевод] G.Weinberg - The Psychology Of Computer Programming

Компьютерное программирование как самостоятельная практически значимая деятельность существует уже около 70 лет (если не брать в расчёт госпожу Аду) — для человеческого ремесла срок небольшой. Однако, за всё это время огромное количество книг было написано по технологиям, парадигмам, методологиям, принципам и паттернам программирования. Но тема психологии такой сложной деятельности как программирование всё время как-то обходится стороной, а ведь все программисты, проектировщики и разработчики — это, в первую очередь, люди. Зачастую принимаемые ими решения — это результат не какого-то строгого машинного сравнения "что лучше", а тех или иных убеждений, попыток найти простое решение сложной проблемы, попыток обойти имеющиеся ограничения в задаче и т.п. Действительно, из знаменитых и читаемых книг по психологии вспоминается лишь "Peopleware: Productive Projects and Teams" Тома Де Марко (в русских изданиях — "Человеческий фактор: успешные проекты и команды") и частично "Facts & Fallacies Of Software Engineering" Роберта Гласса (рус. — "Факты и заблуждения профессионального программирования"). Как-то скудновато, по сравнению с тоннами всяких самоучителей по PHP и "как создать сайт с нуля за 4 часа" для "чайников".
Однако, есть одна тематическая книга, которая до сих пор является бестселлером на западе, несмотря на свой солидный возраст (первое издание отпечатано в 1971 году). Она выдержала несколько переизданий, однако в новых изданиях, если верить этому источнику, не изменилось ни единой буквы. Такой историей может похвастаться только стóящая глыба в IT-литературе.


Почему бестселлер почти неизвестен в России? Ответ прост — за тридцать с лишним лет не было ни единого русского издания этой книги. Откуда я узнал об этой книге? Мне её порекомендовал к прочтению один опытный программист, живущий в англоговорящей стране. К слову, в узких кругах всё же книга в стране известна, но по моему скромному мнению узнать о ней нужно куда большему числу людей. Просто потому что она, в первую очередь,о том, почему программы во всём мире пишутся именно так, как пишутся. А пишутся они зачастую так, что человек со стороны, читающий код программы, ломает над ней голову. А причина этого чаще всего кроется в темных уголках психологии программиста. В эти самые углы автор — образованный человек с ярко выраженным системным мышлением, и пытается проникнуть, чтобы узнать суть и причину действий, а также пытается их объяснить.
К слову, я принял решение публиковать краткий конспект каждой главы этой книги — буквально основополагающие тезисы. Книга любопытная, у меня есть большое желание поделиться её переводом здесь и где-нибудь на Хабре, но, как говорится, авторское право — non penis canina. Поэтому, чтобы не нарушать законодательства, я постараюсь опубликовать самую суть. Итак, поехали. Содержание книги в переводе выглядит так:

  • I. Программирование как человеческая деятельность
  • II. Программирование как социальная деятельность
    • 2.1 Группа программистов
    • 2.2 Команда программистов
    • 2.3 Проект
  • III. Программирование как индивидуальная деятельность
    • 3.1 Отклонения в задачах программирования
    • 3.2 Личностные факторы
    • 3.3 Способность рассуждать разумно, или способность к решению проблем
    • 3.4 Мотивация, обучение и опыт
  • IV. Инструменты для программиста
    • 4.1 Языки программирования
    • 4.2 Некоторые принципы проектирования языков
    • 4.3 Другие инструменты

По мере обновления материала в содержании будут появляться ссылки. Буду рад вашему мнению и комментариям.


Сайт автора: www.geraldmweinberg.com/
Твиттер автора: @JerryWeinberg
Читать дальше......