Pull to refresh
361
3.6
Alex Efros @powerman

Software Architect, Team Lead, Lead Go Developer

Send message
Вы предприниматель? Или программист? Или и то, и другое одновременно? Вы бы определились...

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

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

P.S. Существует мания, когда всё подряд переписывается без веской причины - это имеет смысл только в рамках обучения. Но есть и другая ситуация, когда ты реально можешь написать библиотеку качественнее готовой, причём патчить готовую сложнее написания своей (из-за архитектурных проблем, например), причём в текущем проекте требуется уровен качества выше того, которые предоставляет готовая библиотека - тогда написание своей имеет смысл, хотя и часто не делается из-за экономических причин (повышение качества на X и увеличение времени разработки на Y не оправдывает увеличения расходов на Z). Но этот экономический расчёт зачастую верен только в краткосрочной перспективе, а в долгосрочной это переписывание всё-равно бы окупилось.
Да, очень хороший и взвешенный подход. Но на практике он выливается в то, что после написания первого крупного проекта (и написания для него своего фреймвока), средние, шаблонные проекты тоже оказывается проще писать на своём фреймвоке.
Давайте Вы не будете передёргивать - это просто не культурно.

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

Во-вторых, лично я не просто готов платить больше за качественный софт, вопрос стоит иначе: я не готов платить ни копейки за некачественный софт. Что касается цен M$, то, как известно, они зарабатывают ГОРАЗДО больше, чем тратят на разработку. Так что, я уверен, они имеют возможность писать гораздо более качественно, сохраняя прежний уровень цен. Просто им это не нужно.
Не ожидал увидеть в комментариях столько любителей overquality и изобретателей велосипедов. Нам явно уже можно объединяться и создавать специальную организацию для защиты наших интересов и пропагандирования наших идей. :)

P.S. А по поводу изобретательства велосипедов, я уже когда-то цитировал эту фразу на хабре, но я повторюсь, уж больно хорошо сказано автором JSON:
The good thing about reinventing the wheel is that you can get a round one.
А, вот лично Вы, ещё разве не устали от потоков низкокачественного софта, изливающихся на нас, и с которыми нам приходится иметь дело? Ведь мы, ITшники, работаем с софтом гораздо более интенсивно, чем наши не-ITшники заказчики. Да, overquality не нужно нашим заказчикам, но оно нужно именно нам самим! И с этой точки зрения оно вовсе не "over".
Когда заказчик, после сдачи проекта, просит добавить фичу X, то сложность поддержки зависит от того, насколько эта фича вписывается в архитектуру и насколько читабельно написан код. Совет:
5. Пишите код, который удобно читать вам. ... Когда же вы пишете код для того, чтобы его можно было легко восстановить в памяти, вы скорее предпочтете краткость.
полезен пока изначальный код пишется в "потоке". Когда программист, который написал этот код, полезет его модифицировать через 3 месяца, ему самому будет не просто вникнуть в эти краткие конструкции. Фактически, для того, чтобы нормально менять этот код, ему опять нужно будет войти в "поток", загрузить в голову весь проект целиком. Это, в принципе, правильно, но требует довольно много времени.

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

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

А авторы вышестоящих комментариев, к сожалению, этого не понимают. И поэтому пытаются спорить. Для организаций описанные в статье "способы решения этой проблемы" категорически не подходят (и автор статьи как раз это отлично понимает). Не походят не потому, что проблемы с въезжанием в код у них нет - она есть - а потому, что эта проблема для организации совсем не так важна, как многие другие проблемы - например, проблема взаимозаменяемости людей, проблема сдачи проекта в срок, проблема обеспечения необходимого уровня продаж, проблема минимизации расходов на поддержку проекта, etc. И следование описанным в статье рекомендациям просто не выгодно, ведь задача большинства организаций - "заработать денег", а не "написать максимально качественный код любой ценой".

А вот для описанного в статье программиста, задача стоит "сделать его идеально". И, довольно часто, в определение "идеальности", у программиста не входят такие вещи, как сдача проекта в срок, или облегчение дальнейшей поддержки проекта. Чаще эта "идеальность" относится к внутренней реализации, к элегантности и эффективности кода. А для написания такого кода, и требуется "удерживать его целиком в голове"! Вот для этой ситуации всё описанное в статье абсолютно верно.

Определитесь, в чём Ваша цель, и Вам станет понятно, подходят Вам эти рекомендации, или нет. :)
На самом деле фреймвок был не так уж плох. Просто ему уже шесть с половиной лет (!), в течение которых он активно использовался практически без изменений... он просто морально устарел за это время, вот и всё.
Да ни в какие. Всё это просто было очередным помутнением разума, попыткой опять написать что-то супер надёжное и универсальное.

Теперь Датское королевство переписано надлежащим образом, без перехватов die и вызовов exit. Всё стало очень культурно и аккуратно. И проблема с goto отпала сама собой - сейчас фреймвок сам генерит исключение вместо exit. И никаких вложенных eval-ов в тех местах, где может быть редирект у меня ни в одном проекте нет... и если когда-нить появятся, значит после этого eval необработанные исключения будут передаваться наверх, как и положено.

В общем, ощущения у нас были правильные - изменил архитектуру и проблема исчезла.
Да, идея с END красивая... но не менее стрёмная, чем с goto. Дело в том, что, насколько я знаю, блоки END начинают выполняться когда perl уже входит в состояние завершения работы, освобождает ресурсы, etc. И тут ему говорят, погоди, поработай ещё, а мы тебе ещё один блок END подсунем. :)

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

Что касается утечек - они 100% должны быть, ведь после обработки каждого запроса в память добавляется ещё один блок END, при этом старые блоки из памяти не освобождаются. Другое дело, что много он памяти врядли занимает, и рестарт процесса раз в несколько тысяч запросов эту утечку памяти решит.
Это немного не в тему, но вопрос немного в другом - не в том, как прервать accept, а в том, как перехватить exit (вызываемый в коде обрабатывающем запрос) и вернуться в этот цикл.
YES!!! Чувство жо.. в общем, интуиция меня не подвела и на сей раз. :) Вот он, этот баг с goto:

perl -e '
BEGIN {
  *CORE::GLOBAL::exit = sub {
    goto FASTCGI_NEXT_REQUEST;
  };
}
while (1) {
  eval { that_cgi_script() };
 FASTCGI_NEXT_REQUEST:
  last;
}

sub that_cgi_script {
  local $SIG{__DIE__} = sub { print "<p>error: $_[0]"; exit; print "XXX\n" };
  print "before buggy code\n";
  eval { buggy_code() };
  print "after buggy code\n";
}
sub buggy_code {
  die "error!";
  print "after die\n";
}
'

Этот пример выводит:

before buggy code
<p>error: error! at -e line 20.
after buggy code

т.е. goto не срабатывает как надо, и выполнение продолжается после eval. Фактически exit в обработчике $SIG{__DIE__} срабатывает как return - ведь "print XXX" не срабатывает.

Я нашел как этот баг обойти. Нужно в блоке BEGIN добавить no-op обработчик CORE::GLOBAL::die (который просто симулирует работу системного):

  *CORE::GLOBAL::die = sub {
    if ($SIG{__DIE__}) {
      my $s = $_[0];
      $s .= sprintf " at %s line %d.\n", (caller)[1,2] if $s !~ /\n\z/;
      $SIG{__DIE__}->($s);
    }
  };

и теперь этот тест работает корректно, и выводит:

before buggy code
<p>error: error! at -e line 27.

Впрочем, объяснить почему добавление такого обработчика заставляет goto работать правильно я пока не готов. :( Возможно, дело в том, что мой обрабочик всё-таки не до конца симулирует стандартный обработчик перла. Правда, на множестве разнообразных тестов, которые я прогнал - я разницы между ними не заметил, если не считать вышеописанную.
Ну что тут сказать... Доводы в пользу архитектуры - это как раз те самые пункты, которые я перечислил, плюс достаточно простой и элегантный протокол - а с Вашей стороны доводов по архитектуре я пока не видел. Ваши ответы из серии "а оно вам надо", "это забота сисадмина" - это странные ответы. Во-первых я и есть сисадмин, и это, действительно, моя забота. Во-вторых, Вы попросили рассказать преимущества FastCGI - я рассказал. Надо ли оно Вам - это другой вопрос, но факт наличия этих преимуществ отрицать бессмысленно. И что касается отсутствия FCGI на CPAN - это шутка?

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

Что касается Apache::Request и тяжёлого CGI.pm. Да, в принципе я согласен. Но, а Вам не всё равно, насколько тяжёл CGI.pm, в условиях FastCGI? Параметры он возвращает? Аплоады контролирует? Что ещё от него нужно? Не html же через него выводить... :)

Лично для меня, основной довод в пользу именно FastCGI, это безопасность: CGIшки запускаются от пользовательского аккаунта, внутри веб-сервера они не выполняются, никаких сложных механизмов обеспечения безопасности типа SuExec и CgiWrap не используется. Второй довод - поддержка разных веб-серверов. Мы давно хотели отказаться от апача, и использование FastCGI облегчает попытку попробовать другой веб-сервер.

Кстати, на CPAN появился модуль FCGI::Spawn, который использует FCGI как раз для обеспечения безопасности: он не держит в памяти конкретную CGI, а, вместо этого, при каждом запросе подгружает и запускает CGI, указанную параметром $ENV{SCRIPT_FILENAME}. Как там написано: "hot ready for hosting providing".
  • FastCGI чисто архитектурно значительно лучше альтернатив.
  • В отличие от mod_perl, FastCGI сервер может работать с разными веб-серверами (apache, nginx, lighttpd, etc.).
  • FastCGI лучше масштабируется, т.к. позволяет разнести веб-сервер и CGIшки по разным машинам.
  • По сравнению с mod_perl, FastCGI приложения гораздо удобнее контролировать (запускать, рестартовать, отлаживать, управлять кол-вом одновременно работающих процессов, etc.), и все эти операции не требуют перенастройки и перезапуска веб-сервера.
  • Можно перезапустить веб-сервер не перезапуская CGIшки (удобно при активном использовании кеширования).
  • При использовании апача, запуск FastCGI-приложения от обычного пользовательского аккаунта (не nobody) гораздо проще и безопаснее, чем использование SuExec или CgiWrap.
    Я не совсем понял, что Вы имели в виду под "только применительно к Perl". Все эти особенности FastCGI к Perl применимы так же, как и к другим языкам. Ничего Perl-специфичного в технологии/протоколе FastCGI нет.

    Если это не под NDA, расскажите, пожалуйста, чего именно Вы натерпелись при использовании Perl под FastCGI, и какие библиотеки при этом использовали.
Простите, но с этим я согласиться не могу. Причём - по всем пунктам сразу. :)

  1. Цепочки return-ов - это ужасно. Это означает, что каждая функция должна возвращать смесь результата и ошибки - примерно как это делает весь POSIX с его кошмарным кодом возврата -1 и errno. Чтобы этого избежать, как раз и придумали исключения.
  2. goto (он же setjmp/longjmp) - это вполне адекватная вещь, когда используется по назначению. А когда goto используется вместо управления циклами (next/last/redo), выхода из процудур (return), симуляции исключений (die) - вот тогда он неуместен. Например, в perl есть goto на процедуру, который может использоваться для оптимизации tail recursion.
  3. mod_perl. Во-первых он не предназначен для ускорения CGI, он разрабатывался для ковыряния внутренностей апача на perl, в чём ему равных нет. Во-вторых на хостинге mod_perl это одна большая дыра в безопасности. Ну и кроме того у FastCGI есть много других плюсов, по сравнению с mod_perl - если интересно, могу рассказать.
Возможно, стоит таки потребовать, чтобы необрабатываемые исключения передавались дальше... это, вообще-то, вопрос гигиены. А левые модули с CPAN, которые этого не делают - патчить.

Проблема с этим подходом только одна: на поиск каждого такого бага, обычно в чужом коде, будет уходить куча времени. :(
У меня аналогичное ощущение. Так что если кто-то обоснует - буду только рад.
из perldoc -f next:
"next" cannot be used to exit a block which returns a value such as "eval {}", "sub {}" or "do {}", and should not be used to exit a grep() or map() operation.
Так что, увы, next не годится. Либо die, либо goto...
В топике есть пример. exit делается потому, что выполнение программы может быть прервано в любой момент.

Приходит запрос в некий html-шаблон, передаются параметры. Нормальная работа заключается обычно в том, что выполняется код, связанный с этим шаблоном, и сам шаблон заполняется данными и отдаётся пользователю. В этом случае exit действительно не нужен. Но иногда, в процессе выполнения кода, возникает одна из нескольких исключительных ситуаций: редирект, ошибка либо отправка файла (могут быть и другие примеры, например, при эффективном использовании кеширования может потребоваться отдавать "304 Not Modified", при ручной обработке авторизации может потребоваться "401 Not Authorized", etc.). Иными словами нужно прервать выполнение кода связанного с запрошенным шаблоном, прервать обработку самого шаблона, и вместо всего этого выдать пользователю какой-то специальный ответ.

Так вот, код, который принял решение выдать юзеру редирект или ошибку, может находится на глубине нескольких вложенных функций и eval-ов. И для того, чтобы обойтись без exit, необходимо "всплыть" на самый верх. Это можно сделать либо с помощью goto (ещё, возможно, next), либо через цепочку return-ов, либо через цепочку die. Последние два способа однозначно корректные, но они требуют наличия специального пользовательского когда везде, где он делает eval (в случае die) или вызывает процедуру (в случае return). Вариант с return самый уродливый и сложный, так что его обычно никогда не используют (я его упомянул просто для полноты картины).

И получается, что если не делать exit, и не делать goto/next, то остаётся только die. Причём этот die должен вернуть некую уникальную ошибку, а юзер, после каждого своего eval, должен эту ошибку передавать выше:

...
eval { do_something() };
die if $@ && $@ eq "PLEASE PROPAGATE: NORMAL EXIT\n";
...

Это самый корректный способ, но его сложно гарантировать. Например - использование внешних CPAN-модулей. Они могут принимать от пользователя callback, вызывать его внутри своего eval, и не делать этот die после eval. Полагаться на такое, корректное, поведение чужого когда - это прикапывать грабли, с целью в будущем на них с размаху наступить.

Вот поэтому текущий движок использует exit. Более того, это считается абсолютно нормальным, когда в середине выполнения CGIшка отдаёт юзеру ответ и выходит. И тот же mod_perl считает вызов exit легальным (для чего и перехватывает его превращая в die).
Нда.. Любопытно. Про next я как-то не подумал. Никогда не видел, чтобы с помощью next на метку вываливались из нескольких вложенных вызовов процедур и eval-ов.

Интересно, чем по сути этот next отличается от того же goto?

Information

Rating
1,905-th
Location
Харьков, Харьковская обл., Украина
Date of birth
Registered
Activity

Specialization

Backend Developer, Software Architect
Lead
From 10,000 $
Designing application architecture
Golang
Linux
Docker
Network security
Modular testing
Mentoring
Development of tech specifications
Software development
High-loaded systems