SlideShare a Scribd company logo
1 of 62
Zarządzanie kodem projektu
  Tworzenie i utrzymywanie
       czystego kodu
                             Paweł Stroiński, 01.2011
                                          Pabloware
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
Wprowadzenie
• Rola czystego kodu w
  zarządzaniu projektem
  informatycznym
• Książka
• Zalety tej prezentacji w
  porównaniu do książki
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ć.”
Książka

• Czysty kod. Podręcznik dobrego programisty
• Robert C. Martin
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
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
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
przedstawiają intencje


                     int c;

               int studentsCount;



      List<int> list1 = new List<int>();

  List<int> temperatureValues = new List<int>();
bez mylących i fałszywych
        wskazówek
     int hp; // hiperprzelicznik




       Student[] studentList;

      List<Student> studentList;


       List<Student> students;

         Student[] students;
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
są spójne i dobrze się
     sortują w IDE
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";
          }
ł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)
bez kodowania typów




int securityCodeString; //kiedyś ta zmienna może i była typu string
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;
    }                               }
}                               }
bez kodowania interfejsów (lepszy
    przyrostek „Imp”/”Impl”)



           interface IComputerStudent


           interface ComputerStudent

  class ComputerStudentImp : ComputerStudent
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++)
nazwy klas mają rzeczowniki

                   class Course

                class CourseAdvisor


                   class Advise

                   class Advice



• Unikamy mowy-trawy jak np. „Manager”,
  „Processor”, „Data”, „Info”
               class CourseProcessor
nazwy metod mają czasowniki

                   counter.submit();



• „get” przed akcesorami, „set” przed
  mutatorami, „is” przed predykatami
  if (course.isCompleted())
      totalCompletedHours += course.getTotalHours();
  counter.setTotal(totalCompletedHours);
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ź");
dowcipne nazwy mogą nie być
         zrozumiane
spójny leksykon – jedno
 słowo na zagadnienie


  if (ListenerManager.retrieveCurrent()
         == StudentController.getActual())
bez przesady ze spójnością
– jedno zagadnienie na słowo



              course.title = "C#";


       student.title = "Wiesław Czyżewski";
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ą
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
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();
bez nadmiarowego kontekstu jako wspólnego
     przedrostka na aplikację/moduł



              class XYZAPPStudent

              class XYZAPPCourse



             class StudentAddress

             class LecturerAddress

             class Address
zmiana nazwy na czytelniejszą
   to nic złego (refactoring)

• I tak korzystamy z podpowiedzi IDE nie
  pamiętając większość nazw
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
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
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)
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
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ę
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?
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();
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
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
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
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”!
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
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
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
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
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
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
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
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
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
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
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
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
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
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ę
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ć
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
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.
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
Ź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
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.
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
Zakończenie
• Pytania
• Dziękuję za uwagę 

More Related Content

Viewers also liked

Strategia BRE Banku na lata 2010-2012
Strategia BRE Banku na lata 2010-2012Strategia BRE Banku na lata 2010-2012
Strategia BRE Banku na lata 2010-2012Wojciech Boczoń
 
Leki dermatologiczne pochodzenia roślinnego
Leki dermatologiczne pochodzenia roślinnegoLeki dermatologiczne pochodzenia roślinnego
Leki dermatologiczne pochodzenia roślinnegoHania Dolata
 
Zarządzanie ryzykiem - prezentacja XXIII Promocji KSAP
Zarządzanie ryzykiem - prezentacja XXIII Promocji KSAPZarządzanie ryzykiem - prezentacja XXIII Promocji KSAP
Zarządzanie ryzykiem - prezentacja XXIII Promocji KSAPPaweł Pietrzak
 
Budownictwo mieszkaniowe w gdansku
Budownictwo mieszkaniowe w gdanskuBudownictwo mieszkaniowe w gdansku
Budownictwo mieszkaniowe w gdanskuCity of Gdansk
 
Scalone dokumenty (19)
Scalone dokumenty (19)Scalone dokumenty (19)
Scalone dokumenty (19)Darek Simka
 
Clean Code - Design Patterns and Best Practices at Silicon Valley Code Camp
Clean Code - Design Patterns and Best Practices at Silicon Valley Code CampClean Code - Design Patterns and Best Practices at Silicon Valley Code Camp
Clean Code - Design Patterns and Best Practices at Silicon Valley Code CampTheo Jungeblut
 

Viewers also liked (11)

Strategia BRE Banku na lata 2010-2012
Strategia BRE Banku na lata 2010-2012Strategia BRE Banku na lata 2010-2012
Strategia BRE Banku na lata 2010-2012
 
Leki dermatologiczne pochodzenia roślinnego
Leki dermatologiczne pochodzenia roślinnegoLeki dermatologiczne pochodzenia roślinnego
Leki dermatologiczne pochodzenia roślinnego
 
Zarządzanie ryzykiem - prezentacja XXIII Promocji KSAP
Zarządzanie ryzykiem - prezentacja XXIII Promocji KSAPZarządzanie ryzykiem - prezentacja XXIII Promocji KSAP
Zarządzanie ryzykiem - prezentacja XXIII Promocji KSAP
 
Solid vs php
Solid vs phpSolid vs php
Solid vs php
 
Geografia usa
Geografia usaGeografia usa
Geografia usa
 
Budownictwo mieszkaniowe w gdansku
Budownictwo mieszkaniowe w gdanskuBudownictwo mieszkaniowe w gdansku
Budownictwo mieszkaniowe w gdansku
 
2
22
2
 
Scalone dokumenty (19)
Scalone dokumenty (19)Scalone dokumenty (19)
Scalone dokumenty (19)
 
Clean Code - Design Patterns and Best Practices at Silicon Valley Code Camp
Clean Code - Design Patterns and Best Practices at Silicon Valley Code CampClean Code - Design Patterns and Best Practices at Silicon Valley Code Camp
Clean Code - Design Patterns and Best Practices at Silicon Valley Code Camp
 
Podstawy bhp 1
Podstawy bhp 1Podstawy bhp 1
Podstawy bhp 1
 
Prelekcja15b
Prelekcja15bPrelekcja15b
Prelekcja15b
 

Similar to Tworzenie i utrzymywanie czystego kodu

Design principles 4 hackers - tech3camp (28142014)
Design principles 4 hackers - tech3camp (28142014)Design principles 4 hackers - tech3camp (28142014)
Design principles 4 hackers - tech3camp (28142014)Jakub Marchwicki
 
Więcej testów/mniej kodu - Michał Gaworski, kraQA 13
Więcej testów/mniej kodu - Michał Gaworski, kraQA 13Więcej testów/mniej kodu - Michał Gaworski, kraQA 13
Więcej testów/mniej kodu - Michał Gaworski, kraQA 13kraqa
 
[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariować[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariowaćJakub Marchwicki
 
Metaprogramowanie w JS
Metaprogramowanie w JSMetaprogramowanie w JS
Metaprogramowanie w JSDawid Rusnak
 
Nie tylko C# - Ekosystem Microsoft dla programistów
Nie tylko C# - Ekosystem Microsoft dla programistówNie tylko C# - Ekosystem Microsoft dla programistów
Nie tylko C# - Ekosystem Microsoft dla programistówintive
 
Techniki kompilacji
Techniki kompilacjiTechniki kompilacji
Techniki kompilacjiPiotr B
 
Jak zacząć, aby nie żałować - czyli 50 twarzy PHP
Jak zacząć, aby nie żałować - czyli 50 twarzy PHPJak zacząć, aby nie żałować - czyli 50 twarzy PHP
Jak zacząć, aby nie żałować - czyli 50 twarzy PHPPiotr Horzycki
 
Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)
Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)
Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)Codesushi.co (CODESUSHI LLC)
 
Czym jest złożoność ?
Czym jest złożoność ?Czym jest złożoność ?
Czym jest złożoność ?GOG.com dev team
 
Angular 4 pragmatycznie
Angular 4 pragmatycznieAngular 4 pragmatycznie
Angular 4 pragmatycznieSages
 
Zwiększanie produktywności programisty php (v2)
Zwiększanie produktywności programisty php (v2)Zwiększanie produktywności programisty php (v2)
Zwiększanie produktywności programisty php (v2)adamhmetal
 
Zwiększanie produktywności programisty php
Zwiększanie produktywności programisty phpZwiększanie produktywności programisty php
Zwiększanie produktywności programisty phpadamhmetal
 
Let's Go! - wprowadzenie do Go
Let's Go!  - wprowadzenie do GoLet's Go!  - wprowadzenie do Go
Let's Go! - wprowadzenie do GoRafal Piekarski
 
Od codziennej higieny do strategicznej refaktoryzacji
Od codziennej higieny do strategicznej refaktoryzacjiOd codziennej higieny do strategicznej refaktoryzacji
Od codziennej higieny do strategicznej refaktoryzacjiMichał Bartyzel
 
Open Xml Translator - czyli o oswajaniu formatow
Open Xml Translator - czyli o oswajaniu formatowOpen Xml Translator - czyli o oswajaniu formatow
Open Xml Translator - czyli o oswajaniu formatow3camp
 
PSR czyli dobre praktyki programistyczne
PSR czyli dobre praktyki programistycznePSR czyli dobre praktyki programistyczne
PSR czyli dobre praktyki programistyczneAurora Creation
 

Similar to Tworzenie i utrzymywanie czystego kodu (20)

Design principles 4 hackers - tech3camp (28142014)
Design principles 4 hackers - tech3camp (28142014)Design principles 4 hackers - tech3camp (28142014)
Design principles 4 hackers - tech3camp (28142014)
 
Clean code w Ruby
Clean code w RubyClean code w Ruby
Clean code w Ruby
 
Więcej testów/mniej kodu - Michał Gaworski, kraQA 13
Więcej testów/mniej kodu - Michał Gaworski, kraQA 13Więcej testów/mniej kodu - Michał Gaworski, kraQA 13
Więcej testów/mniej kodu - Michał Gaworski, kraQA 13
 
[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariować[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariować
 
Metaprogramowanie w JS
Metaprogramowanie w JSMetaprogramowanie w JS
Metaprogramowanie w JS
 
Nie tylko C# - Ekosystem Microsoft dla programistów
Nie tylko C# - Ekosystem Microsoft dla programistówNie tylko C# - Ekosystem Microsoft dla programistów
Nie tylko C# - Ekosystem Microsoft dla programistów
 
Techniki kompilacji
Techniki kompilacjiTechniki kompilacji
Techniki kompilacji
 
DSL - DYI
DSL - DYIDSL - DYI
DSL - DYI
 
Jak zacząć, aby nie żałować - czyli 50 twarzy PHP
Jak zacząć, aby nie żałować - czyli 50 twarzy PHPJak zacząć, aby nie żałować - czyli 50 twarzy PHP
Jak zacząć, aby nie żałować - czyli 50 twarzy PHP
 
Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)
Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)
Warsztaty: Podstawy PHP - część 2 - omówienie składni języka PHP (wersja 7)
 
Czym jest złożoność ?
Czym jest złożoność ?Czym jest złożoność ?
Czym jest złożoność ?
 
Angular 4 pragmatycznie
Angular 4 pragmatycznieAngular 4 pragmatycznie
Angular 4 pragmatycznie
 
Open Power Template
Open Power TemplateOpen Power Template
Open Power Template
 
Zwiększanie produktywności programisty php (v2)
Zwiększanie produktywności programisty php (v2)Zwiększanie produktywności programisty php (v2)
Zwiększanie produktywności programisty php (v2)
 
Zwiększanie produktywności programisty php
Zwiększanie produktywności programisty phpZwiększanie produktywności programisty php
Zwiększanie produktywności programisty php
 
Let's Go! - wprowadzenie do Go
Let's Go!  - wprowadzenie do GoLet's Go!  - wprowadzenie do Go
Let's Go! - wprowadzenie do Go
 
Od codziennej higieny do strategicznej refaktoryzacji
Od codziennej higieny do strategicznej refaktoryzacjiOd codziennej higieny do strategicznej refaktoryzacji
Od codziennej higieny do strategicznej refaktoryzacji
 
Open Xml Translator - czyli o oswajaniu formatow
Open Xml Translator - czyli o oswajaniu formatowOpen Xml Translator - czyli o oswajaniu formatow
Open Xml Translator - czyli o oswajaniu formatow
 
Refaktoryzacja
RefaktoryzacjaRefaktoryzacja
Refaktoryzacja
 
PSR czyli dobre praktyki programistyczne
PSR czyli dobre praktyki programistycznePSR czyli dobre praktyki programistyczne
PSR czyli dobre praktyki programistyczne
 

Tworzenie i utrzymywanie czystego kodu

  • 1. Zarządzanie kodem projektu Tworzenie i utrzymywanie czystego kodu Paweł Stroiński, 01.2011 Pabloware
  • 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ć.”
  • 5. Książka • Czysty kod. Podręcznik dobrego programisty • Robert C. Martin
  • 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
  • 12. są spójne i dobrze się sortują w IDE
  • 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)
  • 15. bez kodowania typów int securityCodeString; //kiedyś ta zmienna może i była typu string
  • 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ź");
  • 22. dowcipne nazwy mogą nie być zrozumiane
  • 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