Friday, September 6, 2013

Programisty 7 grzechów głównych

Wszyscy jesteśmy grzesznikami. Jedni są nimi bardzo, inni są nimi mniej.
Również programiści mają swoje grzechy główne, za których popełnianie będą się smażyć w piekle IT :)

Grzechy wybitnie dotyczą świeżo upieczonych absolwentów kierunków informatycznych, albowiem uczelnie z tajemniczego powodu rzadko uczą zasad, które opisuję poniżej.

1.Pycha - nie stosowanie wzorców

Inżynieria oprogramowania jest pełna wzorców - sprawdzonych, nazwanych i zestandaryzowanych rozwiązań, z których składają się duże systemy informatyczne. Oczywiście zawsze cel można osiągnąć "po swojemu", jednak ryzyko popełnienia błędu jest wysokie, a nawet jeśli się uda - inni żeby je zrozumieć będą potrzebowali dużo czasu i wysiłku intelektualnego.

Wzorce najczęściej odnoszą się do programowania obiektowego (chociaż nie tylko) i można próbować je klasyfikować w następujący sposób:

  • Wzorce GRASP (skrót od General Responsibility Assignment Software Patterns) - zasady przydziału odpowiedzialności klas.
  • Wzorce GoF (skrót od Gang of Four) - w latach 90 została opublikowana książka "Design Patterns: Abstraction and Reuse of Object Oriented-Design" autorstwa E. Gamma, R. Helm, R. Johnson, J. Vlissides (stąd nazwa gang of four) która identyfikuje 26 wzorców projektowych podzielonych na 3 kategorie:
    • Wzorce konstrukcyjne - definiują i nazywają zasady tworzenia obiektów
    • Wzorce strukturalne - określają sposób łączenia ze sobą obiektów
    • Wzorce behawioralne - kategoryzują sposoby realizacji algorytmów

      Oprócz wzorców wymienionych powyżej świat IT jest pełen innych typów wzorców (wzorce architektoniczne, integracyjne, UI), ale znajomość wzorców GoF i GRASP ma absolutnie fundamentalne znaczenie w pracy programisty.

2. Chciwość - nie będę dzielił się kodem przy pomocy systemu SCM


Dawno temu wymyślono SCM (skrót od Source Code Managment). Najpierw popularnosć zdobył CVS - systemy pozwalający dzielić wspólny kod między programistów, rejesstrować historię zmian, gałęzie (odgałęzienia). Potem przyszedł SVN, który działał dużo optymalnie. Teraz popularnośc zdobyły takie rozwiązania jak GIT czy Mercurial - rozproszone systemy kontroli wersji, które działają w bardzo optymalny i wydajny sposób.

Co z tego ? Częsty jest widok grup programistów dzielących się kodem przez
pocztę elektroniczną, lub współdzielony zasób. I smutny to widok, bo mimo dobrych chęci - nadpisują sobie kod, a kolejne wersje wywołują chaos.

Tym co tego nie robili - radzę zapoznać się z systemami SCM, mimo pewnego wysiłku na zapoznanie się nowym narzędziem, jego znajomość przyniesie wkrótce korzyści.

3. Nieczystość - niestosowanie zasad Clean Code


Wyobraź sobie że zepsuł ci się rower, chcesz go naprawić i masz do dyspozycji warsztat. Usterka jest nieskomplikowana, ale po wejściu do warsztatu okazuje się że nie możesz znaleźć narzędzi bo są porozrzucane po kątach, kiedy je znajdujesz okazuje się że są brudne i uszkodzone.

Dokładnie takie mam wrażenie gdy zaglądam do kodu w projekcie przy tworzeniu którego programiści nie znali lub nie trzymali się zasad Clean Code.

Szczegółowy opis zasad znajdziesz w książce Roberta Martina "Clean Code: A Handbook of Agile Software Craftsmanship", ja opiszę najczęściej spotykane problemy:

  • Klasy składające się z wielu tysięcy linii - jeśli widzę taką klasę to mogę mieć prawie pewność, że klasa jest odpowiedzialna za wiele czynności, przez co kod jest trudny do zrozumienia. Do takiej klasy jest często wiele powiązań. W żargonie takie klasy określane są często jako "boskie klasy" - klasy które realizują wiele zadań, lub "ośmiotysięczniki" - klasy składające się z tysięcy linii. Staraj się pisać tak od klas by powstało wiele małych, dobrze nazwanych klas, realizujących swoje odpowiedzialności.
  • Nic nie mówiące nazwy klas i zmiennych. Ostatnio zajrzałem do projektu, gdzie klasy nazywały się Rot, Enc, Kod, a zmienne w środku rot, dec, ii. Czy coś wam to mówi ? Bo mi nie. Starajcie się nazywać klasy i metody tak by zadania realizowane przez nie były tak oczywiste, by nie  było konieczne analizowanie ich działania ani sięganie do dokumentacji. Wiem, że jednoliterowa nazywa metody albo zmiennej to oszczędność wciskania klawiszy przy kodowaniu, ale wszystkie współczesne IDE mają funkcję podpowiadania nazw zmiennych i wystarczy wcisnąć ctrl+spacja...
  • Brak jasnych komentarzy, brak javadoc - wiem że jeśli pracujesz przy kodzie klasy oczywiste jest dla ciebie co dzieje się w środku. Ale gwarantuję ci, jeśli wrócisz do projektu po 2 latach, nie będziesz wiedział co się dzieje w środku, a zrozumienie tego innym zajmie czasem tyle samo czasu co tobie. Pisanie dobrych komentarzy i javadoc rozwiązuje ten problem, kod jest czytelny, i świadczy o twojej wysokiej kulturze.
  • Metody i konstruktory przyjmujące wiele argumentów - znów chodzi o czytelność. Trudno domyśleć się jakimi dokładnie zasilać danymi metodę, która przyjmuje 10 argumentów, w szczególności gdy część z nich jest opcjonalna i pozwala na użycie null, a część po podstawieniu null - powoduje wyjątek. Dobrą praktyką jest tworzenie metod, które nie posiadają więcej niż 10 argumentów.
  • Fatalne formatowanie kodu - nawet dobrze napisany kod, gdy jest źle sformatowany - wygląda źle. Proponuje używanie formatterów w IDE, i skonfigurowanie tego tak, by w momencie zapisu - kod był formatowany.

4. Zazdrość - nie dzielenie się projektem przez SCM

Świat IT od lat zna rozwiązania potrafiące zapewnić możliwość pracy kilku osób nad wspólnym kodem. Jeśli okaże się że dwie osoby w tym samym czasie edytowały plik, systemy SCM potrafią rozwiązać takie konflikty automatycznie, lub wspomóc w manualnym rozwiązaniu konfliktu. Możliwe jest śledzenie zmian, cofanie się w dowolny moment czasu, tworzenie wersji kodu (odgałęzień). Kiedyś był CVS, potem SVN, teraz królują takie rozwiąznaia jak GIT i Mercurial.
Co z tego, jeśli wielu świerzo upieczonych programistów nie wie co to system kontroli wersji. I smutek wywołuje widok 5 programistów programujących w grupe, wysyłających sobie kawałki kodu mailem, albo trzymających kod na wspólnym zasobie sieciowym. Po co się męczyć, skoro są dedykowane systemy współdzielenia wersji, i tylko trzeba włożyć trochę wysiłku w ich poznanie...

5. Nieumiarkowanie w jedzeniu i piciu pisaniu na kosolę

Każdy stawaiający swoje pierwsze nieśmiałe kroki programista robi błędy. Błędy te również próbuje lokalizować.Schemat zwykle jest taki sam - po skompilowaniu naszego i uruchomieniu naszego kodu okazuje się, że uruchomiony plik binarny nie funkcjonuje w zamierzony sposób. Jakie genialne rozwiązanie nasuwa się samo ? Umieścimy w kodzie całe mnóstwo poleceń System.out.println(...), jeśli ma być kolorowo, to możemy nawet sięgnąć po Sysstem.err.println(). Zamierzony efekt zostaje szybko osiągnięty -na konsoli pojawiają się wartości zmiennych, nierzadko przeplatanych losowymi lub wulgarnymi słowami (które łatwiej pozwalają nam zlokalizować miejsce ich występowania. Teraz wyobraźcie sobie że kilku albo kilkunastu programistów spotyka się przy pracy nad projektem. Szybko konsola wypełnia się niezrozumiałymi komunikatami, trudno odróżnić moje linijki od kolegów. Log przesuwa się tak szybko że trudno orientować się co się dzieje. Co gorsza taki system może się sprzedać i później administrator serwera zastanawia się jakim cudem po 3 dniach pracy systemu plik dziennika błędów systemu ma więcej niż 1GB. Czy wymyśliłem tą sytuację ? NIE. Sam widziałem takie przypadki wiele razy, sam pisałem uparcie na konsolę.
Czy jest jakaś alternatywa ? No pewnie że jest. Nawet więcej niź jedna.
Używaj debugera wbudowanego w IDE. Java ma możliwosć świetnego debugowania. Wystarczy w twoim ulubionym IDE postawić breakpoint, twój program zatrzyma się w odpowiednim miejscu a IDE  da ci możliwość śledzenia zawartości wszystkich zmiennych i wykonywania programu krok po kroku, z możliwością podglądu aktualnie wykonywanej linijki.
Druga możliwość - skorzystaj z Loggera. Artykuł ciekawy wpis autorstwa Michała znajdziesz tutaj


6. Gniew - u innych którzy będą pracowali z twoim kodem wywoła:

  • nieumieszczanie sensownych komentarzy w kodzie
  • nieumieszczanie Javadoc-ków

7. Lenistwo - grzech nienapisanego kodu


Gdy jesteś programistą, to zwykle - wcześniej lub później będziesz miał okazję w swoim kodzie używać bazy danych.

Jak kod wyglądać powinien ?
      //1 - potrzebujemy deklaracji pól tutaj, by później można było próbować je zamknąć
        Connection conn = null;
        Statement stmt = null;
        try {
            //2 - ładowanie sterownika JDBC
            Class.forName(DB_DRIVER);
            //3 - łączenie z bazą
            conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
            //4 - wykonanie zapytania
            stmt = conn.createStatement();
            String sql = "SELECT * FROM klienci";
            ResultSet rs = stmt.executeQuery(sql);
            //5 - pobieranie wyników
            while (rs.next()) {
                int id = rs.getInt("id");
                int age = rs.getInt("wiek");
                String first = rs.getString("imie");
                String last = rs.getString("nazwisko");
                System.out.printf("%s %s\n",first,last);
            }
            //6 - obsługa wyjątków
            rs.close();
            stmt.close();
            conn.close();
        } catch (SQLException se) {
            //Obsługa błędów JDBC
            se.printStackTrace();
        } catch (ClassNotFoundException e) {
   // W przypadku nieznalezienia sterownika jdbc niewiele możemy zrobić
   e.printStackTrace();
  } finally {
            //finally block used to close resources
            try {
                if (stmt != null) {
                 //jeśli wystąpiły błędy to musimy spróbować zamknąć statement
                    stmt.close();
                }
            } catch (SQLException se2) {
             //jeśli się nie udało - nic już nie możemy zrobić
            }
            try {
                if (conn != null) {
                 //próba zamknięcia połączenia, jeśli istnieje
                    conn.close();
                }
            } catch (SQLException se) {
             //jeśli nie udało się go zamknąć - komunikat
                se.printStackTrace();
            }
        }

jak widać banalne zadanie pobrania danych z bazy wiąże się z koniecznością zadbania o zamykanie obiektów Connection, Statement. zasoby te trzeba zamykać nawet wtedy gdy pojawia się wyjątek (w sekcji finally) A najczęciej wygląda tak:
  

   // 1 - ładowanie sterownika JDBC
   Class.forName(DB_DRIVER);
   // 3 - łączenie z bazą
   Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASS);
   // 4 - wykonanie zapytania
   Statement stmt = conn.createStatement();
   String sql = "SELECT * FROM klienci";
   ResultSet rs = stmt.executeQuery(sql);
   // 5 - pobieranie wyników
   while (rs.next()) {
    int id = rs.getInt("id");
    int age = rs.getInt("wiek");
    String first = rs.getString("imie");
    String last = rs.getString("nazwisko");
    System.out.printf("%s %s\n", first, last);
   }
   // 6 - obsługa wyjątków
   rs.close();
   stmt.close();
   conn.close();
  } catch (Exception ex) {
   ex.printStackTrace();
  }

Jeśli przy pobieraniu wyników wystąpi wyjątek, to obiekt Statement nie zostanie nigdy zamknięty. W praktyce najczęściej oznacza to same problemy....


No comments :

Post a Comment

Social Buttons End POst