Заметки: Готово

Мои мысли о жизни, работе и разных интересных штуках вокруг меня.

Показано: 0 · Страница

Новый скрипт топа исключений
17 октября 2020 ·

Переписал скрипт на баше, строящий топ исключений по собранному ТЖ: хотел решить эту задачу как-то попроще.

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

Получилось явно лучше, чем было — во всяком случае, логика выглядит понятнее. Изначально, правда, я хотел скорее сократить скрипт в размере и получить что-то вроде:

grep -hoP ",EXCP,.*\KDescr=.*" */*.log | uniq -c | sort -rn

То есть фильтруем только строки с событием EXCP, отрезаем всё до описания ошибки и группируем с помощью uniq. По-моему, очень изящно.

Однако описание у EXCP может быть многострочным. То есть мы будем время от времени терять часть данных, нужных для расследования (всё описание после первого же перевода строки). Как решить эту проблему так, чтобы скрипт не разбарабанило втрое — я пока не придумал :-)

Уязвимости в конфигурациях 1С
13 октября 2020 ·

В прошлом месяце был на митапе «Инфостарта» по безопасности решений на платформе 1С. Узнал кучу интересного! Среди прочего, Олег Тымко обзорно рассказывал про подходы в разработке, которые можно считать потенциальными уязвимостями продуктов. Например, зашитых прямо в код IP-адресов, ссылок, e-mail'ов, паролей и так далее.

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

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

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

Профессионал по техвопросам
5 сентября 2020 ·

В конце августа сдал тест на профессионала 1С по техническим вопросам крупных внедрений. Это по сути такой входной билет на основной экзамен, призванный проредить поток претендентов и заставить их подучить теорию.

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

Подбор тем показался мне порядочным винегретом — по чуть-чуть из всего подряд. Впрочем, по факту такой охват скорее полезен: смотришь на очередной непривычный вопрос, отвечаешь, ошибаешься. Тушишь задницу, лезешь разбираться как так. Становишься немного умнее (но это не точно).

Один запрос, что правит всеми
25 июля 2020 ·

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

На первый взгляд структура запроса простая и четкая: пачка запросов к таблицам документов, соединяемых через ОБЪЕДИНИТЬ ВСЁ. Каждая из таблиц фильтруется по примерно одинаковым условиям — тип ссылки, дата и вхождение ссылки в результат подзапроса.

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

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

WHERE VALUETYPE(AdditionalExpenses.Ref) IN (&DocumentsListSelectedTypes)

То есть в динамический список передается список типов документов, которые требуется вывести. Однако это условие будет наложено после выборки данных, и если пользователю нужны только инвойсы — СУБД все равно сначала выгребет все 14 таблиц, а потом отбросит 13 из них.

Но это всё не так критично. Если бы список проблем этим и ограничивался, мы, возможно, и не полезли бы разбираться. Главная проблема — во втором условии секций WHERE: каждый запрос проверяет вхождение ссылки на документ в результат подзапроса.

Использование вложенного запроса в условиях — само по себе почти табу, если речь идет не о временных таблицах: СУБД часто не в состоянии понять, сколько данных вернет подзапрос и, соответственно, какой способ работы с ними подойдет лучше. Однако тут это ещё и усугубляется тем, к какому источнику данных мы делаем запрос. Критерий отбора — это не таблица в базе данных, которую можно прочитать, пусть даже со сканом — это набор таблиц. В критерии отбора DocumentsByProject их тридцать!

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

OMG

Вот теперь занавес :-)

Обновление запускатора служб
22 июля 2020 ·

Доработал логику запускатора служб. Теперь, если вызвать скрипт без параметров (т.е. не указав ни -start, ни -stop) — он сам решит, запускать службы или останавливать их.

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

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

Блог на Vue.js
21 июля 2020 ·

В середине прошлого года я загорелся идеей переписать этот сайт на чем-то посовременнее PHP, выбрал Vue.js в качестве фреймворка и принялся за дело. JavaScript я на тот момент почти не знал, поэтому набил прорву шишек на кейсах в духе «метод вывода даты падает в Safari» — однако в итоге получил вполне жизнеспособное приложение.

Ну, как жизнеспособное? С одной стороны, главное у меня получилось — я разработал клиентское веб-приложение, которое ходит на сервер только за данными, а весь интерфейс собирает само. С другой — пришлось накрутить на фреймворк целую армию костылей хуков даже для самых простых штук (вроде подсветки элементов меню или, скажем, выдачи правильных кодов HTTP). Кое-что я вообще с наскока реализовать не смог — например, выдачу HTML-версий страниц для Яндекса (их пауки в 2020-м году не умеют индексировать сайты на JavaScript).

В общем, опыт вышел полезным, но результат — настолько спорным, что в конце концов я бросил эту затею, а получившегося Франкенштейна выложил на GitHub. Возможно, он кому-то сэкономит время на решение задач в духе «как вывести через Vue.js произвольный HTML» или «как научить VueI18n работать с русским языком».

Документации там нет, правда. Я сначала хотел подробно расписать, как что работает, но быстро понял, что потрачу уйму времени без видимой пользы. Вероятно, буду возвращаться к этой теме под настроение — а пока, так сказать, ограничимся парадигмой MVP :-)

Запускатор служб
13 июля 2020 ·

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

Результат можно посмотреть на GitHub'е. No big deal — хотел поупражняться в языке и упростить ежедневную рутину: в моей системе наберется десятка полтора прожорливых сервисов, которые нужны для работы, но бесполезны в другое время. Вручную останавливать, а потом запускать этот зоопарк неудобно, а вот одной командой — совсем другое дело!

Размер данных базы 1С
11 июля 2020 ·

На прошлой неделе листал комментарии к 8.3.15 и наткнулся на метод ПолучитьРазмерДанныхБазыДанных(). Стало любопытно, как эта штука работает и насколько её данные расходятся с теми, которые можно получить из, например, Management Studio.

В итоге накатал что-то вроде консоли, через которую методу можно передавать разные метаданные, и принялся следить за запросами платформы к БД.

В общем, размер данных платформа считает примерно таким выражением:

CAST(
    SUM(
        CAST(
            DATALENGTH(T1._Fld40) AS NUMERIC(12, 0)
        )
    ) AS NUMERIC(18, 0)
)

И так для каждого поля, которое есть у объекта, включая стандартные. Если есть табличные части — они тоже считаются. Результат суммируется.

Выводы?

Ну, во-первых, понятно, почему у метода такое дурацкое название. Он считает не размер таблиц, как я изначально подумал, а именно размер данных — то есть на оценку не влияют ни расходы на схему данных, ни расходы на индексы, ни механика экстентов. Учитывается только размер самих данных, которые хранятся непосредственно в объекте.

Таким образом, реальный объём места, которое слопал условный справочник номенклатуры, будет больше того, которое покажет метод. Возможно, значительно. Для точной аналитики такой подход не годится, но чтобы быстро оценить распределение данных в БД – вполне подходит.

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

В-третьих, СУБД в ходе расчетов выгребает все содержимое нужных таблиц, а потом считает размер того, что выгребла. То есть вызов, скорее всего, приведет к куче сканирований и может быстро вымыть буферный кэш. На 1cFresh запросы будут делаться с учетом разделителей, но это слабое утешение, как по мне.

В общем, на работающем проде применять с осторожностью.

Просмотр таблицы значений
16 мая 2020 ·

Закинул на GitHub короткую функцию, с помощью которой произвольную таблицу значений можно превратить в табличный документ.

Для чего? Чтобы посмотреть её содержимое. Саму таблицу на клиент передать нельзя, а вот табличный документ на её основе — можно. Кроме того, у табличного документа есть клиентский метод Show(), который отображает его в отдельном окне.

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

Пустая() или ЗначениеЗаполнено()?
11 апреля 2020 ·

Несколько дней назад обсуждал с коллегой, какой способ проверки заполнения ссылки лучше. С одной стороны, время назад на каком-то курсе я слышал авторитетное мнение, что функцию ЗначениеЗаполнено() нужно использовать с осторожностью, так как её логика — сопоставлять переменную всем возможным пустым значениям, которые есть в конфигурации. И это, мол, огромная тормозная лапша. Как альтернативу автор курса советовал использовать метод ссылки Пустая(), а композитные реквизиты проверять примерно так:

СсылкаЗаполнена = Ссылка <> Неопределено И Не Ссылка.Пустая();

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

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

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

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

Да и чисто практически проще делать проверку одним универсальным вызовом, а не пытаться угадать все возможные значения переменной.

Фильтрация стандартных реквизитов
9 марта 2020 ·

Короткий фрагмент кода из обработки для настройки механизма истории данных, о которой я только что писал. Эта функция определяет, является ли Attribute стандартным реквизитом с именем StandardAttributeName, принадлежащим объекту метаданных MetadataObject. Где она нужна? Допустим, вы перебираете стандартные реквизиты объекта и по какой-то причине хотите пропустить один из них.

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

If Attribute = MetadataObject.StandardAttributes.Order Then

Дело в том, что такой код не сработает: результат операции сравнения двух стандартных реквизитов объекта метаданных в платформе 1С — всегда Ложь.

О'кей, скажете вы — может, тогда не будем усложнять и напишем вот так?

If Attribute.Name = "Order" Then

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

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

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

Что до конструкции Try/Catch — она тут на тот случай, если стандартного реквизита с таким именем в конфигурации нет вообще (и попытка обратиться к нему по имени приведет к ошибке).

Настройки истории данных
9 марта 2020 ·

Выложил на GitHub обработку для настройки механизма истории данных, который сравнительно недавно появился в платформе. Эта обработка:

  1. Строит дерево объектов, для которых может вестись история данных;
  2. Подсказывает, для каких объектов история данных ведется сейчас;
  3. Дает возможность включить или выключить историю данных для объектов и их реквизитов.

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

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

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

Топ исключений по ТЖ
16 февраля 2020 ·

Выложил на GitHub скрипт на баше, который по собранному технологическому журналу 1С определяет наиболее частотные исключения.

Скрипт анализирует события EXCP. Это основной источник информации об ошибках, хотя, конечно, не единственный: например, есть EXCPCNTX (событие, которое началось, но не закончилось в тот момент, когда произошла ошибка). Кроме того, если исключение происходит во время серверного вызова, то в поле RetExcp у события CALL будет текст ошибки, которая вернется на клиент.

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

В пятьдесят раз быстрее
5 декабря 2019 ·

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

Лезу в код, делаю замер производительности и вижу, что и почти всё это время платформа тратит на одну-единственную процедуру, выполняя пакет запросов. Смотрю первый из них; ну, думаю, классика — запрос данных через точку от составного типа. Наверняка СУБД пристегивает целый вагон соединений с тяжеленными таблицами документов, вот оптимизатор и не успевает набросать адекватный план.

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

Смотрю размеры таблиц документов — не особенно-то и большие. Выполняю запрос отдельно от пакета — да, работает не мгновенно (читает около 350 000 записей и отбирает примерно 200 000), но никак не час.

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

Проверил — именно тут платформа и проводит большую часть времени, ожидая ответа от СУБД.

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

Это тот случай, когда общая рекомендация 1С сработала идеально — вместо 50 минут документ стал проводиться за три, а после дополнительной оптимизации кода — за минуту. То есть, в пятьдесят раз быстрее того, что я имел вначале.

Такой результат я счел достаточным (минута для закрытия месяца — это в общем случае нормально) и остановился.

Поиск методов с параметром
18 ноября 2019 ·

Задача: нужно найти в общих модулях конфигурации методы с определенным параметром (в этом примере — с параметром DecimalPlacesFor).

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

Первая часть скрипта (до пайпа) выполняет сам поиск, вторая — отрезает лишнее, чтобы на вывод уходили только названия модулей и найденных в них методов. Получится, например, так:

Скриншот консоли

Профит? Профит!

Мультиязычность в Vue.js
25 июня 2019 ·

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

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

На нижнем уровне это, правда, всё те же два массива с фразами (их нужно задать при инициализации), однако выгода здесь в другом. Этот плагин сам по себе закрывает еще две проблемы с генерацией интерфейса:

  1. Склонение существительных. Нужно, чтобы правильно выводить слово «страница» в статистике по тегам — одна заметка, две заметки, пять заметок и так далее.
  2. Вывод дат. Например, дата под этой заметкой.

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

Повторяющийся ключ
1 марта 2019 ·

На Инфостарте вышла любопытная статья про ошибку СУБД «cannot insert duplicate key». Одна из возможных причин сбоя — использование уникального идентификатора объекта для сопоставления при обмене с другими информационными базами (то есть, без служебного регистра, сопоставляющего объекты базы данных с идентификаторами объектов внешней базы).

Такой жести, чтобы уникальные идентификаторы совпали в разных базах для одной и той же таблицы, у меня в практике пока не было. Однако от подхода «единого идентификатора» я отрекся окончательно после того, как делал обмен между FirstBIT ERP и GROTEM / Agent.

Архитектура пилотного решения была такая: данные из FirstBIT ERP выгружались сперва в служебную ИБ 1С, написанную разработчиками GROTEM'а, а уже оттуда — в основную базу данных сервера мобильных приложений.

Сопоставление было сделано очень просто — через идентификаторы объектов. Например, один и тот же документ в обеих базах имел один и тот же идентфикатор. Но были и более сложные схемы: например, контрагент FirstBIT ERP на стороне GROTEM'а превращался в три объекта: собственно контрагента, торговую точку и документ взаиморасчетов.

WAIT WHAT

Да, в документ взаиморасчетов. И все три объекта имели один и тот же идентфикатор — идентификатор контрагента FirstBIT ERP. В общем, не то чтобы это было очень изящным решением, но для платформы такой расклад вполне адекватен и мы решили, что проблем быть не должно.

Однако обмен работать отказался.

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

В итоге выяснилось, что:

  1. В этой самой базе есть таблица, где хранятся все интересующие нас объекты. Документы, элементы справочников, вот это всё.
  2. Эта таблица имеет уникальный индекс по GUID объекта.

В этом месте интрига закончилась. Ну да, первый же выгруженный контрагент создавал целых три сущности с одним и тем же GUID, которые прекрасно лежали в своих таблицах на стороне 1С, но не могли быть записаны в одну общую таблицу базы данных приложения.

I will not use GUIDs to map objects!

Неудачная охота
21 февраля 2019 ·

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

Неприятно, но, в общем, ничего страшного — запатчили баг, описали и зарепортили в 1С. Там пока думают. Но я, внезапно, даже как-то расстроен — когда расследуешь такие штуки, они всегда выглядят немного загадочно и кажется, что ты вот-вот найдешь что-то интересное! Например, новый аспект работы платформы или что-то вроде этого.

И вот, наконец, ты смотришь на проблему под правильным углом, а она — обычный жук. Возможно, полосатый или в крапинку, но жук как жук — сидит себе, шевелит усами. И ты такой: блин, это что — и есть моя добыча? :-)

СКД и право просмотра
28 августа 2018 ·

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

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

У вас настройка кривая, товарищ!

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

Не скажу сходу, что именно тут сбило платформу с толку и привело к не очевидной диагностике — то ли манипуляции с временной таблицей, куда эта константа была прочитана, то ли то, что читалась она в конструкции ВЫБОР, то ли то, что на сам справочник валют все необходимые права были. Искать конкретное условие или их сочетания я не стал — думаю, это просто специфика реализации движка СКД, которая может меняться от версии к версии. Достаточно не забывать о праве просмотра, и проблемы не будет.

Что касается ошибки, при столкновении с ней стоит вывести проблемный отчет под полными правами. Если тут всё о'кей, нужно набросать список таблиц, откуда отчет читает данные, и проверить — все ли они с правами на чтение и просмотр.

Нудно, но может сэкономить прилично времени.

Результаты FCE
15 июля 2018 ·

Кембридж прислал результаты июньских экзаменов. И — вступают трубы — FCE я сдал! Судя по баллам, ухитрился в чём-то налажать во время письменной части, зато неплохо отыгрался во время разговорной. Жаль, что экзаменаторы не дают более подробных комментариев; впрочем, и так понятно, куда дальше воевать :) Преподаватель заметила, что раньше вообще сообщали только итоговый балл — мол, понимайте как хотите, уважаемые претенденты.

В общем, в следующем году перевешу планку повыше — видимо, нужно готовиться к CAE.

По этому поводу вспомнился Фейнман со своей знаменитой «Вы, конечно, шутите». Среди прочего он рассказывал, как во время жизни в Бразилии учился играть на фригидейре и доучился до того, что его стали ставить в пример местным музыкантам:

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

― Ричард Фейнман

Между «все ошибки» и «приятно слушать» непринужденно опущено целое море труда, но в целом этот синопсис мне нравится. Работаем куда-то туда!