Send via SMS

Събота, Ноември 27, 2004

Универсалният боец

За пореден път се убеждавам, че разработчиците на компоненти в целта си да предоставят продукт, който да изпълнява функциите си в различен контекст (и с това да задоволява потребностите на клиента-програмист), изписват цяла каца подлежащ на оптимизация код.

Чудесен пример за това са компонентите на Macromedia Flash. Да вземем ScrollBar-а - със сигурност най-използвания от тях... Той копира 1:1 функционалността на системния scroll bar, което е супер. Заедно с нея е имплементирана и възможността да променяш външния му вид (така нареченото skinning), което отново е супер. Но това има и своята цена - изписан е бая код и поради същността на Macromedia Flash заедно с компонента се утежнява и крайния продукт - стига се до по-голям размер на SWF фаила, респективно по-дълъг момент на зареждане и т.н. Като добавим още няколко от компонентите на сцената и става "гомна с мед"(ударението пада на О-то). Спомняте ли си колко разработчици пуснаха олекотени версии на компонентите (т.нар. LightWeight версии)? Аз си спомням поне 3-ма(GhostWire например)... А да не говорим колко хора пък са пренаписвали компонентите за себе си или за фирмата без да го споделят или да предоставят сорс код!

Друг пример са typed DataSet класовете генерирани от Microsoft Visual Studio .NET или от XSD.exe... Ето задачка-закачка: Имам три бизнес единици - Hotel, Room и Price. Тъй като целта на бизнес процеса е резервация на хотелска стая, то единиците се навързват по следния логичен начин: обект от тип Hotel съдържа колекция от обекти тип Room, а от своя страна Room съдържа колекция от Price обекти - по един за всеки ден от престоя. Всяка бизнес единица разполага с нужния асортимент от членове-данни. Членовете-данни на Hotel и част от тези на Room се взимат от локална база данни, а останалото от хранилище, достъпът до което е скъп откъм ресурси. Като резултат от бизнес операция получавам колекция от Hotel обекти, която искам да кеширам в сесия, за да не се налага да правя ненужни и скъпи запитвания към гореспоменатото хранилище. Сесията ще е out-of-process и затова се налага сериализация(в последствие и десериализация). Тъй като процесът на сериализация/десериализация може да бъде доста скъп, когато трябва да се сериализират по-сложнички структури като Hotel с всичките му Room и Price колекции, реших да запазя и сериализирам само информацията, която не мога да взема от локалната база данни. Замислих се... чоплих си носа... чесах се по врата... ходих до тоалетна. И дойде то... Реших, да ползвам някакъв олекотен контейнер и че една typed DataSet е много подходяща за целта с всичките й там благини като вградена възможност за сериализация, а с помощта на ForeignKeyConstraint-ти и DataRelation щях да запазя взаимоотношението Hotel:Room и Room:Price. Проектирах XSD схема в XmlSpy за DataSet с три таблици (Hotels, Rooms, Prices) - по една за всяка структура и ги навързах с ForeignKeyConstraint-ти т.е връзката между Hotel и Room е общото поле HotelID в таблиците Hotels и Rooms, а връзката между Room и Price е общото поле RoomID в таблиците Rooms и Prices. В дефиниционната схема на DataSet-а указах, че ако HotelID e null искам системата да хвърля изключение.


<xs:element name="HotelID" type="xs:string" minOccurs="0" codegen:typedName="HotelID" codegen:nullValue="_throw"/>
Дотук всичко е чудесно. Сладкиши с мляко. Обаче когато опитах да генерирам DataSet клас с XSD.exe програмката, взеха да ми изкачат съобщения за грешки: схемата не била валидна. Валидирам в XmlSpy - няма проблем, всичко е тип-топ. Брях! От Google разбрах, че понякога, когато в DataSet дефиницията има релации между таблиците XSD.exe се дъни. Отворих схемата в Visual Studio .NET и си генерирах класа безпроблемно.

Реших да погледна що за код ми е генерирало студиото...

try {
    return ((string)(this[this.tableRooms.LocalHotelIDColumn]));
}
catch (InvalidCastException e) {
    throw new StrongTypingException("Cannot get value because it is DBNull.", e);
}
Баси... на всяко поле на което съм дал да хвърля изключение при стойност null имаше по един try-catch блок! А от Microsoft знаем, че try-catch блоковете не трябва да се ползват за подобни проверки т.е за щяло и нещяло... Ето защо промених кода на:
if( this[this.tableRooms.LocalHotelIDColumn] == System.Convert.DBNull )
{
    throw new NullReferenceException("Cannot get value because it is DBNull.");
}

В последствие махнах всякакъв код, който ми се стори излишен за целта. Размера на C# файла се смали от 70К на 43К и сега си имам една наистина лекичка DataSet. Тествах. Всичко си бачка пер-фект-но! Остава да направя един DAL компонент за нея и съм готов :)

И всичко това го пиша в 6 часа сутринта!? Ненормалник... Всъщност не съм единствения - U2 цяла нощ ми пяха A Man And A Woman. Кукуригууу!
*