Перейти к содержимому
Alex Kovalev

Как я узнал, что банки до сих пор кодят как в 1975-м

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

4мин чтения

Получил сегодня странное письмо. У клиента крутится какая-то “local soft”, которая должна забирать последние финансовые данные с нашего сервера. В письме сказано, что интеграция с их накладными сломана. Система не может прочитать наши данные. “Файл должен быть в ASCII с фиксированными отступами”.

ASCII. Фиксированные отступы. Смотрю в экран. На дворе 2026 год. У нас есть ИИ, который пишет стихи и водит машины. А где-то там банковская система требует, чтобы я форматировал данные так, будто их набивают на перфокартах и скармливают компьютеру размером с комнату.

Что вообще такое файл с фиксированной шириной?

Знаете, как обычные люди используют CSV? Или JSON? Форматы, где поля разделены запятыми или данные структурированы скобками? Где “John Doe” может занимать 9 символов в одной строке, а “Ковалев Александр Сергеевич” — 28 в следующей?

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

[127 пробелов] 12345000000000000002000000000000050

Это не ошибка. Код товара живёт в символах 128-147, ширина 20 символов, ни больше ни меньше. Если код товара всего из 5 цифр — дополняете его 15 пробелами. Если он из 21 цифры? Не повезло, обрезаете.

Спецификация приходит в виде двух PDF на иврите. Один описывает структуру заголовка:

  • Позиция 1: тип файла (1 символ)
  • Позиция 19-43: имя клиента (25 символов)
  • Позиция 45-69: адрес (25 символов)
  • Позиция 112-126: итоговая сумма (15 символов)

Другой описывает строки товаров:

  • Позиция 128-147: код товара (20 символов)
  • Позиция 149-163: количество (15 символов)
  • Позиция 165-179: цена (15 символов)

Заметили что-то? Поля заголовка начинаются с позиции 19. Поля строк товаров начинаются с позиции 128. Они и не должны делить одну строку, просто так устроен формат. А значит, 127 символов ничего идут до (или после) ваших настоящих данных.

        10        20        30        40        50        60        70        80
----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8
0·················Kovalev Alexander Sergeev·רחוב הרצל 5···············תל אביב···

25/25 колонок · отрезано:ich

Колонки 19–43 принадлежат имени. Все 25, никогда не 26. Наберите вертикальную черту, если соскучились по сообщениям об ошибках в стиле 1975-го.

С чего бы кому-то так делать?

Мне пришлось погуглить, потому что я искренне не мог в это поверить. Этот формат родом из мейнфреймов IBM 1960-х–1980-х. Банки, страховые компании, госсистемы — они строили всё на этих машинах. Системы, работающие на COBOL, обрабатывающие данные в форматах фиксированной ширины, потому что:

  1. Скорость — компьютер знает, что символ 128 — это всегда код товара. Парсинг не нужен. Прыгай сразу на позицию.
  2. Бинарная безопасность — нельзя использовать разделители, когда ваши данные сами могут содержать эти разделители. Фиксированные позиции устраняют проблему.
  3. COBOL этого ожидает — миллионы строк бизнес-логики написаны на COBOL, переписать которые стоило бы миллиарды.

Когда данных много (гига/терабайты), файлы с фиксированной шириной могут быть намного эффективнее.

Поскольку у каждой записи и поля фиксированный размер, можно просто перейти (seek) сразу к, скажем, n-миллионной строке и прочитать оттуда пару записей. Можно также замапить весь файл в память и получить довольно эффективный и лёгкий произвольный доступ ко всему.

Tommy

Они никогда не обновятся. А зачем? Система работает. Она обработала триллионы долларов транзакций. Она стабильна. Она протестирована. Заменить её — это многомиллиардный проект с огромным риском.

Так что вместо этого каждому современному веб-разработчику, которому нужно интегрироваться с такими системами, приходится телепортироваться назад в 1975 и учиться говорить на языке мейнфреймов.

Код, который я унаследовал

$data .= $year . str_pad($order_id, 6, "0", STR_PAD_LEFT)
. '|' . $order->get_date_created()->format('d/m/y')
. '|' . mb_str_pad($order->get_formatted_billing_full_name(), 25, " ", STR_PAD_LEFT)
. '|' . mb_str_pad($order->get_billing_address_1(), 25, " ", STR_PAD_LEFT)
// ... продолжается ещё для 12 полей

Видите эти символы вертикальной черты? Вот в чём проблема. Это создаёт файл с разделителями:

26240001|19/01/26|יוסי כהן|רחוב הרצל 5|...

А мейнфрейму нужно вот так:

0יוסי כהן רחוב הרצל 5 תל אביב 1234567...

Никаких вертикальных черт. Никаких разделителей. Только точные позиции. Всегда.

Сообщение об ошибке

Система прислала обратно файл ошибок. На иврите. В кодировке Windows-1255 (ещё одна реликвия прошлого). После декодирования:

תקלה ASCII במספר שורה בקריאת: 1
שדה מספר 64 אינו תקין: 26 |00000000039

Перевод: “Поле 64 повреждено. Вы отправили: 26 |00000000039”

Поле 64 должно быть 15-значным числом: 000000000000026

А мы отправили: 26 |00000000039 (неверная длина, есть символ вертикальной черты, не дополнено нулями)

Решение

Убрать все разделители. Дополнить всё до точных длин. Расставить поля по точным позициям символов.

function formatHeaderRecord($order) {
return str_pad('0', 1) // поз 1: тип файла
. str_repeat(' ', 18) // поз 2-18: пусто
. mb_str_pad($order->billing_name, 25, ' ') // поз 19-43: имя
. mb_str_pad($order->billing_address, 25, ' ') // поз 45-69: адрес
. mb_str_pad($order->billing_city, 15, ' ') // поз 71-85: город
. str_repeat(' ', 26) // поз 86-111: пусто
. str_pad(formatNumber($order->total), 15, '0', STR_PAD_LEFT); // поз 112-126: сумма
}
function formatLineRecord($item) {
return str_repeat(' ', 127) // поз 1-127: пусто
. str_pad($item->sku, 20, ' ') // поз 128-147: код товара
. str_pad(formatNumber($item->quantity), 15, '0', STR_PAD_LEFT) // поз 149-163: кол-во
. str_pad(formatNumber($item->price), 15, '0', STR_PAD_LEFT); // поз 165-179: цена
}

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

Что я понял

Ваш код живёт долго. Та хреновая функция, которую я пишу сегодня? Завтра я о ней забуду. Через год её, может, перепишут на rust. Но если я напишу её достаточно хорошо, она вполне может пережить меня. Какой-нибудь разработчик в 2077-м наткнётся на мою интеграцию с WooCommerce, будет проклинать моё имя, гадая, почему поле 64 начинается с позиции 112.

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

Вспомните об этом, когда в следующий раз кто-то предложит “переписать легаси”. Особенно сейчас, когда каждая новая GPT-обёртка делает код дешевле, чем когда-либо, и переписывание выглядит как работа на выходные. Это соблазнительно. Просто помните:

большая часть кода живёт долго не просто так.

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

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

А теперь простите, мне нужно дополнить 127 пробелов.

Полезные ссылки

Расхождение дат в Excel и ошибки на единицу, ещё один устаревший формат файлов, который меня укусил

https://www.easydatatransform.com/help/latest/windows/html/index.html?fixed_width_format.html https://stackoverflow.com/questions/7666780/why-are-fixed-width-file-formats-still-in-use

Комментарии

Комментарии живут в GitHub Discussions (через giscus), так что для участия нужен аккаунт GitHub.

Чирикну, когда выйдет новый пост

Одно письмо на каждый новый пост, обычно по пятницам. Больше ничего, а отписаться можно в любой момент.

Удобнее читать в RSS-ридере? Есть RSS-лента.

© Alex Kovalev. Все права защищены. · Политика конфиденциальности