Jason. Cała informatyka w jednym miejscu!

Przed Wami chwila wiekopomna! Jestem po kolejnej lekturze z dziedziny programowania. Przez te cztery miesiące zabijałem czas przy tytule "C#. Praktyczny kurs", trzecie wydanie. W mojej ocenie część treści nie nadaje się do czytania! Sięgnijcie po moją recenzję, którą starałem się przygotować najbardziej rzetelnie jak tylko potrafię.

TYTUŁ "C#. PRAKTYCZNY KURS" WYMAGA GRUNTOWNEGO REMONTU

Jestem po czwartej książce informatycznej przeczytanej w pełni i muszę przyznać, że ta umęczyła mnie wyjątkowo. Jeden wielki "bajzel" literówek i błędnych stwierdzeń. Troszeczkę czasu robię w programowaniu i daleko mi jeszcze do tytułowania siebie "doświadczonym", aczkolwiek wzrok mam jeszcze sprawny i wyłapał mnóstwo tez, z którymi się po prostu nie zgadzam. Trzecie wydanie książki ma już prawie 6 lat dlatego tym bardziej przydałaby się jego aktualizacja (nie wiem jak wygląda sprawa z prawami autorskimi po śmierci autora, bo mam świadomość że ta osoba już nie żyje).

Co do zagadnień, nie mam żadnych większych obiekcji. Co prawda, tematy powinny zostać trochę inaczej zorganizowane pod kątem kolejności (np. rzutowanie typu można było spokojnie zaprezentować już w lekcji 17. gdzie wystąpił pierwszy przypadek rzutowania podczas przekazywania obiektu do metody, a nie zostawiać na moment tłumaczenia zaawansowanych technik programowania obiektowego) oraz temat typów generycznych i wyrażeń lambda powinny zostać szerzej rozwinięte (albo nie pisać o nich wcale), aczkolwiek ogólnie treść "prowadzi" czytelnika zgodnie z rosnącym poziomem trudności. Nie piszę wcale, że tytuł jest zły. Chodzi mi o wykonanie. Tytuł jest częściowo źle wykonany. Poproszę o pozwolenie mi uzasadnienia tego stwierdzenia.

CO W TEJ KSIĄŻCE JEST ZŁE?

Zostawię tradycyjnie oświadczenie przed ukazaniem gorzkich faktów: nie mam nic do samego autora, śp. Pana Marcina Lisa ani do jakiejkolwiek innej osoby, która pomogła wypuścić książkę na rynek. Wykryte przeze mnie błędy i napisane gorzkie uwagi po których można chcieć życzyć mi źle, nie mają na celu krytykowania go jako osoby ani jakiegokolwiek ośmieszenia go na publicznym forum. Nadal mam szacunek do pracy jaką człowiek włożył w wypuszczenie tytułu "C#. Praktyczny kurs", który stał się elementem mojego nabywania wiedzy w dziedzinie programowania.

Od tej pory, treść będzie prezentować subiektywne zdanie na temat ogółu książki co mi się w niej nie podobało (co nie znaczy, że mam rację). Startuję!

ĆWICZENIA DO SAMODZIELNEGO WYKONANIA

Tu jest potrzebny jeszcze szerszy podział, bo mam do napisania o tym całą litanię.

W STRONĘ MATEMATYKI!

Pierwszy podpunkt, który spowodował grymas na mojej twarzy to to, że część zadań stała się sprawdzianem wiedzy matematycznej. Autor odbiega od misji, która polega na nauczeniu czytelnika podstaw programowania i niektóre ćwiczenia przypominają kartkówkę, na której jeśli człowiek nie posiada wzoru ani metody na policzenie czegoś, na nic przyda się wiedza zaprezentowana na poprzednich stronach. Jeśli zadania dotyczą matematyki, to też powinny być jakieś wyjaśnienia albo wskazówki, albo chociaż przypomnienie ze szkoły co zrobić. Jedyne takie wcześniejsze wyjaśnienie było tylko na stronach 171-172, ale reszta to skok na głęboką wodę.

Zanim rzuci się na mnie sfora matematyków, dajcie wyjaśnić. Nie twierdzę, że mam przez swoje ewentualne braki wiedzy unikać matematyki jak ognia. Matematyka jest ważna w zawodzie programisty i w wielu innych profesjach. Tylko zwróćcie uwagę, że tytuł "C#. Praktyczny kurs" dotyczy programowania, a nie matematyki. To jest tak samo, jakbyście kształcili się przy książce matematycznej i dostali zadanie o treści: "Oblicz miejsca zerowe dla funkcji f(x) = x2 + 6x + 1. Napisz program w języku C#, który sprawdzi otrzymany wynik. Nie używaj kalkulatora ani debuggera.". Nie zgłupielibyście?

NIGDZIE CIĘ NIE PUSZCZĘ BEZ WYKONANIA POPRZEDNIEGO ZADANIA!

Ten moment kiedy patrzysz na ćwiczenie, a w nim zdanie o tym że musisz wykonać poprzednie...Dokładnie tak, kilkanaście zadań w tej książce wymaga od Was żebyście ukończyli poprzednie. I co jest jeszcze nie do przyjęcia, są też takie zadania które oczekują jedynie od Was sprawdzenia poprawności działania ("Napisz przykładowy program ilustrujący działanie […] z ćwiczenia X"), jakby nie można było tego dodać już do tego samego zadania, po prostu "Sprawdź rezultat." i kropka. Nie jest prościej?

Oto ćwiczenia, w których znajdziecie "wymaganie" ukończenia poprzedniego:

  • 9.3 (wymaga ćwiczenia 8.3)
  • 14.3 (wymaga ćwiczenia 14.2)
  • 14.6 (wymaga ćwiczenia 14.2 i 14.3)
  • 15.3 (wymaga ćwiczenia 15.2)
  • 15.5 (wymaga ćwiczenia 15.4)
  • 15.6 (wymaga ćwiczenia 15.4 i 15.5)
  • 16.2 (wymaga ćwiczenia 16.1)
  • 16.4 (wymaga ćwiczenia 16.3)
  • 16.7 (wymaga ćwiczenia 16.6)
  • 18.3 (wymaga ćwiczenia 18.2)
  • 21.2 (wymaga ćwiczenia 21.1)
  • 23.5 (wymaga ćwiczenia 18.4)
  • 23.6 (wymaga ćwiczenia 23.5)
  • 28.4 (wymaga ćwiczenia 28.3)
  • 29.3 (wymaga ćwiczenia 29.2)
  • 29.5 (wymaga ćwiczenia 28.3)
  • 31.4 (wymaga ćwiczenia 31.3)
  • 34.6 (wymaga ćwiczenia 34.5)
  • 36.5 (wymaga ćwiczenia 36.4)
ZADANIE DOTYCZY LISTINGU. ZAWRÓĆ!

Rozumiem, że można odwoływać się do kodów źródłowych, tak żeby na szybko się z nim zapoznać czy baczniej mu się przyjrzeć podczas omawianego zagadnienia. Tylko że ćwiczenia także wymagają powracania do poprzednich stron, szukania tych listingów, przepisywania kodu, tylko po to żeby zacząć rozwiązywanie zadania, gdzie po takim czasie można zapomnieć co w ogóle należało zrobić. I tak jest średnio co trzecie zadanie. No seryjnie?

KURIOZALNA CZĘŚĆ PRZYKŁADOWYCH ROZWIĄZAŃ

W tym podpunkcie przedstawię Wam przykładowe rozwiązania pobrane bezpośrednio ze strony wydawnictwa Helion, które stoją w sprzeczności z treściami dwóch ćwiczeń. Co prawda, to nie stanowi bezpośredniego zarzutu skierowanego w stronę książki, jednak chciałem zwrócić na to Waszą uwagę, bo to też może zadziałać destruktywnie na myśleniu przyszłego programisty.

Pierwszy ancymon, który mnie zaskoczył to numer 24.1. Brzmi ono tak:

"Napisz program wyświetlający na ekranie napis składający się z kilku słów. Nie używaj jednak ani zmiennych, ani literałów, ani innych obiektów typu string."

Macie to? Oto kod źródłowy przykładowego rozwiązania tego zadania pobrany bezpośrednio ze strony Helion o "C#. Praktyczny kurs" (źródło: https://helion.pl/pobierz-przyklady/cshpk3/):

using System;

public class Program
{
  public static void Main()
  {
    char[] buf = {'T','o',' ','j','e','s','t',' ','p','r','z','y','k','ł','a','d','.'};
    Console.Write(buf);
  }
}

Była mowa o nieużywaniu zmiennych, ale ta zmienna występuje! Jest tablica znaków! Oczywiście, że musi istnieć bo inaczej nie dałoby się wypisać łańcucha bez używania typu "string". Można by na upartego wsadzić tę tablicę bezpośrednio do wywołania metody, ale nie o to idzie. Rozwiązanie stoi w sprzeczności z treścią zadania.

Drugie takie kuriozum dla Was, ćwiczenie 24.5 o treści:

"Umieść w kodzie programu metodę przyjmującą jako argument ciąg znaków, która przetworzy go w taki sposób, że każda litera a, b i c, przed którą nie znajduje się litera k, l lub j, zostanie zamieniona na spację. Rezultatem działania metody powinien być przetworzony ciąg. Przetestuj działanie metody na kilku różnych ciągach znaków."

Znowu rozwiązanie moim zdaniem nieeleganckie. Twierdzę tak chociażby z tego powodu, że ćwiczenie należy do rozdziału ukazującego metody manipulowania łańcuchami znaków. Popatrzcie na ten kod należący do tej samej paczki z rozwiązaniami do "C#. Praktyczny kurs":

using System;

public class Program
{
  public static string zmien(string str)
  {
    string wynik = "";
    char lastLetter = '\0';
    for(int i = 0; i < str.Length; i++)
    {
      if((str[i] == 'a' || str[i] == 'b' || str[i] == 'c') &&
        (lastLetter != 'j' && lastLetter != 'k' && lastLetter != 'l'))
      {
        wynik += ' ';
      }
      else
      {
        wynik += str[i];
      }
      lastLetter = str[i];
    }
    return wynik;
  }
  public static void Main()
  {
    Console.WriteLine(zmien("kabc"));
    Console.WriteLine(zmien("bjcd"));
    Console.WriteLine(zmien("gahbjc"));
    Console.WriteLine(zmien("abjckcabblcax"));
  }
}

Nie czepiam się w ogóle treści zadania. Mnie martwi znowu rozwiązanie, które nie wykorzystuje żadnej poznanej w lekcji metody operującej na łańcuchu. Po prostu można to było napisać ładniej. Ja napisałem kod w taki sposób, przy użyciu poznanych w materiale metod (myśląc, że po to zostały zawarte w "C#. Praktyczny kurs"):

using System;
using System.Text;

namespace Exercise24._5
{
	class Program
	{
		static void Main(string[] args)
		{
			Console.WriteLine(ConvertedString("kabc"));
			Console.WriteLine(ConvertedString("bjcd"));
			Console.WriteLine(ConvertedString("gahbjc"));
			Console.WriteLine(ConvertedString("abjckcabblcax"));
		}

		static string ConvertedString(string input)
		{
			string[] inputSplits = input.Split(" ");
			string[] convertedSplits = new string[inputSplits.Length];
			int convertedSplitsIndex = 0;

			foreach (string s in inputSplits)
			{
				StringBuilder sb = new StringBuilder(s);
				
				if(!s.Contains("ka") && !s.Contains("la") && !s.Contains("ja"))
				{
					sb = sb.Replace("a", " ");
				}

				if(!s.Contains("kb") && !s.Contains("lb") && !s.Contains("jb"))
				{
					sb = sb.Replace("b", " ");
				}

				if(!s.Contains("kc") && !s.Contains("lc") && !s.Contains("jc"))
				{
					sb = sb.Replace("c", " ");
				}

				convertedSplits[convertedSplitsIndex++] = sb.ToString();
			}

			return string.Join(' ', convertedSplits);
		}
	}
}

Wydawało mi się intuicyjnie, że po to podawało się te tabelę z metodami, żeby można było odpowiednio je wykorzystać w zadaniu. A tu zastosowano podejście rodem z języka C. Tablice i żywe "skakanie" po indeksach.

MAŁY GALIMATIAS W PODAWANYCH NAZWACH

Wracamy do samej książki "C#. Praktyczny kurs". Na deser, niewielkie tym razem przeoczenie. W ćwiczeniu 23.3 (strona 231) występuje pomyłeczka w nazwie funkcji. Raz jest mowa o zdefiniowaniu metody "Odejmij", a w kolejnym zdaniu jest już metoda "f". Dwa zdania później znowu wspomnienie o "Odejmij". Niedopatrzenie jeszcze do wybaczenia.

Podobna sytuacja ma miejsce w ćwiczeniu 32.2 na stronie 333. Z jego treści wynika, że występują interfejsy o nazwach "PierwszyInterfejs" i "DrugiInterfejs". Jednak kiedy skierujemy się na listing 6.29 (jak nakazuje ćwiczenie), okaże się że interfejsy przyjmują delikatnie inne nazwy ("IPierwszyInterfejs" i "IDrugiInterfejs"). Taka drobnostka, że na nią też można machnąć ręką.

TREŚĆ

Teraz sama treść, czyli poplątanie w stwierdzeniach + moje poprawki.

WARTOŚCI TYPU "ENUM" LICZONE SĄ OD ZERA!

Kurs na stronę #34. Znajduje się delikatna wzmianka o typie wyliczeniowym. Pada tam stwierdzenie, że "Domyślnie [...] elementy są numerowane od 1.". Nieprawda! Sprawdziłem to. Okazało się, że elementy występujące w typie wyliczeniowym domyślnie liczone są od zera! Ponadto, wynika to jednoznacznie z dokumentacji Microsoftu. Jedynym usprawiedliwieniem byłoby to, że w starszych wersjach C# przed 6.0 elementy były faktycznie liczone od jedynki. Jeżeli tak było, zwracam honor.

KLASA ABSTRAKCYJNA MOŻE SKŁADAĆ SIĘ NIE TYLKO Z ABSTRAKCYJNYCH METOD

W opisywanej książce znajdują się informacje o klasie abstrakcyjnej (strona 309 i 319), czym się charakteryzuje i jaką odgrywa rolę w programowaniu. Jest też wspomnienie o metodach abstrakcyjnych oraz...tylko o nich. Zabrakło mi uprzedzenia czytelnika, że pola i właściwości też wchodzą w grę, a nie wierzę żeby to nie było możliwe w C# sprzed wersji 6.0. Nie spotkałem żadnego przykładu kodu źródłowego ukazującego działanie pola albo właściwości abstrakcyjnej. Szkoda. To ważna rzecz powiadomić człowieka, że poza metodą wolno wstawić również pole i właściwość abstrakcyjną. Początkujący ma prawo tego nie wiedzieć.

O PEWNYCH TEMATACH TYLE, CO KOT NAPŁAKAŁ

Zero aluzji do Ciebie, mamo. Książka w mojej ocenie prezentuje zbyt powierzchownie dwa tematy: wyrażenia lambda oraz typy generyczne. O samych wyrażeniach lambda jest zaledwie NIECAŁA STRONA, a co do typów generycznych zabrakło mi pokazania metod wykorzystujących "generic", a także jak nakładać ograniczenia w związku ze wstawianymi typami danych (np. tylko klasa X i jej pochodne). To też jest ważne!

To można interpretować dwojako. Z jednej strony rozumiem, że można jedynie zainicjować temat, tak aby człowiek po prostu wiedział, że takie coś istnieje i w każdej chwili może posiłkować się innymi zbiorami wiedzy, żeby chcieć drążyć temat. Z drugiej strony nie powinno się pisać o jakimś rozdziale fragmentarycznie nie ukazując wszystkich aspektów i ważnych zagadnień. Lepiej byłoby już wspomnieć powierzchownie o każdej możliwości niż skoncentrować się szczegółowo na jednym podpunkcie olewając resztę. Tak uważam.

TO NIE WYGLĄDA JAK KOD ŹRÓDŁOWY

Zanim zaprezentuję literówki, jeszcze jedna rzecz która przykuła moją uwagę. Przed Wami mały spis fragmentów zdań, które zostały opatrzone czcionką przeznaczoną dla kodów źródłowych, a w rzeczywistości są zwykłymi zdaniami:

  1. "Operacja AND będzie zatem miała postać:" (strona 58)
  2. "zawierającą wartości pewnego typu" (strona 90)
  3. "[...], np. w następujący sposób" (strona 140)
  4. "[...], pojawiłaby się wartość 101" (strona 141)
  5. "[...], przyjęłaby ona postać widoczną na listingu 3.39" (strona 167)
LITERÓWKI I BŁĘDY W NUMERACJACH

Gotowi na istny "roller coaster" literówek? Proszę uprzejmie:

LISTINGI

"Listingi" są to kody źródłowe programów umieszczone w książce "C#. Praktyczny kurs". Oto wszystkie znalezione przeze mnie:

  1. "Zmodyfikujmy zatem kod z listingu 2.11 […]" (chodziło prawdopodobnie o listing 2.12, strona 52)
  2. "[...] dokładnie tak jak w programie z listingu 2.11." (chodziło prawdopodobnie o listing 2.12, strona 53)
  3. "Kod z listingu 2.24 […]" (chodziło prawdopodobnie o listing 2.25, strona 79)
  4. "(listing 3.15)" (chodziło prawdopodobnie o listing 3.16, strona 134)
RYSUNKI

Co do rysunków, znalazłem tylko jedną pomyłkę:

  • "[...] wartości widoczne na rysunku 2.39." (chodziło prawdopodobnie o rysunek 2.40, strona 113)
ROZDZIAŁY

Jeden, jedyny rodzynek jeśli chodzi o rozdziały:

  • "Właśnie tej tematyce jest poświęcony rozdział .6" (na stronie 233 jest już rozdział 5)
LEKCJE

Kolejne cztery wykryte pomyłki w numeracji, tym razem w odniesieniu do lekcji:

  1. "[...] w bieżącej, 26. lekcji", zamiast "[...] w bieżącej, 27. lekcji" (zdanie znajduje się na początku lekcji 27., strona 271)
  2. "Lekcja 33. […]", zamiast "Lekcja 35. [...]" (zdanie znajduje się na początku lekcji 35., strona 359)
  3. "[...] które poznaliśmy już w lekcji 33., [...]", zamiast "[...] które poznaliśmy już w lekcji 35., [...]" (temat dotyczący tworzenia menu w aplikacji okienkowej został przedstawiony w lekcji 35., strona 392)
  4. "W lekcji 33. wyjaśniono [...]", zamiast "W lekcji 35. wyjaśniono [...]" (temat dotyczący tworzenia menu w aplikacji okienkowej został przedstawiony w lekcji 35., strona 395)
LITERÓWKI

Teraz najgorsza rzecz na deser. Literówki, których w książce "C#. Praktyczny kurs" jest od cholery:

  1. "np. jeżeli istnieją zmiennej [...]", zamiast "np. jeżeli istnieją zmienne [...]" (strona 46)
  2. "Ponieważ zmiennej liczba przepisaliśmy [...]", zamiast "Ponieważ zmiennej liczba przypisaliśmy [...]" (strona 81)
  3. "[...] natomiast w drugim warunkiem [...]", zamiast "[...] natomiast w drugim warunku [...]" (strona 89)
  4. "[...] aż do ostatniego wiersza w pojedynczą liczbą [...]" zamiast "[...] aż do ostatniego wiersza z pojedynczą liczbą [...]" (strona 97)
  5. "[...] new int[4], new int[2], new int[3], new int[1]" zamiast "[...] new int[4], new int[3], new int[2], new int[1]" (pomylono numeracje, strona 115)
  6. "Można stosować znaki polskie znaki", zamiast "Można stosować polskie znaki" (strona 119)
  7. "[...] a pisząc punkt.y = 200, [...]", zamiast "[...] a pisząc punkt1.y = 200, [...]" (w kodzie źródłowym jest "punkt1", strona 122)
  8. "Listing 3.14: Klasa statyczna w dyrektywnie", zamiast "Listing 3.14: Klasa statyczna w dyrektywie" (strona 129)
  9. "Napisz klasę Protokat [...]", zamiast "Napisz klasę Prostokat [...]" (strona 131)
  10. "W czwartym bloku kodu używana jest metoda Zwieksz3, której przekazywany jest przez referencję z użyciem słowa out argument w postaci zmiennej liczba2.", zamiast "W piątym bloku kodu używana jest metoda Zwieksz3, której przekazywany jest przez referencję z użyciem słowa out argument w postaci zmiennej liczba2." (strona 143)
  11. "(int) y17." (kropka przed zdaniem w nawiasie, strona 172)
  12. "[...] stosować tylko jeden przedstawionych sposobów", zamiast "[...] stosować tylko jeden z przedstawionych sposobów" (strona 174)
  13. "Po kompilacji i uruchomieniu programu zobaczmy widok [...]", zamiast "Po kompilacji i uruchomieniu programu zobaczymy widok [...]" (strona 183)
  14. "Punkt punkt = new Punkt;", zamiast "Punkt punkt = new Punkt();" (brak nawiasów w wywołaniu konstruktora, strona 197)
  15. "tablica.length", zamiast "tablica.Length" (napisanie małą literą spowoduje błąd kompilacji, strona 205)
  16. "Wtedy kod miałby postać przedstawioną na listing [...]", zamiast "Wtedy kod miałby postać przedstawioną na listingu [...]" (strona 226)
  17. "[...] zawartości zmiennych str4 i str4.", zamiast "[...] zawartości zmiennych str4 i str5." (strona 238)
  18. "[...] a otrzymane wartość [...]", zamiast "[...] a otrzymane wartości [...]" (strona 240)
  19. "[...] do wartości typu dobule.", zamiast "[...] do wartości typu double." (strona 240)
  20. "Lenght", zamiast "Length" (strona 242, 243)
  21. "Zostanie bliżej omówiona wykonujące te i wiele innych operacji klasa Console", zamiast "Zostanie bliżej omówiona wykonująca te i wiele innych operacji klasa Console" (TA klasa, strona 247)
  22. "[...] wygenerowanie wyjątku ArgumentException." (kropka przed zdaniem w nawiasie, strona 263)
  23. "Zobaczmy również [...]", zamiast "Zobaczymy również [...]" (poprzednie zdanie jest napisane w czasie przyszłym, strona 272)
  24. "Wywołanie metody Write [...]", zamiast "Wywołanie metody Read [...]" (strona 277)
  25. "Wybrane metody klasy StreamReader [...]", zamiast "Wybrane metody klasy StreamWriter [...]" (strona 281)
  26. "[...] metody WriteLine obiektu sr (klasy StreamReader)", zamiast "[...] metody WriteLine obiektu sw (klasy StreamWriter)" (strona 283)
  27. "jest przez kompilator rozumiana:", zamiast "jest przez kompilator rozumiana jako:" (strona 292)
  28. "Zmienna punkt klasy Punkt3D [...]", zamiast "Zmienna punkt3D klasy Punkt3D [...]" (w kodzie źródłowym jest "punkt3D", strona 300)
  29. [...] zawiera definicje metod Draw oraz Opis, natomiast Triangle - jedynie metody Draw.", zamiast "[...] zawiera definicje metod Draw oraz Opis, natomiast Triangle - jedynie metodę Draw." (strona 311)
  30. "Wywołanie drugie zapisuje w kolejnej komórce (o indeksie 1) wartości rzeczywistej 3.14", zamiast "Wywołanie drugie zapisuje w kolejnej komórce (o indeksie 1) wartość rzeczywistą 3.14" (strona 351)
  31. "Obiekt klasy tablica [...]" ("tablica" małą literą, a w kodzie źródłowym jest "Tablica" – strona 352)
  32. "W efekcie przy w metodach Get i Set [...]", zamiast "W efekcie w metodach Get i Set [...]" (strona 356)
  33. "[...] w wywołaniu instrukcji Aplication.Run().", zamiast "[...] w wywołaniu instrukcji Application.Run()." (strona 360)
  34. "Dopiero ustawienie odpowiedniej opcji komplikacji [...]", zamiast "Dopiero ustawienie odpowiedniej opcji kompilacji [...]" (przy tym miałem mały ubaw, strona 361)
  35. "Okno zawierające określony Tytuł" (dlaczego "Tytuł" wielką literą? - strona 363)
  36. "[...] o czym traktuje kolejna lekcja." ("traktuje"???, strona 370)
  37. "public class MainForm:Form" (wyjątkowo brak odstępów pomiędzy nazwą klasy potomnej, dwukropkiem, a klasą od której się dziedziczy - strony 399, 402 i 405)

A teraz lista takich literówek, które przeczą występowaniu nazw w listingach albo tabelach:

  1. "Działanie metod Zwieksz jest testowane w metodzie main, [...]" (w kodzie źródłowym jest metoda "Main", strona 142)
  2. "public static void main, a nie static public void main", zamiast "public static void Main, a nie static public void Main" (w kodzie źródłowym jest "Main", strona 183)
  3. "W metodzie main [...]", zamiast "W metodzie Main [...]" (w kodzie źródłowym jest "Main", strona 231)
  4. "Metoda concat [...]", zamiast "Metoda Concat [...]" (na liście metod jest "Concat", strona 245)
  5. "Metoda indexOf [...]", zamiast "Metoda IndexOf [...]" (na liście metod jest "IndexOf", strona 245)
  6. "Polimorficzne wywołania metody drawShape", zamiast "Polimorficzne wywołania metody DrawShape" (w kodzie źródłowym jest "DrawShape", strona 306)
  7. "Przykładowo metoda draw [...]", zamiast "Przykładowo metoda Draw [...]" (w kodzie źródłowym jest "Draw", strona 310)
  8. "[...] za pomocą metod get oraz set.", zamiast "[...] za pomocą metod Get oraz Set." (w kodzie źródłowym jest "Get" i "Set", strona 346)

GRZECHY KSIĄŻKI

A teraz seria punktów, które wskazują na to, że treści zaprezentowane w książce "C#. Praktyczny kurs" są chwilami pisane krzywdząco dla odbiorcy (to jest tylko moja opinia!).

CO JĘZYK, TO SKŁADNIA

Wiele przykładów posiada metody których nazwy zaczynają się małą literą. Ja wiem, że to nie jest żaden błąd, tylko kłóci się to z panującą konwencją nazewniczą, zgodnie z którą nazwy metod piszę się WIELKĄ literą na styl "PascalCase"! To nie jest język Java!

Tak wygląda lista znalezionych dowodów:

  1. metody "f" i "g" (strony 329-343, ćwiczenie 32.1 i 32.2 na stronie 333)
  2. metody "g" i "getInside" (strona 339)
  3. metody "getX" i "setX" (strona 382-391)

Żeby nie poprzestać tylko na tym, to jeszcze na dokładkę nazwy prostych typów danych, które z kolei powinny wykorzystywać alias zamiast nazwy z przestrzeni nazw "System", ale to powiedzmy, pół biedy:

  1. "String", zamiast "string" (strony 226, 227, 252, 255, 256, 262, 264, 282, 311)
  2. "Object", zamiast "object" (strona 393)
PRZECZĘ SAMEMU SOBIE

Następna seria "bugów" w których wyodrębniłem zaprzeczenie pomiędzy prezentowanymi kodami źródłowymi, a treścią.

#1

Podpunkt pierwszy, strona 129. Oto zdanie a raczej jego początek, który przykuł moją uwagę:

"Po użyciu zapisu using static Console [...]"

Błąd w zapisie. Chodziło pewnie o przestrzeń nazw "System.Console" więc całość powinna brzmieć:

"Po użyciu zapisu using static System.Console [...]"

Co ciekawe, parę wierszy niżej jest przedstawiony kolejny fragment kodu, w którym pokazane jest przykładowe użycie i tam jest już napisane poprawnie ("using static System.Console;").

#2
"Domyślnie spacje dodawane są z prawej strony; jeżeli mają być dodane z lewej, należy dodatkowo użyć znaku -."

Ten cytat znajdziecie na stronce 241 i dotyczy on opisu działania formatowania łańcucha znaków według specyfikatora w klamerkach, a konkretniej działania znaku minusa po dwukropku dla ustalenia wyrównania treści łańcucha do lewej lub do prawej strony. Wydaje mi się, że miało być odwrotnie. Spacje dodawane są z prawej strony kiedy użyjemy "minusa", bo wtedy jest wyrównanie do lewej strony i vice-versa. Być może źle to wywnioskowałem, bo ja to zrozumiałem w ten sposób, że w przypadku wyrównania do prawej strony, spacje muszą być wówczas wstawione jako pierwsze. A domyślnie występuje wyrównanie do prawej, czyli domyślnie spacje dodawane są po lewej stronie. Autorowi książki "C#. Praktyczny kurs" chyba chodziło o to, że "jeżeli ma wystąpić wyrównanie do lewej, należy dodatkowo użyć znaku -". I wtedy to jest spójne.

#3

Małe sprostowanie odnośnie opisu na stronie 249, ale to już naprawdę mikroskopijna rzecz. W tabelce została wymieniona metoda klasy "Console" o nazwie "SetBufferSize". Autor twierdzi, że "określa wysokość i szerokość bufora tekstu". Przyczepiam się tylko do ostatniego słówka. Zamieniłbym na "ekranu". Bufor ekranu, to jest to. Wyniki są prezentowane na ekranie, nie na łańcuchu. A łańcuch może składać się nie tylko z tekstu, to mogą być też nic nie mówiące znaki jak "!@#$%^&*()". Nawet w oficjalnej dokumentacji jest mowa o EKRANIE.

#4

Podobny punkt dotyczy właściwości "KeyChar" struktury "ConsoleKeyInfo" na stronie 250, też maleńka sprawa. W opisie jest podane, że jest to kod odczytanego znaku. Ja bym napisał po prostu, że zwraca odczytany znak, gdyż jest to typ "char" a nie typ "int". Przecież po podstawieniu do "Console.WriteLine" bez stosowania jakichkolwiek rzutowań zwraca na wyjściu znak więc..."character". Jednakże w C# typy "char" i "int" są ze sobą częściowo spokrewnione, więc od biedy można to tak określić.

#5

Błąd nieco większej wagi znajdziecie na stronie 253. Chochlik wkradł się we fragmencie zdania, tuż przed pierwszą instrukcją:

"Na początku temu ciągowi przypisywana jest wartość uzyskana za pomocą wywołania metody ToString struktury Key obiektu keyInfo:"

A teraz spójrzcie na instrukcję o którą się rozchodzi:

String str = keyInfo.Key.ToString();

"Key" to jest właściwość! Strukturą jest "posiadacz" właściwości "Key", czyli "ConsoleKeyInfo" (typ obiektu "keyInfo"). Takie małe sprostowanie.

#6

Kolejna bzdurka, którą można bezrefleksyjnie wybaczyć. Chodzi mi o "schematyczne konstrukcje interfejsu", jak to zostało określone w książce. Porównajmy sobie same nagłówki znajdujące się na stronach 319 i 331:

[public | internal] interface nazwa_interfejsu

[public] interface interfejs_potomny : interfejs_bazowy

Modyfikatory dostępu. To jest ten drobny element, który przykuł moją uwagę. Interfejsy potomne nie mogą być wewnętrzne? Mogą! Jak wspomniałem - bzdurka.

#7

Listing 6.44 nie powodowałby zaprzeczenia jednego ze zdań, gdyby nie jeden szczegół. Zdanie o które mi chodzi jest na stronie 342 i brzmi:

"We wszystkich przypadkach z wyjątkiem ostatniego przed definicją klasy (wewnętrznej) nie znajdował się żaden modyfikator dostępu."

No to muszę rozczarować autora książki "C#. Praktyczny kurs" i wskazać palcem właśnie na listing 6.44 (strona 339), w którym użyto modyfikatora dostępu przed definicją klasy wewnętrznej, "Inside".

#8

Strona 382, moi mili. Fragment zdania na które chciałem zwrócić uwagę jest takie:

"[...] metody "getX" i "setX", pozwalające na pobieranie jego wartości."

Zaraz, zaraz. Jak to "setX" również pozwala na pobieranie wartości? Chyba jej przypisywanie. Od pobierania wartości jest "getX". Kolejne błędne stwierdzenie.

#9

Na stronie 403 pada takie zdanie:

"W konstruktorze klasy MainForm są tworzone wszystkie trzy kontrolki oraz są im przypisywane właściwości."

ale jak się przyjrzycie listingowi 7.28 to się okazuje, że wspomniane kontrolki są tworzone, ale podczas deklarowania ich jako prywatne dane składowe klasy "MainForm", nie w konstruktorze. Więc poprawna jest tylko druga część zdania na temat przypisywania właściwości. To się zgadza.

NIEPOTRZEBNE KOMPLIKOWANIE

Strona 382 i metody "getX" oraz "setX". Przez wiele stron są ukazywane te dwie metody, które stanowią część eksperymentu związanego z programowaniem obsługi zdarzeń i samych delegatów. Nie lepiej było skorzystać z właściwości? Przecież właśnie po to są "properties", żeby odstąpić od staroświeckich zapisów z języka Java, a one same są znacznie wcześniej tłumaczone po co one są, jak można je stosować. A jak sprawdziłem, można wstawić wywołanie zdarzenia wewnątrz "settera". Ech…

POPLĄTANIE TYPÓW DANYCH

Jeszcze jedno zamieszanie. Błąd o który mi chodzi kryje się na stronie 292, a pod jej koniec pada zdanie:

"[...] w przypadku typów prostych konwersja typu bardziej ogólnego na typ bardziej szczegółowy powodowała utratę części informacji."

Odwrotnie! Tracimy część informacji kiedy przenosimy wartość z typu bardziej szczegółowego (np. "double") na typ bardziej ogólny (np. "int"). Nawet zaraz w następnym zdaniu jest już słusznie napisane, że przenoszenie wartości 4,5 typu "double" na "integer", sprawi że zostanie 4.

SKRÓT MYŚLOWY MOŻE MYLIĆ

Idziemy dalej, strona 310. Właściwie to zdanie jest w porządku:

"Mamy zatem pewność, że jeśli klasa bazowa zawiera metodę abstrakcyjną, to każda klasa potomna również ją zawiera."

Tylko zaraz po nim ja bym jeszcze dorzucił, że metodę abstrakcyjną ma każda klasa potomna, która także jest abstrakcyjna, a każda klasa potomna niebędąca abstrakcyjną musi mieć implementację tej metody. Metoda abstrakcyjna a metoda zdefiniowana nie są sobie równe! Wiem, że autorowi "C#. Praktyczny kurs" chodziło pewnie o sam fakt, że każda klasa potomna zawiera każdą metodę klasy bazowej (jeśli nie jest prywatna), tylko po pierwszym przeczytaniu pojawił mi się w głowie znak zapytania i wątpliwości czy to zdanie aby na pewno jest prawdziwe (przecież klasa potomna nieabstrakcyjna nie może mieć w sobie metody abstrakcyjnej!). Tutaj po mojemu brakuje tego sprostowania.

INTERFEJS != KLASA

Mogę się mylić i nie wiedzieć wszystkiego, niemniej jednak na studiach inżynierskich i w książkach tłuczono mi do znudzenia, że interfejs jest IMPLEMENTOWANY przez klasy. A w książce pojawia się kilka razy stwierdzenie, że następuje dziedziczenie interfejsu przez klasę oraz występuje porównanie do klasy. Mnie uczono, że interfejs to zestaw metod abstrakcyjnych (ewentualnie składowych abstrakcyjnych, bo w C# można umieszczać też właściwości), ale nigdy nie spotkałem się z porównaniem do klasy (co innego kiedy interfejs wykorzystuje inny interfejs). Nie przeczę, że ja popełniam w tej chwili błąd traktując to jak pomyłkę i takie stwierdzenie jest poprawne, aczkolwiek odkąd poznałem interfejsy nie spotkałem się nigdy z utożsamianiem ich z klasą.

Oto stwierdzenia, które uważam za wprowadzające w błąd:

  1. "[...] mogą dziedziczyć po interfejsach." (zaraz po tym zdaniu jest już napisane poprawnie że implementują interfejsy, strona 199)
  2. "Interfejs to klasa czysto abstrakcyjna [...]" (osobiście wątpię, żeby można było nazwać interfejs "klasą" - strona 319)
  3. "Zestaw klas dziedziczących po IDrawable", zamiast "Zestaw klas implementujących IDrawable" (strona 321)
TAM I Z POWROTEM

Następna w mojej ocenie niepotrzebna rzecz. Wyprzedzanie materiału wtrącając zdania że "można pominąć tę część lekcji" i tymczasowo zajrzeć sobie na pół książki dalej, żeby się dowiedzieć o co chodzi w danym momencie.

Proszę bardzo, dowody. Strona 190 i 191. Pod koniec strony 190 pojawiają się fragmenty kodów źródłowych wykorzystujących niewyjaśniony jeszcze temat. Jeśli to już było konieczne, to ja poprzestałbym jedynie na wtrąceniu, że "Wyjątki zostaną omówione dopiero w kolejnym rozdziale, [...]" i wtedy już na miejscu przypomniał o przykładzie ze strony 190. Ale dobrze, mogą być zdania podzielone.

Na stronie 199 mamy to samo. Wspomnienie o terminie, skierowanie do odpowiedniego rozdziału ("Temat interfejsów zostanie omówiony dopiero w rozdziale 6., [...]") i prezentacja kodu źródłowego z użyciem dotąd nieomówionej konstrukcji. W tym przypadku dodatkowo mi się jeszcze nie spodobało to, że budowę interfejsu "IPunkt" także przeniesiono "na potem" (strona 324). Definicji interfejsu brak, a w kolejnym listingu jest już ukazana implementacja przez strukturę bez słowa wyjaśnienia ("[...] tam też został opublikowany kod interfejsu IPunkt, który jest wykorzystany w poniższym przykładzie."). Jak człowiek, który dopiero zaczyna naukę programowania ma sobie z tym poradzić, nie wiedząc o czym jest w ogóle gadka?

To nie wszystko. Ćwiczenie 20.5 i 20.6 to już moim zdaniem przeginka. W treści jest napisane, żeby przerobić listing który wykorzystuje interfejsy zanim zostały one wyjaśnione. Tam jest tylko pokazane jak struktura implementuje interfejs, ale nie ma ani definicji, ani słowa wyjaśnienia na tym etapie (dopiero od strony 319, miłego "czekania" do tej strony!). Jak można oczekiwać od czytelnika wykonania zadania wymagającego znajomości tematu, który nie został jeszcze omówiony? To znowu wymusza na człowieku "przeskakiwanie" do określonej strony, a już sam fakt że trzeba wracać do listingów jest irytujący. Pierwszy raz spotkałem się z takim zadaniem, które mówi: "Stary, co prawda musisz znać podstawy tematu, który nie został jeszcze omówiony, ale i tak sobie poradzisz.". No dobra, ja sobie poradziłem bo wiem już od dawna jak obchodzić się z interfejsami, a także w jaki sposób napisać implementację przez klasę bądź strukturę, aby nie było żadnych błędów składniowych, ale nie wszyscy przecież mają tę wiedzę. I co mają zrobić? Zajrzeć od razu do rozwiązania?

MALUTKI FRAGMENT WIEDZY JEST JUŻ NIEAKTUALNY

Co do zdezaktualizowanej treści, pojawia się tylko jeden taki przypadek. Obiekty typu "MainMenu" i "MenuItem" należących do frameworka służącego do projektowania graficznych interfejsów użytkownika, "Windows Forms" (strona 366). Od wersji ".NET Core" 3.1 używa się odpowiedników o nazwach "MenuStrip" oraz "ToolStripMenuItem". Ale to jest w pełni zrozumiałe, ".NET Core" w wersji 3.1 pojawił się w 2019 roku, więc nie można mieć pretensji do żadnej ze stron.

CZY KSIĄŻKA JEST WARTA ZAKUPU?

W mojej ocenie, absolutnie nie! Książka "C#. Praktyczny kurs" przypomina mi trochę wersję beta, w której jest jeszcze cała masa rzeczy do naprawienia. Odstawanie od konwencji nazewniczych, sprzeczne stwierdzenia w stosunku do kodów, częściowe wybrakowania istotnych informacji przy poruszanych tematach. Do tego jeszcze te literówki, koszmar! Nie mogę jej polecić, jednakże nie mam żadnych obiekcji co do poruszanych zagadnień (z wyjątkiem tych dwóch tematów wymienionych wyżej). Jak dla początkujących jest tam wszystko co powinno być, więc w ostateczności mogą po nią sięgnąć ludzie, którzy nie mają zielonego pojęcia o programowaniu. Większość nie będzie świadoma, że w pewnych miejscach autor korzysta z niewłaściwych określeń przez co nikt nie będzie się czepiał tak jak ja, choć powtarzam: nie robię tego dla przyjemności! Chcę, żeby to stanowiło pełne i rzetelne uzasadnienie mojego postanowienia dlaczego nie radzę sięgać po tę lekturę (a przynajmniej nie jest ona dla mających już jakieś pojęcie w obsłudze C#)!

"C#. Praktyczny kurs"

"C#. Praktyczny kurs" posiada przyzwoicie sporządzony zestaw treści dla adeptów w dziedzinie programowania. Masa literówek i sprzecznych stwierdzeń powoduje, że nie mogę jej polecić (na pewno nie osobom, które już wiedzą co nieco na ten temat).


Tym akapitem kończę całą serię o języku C#, którą dla Was pisałem przez te ponad dwa miesiące. Mam nadzieję, że los będzie sprzyjał i rankingi zaczną na tyle iść do góry, że tę recenzję przeczyta znaczna liczba osób. Mnie jak zwykle cieszy poczucie dobrze spełnionego obowiązku i wierzę, że moje zestawienie wszystkich błędów pomoże ludziom, którzy w przyszłości byliby chętni podjęcia się ponownego wydania książki "C#. Praktyczny kurs", ale już bez literówek i bez głupot (powtarzam jak namolny, że tym zestawieniem nie miałem na celu w żaden sposób obrazić autora).

PODOBNE ARTYKUŁY