Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 22 из 25 ~18 мин +20 XP

Типичные ошибки: стили, строки и ссылки

Ошибки excelize редко бывают экзотикой: почти все они повторяются из проекта в проект и укладываются в три семейства — стили, строки и ссылки. Грабли excelize собраны в этом уроке как карта: что ломается, почему и как чинится за минуты, а не за вечер. Отдельно разберём работу с файлами и дескрипторами — OpenFile, Close, SaveAs, — и закончим культурой проверки ошибок, из-за которой пустой файл иногда выглядит как рабочий.

Содержание урока · 9 разделов

Каждый пункт этого урока — история, которая случалась в реальных проектах, и потому грабли стоит знать до встречи с ними. Семейство стилей — это файлы, которые пухнут и открываются в Excel десятками секунд: причина чаще всего одна, стили создаются в цикле. Семейство строк — паники на недозаполненных строках, пустые объединённые ячейки, съехавшие индексы. Семейство ссылок — формулы, которые ссылаются в никуда после переименования листа, и имена листов с пробелами без апострофов. Отдельная группа — дескрипторы: книга, открытая без отложенного Close, держит ресурсы до конца процесса, а проигнорированная ошибка SaveAs превращает рабочий отчёт в пустышку. Заметьте: ни одна из этих ошибок не проявляется в момент сборки — файл собирается без ошибок и выглядит нормально, а ломается он у пользователя или через сутки. Поэтому ниже для каждой ловушки есть три вещи: симптом, причина и лечение.

Ошибки excelize со стилями: NewStyle и таблица стилей#

Стиль в xlsx — это запись в общей таблице стилей книги, и NewStyle лишь добавляет туда запись и возвращает её номер. Дедупликации нет: два одинаковых вызова дают две записи, а вызов в цикле по ячейкам — таблицу на тысячи строк и файл, который Excel открывает мучительно. Ошибки excelize со стилями лечатся одним правилом: один NewStyle на вид оформления, кэш идентификаторов на время работы генератора. Вторая ловушка — применение: SetCellStyle работает и с ячейкой, и с диапазоном, поэтому заливка шапки A1:F1 — это один вызов, а не шесть. Третья — условное форматирование: у него свой конструктор NewConditionalStyle из урока про условное форматирование, и попытка передать туда идентификатор обычного стиля приводит к молчаливому нулю оформления. Четвёртая — форматы чисел: они задаются полем NumFmt или пользовательской строкой в структуре Style, а не строкой вроде «0.00» отдельным вызовом, и путаница здесь даёт ячейки, которые выглядят правильно, но не суммируются.

Кэш стилей: плохо и хорошо

GO
// Плохо: стиль создаётся в цикле на каждую строку
for r := 2; r <= last; r++ {
    s, _ := f.NewStyle(&excelize.Style{
        Font: &excelize.Font{Bold: true},
    })
    f.SetCellStyle("Отчёт", "A"+strconv.Itoa(r), "F"+strconv.Itoa(r), s)
}

// Хорошо: один стиль на вид оформления, применение диапазоном
style, err := f.NewStyle(&excelize.Style{
    Font: &excelize.Font{Bold: true},
})
if err != nil {
    panic(err)
}
f.SetCellStyle("Отчёт", "A2", "F"+strconv.Itoa(last), style)

В плохом варианте таблица стилей растёт на каждую строку отчёта, хотя оформление одно и то же. В хорошем NewStyle вызывается один раз, а SetCellStyle красит весь диапазон одним вызовом: файл остаётся компактным, и Excel не тратит время на разбор тысяч одинаковых записей.

Строки, колонки и объединённые ячейки#

Индексация в excelize — с единицы, и первая строка листа — это A1, а не A0: сдвиг на единицу всплывает в координатах, номерах строк в журналах ошибок и границах циклов. Хвостовые пустые ячейки вы уже встречали в уроке про импорт: срез строки из GetRows короче ширины листа, и прямое обращение по индексу паникует на первой же строке с дырой. Объединённые ячейки — отдельная история: значение живёт только в верхней левой ячейке диапазона, остальные пусты, и чтение правой части MergeCell даёт пустую строку даже там, где на экране Excel видно содержимое. Для работы с объединениями есть GetMergeCells — список всех диапазонов листа со стартовой и конечной осями и методом чтения значения. Отдельные грабли — вставка строк: InsertRows двигает содержимое, но ссылки в формулах за сдвинутыми строками, как правило, не пересчитывает, поэтому после вставки стоит открывать файл и проверять итоги. И последнее: колонки и строки в API именуются по-разному — буквы для колонок и числа для строк, и перепутанный порядок аргументов даёт запись не туда без всякой ошибки.

Файлы и дескрипторы: Close, SaveAs и чужие книги#

Книга excelize держит распакованные части архива в памяти и временные ресурсы, поэтому Close ставится через defer сразу после создания или открытия — это такой же рефлекс, как defer rows.Close у курсора базы. После Close файл не читается: вызов GetRows вернёт ошибку про закрытый архив, и это не паника, а тихая ошибка, которую легко пропустить, если её игнорировать. SaveAs проверяется в месте вызова: несуществующая директория, занятый файл или недопустимое имя листа вернут ошибку, а без проверки отчёт молча не сохранится, и обработчик ответит клиенту успехом. Открытие чужого файла — OpenFile — читает весь архив в память, и на гигантских файлах это лимит окружения, о котором мы говорили в уроке про производительность; защищённые книги открываются опцией с паролем. Формулы со ссылками на лист, чьё имя содержит пробел, требуют апострофов: формула со ссылкой на Итоги 2024 без кавычек-апострофов не сработает, а после RenameSheet стоит проверить ссылки на переименованный лист — часть этой работы библиотека берёт на себя, но проверка файла после переименования дешевле ночного инцидента.

Дисциплина работы с файлом

GO
f, err := excelize.OpenFile(path)
if err != nil {
    return fmt.Errorf("open %s: %w", path, err)
}
defer f.Close()

if err := f.SetCellFormula("Отчёт", "F10", "'Итоги 2024'!B2*1.2"); err != nil {
    return err
}

if err := f.SaveAs(outPath); err != nil {
    return fmt.Errorf("save %s: %w", outPath, err)
}

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

Аптечка граблей#

СимптомПричинаЛечение
Файл открывается в Excel десятками секундтысячи записей в таблице стилейкэш стилей: один NewStyle на вид оформления
Паника на обращении к ячейке строкистрока короче шапки: пустой хвост не в срезепроверка len(row) или хелпер с пустотой
В объединённой ячейке пустозначение лежит в верхней левой ячейкеGetMergeCells и чтение стартовой оси
Заливка пропала после добавления шрифтаSetCellStyle заменяет стиль целикомодна структура Style со всеми полями
Отчёт не появился, ошибок нетпроигнорирована ошибка SaveAsпроверять err у SaveAs и Close
Формула ссылается в никудалист переименован или имя с пробеломапострофы и проверка ссылок после переименования

Культура проверки ошибок: пустой файл не выглядит пустым#

Почти каждый вызов excelize возвращает ошибку, и главная граблина — не в библиотеке, а в игнорировании: SetCellValue с опечаткой в имени листа вернёт ошибку, которую проглотило подчёркивание, и файл соберётся с дырой там, где по плану была колонка. Рабочая дисциплина простая: ошибки обрабатываются в месте вызова либо через маленькую обёртку, которая логирует координату и шаг генерации; подчёркивание допустимо только там, где отказ ячейки не влияет на результат, и это решение осознанное. Ошибки стоит разделять на два сорта: ошибки данных — битое значение, нестандартный формат — и ошибки библиотеки — неверное имя листа, нечитаемый файл; первые логируются и копятся, вторые останавливают генерацию. Отдельная линия обороны — тесты: чтение файла обратно ловит пропавшую колонку и пустую шапку на этапе CI, а не на телефоне заказчика. Грабли excelize в проверке ошибок не выглядят эффектно, но именно они отличают генератор, который доверяют ночным расписаниям, от генератора, за которым следят вручную.

Чек-лист граблей#

  • Один NewStyle на вид оформления: кэш идентификаторов вместо вызова в цикле.
  • SetCellStyle заменяет стиль целиком: несколько эффектов — одна структура Style.
  • len(row) перед индексами: хвостовые пустые ячейки в срезе не живут.
  • Значение merged-ячейки — в верхней левой: чтение через GetMergeCells.
  • defer Close сразу после открытия, ошибки SaveAs и Close проверяются в месте вызова.
  • Имя листа с пробелом в формуле — в апострофах; после RenameSheet проверяем ссылки.

Кейс о том, как грабли складываются в штабель. Генератор прайс-листа вырос из скрипта: каждую строку красили стилем, созданным на месте, ошибки SetCellValue собирались в подчёркивание, а Close ставился только в одной из двух веток выхода. Месяцами всё держалось, пока файл не перевалил за тридцать мегабайт: таблица стилей выросла до десятков тысяч записей, Excel у менеджера открывал прайс почти минуту, а ночные прогоны начали ловить лимит открытых дескрипторов — вторая ветка выхода не закрывала книгу. Починка заняла день: кэш из трёх стилей, обёртка с логом координаты, defer на всех путях. Мораль кейса не в списке фиксов, а в порядке: симптом нашёлся у пользователя, причина — в трёх независимых местах, и лечились они одним навыком — проверять то, что возвращает библиотека. Спорная часть осталась в команде: кто-то хочет строгий режим, где первая же ошибка записи останавливает генерацию, кто-то — толерантный, где дыры собираются в отчёт; выбрали строгость для загрузок из базы и толерантность для импорта чужих файлов.

Проверка: почему файл тормозит

Сгенерированный отчёт стал открываться в Excel десятками секунд. Что проверить первым?

Проверка: merged cells

Что вернёт GetCellValue для правой ячейки объединённого диапазона?

Что станет с заливкой

Прочитайте код и выберите, что он выведет:

f := excelize.NewFile()
s1, _ := f.NewStyle(&excelize.Style{
    Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"FFFF00"}},
})
s2, _ := f.NewStyle(&excelize.Style{
    Font: &excelize.Font{Bold: true},
})
f.SetCellStyle("Sheet1", "B2", "B2", s1)
f.SetCellStyle("Sheet1", "B2", "B2", s2)

Соберите дисциплину работы с файлом

Допишите открытие книги, закрытие, создание стиля и его применение к диапазону.

f, err := excelize.(path) if err != nil { panic(err) } defer f.() style, err := f.(&excelize.Style{ Font: &excelize.Font{Bold: true}, }) if err != nil { panic(err) } f.("Отчёт", "A1", "F1", style)

Соберите порядок работы с книгой

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

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: грабли

Термин

NewStyle в цикле

нажмите, чтобы перевернуть
Определение

Каждый вызов — новая запись в таблице стилей: файл пухнет, Excel тормозит. Один стиль на вид оформления, идентификаторы в кэше.

1 / 6

Карта граблей пополнилась, и теперь она работает на профилактику, а не на разбор инцидентов. Устройство стилей и их кэша разобрано в уроке про стили ячеек, условное форматирование с собственным конструктором стиля — в уроке про условное форматирование, чтение чужих таблиц с хвостовыми пустыми ячейками — в уроке про импорт в структуры. Ссылки на листы и формулы подробно живут в уроке про формулы, а ловушка «молча не сохранилось» закрывается тестами из урока про тесты генерации. В следующем уроке — свойства книги: метаданные и настройки печати. Домашнее задание — пройдитесь по своему генератору с чек-листом урока и посчитайте, сколько пунктов он нарушает сегодня.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Один NewStyle на вид оформления: вызов в цикле раздувает таблицу стилей и тормозит Excel.
  • SetCellStyle заменяет стиль целиком: несколько эффектов собираются одной структурой Style.
  • len(row) перед индексами: хвостовые пустые ячейки в срезе строки не живут.
  • Значение merged-ячейки лежит в верхней левой ячейке, диапазоны отдаёт GetMergeCells.
  • defer Close сразу после открытия, ошибки SaveAs и Close проверяются в месте вызова.
  • Ошибки делятся на данные и библиотеку: первые копятся, вторые останавливают генерацию.

Домашнее задание

Пройдите по своему генератору с чек-листом урока: посчитайте вызовы NewStyle, найдите подчёркивания на ошибках записи и проверьте, что Close стоит на всех путях выхода. Исправьте найденное, добавьте обёртку с логом координаты ячейки и убедитесь тестом чтения обратно, что содержимое не изменилось. Отдельно воспроизведите merged-ловушку: создайте объединение, прочитайте правую ячейку и напишите, что вернулось.

Личные заметкиПусто
Хранятся только в вашем браузере (Excelize · Типичные ошибки: стили, строки и ссылки)

Вопросы и ответы по уроку

Файл пухнет, хотя данных немного — где искать?

Считайте записи в таблице стилей: чаще всего причина в NewStyle внутри цикла. Уберите вызовы из цикла, кэшируйте идентификаторы стилей и применяйте их диапазоном через SetCellStyle — размер упадёт на порядки.

Почему после InsertRows формулы считают по старым строкам?

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

Как не пропускать ошибки записи в каждой строке кода?

Заведите маленькую обёртку, которая принимает ошибку, координату и шаг генерации: логирует проблему и решает — копить или останавливать. Это концентрирует политику ошибок в одном месте и убирает бесконечные if err != nil в бизнес-коде.

Термины урока

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

Следующая ступень учебного пути

Cron25 уроков~9 чпрактика25 уроков уже доступны

Фоновые задачи в Go: планировщик на robfig/cron

Курс по cron в Go: robfig/cron v3, синтаксис выражений, пояса, wrappers, конкурентность и graceful shutdown. 25 уроков с примерами и проектом.

  • Собирать планировщик на robfig/cron v3 и запускать задачи по расписанию
  • Читать и писать cron-выражения: пять полей, спецсимволы, @daily
  • Управлять жизненным циклом: Start, Stop, Entry, Remove
  • Держать часовые пояса и секундные расписания под контролем