Do ludzi, śmiało ku nowej przygodzie.

Pora wyjść z
piwnicy, a przynajmniej wystawić głowę.
Jakiś czas temu
miałem chęć napisać webowej aplikacji, której głównym założeniem była idea
ułatwienia współpracy pomiędzy ludźmi (oraz poznanie asp mvc). Chciałem
uproszczenia tego w jaki sposób ludzi znajdują ludzi, z którymi mogą działać
razem. Wyszedłem z założenia że są w nas chęci do pracy, ale brak pomysłu oraz
sytuacje odwrotne, gdy jest plan a brakuje rąk do pracy.  Może być też tak, że brakuje środków i nie
mam na myśli pieniędzy, tylko jakieś rekwizyty, czy specjalne umiejętności. (To
w wersji 2.0), czasem też ktoś ma pomysł, ale chciałby się poradzić, czy to co
wymyślił ma sens, albo czy już ktoś tego nie zrobił wcześniej. Właśnie dlatego
wszystkiego powstał “to be implemented” (wiem, że google miało coś
podobnego, o czym dowiedziałem się kilka dni temu).
Na portalu każde
może napisać czego szuka, albo co chciałby stworzyć, może być tak, że nie chce
pisać aplikacji, tylko szuka już gotowego rozwiązania mając nadzieję, że takie
istnieje. Albo chciałby napisać coś, ale nie wie spotka się to pozytywnym przyjęciem,
etc.
Idealnie sytuacja:
strona jest dla wszystkich ludzi, z dowolnej branży, nie tylko dla devów, ale
wiecie jak jest. Tak czy siak, jeśli ktoś znajdzie ktoś czas i chęci to proszę
o parę kliknięć na stronie i podzielenie się wrażeniami.
Technicznie:
Uwagi chętnie
przyjmuje tutaj, na bucket, na trello, czy w innej formie.
Przykłady pomysłów,
dla tych którym nie chciało się wejść na stronę:
1.Szukam aplikacji
która umożliwia mi włączenie / wyłączenie dodatkowego monitora. Np. WIN+1
włączenie/wyłączenie lewego monitora, WIN+2 włączenie/wyłączenie prawego, WIN+3
włączenie/wyłączenie obu zewnętrznych monitorów.
1a. Albo jakiś soft,
który potrafi przełączać także inne urządzenia, np. mam podpiętą kartę muzyczną
na USB i mam wewnętrzną, chciałbym łatwo się przełączać pomiędzy nimi, jakaś
kombinacja klawiszy mile widziana.
2. Wymyśliłem taką
webową aplikacje, do dzielenia się pomysłami, ale nie jestem grafikiem, w
związku z tym chętnie przyjmę pomoc w sprawie designu, ktoś chciałby pomów?
Strona jest już w sporej części zrobiona, ale byłoby miło gdyby wyglądała
lepiej.
3. Ukradzione od
kolegi Damiana: w VS chciałbym przy pomocy jakiejś magicznej kombinacji
klawiszy np. ALT+CTRL+(LEFT|RIGHT) móc obejrzeć historię zmian w pliku, a już
fantastycznie gdyby zasięg był tylko w ramach metody w której jestem.
4. Mam trochę czasu
po pracy i chętnie popisałbym coś w MVC, jeśli ktoś szuka programisty, to
chętnie dołączę do zespołu i pomogę. Mogę zaoferować swój czas i umiejętności w
asp mvc, trochę javascript, czy inne skrypty.

ps.
Aktualnie prowadzę mały refaktoring, także kolejne funkcjonalności czekają.
ps1.
Jeśli macie pomysły to także chętnie przyjmę
ps2.
Wiem już o ‘http://www.builditwith.me/’

Testowanie klas abstrakcyjnych

Pewnie każdy na swojej ścieżce programistycznej spotkał się z klasą abstrakcyjną. Wrzucamy tam kod, który zdaje się być domyślną implementacją pewnej grupy klas i szkoda nam kopiować tego zachowania do każdej z nich osobna. Skoro wszystkie zachowują się podobny sposób, czasem tylko dodając coś od siebie, to warto wykorzystać dziedziczenie i napisać mniej (DRY). Czyli prawdopodobny wydaje się taki scenariusz:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
max-height: 500px;
overflow: auto;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  namespace AbstractTesting
   2:  {
   3:      public abstract class AbstractClass
   4:      {
   5:          public virtual int Method(int input)
   6:          {
   7:              switch (input)
   8:              {
   9:                  case 1:
  10:                      return 1;
  11:                  case 2:
  12:                      return 4;
  13:                  case 3:
  14:                      return 8;
  15:                  default:
  16:                      return 0;
  17:              }
  18:          }
  19:      }
  20:   
  21:      public class ConcreteClass1 : AbstractClass
  22:      {
  23:          public override int Method(int input)
  24:          {
  25:              switch (input)
  26:              {
  27:                  case 1:
  28:                      return -1;
  29:                  default:
  30:                      return base.Method(input);
  31:              }
  32:   
  33:          }
  34:      }
  35:   
  36:      public class ConcreteClass2 : AbstractClass
  37:      {
  38:          public override int Method(int i)
  39:          {
  40:              switch (i)
  41:              {
  42:                  case 4:
  43:                      return 16;
  44:                  default:
  45:                      return base.Method(i);
  46:   
  47:              }
  48:          }
  49:      }
  50:   
  51:      public class ConcreteClass3 : AbstractClass
  52:      {
  53:      }
  54:   
  55:      public class ConcreteClass4 : AbstractClass
  56:      {
  57:          public override int Method(int i)
  58:          {
  59:              switch (i)
  60:              {
  61:                  case 1:
  62:                      return -1;
  63:                  case 2:
  64:                      return -2;
  65:                  case 3:
  66:                      return -3;
  67:                  default:
  68:                      return base.Method(i);
  69:              }
  70:          }
  71:      }
  72:   
  73:      public class ConcreteClass5 : AbstractClass
  74:      {
  75:          public override int Method(int i)
  76:          {
  77:              switch (i)
  78:              {
  79:                  case 1:
  80:                      return -1;
  81:                  case 2:
  82:                      return -4;
  83:                  case 3:
  84:                      return -8;
  85:                  default:
  86:                      return 1;
  87:              }
  88:          }
  89:      }
  90:  }

Celowo pomijam dodatkowe metody w klasie bazowej i klasach pochodnych, nie na tym chce się tutaj skupić.
Teraz można zadać sobie pytanie, jak mam to przetestować? Przynajmniej ja je sobie zadawałem. Czy testować w każdej klasie tylko to co wnosi ona nowego, a dla klasy klasy bazowej napisać specjalnego mocka, który umożliwi stworzenie jej instancji, a następnie przetestowanie wspólnego kodu. Czy też pominąć w testach klasę bazową, ale dla każdej z klas dziedziczących pisać powtarzający się test, sprawdzający wszystkie warunki switcha? Jest też trzecie rozwiązanie tego problemu, sugerując się podpowiedziami kolegów z pracy, trzeba być wystarczająco odważnym, wrzucić na produkcję i zobaczyć czy zabangla. Ale tego przykładu nie będę tutaj analizować.

Zamiast rozwodzić się nad tym, co jest lepsze, a co gorsze, sprawdzimy jak to wychodzi w praniu. Pierwsze podejście, napisać mniej testów:

   1:  [TestFixture]
   2:  public class AbstractTestClass
   3:  {
   4:      [Test]
   5:      public void t0_1()
   6:      {
   7:          var sut = new MockAbstractClass();
   8:          Assert.AreEqual(1, sut.Method(1));
   9:      }
  10:   
  11:      [Test]
  12:      public void t1_4()
  13:      {
  14:          var sut = new MockAbstractClass();
  15:          Assert.AreEqual(4, sut.Method(2));
  16:      }
  17:   
  18:      [Test]
  19:      public void t3_8()
  20:      {
  21:          var sut = new MockAbstractClass();
  22:          Assert.AreEqual(8, sut.Method(3));
  23:      }
  24:   
  25:      [Test]
  26:      public void t4_0()
  27:      {
  28:          var sut = new MockAbstractClass();
  29:          Assert.AreEqual(0, sut.Method(4));
  30:      }
  31:  }
  32:   
  33:  public class MockAbstractClass : AbstractClass
  34:  {
  35:  }
  36:   
  37:  [TestFixture]
  38:  public class TestC1
  39:  {
  40:      [Test]
  41:      public void t0_1()
  42:      {
  43:          var sut = new ConcreteClass1();
  44:          Assert.AreEqual(-1, sut.Method(1));
  45:      }
  46:  }
  47:   
  48:  [TestFixture]
  49:  public class TestC2
  50:  {
  51:      [Test]
  52:      public void t0_16()
  53:      {
  54:          var sut = new ConcreteClass2();
  55:          Assert.AreEqual(16, sut.Method(4));
  56:      }
  57:  }
  58:   
  59:  [TestFixture]
  60:  public class TestC4
  61:  {
  62:      [Test]
  63:      public void t1_m1()
  64:      {
  65:          var sut = new ConcreteClass4();
  66:          Assert.AreEqual(-1, sut.Method(1));
  67:      }
  68:   
  69:      [Test]
  70:      public void t2_m2()
  71:      {
  72:          var sut = new ConcreteClass4();
  73:          Assert.AreEqual(-2, sut.Method(2));
  74:      }
  75:   
  76:      [Test]
  77:      public void t3_m3()
  78:      {
  79:          var sut = new ConcreteClass4();
  80:          Assert.AreEqual(-3, sut.Method(3));
  81:      }
  82:   
  83:      [Test]
  84:      public void t4_0()
  85:      {
  86:          var sut = new ConcreteClass4();
  87:          Assert.AreEqual(0, sut.Method(4));
  88:      }
  89:  }
  90:   
  91:  [TestFixture]
  92:  public class TestC5
  93:  {
  94:      [Test]
  95:      public void t1_m1()
  96:      {
  97:          var sut = new ConcreteClass5();
  98:          Assert.AreEqual(-1, sut.Method(1));
  99:      }
 100:   
 101:      [Test]
 102:      public void t2_m2()
 103:      {
 104:          var sut = new ConcreteClass5();
 105:          Assert.AreEqual(-4, sut.Method(2));
 106:      }
 107:   
 108:      [Test]
 109:      public void t3_m3()
 110:      {
 111:          var sut = new ConcreteClass5();
 112:          Assert.AreEqual(-8, sut.Method(3));
 113:      }
 114:   
 115:      [Test]
 116:      public void t4_0()
 117:      {
 118:          var sut = new ConcreteClass5();
 119:          Assert.AreEqual(1, sut.Method(16));
 120:      }
 121:  }

Pokrycie kodu powinno być 100%. Wszystkie test powinny przejść na zielono. Skoro wszystko działa dobrze, to po co się zastanawiać na innym rozwiązaniem? A to dlatego, drogie dzieciaczki, że jeśli wprowadzicie teraz jakąś zmianę w klasie Abstract, to wszystkie testy klas pochodnych nadal będą się świecić na zielono. Pomimo tego, że zmieni się ich zachowanie. W ten sposób nie dowiecie się (z testów), że np. klasa Concret3 od teraz zachowuje się zupełnie inaczej, inne korzystające z domyślnego zachowania też oszalały. Ktoś może powiedzieć, że to poprawne zachowanie, skoro dziedziczysz po czymś/kimś i zdajesz się na jego domyślną implementacje to zmieniasz się razem z nią jak chorągiewka na wietrze. Tylko czy o to chodzi? Czy klasy nie powinny mieć własnego rozumku? Klient korzysta z implementacji klasy 1,2,3,4 czy 5 a nie klasy podstawowej. W takiej sytuacji każda klasa, a w zasadzie test dla każdej z klas powinien wykryć zmiany w jej zachowaniu, poinformować o tym, że zostało ono zmienione i od teraz nie spełnia wcześniej przyjętego rozumowania. Moim zdaniem tak powinny wyglądać testy:

   1:  namespace AbstractTesting
   2:  {
   3:      [TestFixture]
   4:      public class ConcreteClass1Test
   5:      {
   6:          [Test]
   7:          public void T001_M1()
   8:          {
   9:              var sut = new ConcreteClass1();
  10:   
  11:              var result = sut.Method(1);
  12:   
  13:              Assert.AreEqual(-1, result);
  14:          }
  15:   
  16:          [Test]
  17:          public void T002_M2()
  18:          {
  19:              var sut = new ConcreteClass1();
  20:   
  21:              var result = sut.Method(2);
  22:   
  23:              Assert.AreEqual(4, result);
  24:          }
  25:   
  26:          [Test]
  27:          public void T003_M3()
  28:          {
  29:              var sut = new ConcreteClass1();
  30:   
  31:              var result = sut.Method(3);
  32:   
  33:              Assert.AreEqual(8, result);
  34:          }
  35:   
  36:          [Test]
  37:          public void T004_M155()
  38:          {
  39:              var sut = new ConcreteClass1();
  40:   
  41:              var result = sut.Method(155);
  42:   
  43:              Assert.AreEqual(0, result);
  44:          }
  45:      }
  46:   
  47:      [TestFixture]
  48:      public class ConcreteClass2Test
  49:      {
  50:          [Test]
  51:          public void T001_M1()
  52:          {
  53:              var sut = new ConcreteClass2();
  54:   
  55:              var result = sut.Method(1);
  56:   
  57:              Assert.AreEqual(1, result);
  58:          }
  59:   
  60:          [Test]
  61:          public void T002_M2()
  62:          {
  63:              var sut = new ConcreteClass2();
  64:   
  65:              var result = sut.Method(2);
  66:   
  67:              Assert.AreEqual(4, result);
  68:          }
  69:   
  70:          [Test]
  71:          public void T003_M3()
  72:          {
  73:              var sut = new ConcreteClass2();
  74:   
  75:              var result = sut.Method(3);
  76:   
  77:              Assert.AreEqual(8, result);
  78:          }
  79:   
  80:          [Test]
  81:          public void T004_M4()
  82:          {
  83:              var sut = new ConcreteClass2();
  84:   
  85:              var result = sut.Method(4);
  86:   
  87:              Assert.AreEqual(16, result);
  88:          }
  89:   
  90:          [Test]
  91:          public void T005_M55()
  92:          {
  93:              var sut = new ConcreteClass2();
  94:   
  95:              var result = sut.Method(55);
  96:   
  97:              Assert.AreEqual(0, result);
  98:          }
  99:      }
 100:   
 101:      [TestFixture]
 102:      public class ConcreteClass3Test
 103:      {
 104:          [Test]
 105:          public void T001_M1()
 106:          {
 107:              var sut = new ConcreteClass3();
 108:   
 109:              var result = sut.Method(1);
 110:   
 111:              Assert.AreEqual(1, result);
 112:          }
 113:   
 114:          [Test]
 115:          public void T002_M2()
 116:          {
 117:              var sut = new ConcreteClass3();
 118:   
 119:              var result = sut.Method(2);
 120:   
 121:              Assert.AreEqual(4, result);
 122:          }
 123:   
 124:          [Test]
 125:          public void T003_M3()
 126:          {
 127:              var sut = new ConcreteClass3();
 128:   
 129:              var result = sut.Method(3);
 130:   
 131:              Assert.AreEqual(8, result);
 132:          }
 133:   
 134:          [Test]
 135:          public void T004_M55()
 136:          {
 137:              var sut = new ConcreteClass3();
 138:   
 139:              var result = sut.Method(55);
 140:   
 141:              Assert.AreEqual(0, result);
 142:          }
 143:      }
 144:   
 145:      [TestFixture]
 146:      public class ConcreteClass4Test
 147:      {
 148:          [Test]
 149:          public void T001_M1()
 150:          {
 151:              var sut = new ConcreteClass4();
 152:   
 153:              var result = sut.Method(1);
 154:   
 155:              Assert.AreEqual(-1, result);
 156:          }
 157:   
 158:          [Test]
 159:          public void T002_M2()
 160:          {
 161:              var sut = new ConcreteClass4();
 162:   
 163:              var result = sut.Method(2);
 164:   
 165:              Assert.AreEqual(-2, result);
 166:          }
 167:   
 168:          [Test]
 169:          public void T003_M3()
 170:          {
 171:              var sut = new ConcreteClass4();
 172:   
 173:              var result = sut.Method(3);
 174:   
 175:              Assert.AreEqual(-3, result);
 176:          }
 177:   
 178:          [Test]
 179:          public void T004_M55()
 180:          {
 181:              var sut = new ConcreteClass4();
 182:   
 183:              var result = sut.Method(55);
 184:   
 185:              Assert.AreEqual(0, result);
 186:          }
 187:      }
 188:   
 189:      [TestFixture]
 190:      public class ConcreteClass5Test
 191:      {
 192:          [Test]
 193:          public void T001_M1()
 194:          {
 195:              var sut = new ConcreteClass5();
 196:   
 197:              var result = sut.Method(1);
 198:   
 199:              Assert.AreEqual(-1, result);
 200:          }
 201:   
 202:          [Test]
 203:          public void T002_M2()
 204:          {
 205:              var sut = new ConcreteClass5();
 206:   
 207:              var result = sut.Method(2);
 208:   
 209:              Assert.AreEqual(-4, result);
 210:          }
 211:   
 212:          [Test]
 213:          public void T003_M3()
 214:          {
 215:              var sut = new ConcreteClass5();
 216:   
 217:              var result = sut.Method(3);
 218:   
 219:              Assert.AreEqual(-8, result);
 220:          }
 221:   
 222:          [Test]
 223:          public void T004_M55()
 224:          {
 225:              var sut = new ConcreteClass5();
 226:   
 227:              var result = sut.Method(55);
 228:   
 229:              Assert.AreEqual(1, result);
 230:          }
 231:      }
 232:  }

W tym przypadku gdy zmieni się zachowanie klasy podstawowej, wszystkie testy klas korzystających z domyślnej implementacji, automatycznie zgłoszą niezadowolenie ze zmian.
Nic za darmo, w drugim przypadku trzeba napisać więcej testów, a czasem nawet go powtórzyć. Oczywiście można też pozbyć się tego switcha z kodu, albo wyjechać do Niemiec i zbierać ogórki – byle by tylko nie pisać testów. Tutaj miałem na celu pokazanie tego, jak zachowują się oba rozwiązania i które z nich, moim zdaniem jest tym słuszniejszym.

Jak zwykle pozostaje otwarty na krytykę i zastrzegam sobie prawo do popełniania błędów.

Własna konfiguracja w app.config

Jeśli to czytacie, to znaczy że zdążyłem jeszcze z wpisem przed nowym rokiem.  Udało mi się napisać jeszcze jednego posta przed nowym rokiem – jestem królem świata!

Dzisiaj o wykorzystaniu app.config dla własnych celów. Skąd taki pomysł? Otóż jak każdy pewnie ma lub miał w życiu, czasem mieć taką potrzebę aby zmieniać zachowanie aplikacji inaczej niż przez jej przekompilowanie. Wtedy właśnie z pomocą przychodzą pliki .txt, .xml, .config czy dawno dawno temu .ini. Można napisać własne system do obsługi konfiguracji, własne klasy serializujące i deserializujące, można pisać cudne rzeczy. Ale po co, skoro można skorzystać z tego co oferuje app.config. Też tak pomyślałem i rozpętało się piekło. Bo nie da się ot tak dowalić do app.configu swojego kawałka xmla. Trzba to wszystko dokładnie, ale to dokładnie opisać. O ile wytrzymacie ten wpis, to zaraz wszystko wyjaśnię.

Zacznę od początku na prostych obiektach. Np. gdybym chciał w pliku zdefiniować wygląd jakiegoś prostego obiektu, np.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:      <MyConfiguration>
   4:          <Rectangle Color="Red" Width="10" Height="5" Fill="False"/>
   5:      </MyConfiguration>
   6:  </configuration>

Wiecie co się stanie przy próbie uruchomienia takiego prostego kodu jak ten:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  static void Main(string[] args)
   2:  {
   3:      var cfg = ConfigurationManager.AppSettings;
   4:  }

Aby konfiguracja zadziałała, trzeba dodać informację do app.cofig o naszej sekcji konfiguracyjnej.

   1:  <configSections>
   2:      <section name="MyConfiguration" type="AppConfigExample.MyConfiguration, AppConfigExample"/>
   3:  </configSections>

W części type podajemy gdzie znajdzie się klasa opisująca naszą konfigurację. Wpis musi zawierać pełny namespace, następnie należy podać także assembly w który się ona znajduje. Tutaj wszystko mam w jednym assembly.
Skoro trzeba podać klasę to trzeba ją także gdzieś i jakoś zdefiniować i podziedziczyć:

   1:  public class MyConfiguration : ConfigurationSection
   2:  {
   3:  }

A następnie spróbować się do niej dostać:

   1:  var cfg = ConfigurationManager.GetSection("MyConfiguration");

Niestety to nadal za mało (powoli buduje nastrój grozy):

Lubię .net i jego wyjątki za czytelne błędy i opisy problemów, lubię też google czy SO za pomoc w rozwiązywaniu tych wyjątków. Chwila szukania i już wiem co należy dodać, aby móc odczytać mój kwadracik z konfiguracji:

   1:  public class MyConfiguration : ConfigurationSection
   2:  {
   3:      [ConfigurationProperty("Rectangle")]
   4:      public Rectangle Rectangle
   5:      {
   6:          get { return ((Rectangle)(base["Rectangle"])); }
   7:      }
   8:  }

Skoro wspominamy tutaj o klasie Rectangle to należy ją także jakoś zdefiniować i znowu dziedziczenie:

   1:  public class Rectangle: ConfigurationElement
   2:  {
   3:      [ConfigurationProperty("Color")]
   4:      public ConsoleColor Color
   5:      {
   6:          get { return (ConsoleColor)base["Color"]; }
   7:          set { base["Color"] = value; }
   8:      }
   9:   
  10:      [ConfigurationProperty("Width")]
  11:      public int Width
  12:      {
  13:          get { return (int)base["Width"]; }
  14:          set { base["Width"] = value; }
  15:      }
  16:   
  17:      [ConfigurationProperty("Height")]
  18:      public int Height
  19:      {
  20:          get { return (int)base["Height"]; }
  21:          set { base["Height"] = value; }
  22:      }
  23:   
  24:      [ConfigurationProperty("Fill")]
  25:      public bool Fill
  26:      {
  27:          get { return (bool)base["Fill"]; }
  28:          set { base["Fill"] = value; }
  29:      }
  30:  }

Gdy mamy taki kod możemy spokojnie spróbować swoich sił i uruchomić coś takiego:

   1:  var cfg = ConfigurationManager.GetSection("MyConfiguration") as MyConfiguration;
   2:   
   3:  var oc = Console.ForegroundColor;
   4:  Console.ForegroundColor = cfg.Rectangle.Color;
   5:   
   6:  Console.WriteLine("Width:{0}, Height:{1}, Filled:{2}", cfg.Rectangle.Width, cfg.Rectangle.Height,
   7:                    cfg.Rectangle.Fill);
   8:   
   9:  Console.ForegroundColor = oc;
Co powinno dać taki wynik:

Chyba nie macie dosyć? Nie warto zaprzestawać na tylko jednym takim obiekcie, skoro na świecie jest tyle pięknych prostokątów, załóżmy taki przypadek:

   1:  <MyConfiguration>
   2:      <Rectangle Color="Red" Width="10" Height="5" Fill="False"/>
   3:      <Rectangle Color="Green" Width="12" Height="6" Fill="False"/>
   4:      <Rectangle Color="Black" Width="14" Height="3" Fill="True"/>
   5:      <Rectangle Color="White" Width="15" Height="2" Fill="True"/>
   6:      <Rectangle Color="Gray" Width="16" Height="10" Fill="False"/>
   7:  </MyConfiguration>

Co się stanie po uruchomieniu aplikacji z taką konfiguracją?

No racja racja, przecież to kolekcja prostokątów, powinna wyglądać inaczej, bo skąd biedny komputerek może wiedzieć o który obiekt dokładnie nam chodzi.

Macie tak czasami, że wy wiecie że się nie uda czegoś zrobić, a komputer jeszcze walczy? Np. klikniecie przez przypadek na napędzie CD, tam nie ma płyty, wszyscy to wiedzą, ale nie, 15 sekund trzeba czekać zanim komputerek też się dowie. Sieć nie działa, wszyscy wiedzą, a komp walczy. Najgorsze jest to że się nie uczy. Skoro nie było płyty, i napęd się nie wysunął i nie wsunął, to płyty nadal tam nie będzie. Ale kliknij znowu na ikonę z CD i znowu czekasz, bo może jednak jest. Ech takie szybkie i takie głupie.

Wracając do naszego problemu, jak zrobić żeby ten biedny komputerek poradził sobie z wieloma prostokącikami:

   1:  <Rectangles>
   2:      <Rectangle Color="Red" Width="10" Height="5" Fill="False"/>
   3:      <Rectangle Color="Green" Width="12" Height="6" Fill="False"/>
   4:      <Rectangle Color="Black" Width="14" Height="3" Fill="True"/>
   5:      <Rectangle Color="White" Width="15" Height="2" Fill="True"/>
   6:      <Rectangle Color="Gray" Width="16" Height="10" Fill="False"/>
   7:  </Rectangles>

Ale wtedy znowu będzie marudzić, że unrecognized element i że “Rectangles” to ten unrecognized. Trzeba więc zaktualizować informację z klasie MyConfiguration na taki:

   1:  public class MyConfiguration : ConfigurationSection
   2:  {
   3:      [ConfigurationProperty("Rectangles")]
   4:      public Rectangles Rectangles
   5:      {
   6:          get { return ((Rectangles)(base["Rectangles"])); }
   7:      }
   8:  }

Oraz zdefiniować odpowiednio klasę Rectangles. Możecie spróbować samodzielnie ją zdefiniować i zobaczyć jaki poleci wyjątek, lub też przeczytać dalej i zobaczyć jak robią to inni:

   1:  [ConfigurationCollection(typeof(Rectangle))]
   2:  public class Rectangles : ConfigurationElementCollection
   3:  {
   4:      protected override ConfigurationElement CreateNewElement()
   5:      {
   6:          return new Rectangle();
   7:      }
   8:   
   9:      protected override object GetElementKey(ConfigurationElement element)
  10:      {
  11:          return element.GetHashCode();
  12:      }
  13:  }

Linijka 11 nie wygląda najlepiej. A poza tym może spowodować błąd (podczas domyślnej implementacji GetHashCode), aby go uniknąć należy napisać własne GetHashCode w klasie Rectangle lub dołożyć jakiś klucz, co później ułatwi dostęp do wybranych elementów z kolekcji.
Klasę Rectangle należy uaktualnić o następujące pole:

   1:  [ConfigurationProperty("Id", IsKey = true, IsRequired = true)]
   2:  public int Id
   3:  {
   4:      get { return (int)base["Id"]; }
   5:      set { base["Id"] = value; }
   6:  }

Natomiast metoda GetElementKey wyglądać teraz będzie tak:

   1:  protected override object GetElementKey(ConfigurationElement element)
   2:  {
   3:      return ((Rectangle)element).Name;
   4:  }

Kolekcja elementów w app.config wygląda teraz tak:

   1:  <Rectangles>
   2:      <add Id="1" Color="Red" Width="10" Height="5" Fill="False"/>
   3:      <add Id="2" Color="Green" Width="12" Height="6" Fill="False"/>
   4:      <add Id="3" Color="Black" Width="14" Height="3" Fill="True"/>
   5:      <add Id="4" Color="White" Width="15" Height="2" Fill="True"/>
   6:      <add Id="5" Color="Gray" Width="16" Height="10" Fill="False"/>
   7:  </Rectangles>

I znowu coś nie cool, to idiotyczne add – bleh, brzydal. Gdzie się podziało ładne Rectangle? Ponownie zachęcam do sprawdzenia wyjątku jaki leci, gdy zamiast add będzie Rectangle. Na szczęście i to da się napisać lepiej, wystarczy ponownie zaktualizować informację o klasie Rectangles, brakuje tam małej informacji w atrybutach klasy:

   1:  [ConfigurationCollection(typeof(Rectangle), AddItemName = "Rectangle")]
   2:  public class Rectangles : ConfigurationElementCollection { /* unicorns here */ }

Od teraz znowu zamiast add możemy stosować Rectangle i uruchomić taki kod:

   1:  var cfg = ConfigurationManager.GetSection("MyConfiguration") as MyConfiguration;
   2:  var oc = Console.ForegroundColor;
   3:   
   4:  foreach (Rectangle rectangle in cfg.Rectangles)
   5:  {
   6:      Console.ForegroundColor = rectangle.Color;
   7:      Console.WriteLine("Width:{0}, Height:{1}, Filled:{2}", rectangle.Width, rectangle.Height,rectangle.Fill);
   8:      Console.ForegroundColor = oc;
   9:  }

Co według mojego działania da taki wynik:

A ja wybrać ten jeden w swoim rodzaju Rectangl? Skoro mamy jego Id to powinniśmy z niego jakoś skorzystać. Może linq? Potrzebujemy tylko IEnumerable w klasie Rectangles i będzie działać, a więc:

   1:  [ConfigurationCollection(typeof(Rectangle), AddItemName = "Rectangle")]
   2:  public class Rectangles: ConfigurationElementCollection, IEnumerable<Rectangle>
   3:  {
   4:      { /* dragons here */ }
   5:      public new IEnumerator<Rectangle> GetEnumerator()
   6:      {
   7:          int count = base.Count;
   8:          for (int i = 0; i < count; i++)
   9:          {
  10:              yield return base.BaseGet(i) as Rectangle;
  11:          }
  12:      }
  13:  }

I można już napisać coś takiego w kodzie:

   1:  Rectangle rect = cfg.Rectangles.Single(r => r.Id == 2);
   2:   
   3:  Console.ForegroundColor = rect.Color;
   4:  Console.WriteLine("Width:{0}, Height:{1}, Filled:{2}", rect.Width, rect.Height, rect.Fill);

Od teraz wszyscy mogą zazdrościć wam wiedzy o tym, jak lepiej wykorzystywać app.config w swoich aplikacjach.

A teraz cały kod z wpisu:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Configuration;
   4:  using System.Linq;
   5:  using System.Text;
   6:   
   7:  namespace AppConfigExample
   8:  {
   9:      class Program
  10:      {
  11:          static void Main(string[] args)
  12:          {
  13:              var cfg = ConfigurationManager.GetSection("MyConfiguration") as MyConfiguration;
  14:              var oc = Console.ForegroundColor;
  15:   
  16:              foreach (Rectangle rectangle in cfg.Rectangles)
  17:              {
  18:                  Console.ForegroundColor = rectangle.Color;
  19:                  Console.WriteLine("Width:{0}, Height:{1}, Filled:{2}", rectangle.Width, rectangle.Height, rectangle.Fill);
  20:              }
  21:   
  22:              Console.WriteLine("****************************************");
  23:              Rectangle rect = cfg.Rectangles.Single(r => r.Id == 2);
  24:   
  25:              Console.ForegroundColor = rect.Color;
  26:              Console.WriteLine("Width:{0}, Height:{1}, Filled:{2}", rect.Width, rect.Height, rect.Fill);
  27:   
  28:   
  29:   
  30:              Console.ForegroundColor = oc;
  31:          }
  32:      }
  33:   
  34:   
  35:      public class MyConfiguration : ConfigurationSection
  36:      {
  37:          [ConfigurationProperty("Rectangles")]
  38:          public Rectangles Rectangles
  39:          {
  40:              get { return ((Rectangles)(base["Rectangles"])); }
  41:          }
  42:      }
  43:   
  44:      [ConfigurationCollection(typeof(Rectangle), AddItemName = "Rectangle")]
  45:      public class Rectangles : ConfigurationElementCollection, IEnumerable<Rectangle>
  46:      {
  47:          protected override ConfigurationElement CreateNewElement()
  48:          {
  49:              return new Rectangle();
  50:          }
  51:   
  52:          protected override object GetElementKey(ConfigurationElement element)
  53:          {
  54:              return ((Rectangle)element).Id;
  55:          }
  56:   
  57:          public new IEnumerator<Rectangle> GetEnumerator()
  58:          {
  59:              int count = base.Count;
  60:              for (int i = 0; i < count; i++)
  61:              {
  62:                  yield return base.BaseGet(i) as Rectangle;
  63:              }
  64:          }
  65:      }
  66:   
  67:      public class Rectangle : ConfigurationElement
  68:      {
  69:          [ConfigurationProperty("Id", IsKey = true, IsRequired = true)]
  70:          public int Id
  71:          {
  72:              get { return (int)base["Id"]; }
  73:              set { base["Id"] = value; }
  74:          }
  75:   
  76:          [ConfigurationProperty("Color")]
  77:          public ConsoleColor Color
  78:          {
  79:              get { return (ConsoleColor)base["Color"]; }
  80:              set { base["Color"] = value; }
  81:          }
  82:   
  83:          [ConfigurationProperty("Width")]
  84:          public int Width
  85:          {
  86:              get { return (int)base["Width"]; }
  87:              set { base["Width"] = value; }
  88:          }
  89:   
  90:          [ConfigurationProperty("Height")]
  91:          public int Height
  92:          {
  93:              get { return (int)base["Height"]; }
  94:              set { base["Height"] = value; }
  95:          }
  96:   
  97:          [ConfigurationProperty("Fill")]
  98:          public bool Fill
  99:          {
 100:              get { return (bool)base["Fill"]; }
 101:              set { base["Fill"] = value; }
 102:          }
 103:      }
 104:  }

Oraz app.config:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:      <configSections>
   4:          <section name="MyConfiguration" type="AppConfigExample.MyConfiguration, AppConfigExample"/>
   5:      </configSections>
   6:      <MyConfiguration>
   7:          <Rectangles>
   8:              <Rectangle Id="1" Color="Red" Width="10" Height="5" Fill="False"/>
   9:              <Rectangle Id="2" Color="Green" Width="12" Height="6" Fill="False"/>
  10:              <Rectangle Id="3" Color="Black" Width="14" Height="3" Fill="True"/>
  11:              <Rectangle Id="4" Color="White" Width="15" Height="2" Fill="True"/>
  12:              <Rectangle Id="5" Color="Gray" Width="16" Height="10" Fill="False"/>
  13:          </Rectangles>
  14:      </MyConfiguration>
  15:  </configuration>

To by było na tyle, bierzcie i korzystajcie z tego, bo to dobre jest.
Pozostaje mi tylko życzyć dobrego w nadchodzącym nowym roku!
Tej!

Czym się różni właściwość klasy od pola klasy?

Wracając jeszcze do filozofowania o kodzie, dziś trochę o cechach klas, a dokładniej: pola i właściwości. Zanim wymyślono idee właściwości dostęp do pól odbywał się na dwa sposoby. Pole w klasie (np. name) mogło być publiczne i każdy miotał nim jak szatan, druga opcja to dostęp kontrolowany przez parę metod typu GetName i SetName. Umożliwiały one kontrolowanie tego kto i na jakich zasadach może korzystać z cech wewnętrznych klasy.
Później, aby pominąć pisanie GetName/SetName, wymyślono właściwości, które w sprytny sposób, miały pozbyć się z kodu klienta getów i setów, czyniąc kod bardziej czytelnym. Jednocześnie pozostawić możliwość kontrolowanie kto i jak ma dostęp do pola, poprzez “ukrytą” implementację tychże get i set. Wygląda więc to tak, że klient korzysta z naszego pola w klasie, które naprawdę jest jest właściwością opakowującą to pole. Zresztą każdy wie jak wygląda i działa właściwość w C#.
Teraz czas na właściwą filozofię, czym się różni właściwość klasy od pola w klasie?

  1. Właściwość może zostać tak zaimplementowana, aby była tylko do zapisu lub tylko do odczytu. Pole w klasie jest najczęściej implementowane czytalne/pisalne (uwielbiam nowo mowę). Czasem tworzy się stałe. (ot czasami tak trzeba).
  2. Właściwość podczas zapisu lub odczytu może nas poczęstować wybornym wyjątkiem, ta sama operacja wykonana na polu nigdy.
  3. Właściwość nie może zostać przekazana jako ref lub out argument metody, pole klasy tak.
  4. Czas wykonania właściwości może być czasem bardzo długi, np. podczas wykonywania synchronizacji wątków, leniwej inicjalizacji właściwości, etc. Dostęp do pola zawsze jest operacją natychmiastową.
  5. Dostęp do właściwości może powodować efekty uboczne, np. w cache, każdy dostęp może zmniejszać czas życia keszu. Dostęp do pola nigdy nie ma takiej możliwości.
  6. Odczytanie właściwości klasy czasem może się wymagać zwiększania zapotrzebowania na pamięć RAM, potrzebę na przeliczenie pewnych zależności, czy wykonanie dodatkowych operacji, może także zwrócić nie pełny wynik. Dostęp do pola, nie wymaga dodatkowych środków, oraz zwraca oryginalną zawartość tego pola.

Nasuwa się pytanie, czy wy będąc klientem klasy X, która oferuje dostęp do właściwości, oczekujecie, że dostęp do właściwości Name będzie:

  • trwał strasznie długo
  • każdy operacja odczytu zmienia, zwraca inną wartość
  • dostaniecie tylko spółgłoski
  • zanim dostaniecie wynik, zużycie pamięci wzrośnie o kilkanaście mega

Takich rzeczy można oczekiwać od metod, to one powinny robić coś skomplikowanego, dostęp do pola powinien być możliwie szybki i prosty. Warto o tym pamiętać, pisząc swój kawałek kodu. Może trzeba wrócić do poczciwego GetName(), GetNameConsonantsOnly(),
GetNameVowelsOnly(), GetNameUsing5MbRamMore() ?

Ot takie tam żywcem zerżnięte informacje z książki którą czytam i pomyślałem, że warto o tym napisać.

ps.
DateTime.Now 😉

Przyjemne funkcje

Jak przyjmować i jak zwracać kulturalnie – zastanawialiście się kiedyś na tym? Taki programistyczny savoir-vivre. Jak to zrobić, żeby mi (programiście) było wygodnie, a jednocześnie uszcześliwić przyszłego użytkownika API które tworzymy? Przecież to może być właśnie ja (ja piszący tego bloga)!

Sprzedam wam dwie proste reguły (na bank są inne o których nie wiem), które warto zapamiętać lub przynajmniej sie nad nimi zastanowić.

Przyjmowany parametr powinien być możliwie wysoko w drzewie dziedziczenia, możliwie najogólnieszy, z którego możemy skorzystać. Jak zawsze będę się wspierać na przejaskrawionych przykładach, zupełnie oderwanych od rzeczywistości:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  int NotSoNiceSumFunction(List<int> arg)
   2:  {
   3:      int result = 0;
   4:      foreach (var i in arg)
   5:      {
   6:          result += i;
   7:      }
   8:      return result;
   9:  }

Oraz drugi przyklad:

   1:  int NiceSumFunction(IEnumerable<int> arg)
   2:  {
   3:      int result = 0;
   4:      foreach (var i in arg)
   5:      {
   6:          result += i;
   7:      }
   8:      return result;
   9:  }

Zasadniczo nie różnią się one od siebie poza typem arugumentu. Za pierwszym razem określamy go bardzo dokładnie: pracujemy tylko z listą intów. Podczas gdy w seksownym ciele funkcji, nie ma nigdzie zachowania, które mogłoby uzasadnić potrzebę używania listy. Za drugim razem, wymagamy tylko aby argument implementował interfejs IEnumerable.
Jaki jest zysk z takiego pozytywnego nastawienia?

   1:  void Test(string[] args)
   2:  {
   3:      List<int> input1 = new List<int>();
   4:      IList<int> input2 = new List<int>();
   5:      ICollection<int> input3 = new List<int>();
   6:      IEnumerable<int> input4 = new List<int>();
   7:      int[] input5 = new int[100];
   8:   
   9:      NotSoNiceSumFunction(input1); // :)
  10:      NotSoNiceSumFunction(input2); // :(
  11:      NotSoNiceSumFunction(input3); // :(
  12:      NotSoNiceSumFunction(input4); // :(
  13:      NotSoNiceSumFunction(input5); // :(
  14:   
  15:      NiceSumFunction(input1); // :)
  16:      NiceSumFunction(input2); // :)
  17:      NiceSumFunction(input3); // :)
  18:      NiceSumFunction(input4); // :)
  19:      NiceSumFunction(input5); // :)
  20:  }

Powyżej przykładowy kod z różnymi kontenerami dla intów. Które linijki kodu nie będą chciały banglać? Mój kompilator nie poradził sobie z linijkami 10,11,12,13. Czyli tak jak powiedzieliśmy podczas definiowania funkcji NotSoNiceSumFunction – działamy tylko i wyłącznie z listą intów. Podczas gdy NiceSumFunction poradził sobie z każdym rodzajem parametru. Chyba nie muszę pisać z kim będzie się lepiej współpracowało?

Teraz w drugą stronę, jest juz po wszystkim, wiatraczek wyje, procek gorący, mamy wynik. Jak go przekazać stronie zainteresowanej? Zwrócić jako ogólny typ, czy dokładnie określić co nam wyszło? Myśl, myśl, myśl.

Znowu kod dla przykładu:

   1:  List<int>  GetAllItemsAsList()
   2:  {
   3:      List<int> items = new List<int>();
   4:      // magic goes here
   5:      return items;
   6:  }

Wersja mniej szczegółowa:

   1:  static IEnumerable<int>  GetAllItemsAsEnum()
   2:  {
   3:      IEnumerable<int> items = new List<int>();
   4:      // magic goes here
   5:      return items;
   6:  }

Na którą wersję się decydujecie? Ja przyznam się szczerze, że do tej pory wynik zwracałem zawsze przez IEnumerable. Ale już tak nie robię.
Jak może wyglądać wykorzystanie metod ze strony klienta na przykładach:

   1:  void Test(string[] args)
   2:  {
   3:      List<int> result1 = null;
   4:      IList<int> result2 = null;
   5:      ICollection<int> result3 = null;
   6:      IEnumerable<int> result4 = null;
   7:      int[] result5 = null;
   8:   
   9:   
  10:      result1 = GetAllItemsAsList(); // :)
  11:      result2 = GetAllItemsAsList(); // :)
  12:      result3 = GetAllItemsAsList(); // :)
  13:      result4 = GetAllItemsAsList(); // :)
  14:      result5 = GetAllItemsAsList(); // :(
  15:                                    
  16:      result1 = GetAllItemsAsEnum(); // :(
  17:      result2 = GetAllItemsAsEnum(); // :(
  18:      result3 = GetAllItemsAsEnum(); // :(
  19:      result4 = GetAllItemsAsEnum(); // :)
  20:      result5 = GetAllItemsAsEnum(); // :(
  21:  }

W tym przypadku widać, że klient będzie bardziej szczęśliwy jeśli dokładnie określimy wynik naszej pracy, a decyzję o tym co z nim zrobić zostawimy końcowemu odbiorcy. W przypadku zwracania jako enum, klient będzie musiał przyjąć IEnumerable, a następnie samodzielnie przekształcić to, w razie potrzeby, w klasą bardziej wyspecjalizowaną.

Czyli co, bądźmy mili i proaktywni, przyjmujmy wszystko z czym możemy sobie poradzić. A zwracając wynik, zadbajmy o szczegółową informację o tym, czym jest wynik i jak z niego korzystać, klient sam podejmie najlepszą decyzję czy wykorzysta pełny potencjał naszej ciężkiej pracy, czy potrakutje nas po między mordziu.

Pozdrawiam z IE8 i czekam na riposty.