четверг, 7 июня 2012 г.

Паттерн «Веб-функция»

Допустим, вам нужно написать endpoint для аякс-запроса. Или обычный обработчик обычного POST-запроса от веб-формы. Тогда основные действия, которые обязательно должны быть в процессе обработки, будут такие:

  • Фильтрация входных данных (это НЕ валидация бизнес-правилами, только лишь такие действия, как укорачивание, очистка от окружающих пробелов, исключение непечатных символов, приведение к нижнему регистру и т. д.). Или мы можем даже сыграть роль адаптера и переименовать некоторые параметры запроса в вид, ожидаемый обработчиком.
  • Обработка запроса. Здесь мы собственно делаем то, зачем существуем. Мы ожидаем уже более-менее очищенный от мусора массив входных параметров, возможно, среди которых есть ошибочные (например, числовое значение вне допустимого диапазона, или передан массив вместо скаляра).
  • Форматирование результата для отклика клиенту. Например, мы можем возвращать JSON, или генерировать HTML страницу по шаблону. Или генерировать изображения. Или отдавать файлы с диска.

Если записать в функциональном стиле на PHP, то можно получить следующий шаблон хэндлера:


<?php

echo format(process(clean($_REQUEST)));

/**
 * Здесь очистка суперглобального массива $_REQUEST и генерация массива $request
 * 
 * @param array $request Данные запроса из массива $_POST или $_GET (или $_REQUEST).
 * @return array Очищенные данные из запроса, которые ожидает функция process.
 */
function clean($request)
{
  // TODO
  return $request;
}

/**
 * Здесь выполнение действий согласно запросу $request
 * 
 * @param array $request Параметры запроса, очищенные функцией clean.
 * @return array $result Результат работы со всей информацией, необходимой для форматирования ответа клиенту.
 */
function process($request)
{
  // TODO
  return $request;
}

/**
 * Здесь форматирование результата работы для выдачи клиенту.
 * 
 * @param array $result Результат работы, как он сгенерирован процессором. Нам не дозволяется использовать какие-либо другие данные.
 * @return string Форматированный ответ, готовый к отправке клиенту через echo.
 */
function format($result)
{
  // TODO
  return json_encode($result);
}
?>

Если process сталкивается с ошибкой, он пишет об этом в результат своей работы (например, в поле 'error') и сразу возвращает результат в format.

Если format должен генерировать большой HTML документ, то ничего страшного, он может и делать echo внутри себя, вместо того, чтобы возвращать строку. Тогда вызов всей цепочки будет без echo в начале, конечно же.

Как запускать программы на Common Lisp как консольные скрипты

Допустим, у вас есть классная маленькая программка на Common Lisp под названием filter. Она занимает всего один файл исходного кода под названием filter.lisp примерно в 200 строк длиной. В этом файле вот такой заголовок:


(defpackage :localhost.me.filter
  (:use :common-lisp)
  (:export :run))
(in-package :localhost.me.filter)

И дальше собственно сама программа. Допустим также, что вы пользуетесь своей программой часто. Загвоздка в том, что для того, чтобы её запустить, вам необходимо сделать слишком много действий:


$ cd $PROGRAMDIR
$ sbcl
CL-USER> (load "filter.lisp")
CL-USER> (:localhost.me.filter:run)

Рассмотрим два решения, одно простое, но не всегда поможет, другое посложнее и поможет уже в большем числе случаев.

Решение первое

SBCL умеет запускать программы на CL в режиме скриптов командной строки, для этого используется специальный флаг --script. Более того, благодаря этому можно написать лисп-программу, добавить к ней типичный юниксовый шебанг, вызывающий SBCL с этим флагом, и она будет работать как любой другой шелл-скрипт (за исключением того, что рантайм SBCL весит 50MB бгг). Так что напишем этот скрипт:


#!/usr/bin/sbcl --script
(load "filter.lisp")
(:localhost.me.filter:run)

Теперь положим этот скрипт в ту же директорию, что и filter.lisp, в файл с именем run и можно будет запускать нашу программу так:


$ ./run

Что и требовалось.

Решение второе

Однако, у флага --script есть одна особенность: SBCL не загружает никакие пользовательские скрипты инициализации при старте, так что скрипт будет выполняться в 100% дефолтном рантайме. Это плохо, если, допустим, вы используете Quicklisp и у вас в filter.lisp есть такой вызов:


...
(ql:quickload :CL-FAD)
(use-package :CL-FAD)
...

Ну или вызов любой другой библиотеки, неважно. Если запустить такой скрипт первым способом, то SBCL будет грязно ругаться по поводу того, что он не знает, что такое ql:quickload.

Для решения этой проблемы мы смухлюем, подменив бинарник SBCL таким бинарником, у которого все нужные библиотеки уже загружены. В SBCL есть функция save-lisp-and-die, которая выгружает текущее состояние рантайма в файл с указанным именем. Дальше этот файл можно использовать как обычный бинарник SBCL, в том числе, и для вызовов с использованием --script. Поэтому если запустить SBCL и сразу выгрузить его в файл, то полученный бинарник будет содержать всё, что SBCL подключил при загрузке, основываясь на пользовательских конфигах.


(save-lisp-and-die "/абсолютный/путь/до/файла")

Теперь в скрипте run, который написан по первому варианту, заменяем путь до системного SBCL путём до выгруженного бинарника, и скрипт начнёт работать как должен.

Как писать юнит-тесты к программе на Free Pascal при помощи FPTest

В свете работы над моими старыми программами из КГУ понадобилось покрывать код юнит-тестами. Как выяснилось после гуглопоиска, для Free Pascal, которым я компилирую свою переработку, существует проект под названием FPTest, представляющий собой каркас для написания юнит-тестов на этом замечательном языке.

Документации по этому проекту довольно немного, и официальная вики, как и README, сильно Lazarus-ориентированы. Поэтому расскажу здесь, как подключить FPTest к существующему консольному проекту и собрать тесты, написанные с его помощью.

среда, 23 мая 2012 г.

PHP: архивы PHAR не работают с включённым suhosin patch

Сегодня ставил behat и ужаснулся. phar-архивы не запускаются в командной строке вообще никак. Даже не появляется сообщения об ошибке, просто молча ничего не выполняется.

После некоторого продолжительного поиска в Сети был найден ответ на ServerFault: выполнение phar-архивов блокирует suhosin patch.

Для решения проблемы нужно раскомментировать следующую строку в /etc/php5/cli/conf.d/suhosin.ini:


;suhosin.executor.include.whitelist =

И заменить её на


suhosin.executor.include.whitelist = "phar"

Одноразовое решение выглядит так:


$ php -d suhosin.executor.include.whitelist="phar" MYPHAR.phar


воскресенье, 13 мая 2012 г.

Рефакторинг заданий курса КГУ по программированию 2004-2011 гг.

У нас в набережночелнинском филиале КГУ на факультете прикладной математики и информационных технологий преподавали несколько программистских курсов. На них студентам задавали задания в духе "перемножьте каждый четвертый член массива чисел полученной от пользователя длины", и их нужно было запрограммировать. В первом семестре - на Delphi. Потом - на C++. После третьего курса можно было писать на чём хочешь.

Пожалуй, это были единственные учебные курсы из всех пяти лет обучения, которые хоть что-то дали лично мне как разработчику. Во всяком случае, я узнал об ООП в стиле C++ и о такой вещи, как указатели.

Я ужасно обожаю чистить чужой код, поэтому мне пришла в голову идея поднять старые исходники готовых решений тех задач и переоформить их согласно всему тому, чему я научился за последние 7 лет практики (да, включая универ, конечно :) ).

Изменения, которые я хочу внести, следующие:

  1. Структура каждой программы должна быть как можно проще. Всё должно компилироваться из командной строки. Всю IDE машинерию нужно убрать.
  2. Для сборки будет использоваться GNU Make, для программ на любом языке.
  3. Покрыть код юнит-тестами.  Чистый TDD использовать уже поздно, всё-таки код уже написан.
  4. Код единообразно переформатировать, очистить от "попахивающих" мест и снабдить формальными комментариями.
  5. Текст заданий изъять изо всяких PDF и DJVU файлов, в которых он сейчас находится, и перенести в текстовые README.md.
Кроме того, для разработки на первых курсах активно использовался homemade хак для того, чтобы выводить кириллический текст в консоль. Отдельный хак для программ на Паскале, отдельный для программ на Си. Общаться с пользователем по-русски не так важно, так что для простоты все сообщения от программ будут переведены на английский.

В первом семестре использовался Delphi, с помощью которого компилировались консольные приложения. Вместо Delphi будет использоваться Free Pascal в объектном режиме. Всю машинерию, связанную с Delphi, нужно убрать.

Для тестов программ на Паскале будем использовать FPTest.

Начиная со второго семестра и дальше использовался C++. Для компиляции точно буду использовать g++ вместо Visual Studio, а вот какой каркас для тестов использовать, пока не знаю (просто пока не пробовал xUnit-каркас для C++).

Исходники уже выложил на ГитХабе. Пока более-менее нормально обработано только первое задание.

Думаю, когда закончу задания первого семестра, напишу письмо в КГУ. :)

понедельник, 23 апреля 2012 г.

Мысли о третьем шаге в TDD

Сегодня второй раз в жизни пишу код согласно чистому TDD, прямо как заповедали такие мастера, как Роберт Мартин, Эндрю Хант и Майкл Физерс. Один случай меня заставил серьёзно задуматься о разнице в подходах к разработке.

Третий шаг в TDD гласит: «напиши ровно столько кода, сколько нужно для того, чтобы проваливающийся ранее тест начал завершаться успешно». (Первые два это «напиши проваливающийся тест» и «заставь этот тест компилироваться»). Несмотря на то, что он кажется довольно простым, пункт насчёт «ровно столько, сколько нужно» невероятно важен в рамках этой методологии.

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

Только после раздумья длиной что-то около полудня я понял, почему так важно соблюдать это правило, хотя ответ очень прост. Если ты напишешь код, который не предусматривают твои тесты, пусть даже он будет выполнять какую-то идейно полезную работу (а он, конечно же, будет выполнять идейно полезную работу), он не будет покрыт тестами. А код, который не покрыт тестами — это legacy код, которому положено гореть в аду. Более того, если ты оставишь весь этот код среди другого, чистого и покрытого тестами, он будет, как ржавая арматура, мешать добавлять новую функциональность. Так как он не покрыт тестами, а ты в целом стараешься следовать TDD, значит, все эти буквы становятся просто шумом, про полезность и надёжность которого ты не знаешь ничего.

Наверное, теперь я на самом деле понял, насколько иной философией разработки пользуюсь. Сегодня я удалил целый класс, почти 100 строк кода, который написал, пытаясь закрыть один проваливающийся тест, и ни разу не пожалел об этом. Скорее наоборот: вспоминая ту ужасную массу кода, я уверен, что моя программа стала гораздо лучше, лишившись её.

четверг, 19 апреля 2012 г.

Подключение к Windows VPN серверу из Debian

О-хо-хо, как же я давно не писал!..

Есть важная особенность при настройке VPN соединения к Windows-серверу из Debian. Это если не считать того, что на самом деле это называется не VPN, а PPTP подключение, ну да простим Майкрософту эту мелочь.

При настройке подключения, следует указать, что разрешён только один метод аутентификации: MSCHAPv2. И включить MPPE шифрование. Спасибо orbnajes с форумов Fedora за объяснение.

Без этих действий VPN подключение будет молча обрываться, и сообщение об ошибке в syslog'е мало поможет.

update: Ещё, возможно, придётся уменьшить MTU на сетевой карте, или подключению будет очень дурно, и оно регулярно будет обрываться. Для того сервера, который мне был нужен, сработало уменьшение MTU с 1500 до 800.