konto usunięte

Temat: SOLIDny kod

Prawie dwa lata temu zakończyłem serię o tym jak programować obiektowo, a ponieważ temat został tam jedynie "delikatnie" poruszony zdecydowałem się na kolejną serię wpisów. Tym razem postanowiłem przybliżyć zasady SOLID.
Mam nadzieję, że mi się to udało.

Poniżej zamieszczam link do wpisu ze spisem treści.
Jeżeli znajdziecie czas i zainteresuje Was temat to zapraszam do lektury. Czekam na opinie i krytykę :)

SOLIDny kodTen post został edytowany przez Autora dnia 12.04.14 o godzinie 12:49
Maciej R.

Maciej R. Senior Java
Developer

Temat: SOLIDny kod

Zainteresowala mnie dyskusja pod "Liskov substitution principle". Szczegolnie gafa w przedostatnim komentarzu:
class Invoice implements InvoiceInterface, AccountableInterface {
...
}
Invoice invoice = new Invoice();
if(invoice instanceof AccountableInterface)
bookSales((AccountableInterface) invoice);
Po co w ogole to rzutowanie w gore? Nigdy sie w gore nie rzutuje...

Propozycja z innego komentarza:
public void postInvoice(Invoice invoice) {
if(isAccountable()) {
Make some action here ...
}
}
public void printInvoice(Invoice invoice) {
if(isPrintable()) {
Make some action here ...
}
}
Duzo zalezy od tego co powinno sie stac, gdy warunek nie zostanie spelniony. Tutaj widze zastosowane "fail silently". Czy to wlasciwe rozwiazanie?
Rozumiem dokad autor zmierzal, ale tutaj az sie prosi o zastosowanie wzorca "command":
interface Command {
boolean isExecutable();
void execute();
}

class PostInvoiceCommand implements Command {
...
}

class PrintInvoiceCommand implements Command {
...
}

konto usunięte

Temat: SOLIDny kod

W Liskov chodzi tylko o jedno. Klasa potomna ma jedynie rozszerzać funkcjonalności klasy bazowej a nie ma prawa zmieniać jej funkcjonalności. Dobry przykład z square jako potomek rectangle

http://www.pzielinski.com/?p=423

Sprawdzenie co jest czym jest tutaj jakimś totalnym nieporozumieniem wynikającym z założeń tej zasady.

konto usunięte

Temat: SOLIDny kod

@Maćku, prawda jest taka, że zarówno rzutowanie w dół, jak i sprawdzenie typu jest wskazówką, że coś poszło nie tak w trakcie pisania kodu. Po to wykorzystujesz interfejs i deklarujesz jego użycie, żeby nie zastanawiać się wewnątrz metody nad tym, czego instancją przekazany obiekt jest.

Jeżeli jednak tak się dzieje, to tak jak napisał @Dariusz, jest to nieporozumienie i nie ma nic wspólnego z LSP.

Dobrym rozwiązaniem było to, zaproponowane w pierwszych komentarzach. Z pewnością można to zrobić inaczej (też poprawnie), ale sam też o czymś takim myślałem.Ten post został edytowany przez Autora dnia 14.04.14 o godzinie 15:23

konto usunięte

Temat: SOLIDny kod

Taki mały przykład (oczywiście klasy w osobnych plikach)



import java.io.IOException;
import java.io.FileNotFoundException;

public interface IPropertyStorage
{
void load() throws IOException, FileNotFoundException;
void save() throws IOException;
}

public IniPropertyStorage implements IPropertyStorage
{
private String filename;

public IniPropertyStorage(String filename)
{
this.filename = filename;
}

@Override
public void load() throws IOException, FileNotFoundException
{
// instrukcje odczytu
}

@Override
public void save() throws IOException
{
// instrukcje zapisu
}
}

public XMLPropertyStorage implements IPropertyStorage
{
private String filename;

public XMLPropertyStorage(String filename)
{
this.filename = filename;
}

@Override
public void load() throws IOException, FileNotFoundException
{
// instrukcje odczytu
}

@Override
public void save() throws IOException
{
// instrukcje zapisu
}

}

//i użycie tego

import java.io.IOException;
import java.io.FileNotFoundException;

public class MainClass
{
public static void main(String[] args)
{
IPropertyStorage pps = XMLPropertyStorage("C:\\settings.ini");
try
{
pps.load();
// i dalsze instrukcje
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}

// i alternatywnie

import java.io.IOException;
import java.io.FileNotFoundException;

public class MainClass
{
public static void main(String[] args)
{
IPropertyStorage pps = XMLPropertyStorage("C:\\settings.xml");
try
{
pps.load();
// i dalsze instrukcje
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
}



To tu jest jasne że niezależnie czy podepnę XML czy INI mam gwarancję że zapiszę te same dane, tylko w innym formacie. To samo jeśli chodzi o odczyt. Tylko dane będą w innym formacie. Generalnie sprawdzanie jaka instancja została podpięta nie jest chyba sposobem żeby sprawdzić czy dane są odczytywane z XML czy ini.Ten post został edytowany przez Autora dnia 14.04.14 o godzinie 16:00

konto usunięte

Temat: SOLIDny kod

Sebastian M.:
@Maćku, prawda jest taka, że zarówno rzutowanie w dół, jak i sprawdzenie typu jest wskazówką, że coś poszło nie tak w trakcie pisania kodu.


Chodzi raczej o projektowanie. OO jest o "projektowaniu / organizacji" kodu, a nie o samym kodzie.
Jarosław Żeliński

Jarosław Żeliński Analityk i
Projektant Systemów

Temat: SOLIDny kod

Maciej R.:
Po co w ogole to rzutowanie w gore? Nigdy sie w gore nie rzutuje...

ano ja także się dziwię, klasy abstrakcyjne (super klasy) służą jako budulce, korzysta się z "liści", wołanie przodka to akurat chyba antywzorzec...

tu przykład projektu z przestrzeganiem open/close i SRP, pozostałe zasady są dla mnie troszkę sztuczne...
http://it-consulting.pl/autoinstalator/wordpress/2013/...Ten post został edytowany przez Autora dnia 17.04.14 o godzinie 16:12

Temat: SOLIDny kod

W zasadzie fajne, aczkolwiek pierwsza zasada, to eliminacja ogniem i mieczem zbędnego kodu. Kodu który nic nie wnosi.

Takie kwiatki:
package com.smalaca.liskovsubstitution.invoice;

import com.smalaca.liskovsubstitution.invoice.Item;
import com.smalaca.liskovsubstitution.invoice.Person;

woła o pomstę do nieba :-)
Nie importujemy klas z tego samego pakietu, a jeżeli naprawdę zależny jestem od jakiegoś pakietu to piszę:
import com.costam.*, a nie wszystkie klasy wyliczam.

Po drugie
Mi bardzo bliska jest zasada command/query separation. Wołając metodę o coś się pytam albo coś rozkazuje. Starsi programiści i starszy kod ma tendencję to przyoszczędzenia pięciu taktów procesora i robi dwie rzeczy na raz.

Po trzecie:
Nie nadużywamy wyjątkow. Zacząłem jakiś czas temu dostrzegać fetysz w rzucaniu i wyłapywaniu wyjątków, tam, gdzie sytuacje wcale nie są jakoś szczególnie wyjątkowe.

konto usunięte

Temat: SOLIDny kod

Sylwester R.:
Po trzecie:
Nie nadużywamy wyjątkow. Zacząłem jakiś czas temu dostrzegać fetysz w rzucaniu i wyłapywaniu wyjątków, tam, gdzie sytuacje wcale nie są jakoś szczególnie wyjątkowe.

Ciekawe... Tymczasem pisząc w Eclipse i używając procedur takich jak w przedstawionym przeze mnie kodzie wyżej, aż mi się u boku same podświetlają żółto-czerwone ikonki w Eclipse, że jakiś tam wyjątek (np. IOException, FileNotFoundException, ParseException, nawet InterruptedException w przypadku zwykłego Thread.Sleep) może wystąpić.

Czy to znaczy że wszystkie należy obsługiwać w:


try
{
//........
}
catch (Exception e)
{
System.out.println(e.getMessage()); //albo logowanie do pliku
}


Jestem ciekaw jak w tym przypadku będzie wyglądała sprawa wyłapywania błędów bo jak na moje doświadczenia z androidem i zastosowanym wzorcem MVP w przypadku tych aplikacji, zapewniam że czasami można się przejechać bo trudno wyłapać co i kiedy wyrzuca wyjątek. Tym bardziej że emulator ładuje się wolno a błędy wyłapywałem logując je do plików które następnie odczytywałem później.

Więc chyba w JAVA to nie jest aż takie proste, tym bardziej że w Eclipse widzę tą zaletę, że samo podpowiada gdzie taki może wystąpić... Poza tym może być opcja różnych komunikatów w przypadku IOException, FileNotFoundException (albo nic nie jest wykonywane).Ten post został edytowany przez Autora dnia 03.05.14 o godzinie 22:35

Temat: SOLIDny kod

O wyjątkach.

Co, według mnie, nie ma sensu:

- używanie wyjątków do sprawdzenia, czy plik istnieje czy nie. Takie rzeczy lepiej robić wołając metody zwracające bool. To co można zwalidować bez wyjątku, lepiej sprawdzić bez wyjątku.

- wyłapywanie 5 typów wyjątków i w każdym robienie e.printStackTrace(). Jeżeli reakcja na każdy wyjątek jest taka sama, to nie ma po co oddzielnie ich wyłapywać.

Jeżeli wyjątku nie możemy obsłużyć (typu zrobic reconnect), to musimy się zastanowić, kto może coś naprawić – czy będzie to użytkownik, czy admin. Jeżeli admin, to lepiej zrobić duży try-catch blok obejmujący całą tranzakcję.

Patrząc na krótki kod, fajnie wyglądają wyjątki i try-catch. Tylko, że jeżeli kod jest długi a system skomplikowany, to szybko czyta się go jak film poprzecinany co 3 minuty reklamami.
Jarosław Żeliński

Jarosław Żeliński Analityk i
Projektant Systemów

Temat: SOLIDny kod

Osobiście, z powodów także innych niż samo kodowanie, skłaniam się ku tym by kontrola poprawności była "wiedzą dziedzinową" a nie błędem wyłapywanym jako błąd techniczny (w sumie jako błąd projektanta/programisty, który czegoś nie przewidział ale obłożył się czerwonymi lampkami).

Jeżeli planuję wysyłanie pliku to "świadomie" upewniam się, że mam co wysłać i gdzie wysłać, a nie czekam na alarm mówiący, że "sytuacja idealna" niestety nie miała tym razem miejsca.

Model dziedziny, powinien "modelować" sytuację wraz z jej ograniczeniami z naciskiem na ograniczenia. Moim zdaniem ideał to brak występowania "wyjątków". Innymi słowy, jeżeli jadę samochodem i nagle kończy mi się droga to nie reaguję na wyjątek a postępuję zgodnie z wyuczonym opisem jak reagować w takiej sytuacji, ta reakcja jest w "zakresie odpowiedzialności klasy" jaką jest kierowca.

konto usunięte

Temat: SOLIDny kod

Jarosław Ż.:
Osobiście, z powodów także innych niż samo kodowanie, skłaniam się ku tym by kontrola poprawności była "wiedzą dziedzinową" a nie błędem wyłapywanym jako błąd techniczny (w sumie jako błąd projektanta/programisty, który czegoś nie przewidział ale obłożył się czerwonymi lampkami).
Jeżeli planuję wysyłanie pliku to "świadomie" upewniam się, że mam co wysłać i gdzie wysłać, a nie czekam na alarm mówiący, że "sytuacja idealna" niestety nie miała tym razem miejsca.

OK, prosty przykład. Aplikacja zapisuje ustawienia do pliku XML, który rzecz jasna jest w określonym formacie. Oczywiście jest jasne, że poprawnie napisany kod zagwarantuje, że format pliku XML będzie takim jakim ma być i będzie później poprawnie odczytany.

Ale weźmy przykładowe sytuacje:

- złośliwe oprogramowanie kasuje albo zmienia ustawienia, wpisuje zupełnie co innego niż powinno być w XML, w sposób nie do przewidzenia
- ktoś przez swoje niedouczenie albo dla zabawy, dla przykładu podmienia kropki na przecinki, przez co w JAVA parser nie będzie w stanie zdekodować wczytanego stringu do float

I jak teraz poradzić sobie z takimi sytuacjami w przypadku gdy aplikacja przyjmuje nieprawidłowy plik XML, bo został przez kogoś lub jakiegoś trojana zmieniony (złośliwie lub nie) i program nie może tego poprawnie zdekodować?

Jak mam w try-catch odpowiednio:

IOException - wyświetlam komunikat że np. nie można zapisać do pliku ze względu na brak dostępu
FileNotFoundException - wyświetlam komunikat że plik nie istnieje bo może został przez przypadek skasowany
ParseException - np komunikat o dacie w nieprawidowym formacie

Mówimy tu oczywiście o sytuacjach kiedy to jakieś złośliwe oprogramowanie albo ktoś złośliwie lub dla zabawy pozmieniał dane, co prawdę mówiąc jest mało prawdopodobne ale generalnie chodzi tu tylko i wyłącznie o wyświetlenie odpowiedniego komunikatu dla użytkownika a nie o przykładową sytuację, kiedy to niespodziewanie aplikacja zostanie zatrzymana w wyniku wystąpienia nie obsługiwanych wyjątków.Ten post został edytowany przez Autora dnia 04.05.14 o godzinie 11:07

Temat: SOLIDny kod

Co do zasady Liskova, przykład z rachunkiem jest nietrafiony. Tego typu problemy lepiej rozwiązywać nie w ramach OO ale SOA. Czyli mamy obiekty-rekordy w bazie danych i obiekty-serwisy operujące na obiektach-rekordach. Invoice staje się obiektem-rekordem (domain object w groovy) i zamiast polimorfizmu, chyba lepiej zrobić enum z typem rachunku. To, czy invoice pro-forma wysyłamy do księgowej czy nie, decyduje serwis, sprawdzając typ rachunku.

Jak bym to zrobił:
w bazie danych obiekt-rekrod invoice, z typem w jednym z pól
każda operacja na obiekcie invoice to jeden serwis (stateless), z każdą operacją wiąże się też obiekt-rekord będący pamiątk po przetworzeniu obiektu invoice (np. InvoiceSendToAccountingTag, InvoiceSendToCustomerTag, InvoiceCorrectionTag).
Piotr Głudkowski

Piotr Głudkowski Rzucam się na
wszystko to, co jest
ciekawe i wymaga
rusze...

Temat: SOLIDny kod

Osobiście stosuję od wielu lat zasady następujące:
1. To, co mogę przewidzieć i sprawdzić (np. czy plik istnieje), sprawdzam "ręcznie", bez użycia wyjątków. Stosuję do tego celu metody (walidatory) z utworzonych wcześniej statycznych klas pomocniczych. W praktyce oznacza to, że takie klasy mogę sobie traktować jako swoistą "bibliotekę" i wykorzystywać je w tych projektach, w których ich potrzebuję. Ważne jest to, że metody-validatory z definicji nie rzucają wyjątków same z siebie. Zwykle zwracają bool, ewentualnie jakiś enum. W tych validatorach staram się także nie używać wyjątków.
2. Bardziej upierdliwe rzeczy, jak np. obsługa przypadku, gdy plik XML jest uszkodzony i ma zły format (mowa o formacie "fizycznym" - czyli np. sparowaniach i domknięciach tagów, a nie logicznym, czyli czy oczekiwany tag w pliku istnieje), obsługuję również w podobny sposób, ale tutaj już wewnątrz validatora zwykle stosuję wyjątki: chodzi raczej o pracochłonność - np. "ręczne" sprawdzenie formalnej poprawności pliku XML jest potwornie upierdliwe i nikt nie namówiłby mnie chyba do tego, żeby zrobić parser sprawdzający format XML-a. Oczywiście wyjątek NIGDY nie wydostaje się na zewnątrz validatora - validator zwraca bool lub enum. Dotyczy to również podsystemu FileIO - absurdem dla mnie jest obsługiwanie wyjątkiem w logice aplikacji przypadku, kiedy np. użytkownik podał (wklepał) w formatce nieprawidłową (formalnie) nazwę pliku. To jest błąd dający się przewidzieć, o dużym prawdopodobieństwie wystąpienia, i obsługiwany powinien być przez validator.
3. Prawie każda metoda w moim kodzie sprawdza z definicji poprawność otrzymanych parametrów - mam na myśli poprawność formalną, ale z punktu widzenia aplikacji. Np. jeśli metoda ma zrobić lookup na bazie danych wyszukujący usera po loginie, ZAWSZE sprawdzam czy login nie jest pusty. Albo, jeśli metoda ma dostać jako parametr instancję obiektu DTO, sprawdzam, czy nie jest ona równa null. Zwykle niepowodzenia takiego sprawdzania loguję do pliku logu ze wskazaniem metody (i klasy), w której to wyłapałem. Co więcej, nie boję się sprawdzać tego samego w różnych miejscach (np, przy zagnieżdżeniu wywołań kolejnych metod) - niby nadmiarowy kod, ale doskonale ratuje dupę i oszczędza nasz czas w tych przypadkach, kiedy to my popełnimy błąd w czasie kodowania.
3. Wyjątki stosuję jawnie i "publicznie" wszędzie tam i tylko tam, gdzie może wystąpić błąd nieprzewidywalny i całkowicie niezależny (typu brak zasobów, błąd aplikacji) - czyli w praktyce w bardzo niewielu miejscach. Po prostu wyjątków używam do obsługi sytuacji naprawdę wyjątkowych i nieprzewidywalnych.
4. Jeśli już stosuję wyjątki, staram się obsługiwać je najwcześniej, jak się da (czyli w praktyce zwykle w tej samej jeszcze metodzie, w której wyjątek wystąpił), no i oczywiście wszystkie loguję do pliku logu. Dodatkowo w takich sytuacjach często loguję wartość kluczowych zmiennych czy parametrów metody. A użytkownik dostaje jedynie informację, że określona rzecz się nie powiodła - natomiast nigdy nie pokazuję mu stack trace,a - to jest informacja dla mnie, a nie dla niego. Jedyne, co użytkownik ma ewentualnie zrobić, to przesłać mi mailem plik logu.

Te zasady bardzo dobrze się u mnie sprawdziły, ewentualne błędy mogę poprawiać zwykle jednym strzałem (mam plik logu, więc wiem co i gdzie się powaliło) - nawet, jeśli aplikacja już pracuje u endusera.
Owszem, konsekwentne stosowanie tych zasad oznacza jakieś 10...15% więcej roboty przy kodowaniu, ale to się zwraca z nawiązką podczas wdrożenia i podczas trwania okresu gwarancyjnego - czyli w tych sytuacjach, kiedy muszę pracować szybko, poza harmonogramami, no i w pewnym sensie "za darmo" (gwarancja - a kasa dawno "zjedzona").Ten post został edytowany przez Autora dnia 04.05.14 o godzinie 17:34
Jarosław Żeliński

Jarosław Żeliński Analityk i
Projektant Systemów

Temat: SOLIDny kod

Dariusz R.:
Ale weźmy przykładowe sytuacje:

- złośliwe oprogramowanie kasuje albo zmienia ustawienia, wpisuje zupełnie co innego niż powinno być w XML, w sposób nie do przewidzenia
- ktoś przez swoje niedouczenie albo dla zabawy, dla przykładu podmienia kropki na przecinki, przez co w JAVA parser nie będzie w stanie zdekodować wczytanego stringu do float
[...]

oddzielam sprawdzenie poprawności "obcych" plików XML od korzystania z jego treści, mam jedną klasę "super pewny sprawdzacz XML'eli" i reszta aplikacji w środku (wszystkie inne klasy) uznaje takie dane za zaufane... to jest zachowanie zasady pojedynczej odpowiedzialności klas.

konto usunięte

Temat: SOLIDny kod

Jarosław Ż.:
oddzielam sprawdzenie poprawności "obcych" plików XML od korzystania z jego treści, mam jedną klasę "super pewny sprawdzacz XML'eli" i reszta aplikacji w środku (wszystkie inne klasy) uznaje takie dane za zaufane... to jest zachowanie zasady pojedynczej odpowiedzialności klas.

Ta pojedyńcza odpowiedzialność to prawdę mówiąc sprawa względna. Zgodnie z Twoim rozumowaniem (w zasadzie słusznym) to można zdefiniować:

- XMLReader
- XMLWriter
- XMLValidator (Twoje podejście)

ale z drugiej strony można zaimplementować tylko XMLPropertyStorage, zaś sprawdzanie poprawności może się odbywać chociażby przez wyrzucenie (zgłoszenie wyjątku), czyli typowo: IOException (jak np. XML jest zablokowany przez inną aplikację do odczytu lub zapisu), FileNotFoundException (jeśli w ogóle nie istnieje plik który się próbuje odczytać), ParseException i tym podobne (zgłoszenie następuje w przypadku danych w nieprawidłowym formacie danych).

Idąc jednak zgodnie z tą zasadą SRP, to może dojść nawet do absurdalnych sytuacji rozbijania klasy na nie wiadomo jaką ilość mniejszych klas, z których każda ma bardzo ściśle ustalony zakres odpowiedzialności, problem polega tylko na tym że

1. taki podział to coraz więcej plików JAVA
2. także (warunkowa) definicja interfejsów (również w osobnych plikach JAVA), gdzie te klasy implementują metody interfejsu

W aplikacjach na androida stosowałem podejście wzorca MVP, modele to właśnie XMLPropertyStorage, widoki to activity, to wszystko było zarządzane przez prezenterów (bez możliwości bezpośredniego zarządzania widokami (tutaj Activity) przez modele (więc np. XMLPropertyStorage), to chyba wiki określa jako model pasywny, zarządzanie dzieje się przy udziale prezentera.

Wyjątki były obsługiwane w bloku try-except (i tu wyszczególnienie: IOException, FileNotFoundException, ParseException) w kodzie prezentera, który w tych przypadkach sterował widokiem w celu wyświetlenia komunikatu w temacie jakiego rodzaju błąd wystąpił, no i też logowanie błędów).

Jasne że XMLPropertyStorage mogłem sobie rozbić na: XMLPropertyReader, XMLPropertyWriter, XMLValidator tylko prawdę mówiąc ja się tutaj tylko zastanawiam nad tym jakie ma znaczenie takie rozbijanie na części składowe, skoro XMLPropertyStorage jako serwis, który w MVP należy zaszufladkować do modeli spełniał jasno swoje zadanie - obsługa plików XML. Widok (Activity) odpowiedzialne za wyświetlanie danych i działania na przyciskach (Event Listenery), natomiast rola prezentera była tylko i wyłącznie sprowadzona do sterowania tym. Więc też dopatruje się SRP, z tym że i w przypadku prezentera to też można by jeden rozbić na więcej (każdy implementuje metody wcześniej przygotowanego interfejsu). Jedno zasadnicze pytanie: jakie są korzyści z takiego komplikowania sprawy (więcej plików po których w razie czego trzeba latać a jak się otwierają w zakładkach edytora to czasami nie jest zbyt wygodne), w przypadku małych aplikacji na androida?Ten post został edytowany przez Autora dnia 04.05.14 o godzinie 19:49
Piotr Głudkowski

Piotr Głudkowski Rzucam się na
wszystko to, co jest
ciekawe i wymaga
rusze...

Temat: SOLIDny kod

Dariusz R.:

- XMLReader
- XMLWriter
- XMLValidator (Twoje podejście)

ale z drugiej strony można zaimplementować tylko XMLPropertyStorage, zaś sprawdzanie poprawności może się odbywać chociażby przez wyrzucenie (zgłoszenie wyjątku), czyli typowo: IOException (jak np. XML jest zablokowany przez inną aplikację do odczytu lub zapisu), FileNotFoundException (jeśli w ogóle nie istnieje plik który się próbuje odczytać), ParseException i tym podobne (zgłoszenie następuje w przypadku danych w nieprawidłowym formacie danych).

/..../
Jasne że XMLPropertyStorage mogłem sobie rozbić na: XMLPropertyReader, XMLPropertyWriter, XMLValidator tylko prawdę mówiąc ja się tutaj tylko zastanawiam nad tym jakie ma znaczenie takie rozbijanie na części składowe /.../

W zasadzie wszystko, co komplikuje, nie ma sensu, z jednym wyjątkiem: komplikowanie ma sens, jeśli bierzemy pod uwagę późniejsze utrzymanie i modyfikowanie kodu - w szczególności przez inną osobę/zespół/firmę/whatever.

Jeśli traktujemy naszą aplikację jako dzieło skończone, do którego nikt inny nie ma prawa się dotknąć, to piszmy sobie, jak chcemy - najwyżej kiedyś będziemy mieli trudność ze zrozumieniem samych siebie (znam to z autopsji).

Ale jeśli tworzymy system w ramach działalności biznesowej, róbmy tak, jak byśmy chcieli, aby inni nam robili - to się opłaci. I tutaj wszelkie uporządkowanie kodu, rozdzielenie go na dobrze zdefiniowane (!!!!!) części składowe zawsze ma znaczenie - bo robiąc później zmianę (Ty albo nie Ty) trafiasz szybko w te miejsca, które trzeba zmienić.

Muszę tutaj wspomnieć o opozycyjnym podejściu, które kiedyś usłyszałem: "rób tak, żeby nikt inny po tobie nie umiał tego zmienić/poprawić". Odrzucając aspekty moralne i ideologiczne takiej pracy: kwestią czasu jest to, kiedy sami się wyłożymy na swoim kodzie, nie rozumiejąc go zupełnie :)

konto usunięte

Temat: SOLIDny kod

Piotr G.:
Jeśli traktujemy naszą aplikację jako dzieło skończone, do którego nikt inny nie ma prawa się dotknąć, to piszmy sobie, jak chcemy - najwyżej kiedyś będziemy mieli trudność ze zrozumieniem samych siebie (znam to z autopsji).

Ale jeśli tworzymy system w ramach działalności biznesowej, róbmy tak, jak byśmy chcieli, aby inni nam robili - to się opłaci. I tutaj wszelkie uporządkowanie kodu, rozdzielenie go na dobrze zdefiniowane (!!!!!) części składowe zawsze ma znaczenie - bo robiąc później zmianę (Ty albo nie Ty) trafiasz szybko w te miejsca, które trzeba zmienić.

Żeby jeszcze zrozumienie kodu który ktoś napisał wcześniej było takie proste... Ale skoro już mowa o rozdzielaniu na części składowe (zacieśnianie zakresu odpowiedzialności), weźmy taki przykład. Ogólnie dostępna biblioteka FANN (sztuczne sieci neuronowe MLP), nie wiem jak tam jest w JAVA ale np. jeśli chodzi o DELPHI, to jest dostępna w postaci tylko jednego komponentu który zawiera w sobie:

- mechanizm uczenia
- zapis wyszkolonej sieci do pliku
- odczyt danych z pliku
- obliczenia (odpowiedź na sygnał wejściowy)

Wszystko w ramach jednego tylko komponentu. I nie powiedziałbym żeby korzystanie z niej było przez to jakieś specjalnie trudne. Są też rozwiązania w postaci podziału na:

- data reader (odczyt zestawów: trenignowy, walidacyjny i testowy, przyrostowy i okienkowy zestaw danych do celów uczenia)
- właściwa klasa obrazująca sieć MLP
- trener (przyjmuje w konstruktorze klasę sieci, działając na wzór wzorca dekorator)

Oczywiście trener może mieć w sobie kilka znanych algorytmów treninigu, więc wybieranie odpowiedniego odpowiedniego może być przez właściwość enumeryczną, alternatywne rozwiązanie to ISupervisedLearning i klasy implementujące ten interfejs np. BackPropagationLearning lub ResilientPropagationLearning.

Ja to wszystko rozumiem, tylko że na podstawie wielu ogólnie dostępnych już rozwiązań open source, które z powodzeniem się wykorzystuje w praktyce, za nic nie jestem w stanie dopatrzeć się jednolitego standardu, bo zgodnie z tym rozumowaniem, to żeby można było mówić o sytuacji, kiedy to kolejny programista mógł bez problemu zrozumieć to co stworzył poprzedni, to niestety należałoby zachować ścisłe standardy, których trzymaliby się wszyscy a tak niestety nie jest.
Jarosław Żeliński

Jarosław Żeliński Analityk i
Projektant Systemów

Temat: SOLIDny kod

Dariusz R.:
zgodnie z tym rozumowaniem, to żeby można było mówić o sytuacji, kiedy to kolejny programista mógł bez problemu zrozumieć to co stworzył poprzedni, to niestety należałoby zachować ścisłe standardy, których trzymaliby się wszyscy a tak niestety nie jest.

niestety ... ale to moim zdaniem jak z wypluwaniem gumy do żucia na chodnik: czemu tego nie robię skoro tych gum na chodniku jest masa i wielu ludzi wypluwa je na chodnik? Ja nie wypluwam, bo jak ktoś wdeptuje w taka gumę i myśli sobie: "znowu jakiś kretyn wypluł gumę na chodnik" to wiem, że nie myśli o mnie ;)

a tak poważnie, to od programistów wiem, że wiele frameworków i bibliotek nie trzyma się SOLID bo są pisane nadal w starym strukturalnym podejściu ....ale powolli, powoli... ;) Ten post został edytowany przez Autora dnia 04.05.14 o godzinie 21:11
Jarosław Żeliński

Jarosław Żeliński Analityk i
Projektant Systemów

Temat: SOLIDny kod

Dariusz R.:
1. taki podział to coraz więcej plików JAVA

wiem, ale to argument "piszącego to programisty" a nie mój, jako osoby odpowiedzialnej za cały cykl życia wytworzenia aplickaji a nie tylko za samo jej powstanie... ;)

2. także (warunkowa) definicja interfejsów (również w osobnych plikach JAVA), gdzie te klasy implementują metody interfejsu

jak wyżej....

to zawsze jest i pewnie będzie kompromis...

Wracając do przykładu XML, całą wiedzę o XML (składnia itp...) wrzucił bym do jednej klasy z kilkoma operacjami (analogicznie jak we jednej klasie trzymam motor tworzenia plików PDF). Tu niestety pojawia się problem (o ile wiem) z wieloma frameworkami, które "nie trzymają się SOLID" ... i pewnie dlatego wielu programistów modyfikuje je a nie raz tworzy własne....



Wyślij zaproszenie do