964 заметки с тегом

программирование

AppleScript Tetris

Я очень люблю «ненормальное программирование» — где вместо получения результата (такого мне и на работе хватает), приходится преодолевать нетривиальные трудности, придумывать какие-то нестандартные ходы и решать нерешаемые задачи.

Недостатка в таких задачах у меня в голове не бывает, но руки приложить к ним получается нечасто. «Тетрис» на ЭплСкрипте — один из примеров такого «приложения рук», прошло три года, но он до сих пор мне так нравится, что я решил рассказать о нём ещё раз.

Для начала немного об ЭплСкрипте. Это скриптовый язык для автоматизации, который встроен в МакОС в незапамятные времена — в «Википедии» написано, что он появился аж в 1993 году, то есть широкоизвестный ПХП его младше аж на два года!

Несмотря на то, что язык уже несколько лет не обновляется, заброшенным его не назовёшь, скорее всего в нём просто нечего обновлять — все задачи, которые он призван решать, он решает, в МакОСи есть даже встроенный визуальный редактор и я знаю ребят, которые делают на нём потрясающие вещи в плане создания каких-либо удобств в операционной системе!

Естественно можно программировать и в обычном текстовом редакторе, программы в этом случае выглядят как текст на немного необычном английском языке:

tell application "Microsoft Word" to quit
display dialog "Hello, world!"

print pages 1 thru 5 of document 2

Сильно популярным его не назовёшь, но в своей нише он хорош и продвинутые «маководы» о нём знают. До «Тетриса» я писал на нём «песню о пиве» и интерпретатор Брейфака.

За всё историю существования на ЭплСкрипте никто никогда не писал графических интерактивных игр. Причина проста: в языке нет поддержки графики и опроса клавиатуры. 😭, расходимся!

Тем не менее идея создать на нём что-то подобное «Тетрису» мучала меня несколько лет, видимо интуиция жужжала где-то в фоне, что это возможно.

Первым придумался способ вывода графики — я подумал, что фигуры можно рисовать свёрнутыми окошками какой-нибудь программы, например обычного маковского текстового редактора. В моём воображении это выглядело примерно так:

Для этого, в припципе, всё есть — ЭплСкрипт умеет запускать программы и управлять размерами и расположением окон:

tell application "TextEdit"
    set the bounds of 1st window whose id is wid to {x, y, (x + |size|), (y + |size|)}
end

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

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

В первом же прототипе появилась ещё одна проблема — наверху у «МакОСи» есть строка меню, приложения не могут на неё залезать, при попытке вывести в эти координаты окно, оно «сползает» вниз. В итоге первые несколько точек выводились в других координатах и всё рушилось. Было два решения — центрировать ещё и по вертикали или как-то измерить размер меню (кодировать его высоту константой мне показалось плохим решением — вдруг изменится).

В итоге, способ придумался. Сразу после запуска я вывожу окно в нулевые координаты и смотрю на сколько пикселей оно съехало, это и будет высотой меню:

-- Get height of menu bar
tell application "TextEdit"
	activate
	make new document at the front
	set the bounds of 1st window to {0, 0, 0, 0}
	set coords to bounds of 1st window
	close 1st window
end

set minimalY to coords's item 2

С графикой разобрались, а что с клавишами? В ЭплСкрипте, как я уже говорил, нет способов опросить клавиатуру, существуют плагины и сторонние решения, но их использование рушит всю идею. Хотелось чистого решения.

Голову пришлось ломать очень долго, пока в один момент я не додумался заиспользовать под это опрос значения громкости — уменьшение и увеличение громкости как кнопки влево/вправо, а отключение динамика — для поворота фигуры.

Сейчас на моём «Макбуке» аппаратных кнопок громкости уже нет, но «Тетрис» слушается и нарисованных на тачбаре

Для этого на пуске я устанавливаю громкость на середину и в цикле опрашиваю текущее значение. После каждого изменения значение опять сбрасывается на среднее:

on levelReset()
    set volume output volume volumeMiddle without muted
end

on checkDirection()
    set level to output volume of (get volume settings)
    levelReset()
    _sign(level - volumeMiddle)
end

on checkRotate()
    return output muted of (get volume settings)
end

Тут _sign — моя собственная функция взятия знака, у ЭплСкрипта нет встроенной.

В получившийся вариант вполне можно играть — я пробовал. Немного странное управление через несколько минут уже перестаёшь замечать, единственное, чего не хватает — кнопки сброса фигуры, но тут уж ничего не попишешь.

Если вдруг кому-то станет интересно запустить игру, то убедитесь, что всё, что у вас есть во встроенном текстовом редакторе (TextEdit) сохранено, — приложение создаст целую кучу мелких дополнительных окон, которые не будут очищены после остановки программы, их придётся закрыть вручную, в этом случае легко потерять что-нибудь нужное.

Полный исходный код есть на гитхабе.

 Нет комментариев    73   6 ч   applescript   программирование

99 бутылок: V

71. V — один из языков, за развитием которых я пристально наблюдаю. Искренне желаю ему развития и процветания — под влиянием «Раста», «Гоу» и чего-то вроде Пе́рла, у авторов получается довольно интересный язык, который пока, правда, находится в стадии альфы.

Сил авторам не занимать — работает (за редким исключением) почти весь заявленный синтаксис, есть компиляция, трансляция в Си и ДжаваСкрипт, а так же обратная трансляция из Си в Ви (на гитхабе есть оттранслированный в Ви «Дум»).

Сильнее всего язык, кажется, похож на Гоу — есть даже синтаксически такой же оператор go, позволяющий запускать что-нибудь в отдельном потоке (позднее обещают сделать корутины), но и уши «Раста» торчат даже в моей небольшой программе: совпадение с образцом, условные операторы-конструкции, константные переменные (если не указан mut).

Есть обобщённое программирование (которого так не хватает в Гоу), интерполяция переменных в строках (привычная ребятам, программирующим на ПХП, Перле и шеллах) и ограниченная перегрузка операторов (как в Си++).

Насколько я понял из сайта и блога, язык нацелен на безопасное программирование, скорость, как выполнения (проигрывает Си всего на три процента), так и выполнения и простоту кода. Повторюсь, выглядит очень интересно.

// 99.v by Evgeny Stepanischev Feb 2020

fn bottles(beer byte) string {
    return match beer {
        0 {
            'no bottles'
        }
        1 {
            '1 bottle'
        }
        else {
            '$beer bottles'
        }
    }
}

fn main() {
    mut b := bottles(99)

    for i := 98; i >= 0;  {
        println('$b of beer on the wall, $b of beer.')
        b = bottles(i--)
        println('Take one down and pass it around, $b of beer on the wall.\n')
    }

    println('No more bottles of beer on the wall, no more bottles of beer.')
    println('Go to the store and buy some more, 99 bottles of beer on the wall.')
}
 1 комментарий    183   4 дн   99   программирование

Забавный баг в FFI (PHP 7.4.2)

В последнее время очень заинтересовался FFI в ПХП — интерфейсом к языку Си, появившимся в версии 7.4. Очень полезное, как по мне, нововведение, позволяющее расширять язык, обходясь минимальными знаниями о других языках программирования.

Очевидно, потребность в чём-нибудь таком давно назрела, появлялся даже специальный язык для написания расширений — «Зефир», очень похожий на ПХП, принципиальных отличий нет, порог входа — низкий. При этом он транслируется в Си, благодаря чему достигается почти нативная производительность.

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

Небольшой код для иллюстрации проблемы:

const IRRELEVANT = "Hello ";

echo IRRELEVANT;  // Выведет «Hello»

FFI::cdef('char *strcpy(char *dst, const char *src);')->strcpy("Hello ", "world!");

echo IRRELEVANT; // Выведет «world!»

В чём суть? Мы объявляем константу IRRELEVANT, убеждаемся, что она содержит присвоенное ей значение, потом вызываем через FFI копирование одной строки в другую, и константа внезапно меняет своё значение. Как это произошло? Чтобы понять, немного модифицируем программу.

function strcpy(string $dst, string $src):void
{
    FFI::cdef('char *strcpy(char *dst, const char *src);')->strcpy($dst, $src);
}

$rock1 = str_repeat('ROCK', 1);
$rock2 = str_repeat('ROCK', 1);

var_dump($rock1, $rock2); // Выведет «ROCK» два раза

strcpy($rock1, "SOCK");
strcpy($rock2, "LOCK");

var_dump($rock1); // выведет «SOCK»
var_dump($rock2); // выведет «LOCK»

Что у нас тут? Вызов strcpy обёрнут в функцию, которая ничего не возвращает и ничего не должна модифицировать — параметры передаются по значению, а не ссылке. Тем не менее, если вызвать объявленную функцию и передать в неё переменные, их значение будет изменено.

В ПХП для обеспечения производительности используется механизм копирования при записи — если куда-то переменную нужно передать по значению, то интерпретатор для скорости передаёт её по ссылке, а если требуется модификации переданного значения, копирует его. Модификацию отслеживает сам интерпретатор, но в случае FFI этого, по всей видимости, не происходит.

В силу этой особенности, переменные остаются ссылочными, попадая в таком виде в сишный код, где модифицируются — вызов strcpy меняет первую переданную в него переменную, копируя в неё значение второго параметра.

Кстати, если изменить строку инициализации переменных на вот такую:

$rock1 = $rock2 = str_repeat('ROCK', 1);

то после вызовов strcpy обе переменные получат значение «LOCK» — работает тот же самый механизм.

Почему же в первом листинге изменилось значение константы? Сейчас разберёмся, осталось совсем немного.

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

Таким образом константа IRRELEVANT и строка «Hello » в первом параметре — суть одна и та же область памяти. Когда функция strcpy модифицирует эту область, меняются сразу все значения всего, что, для оптимальности, ссылается на это же место. Естественно, константа изменяется тоже.

Поскольку интернирование не работает во время выполнения, результат str_repeat('ROCK', 1); не интернируется и во втором листинге такого эффекта не создаётся.

Как же разорвать эту мистическую связь и заставить работать код так как задумывалось? Для этого нужно, чтобы интерпретатор создал новую область памяти, которую будет портить FFI, не затрагивая нашу переменную. Это можно сделать, например, при помощи уже упомянутой функции str_repeat:

function strcpy(string $dst, string $src):void
{
    // Создаём новое место, которое испортит strcpy
    $dst = str_repeat($dst, 1);
    FFI::cdef('char *strcpy(char *dst, const char *src);')->strcpy($dst, $src);
}

$rock = 'ROCK';
var_dump($rock); // Выведет «ROCK»

strcpy($rock, "SOCK");
var_dump($rock); // Так же выведет «ROCK»

Естественно, подойдут и substr, и sprintf, и вообще любые функции, в общем случае возвращающие модифицированную строку. Интересно, что implode([$dst]) от бага не защищает, видимо для этого случая внутри работает какая-то оптимизация, возвращающая значение $dst по ссылке до первой модификации.

Всё описанное верно для версии актуальной на текущий момент — 7.4.2. Надеюсь, в скором времени этот довольно забавный баг устранят и необходимость в таких ухищрениях исчезнет.

 1 комментарий    277   8 дн   php   php7   программирование

PHP: FFI vs. exec

Нередко отсутствующую в ПХП функциональность наскоро заменяют вызовом утилит командной строки. И мне стало интересно взглянуть на досуге — может ли вызов через ffi (интерфейс к Си, который появился в ПХП 7.4) быть хорошей альтернативой.

Говорят, вызов через ffi работает очень медленно, такова природа этого способа (и мы это видим в других языках), это, конечно, очевидно, но медленно насколько?

Для сравнения была взята функция, создающая временный директорий по маске, которую я использовал в одной из заметок, и её аналог, использующий внешнюю утилиту:

$mkdtemp = function (string $template): string {
    $cmd = '/usr/bin/mktemp -d '.escapeshellarg($template);
    $dir = exec($cmd, $out, $ret);

    if ($ret === 0) {
        return $dir;
    } else {
        ['message' => $message, 'type' => $type] = error_get_last();
        throw new RuntimeException($message, $type);
    }
};

Весь код был обёрнут в цикл на 10000 итераций и запущен. Я провёл по десять запусков, а результат усреднил.

Создание директория через ffi заняло примерно 0,77 секунд на 10 тысяч итераций, а через утилиту командной строки — примерно 72,62 секунды на те же 10 тысяч итераций.

Конечно какое-то время занимает запуск интерпретатора и прочие расходы, но даже в этом случае разница более чем очевидна — запуск утилиты несомненно хуже.

 3 комментария    249   11 дн   php   php7   программирование

InlineC

Продолжая тему интерфейсов к языку Си, кстати будет упомянуть появившийся недавно модуль inlinec для Пайтона. Он позволяет вставлять в программу на Пайтоне Си-код довольно естественным способом. Вот пример из документации:

# coding: inlinec
from inlinec import inlinec

@inlinec
def Q_rsqrt(number):
    float Q_rsqrt( float number )
    {
        long i;
        float x2, y;
        const float threehalfs = 1.5F;

        x2 = number * 0.5F;
        y  = number;
        i  = * ( long * ) &y;
        i  = 0x5f3759df - ( i >> 1 );
        y  = * ( float * ) &i;
        y  = y * ( threehalfs - ( x2 * y * y ) );

        return y;
    }

print(Q_rsqrt(1.234))

Я проверил, это действительно работает. Тут вычисляется так называемый быстрый обратный квадратный корень от числа 1,234.

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

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

Приём не новый, несколько лет назад я про него уже рассказывал — в Пайтоне в специальном магическом комментарии можно указать кодек, который пропускает через себя текст программы. Обычно он используется для программирования в кодировках, отличных от ЮТФ-8, но ничего не мешает использовать его иначе.

Итак, код перед выполнением скармливается специальному кодеку inlinec, который разбирает программу, находит в ней метки-декораторы, компилирует вставки на Си через gcc и заменяет тело на вызовы скомпилированного через библиотеку cffi.

 Нет комментариев    275   14 дн   python   программирование   си

Программисты-«волшебники»

Про программистов-«волшебников», большая цитата (ниже весь текст не мой).

Хочу высказаться также по триаде «объект — указатель — ссылка».

Изначально в Си (не «++») ссылок не было. Были только объекты и указатели. Любой начинающий сталкивался с указателями, как только ему требовалось впервые изменить в теле функции значение переданного функции параметра:

void func1(int i) { i = 1; }
void func2(int* i) { *i = 1; }

Далее:

int i = 0;
func1(i); // всё ещё i==0
func2(i); // вот теперь i==1

Этот код демонстрирует то свойство Си, из-за которого он называется «высокоуровневым ассемблером». В тексте языка явно указано, что происходит на уровне ассемблера. В func1 в ячейку на стеке, отведённую под параметры функции, кладут целое значение, i — имя этой ячейки. Туда пишется «1». В func2() в ячейку кладётся указатель i, то есть адрес некоторой другой ячейки, где и лежит значение. В теле явно стоит операция разадресации (*i) — «обратимся к той другой ячейке, адрес которой лежит i». Именно в ту ячейку засовываем «1».

Спрашивается, так зачем ещё потребовались ссылки? Ответ таков. В мире существует, не исчезает и даже порой ширится класс «программистов-волшебников», то есть любителей «программирования с волшебными словами». Программисты-волшебники рассуждают так: запись i = 1; хорошая и понятная. «Присвой i единицу». Запись *i = 1; — противная, компьютерная, не для людей. «Положи „1“ в ячейку, адрес которой лежит в i». Фу.

Пусть лучше в теле func2 тоже будет написано по-человечески: i=1. Однако работает пусть всё, как раньше — func1 получает на стек значение, а func2 — указатель. И оператор присваивания, с виду теперь одинаковый, пусть работает по разному. В func1 — обычное присваивание, в func2 — с разадресацией указателя. А чтобы теперь понять, как это работает, мы введём в язык волшебный значок «&». Теперь всё просто:

void func1(int i) { i = 1; }
void func1(int &i){ i = 1; }
int i = 0;
func1(i); // волшебного значка нет, i не изменилось
func2(i); // волшебный значок! i изменилось.

Смотрите, операторы в теле функций одинаковые, а работает по-разному, и всё из-за волшебного значка! При этом можно даже не разбираться, как это работает, даже не знать про какие-то указатели. Как-то само присваивается, из-за чудесного значка.

Программисты Си долго сопротивлялись, но программисты-волшебники победили, и ссылки появились в Си++.

 Нет комментариев    291   16 дн   программирование   си

PHP FFI

В ПХП 7.4 предпринята очередная попытка обзавестись интерфейсом к языку Си. Кажется наконец за это расширение взялись по-серьёзному, и его поддержка не исчезнет со временем, как это уже случалось ранее с прошлыми реализациями.

Для интереса посмотрел что за АПИ получилось. В принципе, всё очень просто. Вот как выглядит функция-обёртка вокруг вызова mkdtemp с обработкой ошибок (проверял у себя на «Маке»):

/**
 * @method object mkdtemp(string $template)
 * @method object strerror(int $errnum)
 * @property-read int $errno
 */
$ffi = FFI::cdef('
    // Импортируем функцию создания временного директория
    char *mkdtemp(char *template);

    // Импортируем функцию перевода кодов ошибок в строку
    char *strerror(int errnum);

    // Импортируем переменную, хранящую коды ошибок
    int errno;
');

/**
 * Фунция для создания временных директориев по маске
 * @param string $template Шаблон создания (см. man mkdtemp)
 * @return string Путь до временного директория
 */
$mkdtemp = function (string $template) use ($ffi): string {
    $result = $ffi->mkdtemp($template);

    if ($result === null) {
        $errno = $ffi->errno;
        $errstr = $ffi->strerror($errno);
        throw new RuntimeException(FFI::string($errstr), $errno);
    }

    return FFI::string($result);
};

Как можно понять из кода, создаётся объект, в который импортируются сущности, перечисленные в сишной нотации в первом параметре. В общем случае вторым параметром требуется указать библиотеку для импорта, но в данном случае работает и так.

Не знаю заработает ли этот код без изменений под Линуксом и Виндоузом, у кого есть под рукой, посмотрите, пожалуйста.

В этом примере параметры в импортированные функции передаются как обычные типы ПХП, а некоторые возвращаемые значения требуют конвертации. Так char * возвращается как специальный объект, который нужно преобразовывать в строку вызовом метода FFI::string.

Вот тривиальный текстовый пример использования созданной выше функции:

$template = implode(DIRECTORY_SEPARATOR, [sys_get_temp_dir(), 'ffiphp.XXXXXX']);

try {
    $tmp = $mkdtemp($template);
    rmdir($tmp);
    echo $tmp, "\n";
} catch (RuntimeException $e) {
    echo $e->getMessage(), "\n";
}

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

 1 комментарий    308   17 дн   php   php7   программирование

PHP8 и strpos

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

В процессе изучения списка изменений, иногда находятся грабли, на которые я, к счастью, никогда не вставал. Хорошо, что они со временем из языка исчезают. Например:

The needle argument of strpos(), strrpos(), stripos(), strripos(), strstr(), strchr(), strrchr(), and stristr() will now always be interpreted as a string. Previously non-string needles were interpreted as an ASCII code point. An explicit call to chr() can be used to restore the previous behavior.

Я даже не подозревал, что в качестве параметра в функции поиска подстроки сейчас можно (в восьмой версии будет нельзя) передать код символа. Так как в ПХП мало кто следит за типом переменной, можно получить несколько обескураживающие результаты:

// объявили строковую переменную и где-то по коду она превратилась в число,
// 49 — это код символа «1»
$needle = (int) "49";

// выдаёт «Есть»
echo @strpos("12349", $needle) === false ? "Нету\n" : "Есть\n";

// Несколько неожиданно выдаёт «Нету», ищется символ с кодом «49» (единица)
echo @strpos("02349", $needle) === false ? "Нету\n" : "Есть\n";

В ПХП8 число будет преобразовано в строку и искаться будет именно строка, то есть оба этих примера выдадут «Есть».

 1 комментарий    228   18 дн   php   php8   программирование

Выполнение запроса с таймаутом

В продукте, который мы делаем в нашей компании, не используется PDO для соединения с базой данных — так исторически сложилось и вряд ли имеет смысл менять. Причина тому — оптимизации; мы широко используем эскуэль и задействуем диалектные особенности используемой СУБД (в нашем случае это «Постгрес»). То есть универсальность PDO нам ничего бы не дала.

Недавно возникла задача выполнения запроса с таймаутом — если выполнение запроса занимает больше времени, чем по нашему мнению пользователь готов ждать, то лучше прервать запрос и показать заглушку. В PDO кажется такая возможность есть (параметр можно задать, но мы не тестировали), а в постгресовском модуле для ПХП готового способа не оказалось.

Мы долгое время перебирали идеи (вплоть до внешнего процесса с таймером), но ничего не выглядело достаточно хорошо, чтобы помещать это в код. В конечном счёте мой братишка предложил очень простое решение. Ответ лежал в работе с асинхронным АПИ. Обычно это выглядит как-то так:

$con = pg_pconnect("…");
if ($con === false) {
    throw new \RuntimeException('Could not connect');
}
// асинхронно выполняем запрос, в синхронном варианте тут было бы pg_query
if (pg_send_query($conn, "select * from users")) {
    // тут можно делать что угодно, пока мы не будем готовы принять результат
    // …
    // синхронно получаем результат
    var_dump(pg_get_result($conn));
}

Поскольку pg_send_query неблокирующий, то можно было бы попытаться как-то проконтролировать не истёк ли таймаут. Но как это сделать? Вот что он придумал:

function waitForResult($connection, callable $callback, int $timeout): void
{
    $stream = pg_socket($connection);
    while (pg_connection_busy($connection)) {
        $read = [$stream];
        $write = $except = null;

        $numChangedStreams = stream_select($read, $write, $except, 0, $timeout);
        if ($numChangedStreams > 0 || $numChangedStreams === false) {
            break;
        }

        $callback();
    }
}

После отсылки запроса мы получаем сетевой сокет соединения с базой данных и ожидаем в нём появления данных для чтения. Если за таймаут этого не происходит (или возникает ошибка), то цикл ожидания прерывается, иначе вызывается функция обратного вызова для получения порции данных.

 2 комментария    304   20 дн   php   postgres   программирование

Y2K79

«Проблема 2000-го года» — теперь уже история, которую мало кто помнит. Некоторые, вечность назад написанные программы имели только два разряда для хранения года. Считалось, что это может привести к серьёзным неприятностям — ведь и тогда были программы, участвующие в управлении, например, атомными электростанциями или самолётами.

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

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

my ($sec, $min, $hour, $mday, $mon, $year) = localtime;
printf "%d-%d-%d\n", $mday, $mon, '19'.$year;

1999-й год был последним, в котором это работало правильно — функция возвращала число 99, программист соединял это значение со строкой '19', получалось '1999', а в 2000-м году неожиданно многие программы стали показывать год как '19100'.

Атомными станциями такие программы, разумеется, не управляли, но править их всё равно пришлось, мы просто заменяли конкатенацию сложением с числом «1900».

Недавно я об этом вспомнил. Мы разбирались с ФИАСом — российской базой данных адресов, там у каждого адреса есть период его существования: начальная дата — когда адрес появился (например, дом построили) и конечная — когда исчез (дом снесли).

И оказалось, что «06.06.2079» в этой базе имеет магическое значение — если конечная дата содержит это значение, считается, что адрес существует. Сейчас все программы, обрабатывающие адреса из ФИАСа используют это значение как признак.

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

 2 комментария    286   27 дн   perl   программирование
Ранее Ctrl + ↓