środa, 20 kwietnia 2016

Podstawowe błędy w programowaniu (początkujących)

1. Indeksowanie

Zasadnicze pytanie: indeksujemy od 0 czy 1? Pierwszy element ma indeks 0 czy 1? Problem który dotyka niektórych początkujących uczących się nowego języka, szczególnie gdy pierwszym z nich był taki, który obsługiwał indeksowanie tablic bardziej "matematycznie", formalnie czyli pierwszy element w tablicy miał indeks 1. Może to prowadzić do szeregu błędów w stylu "off by one".

int n = 10;
int[] tabs = new int[n];
for (int i=1; i<=n; i++)
{
 tabs[i] = i+1;
}

W powyższym przypadku popełniono dwa błędy: pierwszy polega na pominięciu pierwszego elementu (o indeksie zero i rozpoczęciu indeksowania od 1). Drugi błąd to wyjście poza granice tablicy (IndexOutOfRangeException) przez źle skonstruowany warunek wykonania pętli.

2. Wyrażenia warunkowe

Początkujący programiści dosyć często zachłystują się możliwościami jakie daje instrukcja if. Są w stanie "zaprogramować" rozmaite działania programu bazując jedynie na tej instrukcji (oraz instrukcji switch..case gdy ją poznają) poprzez budowanie rozległych łańcuchów if..else..if..else.

int e = 2;
//...
if (e==0)
{
 Console.Write("Brak danych");
}
else if (e==1)
{
 Console.Write("Błędne dane");
}
else if (e==2)
{
 Console.Write("Inny problem");
}

W takim przypadku warto rozważyć inne podejście i zaprogramować bardziej czytelne, i skuteczne rozwiązanie.

int e = 2;
//...
string[] errors = new string[] { "Brak danych", "Błędne dane", "Inny problem" };
Console.WriteLine(errors[e]);

3. Wyrażenia logiczne

Logika boolowska to podstawa w formułowaniu działania programu. Opierają się na niej wspomniane wcześniej wyrażenia warunkowe. Budowanie takich wyrażeń musi być zawsze przemyślane, gdyż nieodpowienio zbudowane wyrażenia mogą być przyczyną błędów, a nawet mieć wpływ na wydajność.

W przypadku zastosowania operatorów logicznych && i || ważna jest kolejność operandów. W przypadku sumy logicznej, gdy pierwszy z nich jest false to zaniechane jest dalsze przetwarzanie wyrażenia. W przypadku alternatywy, takie zaniechanie następuje gdy pierwszy z operandów jest true.

Skrócone przetwarzanie wyrażeń logicznych zyskuje na znaczeniu gdy, przewidujemy która część wyrażenia będzie powodowała skrócenie przetwarzania wyrażenia.

4. Operatory as i is oraz rzutowanie

Rzutowanie typów w C# można przeprowadzić na dwa sposoby. Podając nazwę typu w nawiasie przed odwołaniem do obiektu (Control)zmienna; lub przy pomocy operatora as zmienna as Control.

W pierwszym przypadku gdy rzutowanie nie jest możliwe, zostanie wyrzucony wyjątek, w drugim przypadku otrzymamy null. Rzutowanie przez operator as można stosować tylko dla typów referencyjnych i zerowalnych (Nullable<>).

W większości przypadków, wyjątków związanych z nieprawidłowym rzutowaniem typów można uniknąć przez stosowanie operatora is. Operator ten zwraca true jeżeli sprawdzana zmienna jest typem podanym z prawej strony operatora, false w przeciwnym wypadku.

if (zmienna is Control)
{
   zmienna2 = (Control)zmienna;
}

5. Konkatenacja string'ów

Nieznajomość klas i metod z frameworka może być przyczyną powstawania problemów wydajnościowych. W tym przypadku nieodpowiednie

string[] animals = { "kot", "pies", "mysz" };

string allAnimals = string.Empty;

foreach (string s in animals)
{
    allAnimals += s + ", ";
}

Console.Write(allAnimals);

Powyższy kod jest bardzo nieefektywny pod kątem wykorzystania pamięci. Łącząc elementy z tabeli animals w pętli foreach, na każdą iterację przypadają aż trzy alokacje pamięci dla nowych ciągów znakowych. String jest typem, którego rozmiar jest ustalany w momencie przypisania. W momencie przypisywania nowego ciągu znaków do zmiennej tego typu, następuje powstanie nowego obiektu klasy string. Przy dużej ilości iteracji kod taki staje się problematyczny, bo zaczyna mocno obciążać zasoby przez ciągłe alokacje pamięci, oraz zmusza Garbage Collector do częstszego porządkowania pamięci - co ma wpływ na wydajność.

Kod poniżej działa prawie tak samo, ale znacznie efektywniej. Wykorzystuje wbudowaną metodę Join(), która jest zoptymalizowana do wykonania tego zadania. Wewnętrznie sama alokuje odpowiednią ilość pamięci dla łączonych ciągów.

string[] animals = { "kot", "pies", "mysz" };

string allAnimals = string.Join(", ", animals);

Console.Write(allAnimals);

Brak komentarzy: