May 2008 - Posts

Stila policists

Labrīt, lasītāj!

Tev droši vien arī ir kolēģis, kurš izmanto ungāru pierakstu (es ceru, ka Tavā uzņēmumā tas nav standarts)? Kolēģis, kurš spītīgi neraksta kodā dokumentējošos komentārus? Uzņēmumā ir pieņemts standarts par to, kā jāizkārto kods, bet neviens to neievēro? Varbūt Tev pašam piemirstās, ka kodam ir jābūt saprotamam, nevis smilšu kastei kur Tu spēlējies ar dažādām valodas piedāvātām konstrukcijām? Man reizēm tā gadās.

Tomēr, turpinot labākajās reklāmu tradīcijās, varu teikt, ka Tavām raizēm ir pienācis gals. Microsoft partizāni ir devuši pasaulei rīku ar nosaukumu StyleCop, kas ir koda analīzes (FxCop) radinieks. Atšķirības no FxCop, kurš analizē sakompilētās asemblijas, StyleCop analizē pirmkodu un tā atbilstību noteiktiem noteikumiem. Pēc StyleCop uzinstalēšanas, Visual Studio Tools izvēlnē parādās jaunas iespējas "Run Source Analysis" un "Run Source Analysis (Rescan All)", tiesa, es tā steidzos jums paziņot jaunumus, ka vēl neesmu pietiekami ar šo rīku izspēlējies, lai varētu sīki pastāstīt par atšķirībām starp šiem punktiem.

tool_source_analysis

 

Pēc "Run Source Analysis" izvēles, StyleCop veic pirmkoda analīzi un līdzīgi, kā koda analīze, parāda neatbilstības noteikumiem. Vienīgā būtiskā atšķirība ir tas, ka noteikumu kodi sākas ar "SA", nevis "CA".

sa_results

 

Tā kā pie mums pieņemtās vadlīnijas nedaudz atšķiras no noteikumiem, kas pēc noklusējuma ir uzstādīti StyleCop, tad mani ieinteresēja konfigurācijas iespējas. Jāsaka, tās ir nedaudz noslēptas un man nācās jautāt Gūgles tantei. Izrādījās, ka pie konfigurācijas tiek no projekta konteksta izvēlnes, kā parādīts attēlā.

project_context_menu

 

Konfigurācijas iespēju gan nav pārāk daudz, bet kā var lasīt StyleCop komandas emuārā, tas esot nepieciešams, lai varētu sasniegt galveno mērķi - elegantu un viendabīgu kodu, kuru būtu viegli lasīt. Pēc ekrānšāviņa gan neizskatās, ka iespēju būtu maz :)

sa_settings

Serializācija un saraksta elementu dublēšanās

Sveiki! Sen neesmu rakstījis un varētu atrast vairākus objektīvus un subjektīvus iemeslus sevis attaisnošanai. Tomēr interesantāk par iemesliem ir pastāstīt par .NET platformas gotču, ar kuru nupat sastapos.

Iespējams, arī Tev ir gadījies sastapties ar situāciju, kad "pa drāti" ir jānodod objekts ar datiem. Šādos gadījumos izmanto XML serializāciju, kas ļauj objektu pārvērst par XML dokumentu un otrādi. Mēs ar kolēģiem tā arī darījām, līdz šodien uzdūrāmies problēmai, kad pie deserializācijas tiek dublētas vērtības List<T> tipa īpašībai. Pieļauju, ka programmētājiem problēmu uzskatāmāk demonstrēs kods.

using System.Collections.Generic;

namespace TestSerialization
{
public class ListContainer
{
private List<int> _lazyList;

public List<int> LazyList
{
get
{
if (_lazyList == null)
{
_lazyList = new List<int>();
_lazyList.Add(1);
_lazyList.Add(6);
}
return _lazyList;
}
set { _lazyList = value; }
}
}
}

Tātad, vienkārša klase, kas sastāv no vienas īpašības, kas tiek aizpildīta pirmajā pieprasījumā. Domāju, ka līdzīgu kodu visi ir rakstījuši. Lai pārbaudītu serializāciju, uzrakstīju vienumtestu:

[TestMethod]
public void LazyListTest()
{
ListContainer target = new ListContainer();
int expected = target.LazyList.Count;

XmlSerializer serializer = new XmlSerializer(target.GetType());
string xmlString;

using (StringWriter writer = new StringWriter(CultureInfo.InvariantCulture))
{
serializer.Serialize(writer, target);
xmlString = writer.ToString();
}

XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlString);

ListContainer result = (ListContainer)serializer.Deserialize(new XmlNodeReader(doc));
int actual = result.LazyList.Count;
Assert.AreEqual(expected, actual,"Item count differs");
}

Screenshot showing failed test

Palaižot testu tas izgāžas, jo deserializētajā objektā ir divreiz vairāk elementu, nekā oriģinālajā objektā. Kāpēc tā?

Loģiski liekas, ka saraksta deserializācijai vajadzētu notikt apmēram šādi:

  1. Izveidojam objektu ListContainer
  2. Izveidojam jaunu sarakstu ar LazyList elementu vērtībām
  3. Piešķiram jaunizveidoto sarakstu ListContainer.LazyList īpašībai

Tādā gadījumā augstāk parādītais tests darbotos. Tomēr sūrā dzīves realitāte rāda, ka tas tā nav, jo patiesībā deserializācija notiek pēc šāda scenārija:

  1. Izveidojam ListContainer objektu
  2. Pievienojam XMLā esošos elementus sarakstam, kuram piekļūstam izmantojot ListContainer.LazyList get aksesoru (nezinu, kā latviski pateikt)

Līdz ar to kļūst skaidrs, kāpēc elementi dublējas - sākumā tiek sapildītas noklusētās vērtības un pēc tam pievienotas XML dokumentā esošās.

Diagnoze ir noteikta, jāsāk ārstēt. Laikam vienkāršākais risinājums ir noslēpt sarakstu no serializātora izmantojot atribūtu XmlIgnore, un serializācijai izveidot jaunu īpašību - masīvu. Šo masīvu izmantos tikai lai apmanītu serializāciju un tam nebūs atsevišķa lauka, kurā glabāt vērtības. Tātad, uzlabotais kods:

using System.Collections.Generic;
using System.Xml.Serialization;

namespace TestSerialization
{
public class ListContainer
{
private List<int> _lazyList;

[
XmlIgnore]
public List<int> LazyList
{
get
{
if (_lazyList == null)
{
_lazyList = new List<int>();
_lazyList.Add(1);
_lazyList.Add(6);
}
return _lazyList;
}
set { _lazyList = value; }
}

[
XmlArray (ElementName = "LazyList")]
public int[] LazyListDisguisedAsArray
{
get
{
int[] items = new int[LazyList.Count];
LazyList.CopyTo(items, 0);
return items;
}
set
{
_lazyList = new List<int>(value);
}
}
}
}

Kā redzams, ir realizēts viss iepriekš aprakstītais un izveidots papildus slānis, lai serializācija domātu, ka saraksts glabājas masīvā. Lai ģenerētais XML dokuments izskatītos smukāk un tajā nebūtu redzamas neglītās implementācijas detaļas, LazyListDisguisedAsArray tiek maskēts, izmantojot XmlArray  atribūtu un norādītu elementa nosaukumu. Atkārtoti palaižot testu redzam, ka pacients ir izārstēts un var doties mājās.

Screenshot showing successful test