Показаны сообщения с ярлыком Разное. Показать все сообщения
Показаны сообщения с ярлыком Разное. Показать все сообщения

четверг, 22 марта 2012 г.

Пара заметок об Open XML

В очередном проекте в очередной раз нужно генерировать отчеты в Excel (ну ладно: наконец-то выпала возможность “попедалить”! Улыбка ). Из имеющихся в распоряжении средств: генерировать html (просто, но слишком коряво), office xml (по-моему до сих пор вполне пристойный вариант) и Open XML aka Office 2007+ – был выбран последний. Аргументы: по отзывам на habrahabr все должно быть просто и быстро, к тому же на выходе мы получаем актуальную версию формата.

Open XML SDK 2.0 “весит” немногим больше 100 Мб – по нынешним временам, конечно, не объем, но все-равно возникает вопрос: “А что туда такого напихали и почему так много?” В комплект входит “Open XML SDK 2.0 Productivity Tool for Offce”, который может по данному файлу сгенерировать код, который сгенерирует точно такой же файл. Вроде бы все просто, но когда начинаешь разбираться в коде, становится грустно.

  1. API – жуть какое неудобное.
    Вроде как обещается, что это высокоуровневое API, упрощающее создание/редактирование документов (цитирую: “The Open XML SDK 2.0 encapsulates many common tasks that developers perform on Open XML packages, so that you can perform complex operations with just a few lines of code.”).
    • Почему тогда изо всех углов торчит XML и API по работе с ним (все эти бесконечные Append, Text, AddNamespaceDeclaration)?
    • Зачем заставлять меня пользоваться специальными типами для всего, что только можно придумать (включая специальный тип UInt32Value)? В результате приходится пользоваться прямо-таки умопомрачительным количеством разнообразных типов.
    • В высокоуровневом API(!) для создания документов нужно не забыть закрыть пакет (о, вы не знали, что документ – это на самом деле пакет?) – иначе на выходе получим “битый” файл.
    • Как бы глупо это не звучало, но строго типизированное API “молча” позволяет вам сгенерировать невалидный документ. Вот так-то. Добиться этого довольно легко – просто попробуйте сохранить в ячейку какую-нибудь строку в обход таблицы SharedStrings. Для того, чтобы понять, как же это делать “по-правильному”, нужно потратить уйму времени и [внимательно] перечитать примеры кода (код, сгенерированный Productivity Tool-ом вам в этом не поможет).
  2. Убогая документация.
    • Есть примеры. Но почему-то код в примерах “ни разу не похож” на то, что генерирует тот самый Productivity Tool. Более того,
    • Описание классов поражают своей информативностью. Для большинства классов все описание – “When the object is serialized as xml, its qualified name is XXX”. В некоторых случаях еще ссылаются на раздел стандарта. Такая документация очень помогает разобраться в том, как мне сделать что-то.

Часть проблем мне удалось побороть, но добавление графиков пока “продолжает сопротивление” (количество всяких параметров/атрибутов и запутанность их значений навевают уныние).

Гораздо приятнее выглядит проект с издевательским (для формата Open XML) именем ClosedXML – поверх стандарта соорудили человеческое API, которое действительно позволяет достигнуть нужного результата буквально парой строк кода.

HTH,

AlexS

среда, 1 декабря 2010 г.

“В жизни всегда есть место для подвига” © или как реализовать Singleton по-новому

Тема реализации шаблона Singleton так или иначе затрагивает жизнь любого разработчика – толи на собеседовании спросят (или самом спросить придется), толи в реальном проекте найдется применение (не приведи Господи). О том, как этого зверя вырастить, написано немало – и в MSDN, и в прочих интернетах (последняя статься считается канонической и ранее располагалась по другому адресу). В какой-то момент казалось (мне по крайней мере), что придумать что-нибудь новое в этой области невозможно. Ошибся.

Недавно на одном из проектов заказчик прислал кусок [псевдо]кода, в котором кроме всего прочего содержалась крайне любопытная реализация привычного шаблона (тот еще затейник – архитектура решения тоже заставляем “много думать”). В чистом виде (убирая конкретику проекта и применяя привычные для .NET схемы именования и форматирования) это выглядит так:





public interface ISingleton
{

}

class Singleton : ISingleton
{
public delegate ISingleton InstanceGetterDelegate();

private static Singleton _singleton;
private static InstanceGetterDelegate _instanceGetter;
static Singleton()
{
var type = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;
// assign the closure that executes on the first call of GetInstance() method
_instanceGetter = () =>
{
Singleton instance = null;
lock (type)
{
var rslt = Interlocked.CompareExchange<Singleton>(ref _singleton, null, null);
if (rslt == null) // indicates is first call
{
instance = new Singleton(/* init arguments go here*/);
Interlocked.CompareExchange<Singleton>(ref _singleton, null, instance);

// now replace with a closure that executes on all subsequent calls of GetInstance() method
InstanceGetterDelegate fn = () =>
{
return Interlocked.CompareExchange<Singleton>(ref _singleton, null, null);
};
Interlocked.Exchange<InstanceGetterDelegate>(ref _instanceGetter, fn);
}
else
{
instance = rslt; // was already set with singleton instance
}
}
return instance;
};
}
private Singleton() { } // disable default instance constructor
public static ISingleton GetInstance() { return _instanceGetter(); }
}


И вот тут, что называется, “накрывает”. Я бы назвал этот способ “JavaScript-style Singleton” (конечно же за использования замыканий). В целом (за исключение блокировки по Type (о чем я уже писал) и использования рефлексии в статическом конструкторе) реализация выглядит любопытной и уж во всяком случае расширяет горизонт.



HTH, AlexS

вторник, 6 апреля 2010 г.

Бытовые особенности SQLite

В последние пару недель работаю с SQLite (не то чтобы очень плотно, но плотнее, чем “просто поковырять”). Штуковина безусловно полезная и нужная, но не без особенностей. Ниже приведу список того, обо что споткнулся сам:

  • Нет типа данных DateTime или чего-то похожего – все даты нужно хранить в виде строки; проблема мелкая, но стоит о ней помнить. Более подробное описание типов данных SQLite лежит здесь.
  • Нет функции поиска подстроки/символа в строке (т.е. ничего, аналогичного CHARINDEX в SQL Server-е). Можно, конечно, самому написать такую функцию (на C) и подключить ее на лету, но … в общем лучше бы она была сразу.
  • Не поддерживается [ставшая уже привычной] конструкция UPDATE|DELETE|INSERT …. FROM ….
  • Крайне любопытно организовано хранение данных, в частности – в каждой таблице неявно присутствует “автоматический первичный ключ” (поле ROWID), который к тому же является “кластерным” (в привычных терминах). Причем явное объявление “целочисленного, автоинкрементного первичного ключа” в большинстве случаев – просто псевдоним для ROWID.
  • Есть доступ к метаданным (их, правда, немного) – таблица sqlite_master.
  • Сравнение строк может производиться как с учетом регистра, так и без оного – зависит от того, как была собрана библиотека и от того, какие строки (ASCII/Unicode) сравниваются.
  • Поддерживаются триггеры, причем можно повесить триггер на обновление какой-то конкретной колонки.
  • Параметры могут определяться так же, как и в SQL Server-е - @ParamName (но поддерживаются и другие варианты: ?Name, :Name, $Name).

HTH,

AlexS

четверг, 11 февраля 2010 г.

Синхронизация доступа: on Type or not on Type

Сегодня на собеседовании задал кандидату вопрос о том, как реализовать Singleton в C#. Сначала он предложил самый простой вариант (не thread safe) и в ходе дальнейшего обсуждения (“а почему так? а что если у нас многопоточное приложение? и что делать в этом случае?” и т.п.) предлагается вариант с блокировкой (внимание):

lock(typeof(MySingleton)) { …

В этот момент я подумал: “однако…”. На вопрос: “А почему так?” кандидат ответить затруднился, сославшись на некую статью, читаную им когда-то на просторах интернета.

На первый взгляд идея выглядит подкупающе свеж: не нужно держать отдельный объект, по которому идет синхронизация, все просто и элегантно. Даже MSDN подтверждает, что объекты класса Type уникальны (т.е. в своем роде синглтоны):

A Type object that represents a type is unique; that is, two Type object references refer to the same object if and only if they represent the same type.

Там же сказано, что объекты этого класса являются безопасными с точки зрения многопоточного доступа – ну прямо идеальный кандидат для наших нужд. Но мысль о том, почему же я никогда раньше не слышал о таком варианте, так и не давала покоя. Ответ содержится в той же самой статье:

Note:

In multithreading scenarios, do not lock Type objects in order to synchronize access to static data. Other code, over which you have no control, might also lock your class type. This might result in a deadlock. Instead, synchronize access to static data by locking a private static object.

Так что “увы и ах” - не судьба (даром что выглядит элегантно). И не забываем о пользе чтения документации.

HTH,

AlexS

суббота, 31 октября 2009 г.

Dev magic и прочий ooops

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

DevMagic

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

Конечно же это не более чем баловство (от которого именно в этом конкретном случае можно, и даже нужно, было бы отказаться), но оно поднимает более важный вопрос об отношении к тому, что ты делаешь и как. На эту тему интересно высказался Jeff Atwood в своем посте о сообщениях об ошибках в Google Chrome, и я его поддерживаю - серьезное и вдумчивое отношение к продукту своей работы совершенно не обязательно должно выражаться в стандартизированной скуке всего и вся.

 

HTH,

AlexS

воскресенье, 18 октября 2009 г.

Объединение истории сообщений Skype

В последние полтора года я стал достаточно активно использовать Skype в качестве IM-клиента. Причем корпоративная политика отразилась и на персональных предпочтениях, ведь привычка - вторая натура. Тем более, что начиная с версии 4 Skype стал достаточно удобным в повседневном использовании (до этого я очень плохо с ним уживался).

В связи со сменой работы возник вопрос синхронизации истории сообщений между профилем, который я забрал с работы и личным профилем, который хранился дома. Ну или экспорт/импорт (в крайнем случае). Поясню: я использую одину и ту же учетную запись дома и на работе. Частенько получается, что начинаешь разговор с кем-то с работы, а заканчиваешь его дома. В результате история сообщений разорвана между двумя (а то и больше) компьютерами. Гугл мало чем смог помочь: есть плагин к Skype, который автоматом сохраняет все звонки и исообщения в гуглопочте - неплохо, конечно, но во-первых избыточно, во-вторых за деньги ($25 может и немного, но нет, спасибо). Еще на форуме Skype можно найти пару ссылок на программы, которые могут вытащить историю и выгрузить ее в txt или html. Тоже не совсем то. Пришлось разбираться самому.

На проверку все оказалось не так плохо (хотя и несколько запутано):

  • для хранения профиля (в том числе и истории звонков и сообщений) Skype использует базу данных SQLite - что хорошо (ибо эта СУБД открытая и для нее имеется .NET Provider)
  • схема не то чтобы сложная (16 таблиц), но о связях приходится догадываться (ибо внешние ключи не определены) и таблицы довольно "широкие" (до 40 полей)

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

image

Скачать его можно отсюда (выложил на SkyDrive на случае если кому еще пригодится).

Что следует иметь в виду, в ходе синхронизации:

  • профили должны быть от одной версии Skype (строго, ибо даже между v.4.0 и v.4.1 схема имеет некоторые различия)
  • профиль хранится в каталоге %user dir%\AppData\Roaming\Skype\<your skype name>\main.db
  • перед внесением изменений в целевой профиль, делается его резервная копия (так что в случае чего его можно вернуть)
  • свойства чата не обновляются - так что в списке "последних разговоров" можно не увидеть действительно последние разговоры, которые были добавлены в ходе синхронизации (но в базе они будут)
  • иногда происходит дублирование сообщений (не всегда) - я так и не смог надежно определить условия, при которых это происходит
  • в некоторых случаях в истории появляется сообщение "это сообщение было удалено из чата" - тоже не смог понять из-за чего (подозреваю что где-то не хватате связанной записи ... но вот где?)

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

HTH,

AlexS

четверг, 2 июля 2009 г.

Забавные картинки

Недавно разжился лицензией на Red-Gate SQL Toolbelt. Среди прочего, в него входит инструмент с незатейливым названием SQL Dependency Tracker. Название меня особенно не впечатлило, казалось: ну что нового можно сказать о взаимозависимостях объектов в базе данных (хоть sp_MSdependencies и является недокументированной, но используется довольно широко)? Но из любопытства решил посмотреть.

И вот оно: схема зависимостей между объектами в базе данных нашего проекта.

ClientDatabaseDiagram

Вроде бы банальная штука, зато как смотрится! Ну просто форменный computer art! И главное: одного взгляда достаточно для того, чтобы понять вокруг чего крутятся колеса системы.

За всеми "профессиональными"/минималистическими рабочими привычками вроде набора кода вслепую, консолей, запросов, комбинаций клавиш и прочего, как-то забывается великая сила толковой визуализации ... хорошо когда попадается что-то, о ней напоминающее :-)

HTH

AlexS

вторник, 11 ноября 2008 г.

svn:keywords и Unicode

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

Для тех, кому повезло использовать SVN для контроля за версиями (мне, как раз повезло), есть, казалось бы, простой и безболезненный способ - svn:keywords. Выставляем для sql (или любого другого) файла это свойство (см. руководство по TortoiseSVN здесь), и в файле используем ключевые слова, например так:

-- $Rev$

-- $Author$

CREATE PROCEDURE ...

и вот оно счастье...

Но не тут-то было! Проблема появляется оттуда, откуда ее никто не ждал. SQL Server Management Studio по-умолчанию сохраняет все файлы в формате Unicode. Причем не просто Unicode, а UTF-16 с byte-order mark. Точно также поступают и некоторые библиотеки (в частности SMO). А SVN помечает файлы в этой кодировке как имеющие mime-type: octet-stream/application - т.е. фактически считает их бинарными и, соответственно, никакой подстановки ключевых слов не делает. Обидно, тем более, что файл-то текстовый.

Поиск в Google дает мало толковых результатов: да, есть такая проблема, можно попытаться обойти (и то, сайт недоступен, можно почитать только из кэша Google). И только покопавшись еще глубже, можно обнаружить вот это: SVN поддерживает Unicode, но текстовым будет считаться только файл в кодировке UTF-8 (а не UTF-16 или UTF-32) - соответсвенно и ключевые слова будут работать только если файл в одной из однобайтовых кодировок (верх "крутизны" - та самая UTF-8).

Ну лучше уж так, чем никак. В результате:

  • быстренько сделал утилиту по переконвертированию файлов в кодировку UTF-8 (зачем пользоваться shareware, если самом сделать - дело 5ти минут? ;) ). Исполняемый файл здесь, исходник (если кому интересно) здесь.
  • сделал так, что dbbuilder теперь сохраняет все скрипты в кодировке UTF-8

Надеюсь кому-нибудь поможет.

AlexS