Coding standards, cz. 2 – ogólne zasady pisania dobrego kodu
Witam w kolejnej odsłonie „Coding Standards”. Nadal nie mam czasu na skończenie kontrolki GoogleMaps, która wygrała ankietę, dlatego proponuję wpis kontynuujący szumnie zapowiadaną serię ;-)
W dzisiejszym odcinku zajmiemy się ogólnymi zasadami „dobrego” programowania. Mam nadzieję, że uda mi się ogrom informacji sensownie posegregować. Let’s go.
1. Wszystkie pola w klasach deklaruj jako prywatne
Wyjątek: pola oznaczone jako readonly lub const.
2. Przeciążone metody powinny mieć podobne zachowanie i być wykorzystywane w zbliżonych celach
Nie stosowanie tej zasady podpadać może pod łamanie innej ;-) – tzw. zasady najmniejszego zaskoczenia (principle of least surprise).
3. Używaj instrukcji warunkowych (switch-case, if-else) gdy jakieś działanie zależy od wartości obiektu, nie od jego typu.
4. Instrukcje warunkowe obejmuj nawiasami klamrowymi
Wszystkie bloki warunkowe if-else, while, do-while, switch-case obejmuj w bloki nawiasami klamrowymi. Nawet, jeśli zawierają tylko jedną instrukcję. Ta zasada zapewne wielu wyda się kontrowersyjna, ale jestem ogromnym zwolennikiem jej stosowania. Zdecydowanie wolę czytać:
{
...
}
else
{
...
}
zamiast
...
else
...
5. W switch-case zawsze umieszczaj default
nawet jeśli nie wykona on żadnej akcji i znajdzie się tam tylko komentarz. Pozostawi to jasność sytuacji.
6. Pamiętaj o Liskov Substitution
… czyli o tym, że zapewniona powinna być możliwość użycia referencji do obiektu klasy pochodnej wszędzie tam, gdzie użyta jest referencja do obiektu będącego instancją jego klasy bazową. Jest to zasada Liskov Substitution.
7. Szereguj atrybuty, właściwości oraz metody według poziomu dostępu
Deklarując pola, property i metody w klasie szereguj je w pliku wg poziomu dostępu: public, protected, private
8. Kolejność ustawiania / pobierania properties nie może mieć znaczenia
Zapewnij możliwość ustawiania właściwości (Property) klasy w dowolnej kolejności. Nie powinna zdarzyć się sytuacja, gdy nie ustawienie jednego property przed ustawieniem innego spowoduje nieprawidłowe zachowanie.
9. Property – tam, gdzie potrzebny jest prosty dostęp do danych
Używaj właściwości (Property) wszędzie tam, gdzie zapewniają one prosty dostęp do danych.
10. Metody – tam, gdzie dzieje się „coś więcej”
Używaj metod wszędzie tam, gdzie odbywa się „coś więcej” niż tylko dostęp do danych. Nad tym problemem dość mocno pochyla się Jeffrey Richter w CLR via C#, wskazując więcej wad niż zalet properties’ów i zalecając używanie metod. Generalnie metody należy stosować, gdy:
- odbywa się konwersja danych (np. ToString(), ToInt32() itp.)
- operacja może być na tyle czasochłonna, że niezbędne może być cache’owanie wyników
- wywołanie property dwa razy pod rząd może dać inne wyniki (patrz principle of least surprise)
- kolejność wykonywanie jest istotna
- gdy pole, do którego odnosi się property jest statyczne ale jego wartość może być zmieniona
- gdy zwracana jest kopia obiektu referencyjnego
- gdy udostępniony byłby tylko akcesor set property – wtedy zdecydowanie zastąp property metodą SetSomething(…)
11. Konstruktor ma inicjować obiekt. Cały.
Nie twórz konstruktora, który w całości nie zainicjalizuje obiektu (wszystkich jego pól)
12. EIMI używaj tylko tam, gdzie jest to niezbędne i robisz to świadomie
Używaj EIMI (explicit interface method implementation) tylko wtedy, gdy jest to niezbędne do rozwiązania problemu zduplikowanych nazw.[1] wtedy, gdy jest to uzasadnione. Rób to świadomie – pamiętaj, że implementacja metody interfejsu wprost jest ukryta przed obiektami dziedziczącymi z klasy, w której się to dzieje i dostęp do takiej metody będzie dostępny wyłącznie po jawnym rzutowaniu instancji na interfejs.
13. Po użyciu operatora as sprawdź czy obiekt nie jest nullem
Gdy używasz operatora as do rzutowania, zawsze sprawdzaj czy wynikiem tej operacji nie jest null. Zawsze. NullReferenceException nigdy nie wybacza.
14. Implementujesz przez override Equals, operator == lub GetHashCode – nie zostawiaj ich samych
Gdy decydujesz się na override metody Equals, operatora == lub metody GetHashCode – zaimplementuj wszystkie trzy.
15. Oczekujesz konkretnej wartości w parametrze lub zwracasz konkretną wartość – używaj enum
Używaj typu enum do konkretnie zdefiniowanych parametrów i zwracanych wartości – to zwiększa czytelność.
16. Wykonujesz operacje bitowe na wartościach enum – użyj [Flags]
Gdy planujesz używać operacji bitowych na typie enum oznacz go atrybutem [Flags]
17. Nie lubimy „magicznych liczb”
Nie używaj „magicznych” liczb. Jeśli coś jest zdefiniowane na stałe – użyj const lub readonly. Bo kto się domyśli, że linia w Twoim kodzie:
int i = 45;
...
oznacza, że w tym autobusie może siedzieć maksymalnie 45 osób?
18. Unikaj rzutowania z obcięciem
Nie wykonuj rzutowania, które może spowodować utratę precyzji (np. z long na int32) – chyba, że zagwarantujesz jego poprawność
19. Długie linie kodu to zło
Nie twórz długich linii kodu – utrudnia to czytanie i zrozumienie kodu. W C++ przyjeła się długośc linii na poziomie 80-90 znaków, C# ma trochę dłuższe namespace’y – ja 120 znaków uznaję za maksimum.
20. Konsekwentny bądź
Jeśli zdecydujesz się na styl pisania kodu (wcięcia, stosowanie nawiasów klamrowych w specyficzny sposób) – trzymaj się go konsekwentnie w całym kodzie.
21. Twardy bądź – nie zmieniaj wartości parametru, na którym opierasz iterację pętli for
Oczywiście nie zmieniaj go dodatkowo wewnątrz pętli.
22. Skromny bądź – prostota zapisu to cnota
Nie ma potrzeby porównywania wartości typu bool do wartości true lub false. Wystarczy zwykłe:
{
...
}
Podobnie zamiast konstrukcji:
if(checkbox1.Checked)
{
isChecked = true;
}
else
{
isChecked = false;
}
zdecydowanie łatwiej napisać:
Zapamiętaj również – trójwarunkowy operator logiczny przyjacielem każdego programisty :) Zamiast:
if( someObject.Id == 0)
{
isNew = true;
}
else
{
isNew = false;
}
napisz:
lub jeszcze prościej:
23. Jedno wejście – jedno wyjście. Unikaj wielu instrukcji return
Unikaj wielu instrukcji return w metodach. Złota zasada brzmi „jedno wejście – jedno wyjście”. Oczywiście tego warunku używaj z głową. Jeśli oczywiste jest niespełnienie jakiegoś założenia na samym początku i będzie to jasne dla czytającego kod – od zasady można odstąpić.
23 zasady… Tak się właśnie zastanawiam, czy to w ogóle realne zebrać wszystko w jakąś całość.. No nic – będę próbował. Ja „prześpię” się z tematem – być może przydałaby się część B ninejszego odcinka – tymczasem kurtyna podniesiona. Zapraszam do komentowania. Co według Was pominąłem, z czym się nie zgadzacie?
Przypisy
- zmodyfikowane po komentarzu hamera↩
Podobne wpisy
Coding standards, cz. 1 – konwencje nazewnictwa
Coding standards – intro
Możesz śledzić odpowiedzi do tego wpisu za pomocą RSS 2.0 feed. Możesz leave a response, or trackback z Twojej własne strony.





Coding standards, cz. 2 – ogólne zasady pisania dobrego kodu | Blog o programowaniu C#, ASP.NET…
Dziękujemy za publikację – Trackback z dotnetomaniak.pl…
W ostatniej zasadzie chyba został pominięty przykład, gdyż kończy się ona:
„Czy jednak to jest czytelne:” …i dalej pustka :)
Bardzo dobra seria, czekam na następne odcinki :)
Pustka, bo przykład miał być ale ten punkt (23) jest zbyt oczywisty żeby zawracać Wam głowę przykładem ;-) Czujny czytelnik z Ciebie – widać, że czytasz do końca :D
Nie zgadzam się że EIMI powinno „być używane tylko gdy jest to niezbędne do rozwiązania problemu zduplikowanych nazw.”
Przy pomocy EIMI można bardzo ładnie posługiwać się do zapewnienia ciekawej hermetyzacji, przykład:
A.dll:
internal interface IA
{
void Foo();
}
public class A : IA
{
void IA.Foo();
}
Takie rozwiązanie spowoduje że wszędzie gdzie
gdzie będziemy używać instancji klasy A, nie będzie można użyć
metody Foo, jednak wewnątrz assembly A.dll będzie to możliwe po rzutowaniu na interface IA.
Takie rozwiązanie można wykorzystać np. w sytuacji gdy chcemy
raz (bądź bardzo rzadko) używać metody Foo, jednocześnie przysłaniając (publicznie na stałe, w assembly częsciowo) przed zbyt ciekawymi programistami ;-)
W sumie masz rację – dość niefortunnie sformułowałem ten punkt dotyczący EIMI. Forma w jakiej był pierwotnie zapisany wykluczałaby Twój poprawny przykład. Zmodyfikowałem punkt – jest teraz bardziej liberalny ale mam nadzieję zwraca uwagę na dwa istotne aspekty ;-)
podobaly mi sie te 2 arty z tej serii ale mysle, ze mozesz na tym zakonczyc. Znalazlem niedawno 2 super ebooki (free) z .NET coding guidelines. Jesli ktos bedzie zainteresowany to po Twoim „wprowadzeniu” moze reszte wziasc stamtad.
Polecam:
http://submain.com/download/Submain_DotNET_Coding_Guidelines.zip
jako mail wpiszcie byle co i download wystartuje
oraz
http://www.idesign.net/idesign/download/IDesign%20CSharp%20Coding%20Standard.zip
Pozdr.
Dlaczego od razu kończyć ;-) Ja chcę pisać! Dzięki za linki, ja zapmniałem wrzucić bibliografię, z której korzystalem. Myślę, że 2 odcinki (tyle jeszcze wg moich początkowych planów ma sens) nikogo nie zamęczą ;)
Nie zgadzam się z zasadą nr 23. Prowadzi to często do wielu zagnieżdżonych warunków if – czyta się to fatalnie, albo co gorsza do definiowania zmiennej o nazwie „success”, itp.
Moje podejście jest takie, że instrukcji if używam do sprawdzenia, czy nie występuje sytuacja, która zakłóca normalny przebieg wykonania, np. gdy parametr jest nullem, itp.
Czy nie bardziej czytelnie jest:
private string SomeMethod(string param1, string param2)
{
if( string.IsNullOrEmpty(param1) )
return „someSpecialValue1″;
if( string.IsNullOrEmpty(param2) )
return „someSpecialValue1″;
return „normalvalue”;
}
niż:
private string SomeMethod(string param1, string param2)
{
if( !string.IsNullOrEmpty(param1) )
{
if( !string.IsNullOrEmpty(param2) )
{
return „normalvalue”;
}
else
return „someSpecialValue2″;
}
else
return „someSpecialValue1″;
}
Chodziło o wyjście na zasadzie:
private string SomeMethod(string param1, string param2)
{
string ret = “normalvalue”;
if( string.IsNullOrEmpty(param1) )
ret = “someSpecialValue1″;
if( string.IsNullOrEmpty(param2) )
ret = “someSpecialValue1″;
return ret;
}
Pozwala to czasem oszczędzić oczy ;)
Jednak jeśli już jesteśmy tak szczegółowi, napisane jest, że:
„Jeśli oczywiste jest niespełnienie jakiegoś założenia na samym początku i będzie to jasne dla czytającego kod – od zasady można odstąpić.”
Także dramatu nie ma :)
rulez piątka