2. Plan prezentacji
• Wprowadzenie
• Definicja czystego kodu
• Dobre nazwy w kodzie
• Zrozumiały kod funkcji
• Komentowanie kodu jest
przereklamowane
• Formatowanie kodu to nie problem
• Obiekty i struktury danych w
czystym kodzie
• Zakończenie
3. Wprowadzenie
• Rola czystego kodu w
zarządzaniu projektem
informatycznym
• Książka
• Zalety tej prezentacji w
porównaniu do książki
4. Rola czystego kodu w zarządzaniu
projektem informatycznym
„Nic nie ma tak głębokiego i w długim czasie
degradującego wpływu na prowadzenie projektu, jak
zły kod. Złe harmonogramy mogą być
zmodyfikowane, złe wymagania przedefiniowane. Złą
dynamikę zespołu da się poprawić. Jednak zły kod
psuje się, stając się coraz większym ciężarem dla
zespołu. Widziałem wiele zespołów, które się
rozsypywały, ponieważ w pośpiechu tworzyły złośliwe
bagnisko kodu, który na zawsze zdeterminował ich
przeznaczenie. (…) Tak więc rozwiązaniem jest ciągłe
zachowywanie czystości i maksymalnej prostoty kodu.
Nigdy nie pozwalajmy, by zaczął się psuć.”
6. Zalety (wady) tej prezentacji w
porównaniu do książki
• Jest mniej kodu
• Trwa krócej niż przeczytanie książki
• Z powodu ograniczeń czasowych obejmuje
około ½ (pierwszą połowę) książki
7. Definicja czystego
kodu
• elegancki i efektywny
• prosty i bezpośredni
• można go łatwo zmienić
• posiada testy
• minimalny
• opisywany
• ktoś się nim przejął
• taki jakiego się spodziewaliśmy
• nie czyni go język a programista
8. Dobre nazwy w kodzie
• przedstawiają intencje
• bez mylących i fałszywych wskazówek
• bez zbyt subtelnych różnic
• są spójne i dobrze się sortują w IDE
• można je wymówić!
• łatwo je wyszukać
• bez kodowania typów
• bez przedrostków składników
• bez kodowania interfejsów
• bez jedno-literówek
• nazwy klas mają rzeczowniki
• nazwy metod mają czasowniki
• zamiast przeciążonych konstruktorów stosujemy metody fabryk
• dowcipne nazwy mogą nie być zrozumiane
• spójny leksykon
• bez przesady ze spójnością
• używamy terminologii informatycznej
• w drugiej kolejności używamy terminologii klienta
• dodajemy znaczący kontekst
• bez nadmiarowego kontekstu
• zmiana nazwy na czytelniejszą to nic złego
9. przedstawiają intencje
int c;
int studentsCount;
List<int> list1 = new List<int>();
List<int> temperatureValues = new List<int>();
10. bez mylących i fałszywych
wskazówek
int hp; // hiperprzelicznik
Student[] studentList;
List<Student> studentList;
List<Student> students;
Student[] students;
11. bez zbyt subtelnych różnic
Student VeryGoodStudentInThisCourseOfPhysics;
Student VeryGoodStudentInThatCourseOfPhysics;
void copyStudents(Student[] a1, Student[] a2)
void copyStudents(Student[] source, Student[] destination)
Student student;
int ECTS;
Student aStudent;
int ECTSpoints;
Student theStudent;
string TitleString;
class StudentObject
13. można je wymówić!
• Nasze mózgi lubią mowę.
• Żeby możliwa była dyskusja o kodzie.
• Żeby nowe osoby w zespole rozumiały kod.
class SdntRcrd126
{
private DateTime admymdhms;
private const string pszqint = "126";
}
class Student
{
private DateTime admissionTimestamp;
private const string recordId = "126";
}
14. łatwo je wyszukać, stałe
zamiast liczb
if (s.Count > 9)
const int MAX_STUDENTS_PER_YEAR = 9;
if (students.Count > MAX_STUDENTS_PER_YEAR)
16. bez przedrostków składników
• Najnowsze IDE wyróżniają zmienne składowe.
• Czytając kod i tak ignorujemy przedrostki.
• W małych klasach i funkcjach są zbędne.
class Student class Student
{ {
private String m_name; private String name;
void setName(String name) void setName(String name)
{ {
m_name = name; this.name = name;
} }
} }
17. bez kodowania interfejsów (lepszy
przyrostek „Imp”/”Impl”)
interface IComputerStudent
interface ComputerStudent
class ComputerStudentImp : ComputerStudent
18. bez jedno-literówek, które
trzeba sobie potem tłumaczyć
Uri r = new Uri("http://usosweb.uni.lodz.pl");
• Może dla nas nie problem to zapamiętać, ale
„profesjonaliści piszą kod zrozumiały dla innych”
• Wyjątkiem są liczniki pętli ale tylko „i”, „j” i „k”
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
for (int k = 0; k < 4; k++)
19. nazwy klas mają rzeczowniki
class Course
class CourseAdvisor
class Advise
class Advice
• Unikamy mowy-trawy jak np. „Manager”,
„Processor”, „Data”, „Info”
class CourseProcessor
20. nazwy metod mają czasowniki
counter.submit();
• „get” przed akcesorami, „set” przed
mutatorami, „is” przed predykatami
if (course.isCompleted())
totalCompletedHours += course.getTotalHours();
counter.setTotal(totalCompletedHours);
21. zamiast przeciążonych konstruktorów
stosujemy metody fabryk
• … o nazwach pochodzących od argumentów,
np. City.FromName(„Łódź”) tworzy obiekt
City z nazwą „Łódź”
City lodz = new City("Łódź");
City lodz = City.FromName("Łódź");
23. spójny leksykon – jedno
słowo na zagadnienie
if (ListenerManager.retrieveCurrent()
== StudentController.getActual())
24. bez przesady ze spójnością
– jedno zagadnienie na słowo
course.title = "C#";
student.title = "Wiesław Czyżewski";
25. używamy terminologii informatycznej
– z tzw. dziedziny rozwiązania
• Możemy swobodnie stosować terminy
informatyczne (jak nazwy algorytmów czy
wzorców) bo czytelnicy kodu – programiści –
je znają
26. w drugiej kolejności używamy terminologii
klienta – z tzw. dziedziny problemu
• Tylko jeśli nie mamy na dane pojęcie terminu
technicznego
• Gdy istnieją dwa terminy w języku klienta na
jedno pojęcie, stosujemy tylko jeden termin
• Im bardziej kod jest z koncepcji dziedziny
problemu, tym częściej należy stosować jej
terminy
27. dodajemy znaczący kontekst
(wydzielamy klasy i funkcje!)
name = "Wiesław Czyżewski";
id = 15640;
student.name = "Wiesław Czyżewski";
student.id = 15640;
public Course adviseCourse()
{
if (student.likesMath())
{
if (student.knowsHowToAdd)
public Course adviseCourse()
{
if (student.likesMath())
return adviseMathCourses();
28. bez nadmiarowego kontekstu jako wspólnego
przedrostka na aplikację/moduł
class XYZAPPStudent
class XYZAPPCourse
class StudentAddress
class LecturerAddress
class Address
29. zmiana nazwy na czytelniejszą
to nic złego (refactoring)
• I tak korzystamy z podpowiedzi IDE nie
pamiętając większość nazw
30. Zrozumiały kod
funkcji (1)
• bardzo krótkie (najlepiej tylko 2–4 wiersze a
maksymalnie 20)
• bloki w instrukcjach warunkowych i pętlach mają
po jednym wierszu
• często wywołujemy kolejne funkcje o
opisowych nazwach
• struktura prosta, bez zagnieżdżeń, czyli
maksymalnie 2 wcięcia
• złota zasada: „FUNKCJE POWINNY
WYKONYWAĆ JEDNĄ OPERACJĘ. POWINNY
ROBIĆ TO DOBRZE. POWINNY ROBIĆ TYLKO
TO”
• aby kod czytało się jak artykuł prasowy (piramidę
„od ogółu do szczegółu”) bezpośrednio pod
funkcją znajdują się funkcje będące od niej o
jeden poziom abstrakcji niżej
31. Zrozumiały kod
funkcji (2)
• ukrywamy instrukcje „switch” w podstawie
fabryki abstrakcyjnej, używając polimorfizmu
• jak najmniej argumentów (najlepiej zero, 3 w
ostateczności)
• typy funkcji jednoargumentowych
• argumenty przekazujące true/false są złe. lepiej
podzielić na osobne funkcje dla true i false
• funkcje dwuargumentowe są trudniejsze do
zrozumienia niż jednoargumentowe, dlatego
często warto zrezygnować z jednego z
argumentów korzystając z klas
• gdy funkcja potrzebuje więcej niż 2 argumentów
możliwe, że powinniśmy część z nich umieścić w
osobnej klasie
32. Zrozumiały kod
funkcji (3)
• listy argumentów jak w funkcji „printf” są w
porządku nawet jeśli pozornie przekazujemy ich
np. 5, bo tak samo traktujemy wszystkie
argumenty z listy argumentów
• kodujmy porządek i przeznaczenie argumentów
w nazwie funkcji, tworząc pary czasownik-
rzeczownik, np. „write(name)” zamieniamy na
“writeField(name)”, a „assertEquals(expected,
actual)” zamieniamy na
“assertExpectedEqualsActual(expected, actual)”!
• funkcje powinny być bez efektów ubocznych,
ponieważ powoduje to naruszenie zasady
wykonywania jednej operacji oraz generuje
sprzężenie czasowe które jest niebezpieczne
(jeśli już sprzężenie czasowe jest nie do
uniknięcia to powinno być podane w nazwie
funkcji)
33. Zrozumiały kod
funkcji (4)
• funkcja nie powinna jednocześnie czegoś robić
i sprawdzać coś zwracając jakąś wartość,
nawet typu true/false — należy w tym
przypadku zrobić osobne funkcje – rozdzielić
polecenie od zapytania
• zawartość bloków „try-catch” warto
wyodrębniać do osobnych funkcji, które
zajmują się tylko wywołaniem „kłopotliwej”
funkcji owiniętym w blok „try-catch”
• zasada DRY: nie powtarzaj się, bo to źródło
wszelkiego zła w oprogramowaniu
• najłatwiej wyeliminować powielanie tego
samego kodu w dwóch metodach tworząc nową
metodę z tym kodem
• pisanie zrozumiałych funkcji
34. często wywołujemy kolejne
funkcje o opisowych nazwach
(sama tworzy się dokumentacja!)
• dla małych funkcji łatwiej o precyzyjną nazwę
• nie bójmy się długich nazw
• próbujmy różnych nazw
• spójne nazwy, opowiadające historię
35. złota zasada: „FUNKCJE POWINNY WYKONYWAĆ JEDNĄ OPERACJĘ.
POWINNY ROBIĆ TO DOBRZE. POWINNY ROBIĆ TYLKO TO”
• czy kod ma tylko jeden poziom abstrakcji (tuż
pod poziomem abstrakcji nazwy funkcji)?
• czy da się z niego jeszcze coś sensownego
wyodrębnić?
• czy da się go sensownie podzielić na sekcje?
36. ukrywamy instrukcje „switch” w podstawie
fabryki abstrakcyjnej, używając polimorfizmu
public decimal getMonthTuitionFee abstract public class Student
(Student student) {
{ abstract public decimal getMonthTuitionFee();
switch (student.type) abstract public decimal getYearTuitionFee();
{
case StudentType.Day: public interface StudentFactory
return 0; {
case StudentType.Evening: Student makeStudent(StudentRecord r);
return 250;
public class StudentFactoryImpl : StudentFactory
public decimal getYearTuitionFee {
(Student student) public Student makeStudent(StudentRecord r)
{
switch (r.type)
{
case StudentType.Day:
return new DayStudent();
case StudentType.Evening:
return new EveningStudent();
37. typy funkcji
jednoargumentowych
• zadajemy pytanie na temat argumentu, np.
fileExists(„wykład.doc”)
• operujemy na argumencie, przekształcając go w coś
innego i zwracając(!) wynik, np. fileOpen(„wyklad.doc”)
• zdarzenia wykorzystujące argumenty do zmiany stanu
systemu, ale niczego nie zwracające, np.
passwordAttemtFailedNtimes(int attemps) — nie należy
nadużywać
• innych typów unikać, np. nie używać argumentu
wyjściowego zamiast zwracanej wartości, poza tym jeżeli
funkcja musi zmieniać stan czegokolwiek, powinna
zmieniać stan własnego obiektu
38. funkcje dwuargumentowe są trudniejsze do zrozumienia
niż jednoargumentowe, dlatego często warto
zrezygnować z jednego z argumentów korzystając z klas
• można przenieść dwuargumentową metodę
do klasy jednego z argumentów
• można umieścić jeden z argumentów w
zmiennej składowej bieżącej klasy
• można wyodrębnić nową klasę i jeden z
argumentów przekazywać jej w konstruktorze
39. funkcja nie powinna jednocześnie czegoś robić
i sprawdzać coś zwracając jakąś wartość, nawet
typu true/false — należy w tym przypadku zrobić
osobne funkcje – rozdzielić polecenie od zapytania
• subtelnym przykładem jest stosowanie
kodów błędów – nie należy tego robić, od
tego są wyjątki
• stosowanie kodów błędów zamiast
mechanizmu wyjątków znacznie komplikuje
kod
40. zawartość bloków „try-catch” warto wyodrębniać do
osobnych funkcji, które zajmują się tylko wywołaniem
„kłopotliwej” funkcji owiniętym w blok „try-catch”
• w ten sposób nie mieszamy przetwarzania
błędów ze zwykłym przetwarzaniem
• obsługa błędów już jest jedną operacją (patrz
złota zasada) więc jeżeli w funkcji jest słowo
„try”, to powinno ono być jej pierwszym
słowem, a funkcja powinna kończyć się na
bloku „catch/finally”!
41. zasada DRY: nie powtarzaj się, bo to
źródło wszelkiego zła w oprogramowaniu
• powtarzanie powoduje rozdęcie kodu
• wymaga wielokrotnych modyfikacji
• zwiększa możliwość popełnienia błędu
42. pisanie zrozumiałych funkcji
• pierwszy szkic funkcji jest często zbyt długi,
skomplikowany, ma wiele wcięć,
zagnieżdżonych pętli i argumentów, o
dowolnych nazwach i powielonym kodzie
• należy wydzielać funkcje, zmieniać nazwy,
eliminować powtórzenia, zmniejszać metody
i je przestawiać, a czasem nawet
wyodrębniać całe klasy
43. Komentowanie kodu
jest przereklamowane
• Nie trać czasu na komentowanie
złego kodu. Popraw go
• Przykład: zamiast pisać
komentarz objaśniający
czynność, umieść tę czynność w
nowej, dobrze nazwanej funkcji
• Wyjątki od reguły – kiedy
można komentować
• Reguła – złe komentarze
44. Wyjątki od reguły – kiedy
można komentować (1)
• copyright u góry pliku (cytować licencję tylko z nazwy)
• komentarz informacyjny, np. obok wyrażenia
regularnego w komentarzu jest akceptowany format
• tłumaczymy się z naszych decyzji jak coś ma działać
(wyjaśnianie zamierzeń), np.:
• zasady sortowania w funkcji porównującej w nietypowym
przypadku
• cel użycia absurdalnie dużej pętli w przypadku testowym
• komentarze objaśniające zastosowanie niejasnego
kodu z biblioteki standardowej lub innej której nie
możemy zmienić aby jej zastosowanie było
czytelniejsze
45. Wyjątki od reguły – kiedy
można komentować (2)
• komentarze ostrzegające o tym co by było gdybyśmy
zmienili kod w narzucający się sposób – np.
• dlaczego wyłączyliśmy przypadek testowy (bo jest czasochłonny)
• dlaczego w statycznej metodzie inicjalizujemy zmienną lokalną
niestatycznie (bo nie jest bezpieczna dla wątków)
• komentarze TODO mówiące o rzeczach do zrobienia w
danym miejscu kodu, pod warunkiem że są regularnie
przeglądane i usuwane
• komentarze podkreślające wagę naprawdę istotnych
operacji, które pozbawione komentarza wydają się
niekonsekwencją i ktoś mógłby je usunąć (np. wywołanie
funkcji Trim())
• komentarze służące do generowania dokumentacji
publicznego API, pod warunkiem że są dobre
46. Reguła – złe komentarze (1)
• Jeśli musimy przeglądać inne moduły żeby zrozumieć komentarz
to jest to bełkot
• Komentarze powtarzające kod
• Z natury rzeczy są mniej precyzyjne niż kod bo język naturalny jest
mniej precyzyjny niż kod (mogą więc być mylące), a dłużej się je czyta
niż klarowny kod
• Nie dodają żadnej informacji, tylko zaciemniają kod
• Dezinformujące komentarze (np. mówiące tylko część prawdy,
nieprecyzyjne)
• Wymaganie komentarzy np. dla wszystkich funkcji lub zmiennych
„bo tak i już” – zaciemniają kod, powstają powszechne pomyłki i
dezorganizacja
• Dzienniki zmian na początku modułu pogarszają jego czytelność –
od tego są logi SVN
47. Reguła – złe komentarze (2)
• Szum informacyjny
• Np. komentowanie konstruktora domyślnego „konstruktor
domyślny”
• Szum informacyjny jest ignorowany automatycznie przez
czytających, przez co może stać się nieaktualny i mylący
• Kiedy mamy ochotę napisać komentarz będący szumem
informacyjnym, lepiej przeznaczyć tę chwilę na wyczyszczenie kodu
• Komentarz będący dokumentacją publicznego API sprowadzający się
do powtarzania nazw z kodu to straszny szum
• Komentarz objaśniający nieczytelny kod który można zamienić na
wyodrębnienie funkcji lub dodanie kilku zmiennych uczytelniając
kod
• Rzucające się w oczy nagłówki typu „// Nagłówek /////////////////////////”
(znaczniki pozycji, transparenty) przydatne bardzo rzadko i jeśli
już to bez tych ukośników na końcu. Powinny być używane
oszczędnie tylko gdy są naprawdę potrzebne
48. Reguła – złe komentarze (3)
• Komentowanie klamry zamykającej informując co było przy
klamrze otwierającej 2 kilometry wcześniej. Przecież mamy krótkie
funkcje, po co nam to?
• Komentarze oznaczające autorstwo dopisanych fragmentów kodu
(śmieci które później zalegają w kodzie nieprecyzyjne i zbędne) –
od tego jest SVN
• Usuwanie kodu przez jego zakomentowanie. Skąd później inne
osoby mają wiedzieć dlaczego ten kod pozostał w tej formie? Może
jest istotny? Więc nikt go nie usunie. I taki zakomentowany kod
później zalega. Jeśli chcemy zachować ten kod na wszelki wypadek
– od tego jest SVN
• Stosowanie kodu HTML w komentarzach. Jeśli używamy narzędzi
do generowania dokumentacji z komentarzy to one powinny
zadbać o jej sformatowanie, a komentarz w kodzie powinien
być czytelny dla czytającego kod
49. Reguła – złe komentarze (4)
• Komentarz lokalny opisujący informacje nielokalne. Np. domyślną
wartość parametru w funkcji która nie określa tej domyślnej
wartości. Komentarz taki najprawdopodobniej stanie się
nieaktualny. Jeśli już komentujemy, to tylko to co dzieje się tuż obok
• Za duży komentarz – zamiast cytować cały opis specyfikacji RFC
wystarczy napisać jej numer, należy też unikać wszelkich luźnych
opisów, opisów historycznych itp.
• Komentarz nieprecyzyjnie powiązany z elementami kodu, np. nie
wskazujący precyzyjnie zmiennych komentarz do kodu
wykonującego obliczenia na bazie niejasnych zmiennych
• Komentarze opisujące w nagłówkach funkcje nie są potrzebne,
kiedy mamy krótkie dobrze nazwane funkcje wykonujące
pojedyncze operacje
• Komentarze służące do generowania dokumentacji
niepublicznego kodu (wewnątrz systemu) są najczęściej
nieprzydatne. Poza tym są podatne na błędy i rozpraszające
50. Formatowanie kodu to
nie problem (1)
• Formatowanie pomaga w uporządkowaniu i
czytelności kodu, które pomagają w łatwości
utrzymania i rozszerzania kodu, dlatego jest
ważne
• Małe pliki łatwiej zrozumieć niż duże, więc
rozsądnym zaleceniem jest utrzymywanie plików
o długości przeciętnej 200 wierszy i maksymalnej
500 wierszy
• Pliki naśladujące artykuły w gazetach
• Puste wiersze oddzielają osobne koncepcje takie
jak deklaracja pakietu/przestrzeni nazw, importy
i każda z funkcji
• Nie stosujemy pustych wierszy tam gdzie nie
trzeba, np. nie ma sensu rozdzielać pustym
wierszem deklaracji dwóch zmiennych
instancyjnych
51. Formatowanie kodu to
nie problem (2)
• Jeśli między koncepcjami jest bliski związek,
powinny być również blisko siebie w kodzie
• Znane i oczekiwane stałe należy trzymać w
funkcjach znajdujących się na wysokim poziomie
i przekazywać do funkcji znajdujących się na
niższym poziomie zamiast trzymać je w
funkcjach niższego poziomu
• W poziomie nie należy przekraczać długości
wierszy 120 znaków, żeby nie przewijać kodu
poziomo
• Spacje stosujemy do oddzielenia osobnych
elementów, np. jest sensowne oddzielenie spacją
operatora przypisania po obu stronach albo
argumentów wywołania funkcji, ale nie
ma sensu oddzielać spacją nawiasu
od nazwy funkcji
52. Formatowanie kodu to
nie problem (3)
• Nie wyrównujemy w kilku kolumnach deklaracji
lub przypisań (stosujemy pojedyncze spacje)
• Wcięcia stosujemy do podkreślenia zakresów, np.
instrukcje na poziomie plików nie są nigdy
wcinane, ale już kod w obrębie metody jest
zawsze wcinany, a implementacje instrukcji „if”
umieszczamy na kolejnym poziomie wcięcia. Bez
wcięć kod byłby nieczytelny
• Nie łamiemy wcięć i nie łączymy wierszy nawet
dla bardzo krótkich instrukcji „if”, pętli „while”
lub jednowierszowych metod
• Zawsze wcinamy puste zakresy, np. średnik na
końcu pustych pętli umieszczamy wcięty w
osobnym wierszu
53. Formatowanie kodu to
nie problem (4)
• Zespół programistów używa wspólnych
zasad formatowania kodu. Należy ustalić
takie zasady jak miejsce umieszczania
klamr, wielkość wcięcia, sposób nazywania
klas, metod, zmiennych itd. i wprowadzić
je do formatera kodu
• Wg zasad formatowania kodu autora
książki pojedyncze wcięcia mają dwie
spacje, klamry otwierające nie są w
osobnym wierszu, nazwy klas zaczynają się
wielką literą, nazwy metod i zmiennych
małą literą, zmienne instancyjne są na
początku klas
54. Pliki naśladujące artykuły w
gazetach
• Nazwa modułu jak tytuł artykułu. Prosta, sugestywna,
informująca nas czy jesteśmy we właściwym module
• Górne partie kodu źródłowego jak pierwszy akapit
artykułu. Algorytmy i koncepcje najwyższego
poziomu.
• Dalsze partie kodu jak kolejne akapity artykułu. Coraz
więcej szczegółów w miarę schodzenia w dół, aż do
funkcji oraz szczegółów najniższego poziomu na
samym dole
• Użyteczne jest, że artykuły są krótkie, rzadko na całą
stronę
55. Jeśli między koncepcjami jest
bliski związek, powinny być również
blisko siebie w kodzie
• Zmienne zadeklarowane jak najbliżej miejsca ich użycia
• Zmienne lokalne deklarujemy na początku krótkich funkcji
• Zmienne sterujące pętli deklarujemy zazwyczaj wewnątrz
instrukcji pętli. Rzadko na początku bloku lub w dłuższych
funkcjach przed samą pętlą
• Zmienne instancyjne deklarujemy zawsze na początku klasy (albo
zawsze na końcu, ale konsekwentnie). Nigdy w środku, bo łatwo je
pominąć
• Funkcja wywołująca powinna być powyżej funkcji wywoływanej w
bliskiej od niej odległości (najlepiej bezpośrednio przed nią). W ten
sposób łatwiej znaleźć wywoływane funkcje i moduł jest
czytelniejszy
• Jeśli funkcje wykonują podobne operacje, powinny ze sobą
sąsiadować
56. Obiekty i struktury
danych w czystym kodzie
• Tworzenie abstrakcyjnego sposobu
dostępu do danych obiektu nie polega na
prostym dodaniu getterów i setterów do
zmiennych
• Kod obiektowy i kod proceduralny
(korzystający ze struktur danych) są
uzupełniającymi się przeciwieństwami.
Stosujmy korzystniejszy dla naszego
przypadku
• Źle, kiedy moduł wie coś o wewnętrznej
budowie obiektów z których korzysta
• Unikajmy hybryd przypominających trochę
struktury danych, a trochę obiekty
• Niektóre formy struktur danych
57. Tworzenie abstrakcyjnego sposobu dostępu
do danych obiektu nie polega na prostym
dodaniu getterów i setterów do zmiennych
• Zamiast tego trzeba udostępnić możliwość pracy
z istotą tych danych, aby implementacja była dla
korzystającego z obiektu nieważna i
niewidoczna.
• Należy określić najlepszy sposób
reprezentowania danych zawartych w obiekcie,
• np. wartość procentowa zamiast absolutnej,
• przeliczanie punktu na współrzędne prostokątne i
kątowe,
• wymuszenie jednoczesnego określenia obu
współrzędnych.
58. Kod obiektowy i kod proceduralny (korzystający ze struktur
danych) są uzupełniającymi się przeciwieństwami. Stosujmy
korzystniejszy dla naszego przypadku
• Kod obiektowy ukrywa dane. Oferuje zamiast nich warstwę
abstrakcji w postaci zbioru funkcji do operowania na tych danych
• Kod proceduralny składa się z procedur operujących na strukturach
danych, które to nie ukrywają danych i nie mają istotnych funkcji
• Mocną stroną kodu obiektowego jest dodawanie nowego typu
danych – tworzymy tylko nową klasę (reszty kodu nie zmieniając)
• Mocną stroną kodu proceduralnego jest dodawanie nowej operacji
– tworzymy tylko nową funkcję (reszty kodu nie zmieniając)
• Słabą stroną kodu obiektowego jest dodawanie nowej operacji
– musimy dodać ją we wszystkich klasach
• Słabą stroną kodu proceduralnego jest dodawanie nowego typu
danych – musimy dodać go we wszystkich funkcjach
59. Źle, kiedy moduł wie coś o wewnętrznej
budowie obiektów z których korzysta
• Prawo Demeter: metoda powinna korzystać jedynie z metod
bezpośrednio udostępnianych przez:
• macierzysty obiekt,
• zmienne instancyjne macierzystego obiektu,
• obiekty będące argumentami wywołania,
• obiekty samodzielnie utworzone.
• Prawo Demeter zabrania korzystać z metod niewymienionych
obiektów („rozmawiajmy z przyjaciółmi a nie obcymi”), ale nie
dotyczy struktur danych (możemy dowolnie dostawać się do ich
danych)
• Przykład: „wrak pociągu”
(wagon.getWagon1().getWagon2().getWagon3())
• Jeśli są to struktury danych, wystarczy wydzielić lokalne zmienne
• Jeśli są to obiekty, należy ukryć strukturę, udostępniając kompletne
operacje na odpowiednim poziomie. Obiekt powinien coś robić dla
nas zamiast udostępniać nam swoje szczegóły wewnętrzne
60. Unikajmy hybryd przypominających
trochę struktury danych, a trochę
obiekty
(np. zmienne publiczne lub proste gettery i
settery do zmiennych wymieszane z
metodami realizującymi operacje)
• Hybrydy mają same słabe strony – utrudniają
dodawanie nowych typów danych i operacji.
• Trzeba zdecydować, czy w naszym przypadku
bardziej potrzebna jest ochrona funkcji, czy
typów.
61. Niektóre formy struktur
danych
• Klasa nie zawierająca nic poza publicznymi zmiennymi.
• Można to nazwać obiektem transferu danych (DTO), który ma
zastosowania takie jak: komunikacja z bazą danych,
komunikacja przez sockety itp. Stosuje się je też na początku
przekształceń kodu przechodzących od surowych danych z
bazy do obiektów
• Javowy bean – zmienne prywatne z publicznymi
akcesorami i mutatorami. Fałszywa hermetyzacja
• Active Record. Zbudowane jak DTO lub beany, ale mają też
metody nawigacyjne np. „save”, „find”. Udostępniają całą
zawartość baz danych
• Nie należy umieszczać w nich operacji
• Zamiast tego można umieszczać je jako ukryte dane obiektów
udostępniających operacje