Uwaga, blog przeniesiony

Posty na tym blogu już nie będą się pojawiać. Zapraszam gorąco pod nowy adres: blog.grzegorzpawlik.com



Subskrybuj ten blog...

środa, 13 maja 2009

Przenosiny...

Przez dłuższy czas nie pojawiało się nic na tym blogu, a to z uwagi na to, że zajęty byłem przeniesieniem postów do nowej lokalizacji.

Głównie z uwagi na brak "syntaks hajlajting" oraz przez to, że mój ulubiony hosting jest coraz bardziej stabilny zdecydowałem się na wordpressa.

Zapraszam wszystkich do nowej lokalizacji:
blog.grzegorzpawlik.com

Rss'ami* się nie martwcie - dzięki Feedburner przepnę kanał do nowej lokalizacji ;)

(*)podobno mam już 8 subskrybentów kanału rss :D

wtorek, 21 kwietnia 2009

Nowe odkrycie - Phing

Zawsze myślałem, że z uwagi na to iż PHP jest językiem skryptowym wszysto co jest związane z procesem budowania i kompilacji - nie dotyczy PHP właśnie...

Tzn. zawsze wiedziałem, że kompilacja się odbywa podczas wykonywania kodu, więc mnie nie zajmowała. Klawisz F9 z Delphi czy BorlandBuildera zastąpiony został przez klawisz F5 w przeglądarce ;)

Jeśli chodzi o budowanie (Crtl+F9 zdaje się ;)) to wiedziałem, że zawarta jest w tym procesie. Reszta mnie nie interesowała, więc osunąłem się w słodkie objęcia ignorancji.

Czytając książki sugerujące stosowanie systemów ciągłej integracji (CI) i konsolidacji myślałem sobie, że spoko, fajnie by było, ale jednak w PHP nie ma czegoś takiego jak integracja i konsolidacja.

Nie mogłem bardziej się mylić ;) Dopiero przy trzeciej książce, w której wspomniana jest CI mnie olśniło:
Budowanie aplikacji to cały proces, który zmienia czysty kod z repozytorium w działającą aplikację!

A zatem obejmuje stworzenie bazy danych o odpowiedniej strukturze(*), ustawienie odpowiednich uprawnień do katalogów, które tego wymagają, wypełnienie bazy danymi startowymi i wszystkie inne czynności wymagane do poprawnego używania aplikacji.

Niezwykle ważne jest, aby móc cały ten proces zautomatyzować. Jedna z rzeczy jakich nauczyłem się podczas mojego, dwuletniego już, stażu w zawodzie to:
Jeśli jest do wykonania skończony ciąg operacji oraz
informacja o tych operacjach zapisana jest w pamięci ludzkiej
to
istnieje skończona, stosunkowo mała, liczba powtórzeń ciągu tych operacji, że
przynajmniej jedna z operacji nie zostanie wykonana.

Nazywę tą regułę "Zasadą zapominania". Dlatego warto zautomatyzować ten proces przy pomocy chociażby narzędzia Phing (jest to php'owy odpowiednik Ant'a).

Możliwe, że niedługo napisze Wam co i jak z tym Phing.

* Nigdy nie wiedziałem dlaczego przechowywanie struktury bazy w repozytorium jest moją obsesją. Widać podświadomie czułem, że kiedyś będę używał Project Build System(s).

środa, 15 kwietnia 2009

Dlaczego TRAC + SVN

W firmie używamy FlySpray'a. Jest to niezły system, ale na hostingu, który udostępnia mi SVN jest zintegrowany z nim trac, więc siłą rzeczy go używam.

Jest on bardziej "toporny" na pierwszy rzut oka (choć bugzilli nic nie pokona), ale ostatnio zauważyłem w nim bajer, którego nic nie pokona:

Zatwierdzając zmiany do repozytorium, gdy użyję w komentarzu #[id_ticketa] - pojawi się w komentarzach dla tego zgłoszenia:

Fajne, nie? Mnie się podoba... ciekawe, czy da radę kolejną rewolucję w firmie wywołać? ;)

środa, 8 kwietnia 2009

Działanie 8.1 i 8.2: Dlaczego warto dwa razy się zastanowić zanim się na nie rzucimy

O tej porze roku - prawdziwy urodzaj. Wszyscy razem z wiosną budzą się i piszą projekty o unijne dotacje. Te o których myślę to działania 8.1 i 8.2 - projekty, których osią często jest szeroko rozumiany "system informatyczny".

W tym artykule chciałbym zwrócić uwagę na ukryte problemy, których możesz nie być świadom pisząc projekt z myślą o tym, żeby po akceptacji zlecić go zewnętrznej firmie budującej systemy informatyczne.

Przede wszystkim specyfika wsparć unijnych wymaga, aby taki system został w (niemal) 100% wyspecyfikowany. To jest wymóg szkodliwy, zakładający że
  • klient dokładnie wie czego mu potrzeba
  • potafi doskonale wyartykuować swoje myśli
  • analityk w lot złapie o co klientowi chodzi i...
  • ... idealnie zamodeluje te wymagania
Takie rzeczy możliwe są co najwyżej w przyszłowiowej "erze" (czyt. w reklamie i marketingu). Każdy kto miał styk z wytwarzaniem oprogramowania w stylu wodospadowym (dokładnie takim jaki wymusza UE) wie, że to bardzo rzadko jest możliwe.

Wykorzystując analogię budowlaną, gdyby projektem był dom, to musiałbyś najpierw móc opisać słowami jaki dom potrzebujesz. Nie zamówiłbyś u architekta szkicu (prototypowanie!) bo masz nadzieję, że szkic upchniesz w całym projekcie i zapłaci Ci za niego unia. Wszystko co możesz to powiedzieć co chcesz móc robić w tym domu:
spać, kąpać się, zjeść obiad, zaparkować samochód, oglądnąć telewizję, urządzić przyjęcie.
Czy z czymś takim poszedłbyś do firmy budowlanej, żeby oszacowali koszty zbudowania takiego domu?
Niech masz wstępnego projektu, bo zapłaci za to Unia. Nie masz żadnego szkicu i chcesz do planu dodać jakieś szacowanie, bo Unia wymaga, żeby na samym początku podać kwoty.

Z powyższej listy, można wywnioskować, że potrzebna Ci kuchnia (chcesz jeść), salon (tv + przyjęcie), garaż, sypialnia, łazienka. Doświadczony wykonawca domyśli się, że potrzebujesz też toalety mimo iż na liście "zrobić siku" zapomniałeś umieścić. Ale nie domyśli się, że salon powinien mieć przynajmniej 200m bo przyjęcia chcesz dla 100 osób organizować. Nie wie ile tych sypialni. Czy w ogóle chcesz ogród? Może ogrzewanie? Nie wspomniałeś nic o oknach, ani drzwiach.

Przejdźmy dalej. Kolejną wadą projektów wspieranych przez Unię jest to, że Unia za to płaci. Tym razem będzie analogia zakupowa. Wyobraź sobie, że idziesz do sklepu na zakupy i:
  1. bierzesz 500zł własnej ciężko zarobionej krwawicy
  2. ktoś daje Ci 500zł i to co wydasz to Twoje, czego nie wydasz - musisz oddać
Zagadka: Które zakupy zaowocują zakupem naprawdę potrzebnych rzeczy?
Dokładnie tak samo jest z "systemem informatycznym". Gdy Ty za niego płacisz - jesteś zainteresowany funkcjonalnościami, które naprawdę zwiększą Twoją wydajność. Gdy płaci Unia - upychasz tam ile się da bo "a nuż się przyda". Najważniejszym problemem w tym wypadku jest to, że takie upychanie kilkuktornie komplikuje projekt. A to z kolei
  • zwiększa koszt całego projektu (tym się nie przejmujemy, bo unia płaci),
  • implikuje jeszcze mniej dokładne niż "wróżenie z fusów" szacowanie (ryzykujemy obsówę projekty - Unia może nie zapłacić, ale nie przejmujemy się, bo w umowie załączymy kary umowne i wykonawca zapłaci)
  • skutkuje skomplikowanym do wdrożenia systemem, pełnym niepotrzebnych funkcji, który muszą opanować Twoi pracownicy (robi się nieprzyjemnie, bo za to Unia nie zwraca, płacisz tylko Ty)
Dlatego warto się zastanowić, czy zwyczajnie warto. Czy na prawdę chcesz mieć Wielką Kobyłę za 150 tysięcy złotych, pełną niepotrzebnych funkcji, która była robiona w pośpiechu i przez to nikt nie chce nawet zaglądnąć do jej kodu, żeby cokolwiek zmodernizować? Czy chcesz czekać na Wielką Kobyłę za 150 tysięcy półtorej roku i kiedy dostaniesz ją do testów (sic!) wiesz, że otoczenie biznesowe i konkurencja zmieniły się na tyle, że przydało by się mieć juz coś lepszego?

Alternatywą jest system mniejszy. Zawierający 10% funkcjonalności Wielkiej Kobyły. Ale są to te rzeczy, które zwiększają Twoją wydajność (a co za tym idzie konkurencyjność) na przykład trzykrotnie. Może zapłacisz za nie więcej niż 10% ceny Wielkiej Kobyły i z własnej korzyści, ale spójrz na stosunek cena/korzyści. A co jeśli dodam do tego taki bajer:
Po dwóch miesiącach Twoi pracownicy uzywają systemu. Oni stają się specjalistami w zakresie jego obsługi i mogą współpracować przy jego rozwoju. Na pewno będą miec świetne pomysły, aby nieco ulepszyć działanie programu znów poważnie zwiększając swoją wygodę (wydajność!) pracy.
Przypomnij sobie ile razy używając Standardowego Programu (MsExcell, Outlook, Firefox etc.) miałeś poczucie "Kurcze, gdyby zmienić tutaj to i to, to bym się mniej naklikał przy tych codziennych ... (raportach, analizach, wstaw cokolwiek)".

To są rzeczy, których nie przewidzisz projektując Wielką Kobyłę.

Dlatego zamiast liczyć złotówki (150tys.) których nie musiałeś wydawać, policz korzyści których nie udało Ci się osiągnąć. Wiem, że to boli bardziej i nie jest takie proste. Nie mam pojęcia czy Twoja księgowa to potrafi ;).

Możesz też spróbować odpowiedziec na pytanie: Czy na przyjęciu/konferencji/piwku z ludźmi z branży wolisz:
a\ Opowiedzieć o tym jak to ostatnio wdrożyliście Customer Relationship Management Entenrprise Edition (nazwa kodowa Wielka Kobyła) za 150 tysięcy złotych, podciągnąć spodnie, odchrząknąć, a potem obwąchać pobliską latarnię i zaznaczyć swoje terytorium*
b\ Pochwalić się jak udało Wam się zwiększyć wydajność jednego z działów trzykrotnie za mniej niż miesięczną pensję dla tego działu. Polecić ekipę, z którą naprawdę dobrze się Wam pracowało. Następnie wrócić tego wieczoru do domu z długonogą menadżerką (ew. wspaniale umięśnionym, przystojnym menadżerem) spragnioną szczegółów na temat tego projektu.

Na zakończenie: pamiętaj, że ekonomia to coś więcej niż rachunkowość. Więc przestań liczyć tylko dutki na koncie.

(*)wiem, odpowiedzi są tendencyjne, ale to nie jest nielegalne ;)

środa, 1 kwietnia 2009

SVN i zarządzanie wersjami (cykl pracy)

Przy okazji ostatniego projektu udało nam się wypracować dobrze spisujący się proces wgrywania poprawek do działających serwisów. Chciałbym się nim z Wami podzielić...

Oczywiście korzystamy z SVN'a na jego mechanizmach ten proces został oparty.
Zasada jest dość prosta: kolejne działające wersje to tagi. A żeby kopię roboczą ustawić na nowy tag wykonujemy tak zwany switch.

Jedynym problemem była kwestia poprawek, które należy na przykład wprowadzić bezpośrednio w prodykcyjnej wersji systemu. Pomysł, aby każdą taką poprawkę wprowadzić najpierw do repozytorium, a następnie update wersji jest zbyt kosztowny. Z kolei wprowadzenie tych poprawek na wersji produkcyjnej często owocował wieloma konfliktami, które są bardzo nieporządane z uwagi na to, że serwis jest ogólnodostępny i priorytetem jest jego dostępność.

Rozwiązaniem jest następująca zasada:
Możesz zawsze dokonać poprawki na wersji produkcyjnej. Jednak, żeby poprawka była trwała - musi być niezależnie wprowadzona do repozytorium (commit do trunk). Poprawki dokonane tylko w produkcyjnej kopii roboczej zostaną najprawdopodobniej utracone przy następnym uaktualnieniu.

W takim wypadku przed switch'em wystarczy wykonać polecenie revert, np
svn revert ./ && svn sw https://moje.repozytorium.com/tags/beta-1
Lokalne zmiany są wycofywane, a kopia robocza zostaje przełączona na wersję beta-1.

W takim podejściu do problemu update'y zajmują od 3 do 10 minut, co jest akceptowalne.

środa, 25 marca 2009

Zarządzanie wersjami STRUKTURY bazy danych w cakePHP 1.2

W poprzednich postach (m.in. zarządzanie wersjami oprogramowania) udało mi się nakreślić problem przy zarządzaniu oprogramowaniem pojawiający się na styku kod-baza danych. Nawet mogę powiedzieć, że mały sukces na tym polu odnotowałem przy pomocy ImageBehavior, jednak jeśli chodzi o strukturę - ciągle zmagałem się do tej pory z przeciwnościami.

Jednak okazuje się, że cake w nowym wydaniu wychodzi nam na przeciw razem z klasą Schema, oraz z narzędziem konsolowym ./cake schema ... po krótce opowiem o co chodzi.

Zabawę z tym narzędziem najlepiej zacząć mając już jakiś zalążek aplikacji (tabele + modele). Jeśli sprawiamy ten podstawowy warunek możemy wpisać w konsoli ./cake schema generate ... ot tak, dla jaj.

Następnie możemy się w katalogu app/config/sql/ namierzyć plik schema.php. To właśnie artefakt wygenerowany przez nas przed sekundą. Można w celach samorozwojowych zajrzeć do środka...

Jednak ciekawe rzeczy dzieją się, kiedy ponownie wywołamy to samo polecenie: otóż cake rezolutnie zauważy, że plik schema.php już istnieje i zapyta nas co dalej. Polecam wybór opcji [S]napshot i ponowny rzut oka do wspomnianego wyżej katalogu. Co widzimy? Dokładnie! Nowy plik o nazwie schema_2.php :D Zachęcam do zapoznania się z helpem (./cake schema help).

Wystarczy, że teraz przekonam zespół, aby w sytuacji, gdy nastąpiły zmiany w bazie, przed commitem wywołali to polecenie. Jest jeden problem, którego ewentualnie można się spodziewać - sporadycznych konfliktów. To znaczy sytuacji, w której dwóch programistów:
  1. ściąga repozytorium, 
  2. dokonuje (nawet różnych) zmian w bazie, 
  3. zatwierdza dane: 
    1. schema generate, 
    2. svn add schema_X.php, 
    3. svn commit
Problem w tym, że w takiej sytuacji w punkcie 3.3 jeden z nich dostanie informację

Nie mogę dodać schema_X.php do repozytorium, gdyż takowy  już w repozytorium istnieje.
Z poważaniem Twój
SVN
Nie jest to jakaś wielka tragedia, jak przy każdym konflikcie trzeba będzie go rozwiązać (w tym wypadku przy spotkaniu tych dwóch programistów). Jednak myślę, że takie sytuacje można by zlikwidować wywołując tą sekwencję w jednym ciągu (nie np. commit po dwóch godzinach od schema generate), może nawet napisać prosty skrypt, który załatwi to za nas (taki svncommitwithcakeschemagenerate.sh ;))

wtorek, 24 marca 2009

CakeFest o rzut beretem


Jeśli masz niedaleko i cake to Twoje środowisko pracy - nie wahaj się ani chwili ;)

wtorek, 17 marca 2009

Przyspieszyć cake 1.1.x

Przy okazji usprawnień dla portalu dobrzemieszkaj.pl i zabawy z xdebug.profiler udało mi się znaleźć ciekawe wąskie gardło w cakePHP.
Niestety jego usunięcie wymaga ingerencji w core frameworku, ale z uwagi na to, że wersja 1.1 nie będzie już rozwijana, a do tego przy tak zaawansowanym poziomie prac nad portalem na pewno nie będziemy migrować do żadnej innej nowiści - uważam, że można było sobie pozwolić na grzebanie w core.

O co chodzi?
Konstruktor modelu wywołuje metodę Model::setSource(), która przede wszystkim sprawdza, czy wśród wszystkich tabel jest ta, z którą powiązany jest dany model. W portalu na stronie głównej było ok 50 wywołań konstruktorów modeli, a co za tym idzie- również Model::serSource.

Wszystko byłoby w porządku, gdyby nie to, że ta funkcja zabezpiecza się przed przypadkiem niestosowania się w 100% do konwensji cake. Dlatego wszystkie elementy (nazwy tabel) traktowała funckją strtolow().

Z uwagi na to, że w naszym projekcie jest 150 tabel, to przebiegnięcie po tablicy 150 stringów i zamiana na lowercase odbywała się 50 razy, czy razem było  750 stringów było zamienianych na lowercase. Sęk w tym, że operacja nie była potrzebna, gdyż wszystkie nasze tabele są lowercase.
Dlatego dobrą opcją było zamiana cake/libs/model/model_php5.php:523 z
if (is_array($sources) && !in_array(low($prefix . $tableName), array_map('low', $sources))) {
na
if (is_array($sources) && !in_array($prefix . $tableName,  $sources)) {
proste jak budowa cepa, a ubyło kolejne ćwierć sekundy na generowanie strony :)

W prawdzie nie jest to oszołamiające przyspieszenie, ale przy bardzo wielu zapytaniach na raz - przydatne.

Edit: członkini teamu cake Jitka Koukalova potwierdziła, że ten fragment kodu jest obejściem, dzięki któremu cake działa również z php4. Twierdzi też, że to jeden z wielu elementów, bez których cakePHP będzie działał szybciej, ale tylko na php5.

You discovered one of many workarounds which are necessary for PHP4.
It is just one of many things which will make PHP5 only CakePHP much
faster.
Nic tylko dalej szukać i usprawniać ;)

piątek, 13 marca 2009

CakeTestSuite i pokrycie kodu (code coverage)

Nareszcie działa ;) Okazuje się, że zmuszenie powyższego do poprawnego działania za pomocą dostępnych oficjalnych opisów nie jest takie proste.

Np. metoda podana pod http://bakery.cakephp.org/articles/view/testing-models-with-cakephp-1-2-test-suite dla testu modelu się nie sprawdza. Okazuje się, że tam jest podany stary model tworzenia testów. Poprawna klasa powinna wyglądać mniej więcej tak:


<?php
App::import('model', 'Article');

class ArticleTestCase extends CakeTestCase {
var $fixtures = array( 'app.article' );

function start() {
parent::start();
$this->Article= & ClassRegistry::init('article');
}

function testPublished() {

$result = $this->Article->published(array('id', 'title'));
$expected = array(
array('Article' => array( 'id' => 1, 'title' => 'First Article' )),
array('Article' => array( 'id' => 2, 'title' => 'Second Article' )),
array('Article' => array( 'id' => 3, 'title' => 'Third Article' ))
);

$this->assertEqual($result, $expected);
}
}
?>

zamiast podanej:



<?php
loadModel('Article');

class ArticleTest extends Article {
var $name = 'ArticleTest';
var $useDbConfig = 'test_suite';
}

class ArticleTestCase extends CakeTestCase {
var $fixtures = array( 'article_test' );

function testPublished() {
$this->ArticleTest =& new ArticleTest();

$result = $this->ArticleTest->published(array('id', 'title'));
$expected = array(
array('ArticleTest' => array( 'id' => 1, 'title' => 'First Article' )),
array('ArticleTest' => array( 'id' => 2, 'title' => 'Second Article' )),
array('ArticleTest' => array( 'id' => 3, 'title' => 'Third Article' ))
);

$this->assertEqual($result, $expected);
}
}
?>

Drugi przypadek generuje następujące problemy:
  1. Gdy testy masz ustawione tak, żeby korzystały z tej samej bazy, gdzie masz "normalne" tabele, to testy na zmianę będą wykonywać się poprawnie i zgłaszać błąd z nieistniejącą tabelą (`articles_test`)
  2. Gdy masz osobną baze do testów - będą się sypać relacje. Pewnie zdefiniowanie wszystkich fixtures powiązanych z testowanym modelem rozwiązało by problem, ale nie sprawdzałem.
Kolejnym przypadkiem jest zmuszenie CakeTestSuite do wywalenia informacji o procentowym pokryciu kodu testami. Pominę problemy przy instalacji xdebug, założę, że już to masz za sobą.

Prawdopodobnie będziesz dostawał Segmentation Fault po kliknięciu "Analyze Code Coverage". Jeśli tak, odnajdź poniższą linię w pliku cake/test/lib/code_coverage_manager.php:
xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
Niestety Xdebug sypie się przy takim wykonaniu xdebug_start_code_coverage. Możesz zmienić tą linię na
xdebug_start_code_coverage(XDEBUG_CC_UNUSED)
lub
xdebug_start_code_coverage(XDEBUG_CC_DEAD_CODE)
O rożnicy możesz poczytać w dokumentacji Xdebug.

Ostatnia sprawa to pokrycie kodu, gdy wykonujesz test całej grupy. Jeśli chcesz dostawać informacje o pokryciu dla poszczególnych testów, nie używaj TestManager::loadTestCasesFromFile, ale raczej ładuj każdy plik osobno za pomocą TestManager::addTestFile.

To na razie tyle. Miłego testowania ;)


opisać problem z coverage, jeśli grupa jest zdefiniowana jako loadTestCasesFromFile

środa, 4 marca 2009

W tej branży 2+2 nie równa się 4

Ten problem nakreślił niedawno Patrys na blogu, który czytuję (http://room-303.com/blog/2009/02/20/przypowiesc-o-osobomiesiacu/) Mam ochotę napisać o tym nieco więcej.

Ciągle mam problem ze złym podejściem do produktu jakim jest oprogramowanie. Dlatego będę pisał o tym pewnie za każdym razem, gdy da mi się we znaki. Jak zwykle zpróbuję opisać to na przykładzie.

Załóżmy, że pytasz mnie ile czasu pracy jednego programisty zajmnie gdyby miał zaprogramować:

- Prosty blog
 Ja odpowiem, że dwa miesiące.
- Galerię zdjęć
Ja również odpowiem, że dwa miesiące.

Jeśli uważasz, że to wystarczające szacowania i gdybyś chciał dostać system, który ma funkcjonalności kryjące się pod hasłem "Prosty blog" i "Galeria zdjęć", oszacujesz już sobie sam, że to razem 4 miesiące...

Otóż jesteś w błędzie. Ten nowy system zajmie więcej czasu.

Jeśli uważasz, że przypisując czterech programistów do tego projektu czas jego wykonania skróci się czterokrotnie - również się pomyliłeś. Zapomniałeś o tym, że nie zawsze można podzielić projekt na kilka niezależnych elementów, które można implementować współbierznie. Nawet gdyby się dało - ktoś musi odwalić tą robotę, odpowiednio zaprojektować i koordynować współbierzną implementację.

Dlatego jeśli jesteś menadżerem, kierownikiem projektu, sponsorem lub klientem - oddasz przysługę wielu ludziom, jeśli przestaniesz traktować tworzenie oprogramowania jak skręcanie długopisów.
Nam zaoszczędzisz nerwów i wrzodów na żałądku, a sobie - rozczarowań.

wtorek, 3 marca 2009

ImageBehavior - uploaduj pliki prosto do bazy

Jakiś czas temu napisałem o pomyśle cake'owego Behavior (http://webbricks.blogspot.com/2009/02/pliki-w-formie-binarnej-w-bazie.html). Poniżej prezentuję pierwsze podejście do problemu:

<?php
/**
 * ImageBehavior - take best from database blobs adn file image storage

 * requires 'content' field that is a blob (mediumblob or longblob), and
 * 'ext' varchar(10) field  and

 * 'modified' datetime field
 * @author Grzegorz Pawlik
 * @version 1.0
 */
class ImageBehavior extends ModelBehavior {

  
   
/**
    * directory in which cached files will be stored
    *
    * @var string
    */
   
var $cacheSubdir 'filecache';

   
/**
    * if set to false - never check if cached file is present (nor actual)
    *

    * @var bool
    */
   
var $usecache true;
  
   function 
setup(&$Model) {
      
// no setup at this time

   
}
  
   
/**
    * Insert proper blob when standard data after upload is present
    *
    * @param object $Model

    * @return bool true
    */
   
function beforeSave(&$Model) {

      if(isset(
$Model->data[$Model->name]['file']['tmp_name']) && is_uploaded_file($Model->data[$Model->name]['file']['tmp_name'])) {

      
// podnieś wyżej parametry
      
$Model->data[$Model->name] = array_merge($Model->data[$Model->name],  $Model->data[$Model->name]['file']);

      
// przygotuj blob
      
$this->_prepareBlob($Model);
     
      
$this->_getExt($Model);
      }

     
      return 
true;
   }
  
   
/**
    * prepares blob contents
    *
    * @param object $Model

    */
   
function _prepareBlob(&$Model) {
      
App::import('Core''File');
      
$file = new File($Model->data['Medium']['tmp_name'], false);

      
$content $this->addSlashes$file->read() );
      
$Model->data[$Model->name]['content'] = $content;

   }
  
   
/**
    * Get uploaded file extension
    *
    * @param object $Model
    */
   
function _getExt(&$Model) {

      
$file explode('.'$Model->data['Medium']['name']);
      
$ext array_pop($file);

      
$Model->data[$Model->name]['ext'] = $ext;
   }
  
   
/**
    * replace blob contents with file path

    * After reading database checks if cached file is present. If not creates it (from blob contents) and

    * returns a 'file' field with path relative to /app/webroot/img
    *
    *
    * @param object $model

    * @param array $results
    * @param unknown_type $primary
    * @return unknown
    */
   
function afterFind(&$model$results$primary) {

      foreach(
$results as $key => $val) {
        
        
        
         
$relpath $this->cacheSubdir DS .

                 
$val[$model->name]['id'] . '_' $model->name '_' .

                 
$val[$model->name]['modified'] . '.' $val[$model->name]['ext'];

         
$relpath str_replace( array(' '':') , '_'$relpath);
        
         
$fullpath IMAGES $relpath;

        
         if(!
file_exists($fullpath) || !$this->usecache ) {
            
file_put_contents($fullpath$this->stripSlashes($results[$key][$model->name]['content']));

         }
        
         
$results[$key][$model->name]['file'] = $relpath;
         
// remove blob from results (its messy when You want to output results in debug)

         
unset($results[$key][$model->name]['content']);
      }
      return 
$results;
   }
  

   
/**
    * add slashes (just wrapper)
    *
    * @param string $string
    * @return string with slashes
    */

   
function addSlashes($string) {
      return 
addslashes($string);
   }
  
   
/**
    * strip slashes (just wrapper)

    *
    * @param string $string
    * @return string without slashes
    */
   
function stripSlashes($string) {

      return 
stripslashes($string);
   }
}
?>


Zasada działania jest dość prosta. Wyjaśnię ją na przykładzie.
Tabela media:


CREATE TABLE IF NOT EXISTS `media` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) NOT NULL,

  `ext` varchar(10) NOT NULL,
  `content` longblob NOT NULL,
  `size` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,

  `type` varchar(20) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM;


Model:
<?php
class Medium extends AppModel {

    var 
$name 'Medium';
   var 
$actsAs = array('Image');

}

?>


Kontroler:
<?php

class MediaController extends AppController {

    var 
$name 'Media';
    var 
$helpers = array('Html''Form');

   
    function 
index() {
     
      
$this->set('media'$this->Medium->findAll());

     
    }
   
    function 
add() {
       if(!empty(
$this->data)) {
          
$this->Medium->save($this->data);

       }
    }
   
}

?>

Przy uploadzie ImageBehavior oczekuje, że plik będzie przekazany w poly ModelName.file (tutaj Media.file).

add.ctp:
span style="color: rgb(0, 0, 187);"><?php
   echo $form->create(
      array(
'url' => array(
                           
'controller' => 'media',

                           
'action'    => 'add'
                     
),
            
'enctype' => 'multipart/form-data'
            
)
   );
?>

<?php echo $form->file('Medium.file'); ?>


<?php echo $form->end('submit'); ?>



Przy odczycie dzieje się to co mnie najbardziej interesowało. Zamiast dostać zawartość (BLOB) pliku, dostajemy w polu file ścieżkę (relatywną do app/webroot/img). Domyślne ustawienia wymagają, żeby był tam katalog filecache (z możliwością zapisu). Przy operacji read behavior sprawdzi, czy istnieje aktualy plik w filecache, i jesli nie - utworzy go.

index.ctp:
<?php foreach($media as $medium): ?>
   <?php echo $html->image($medium['Medium']['file']); ?>

<?php 
endforeach; ?>


To rozwiązanie ma przynajmniej dwa zauważalne braki:
  1. Gdy dodamy taki plik do treści np. postu, po jego updacie - nie będą widoczne zmiany
  2. Dobrze byłoby, gdyby przy operacji read nie zwracał zawartości BLOB (ważne, gdy baza jest gdzieś dalej), ale odpytywał tylko wtedy, gdy jest potrzebne zaktualizowanie zawartości pliku w filecache.
To rzeczy do zrobienia w kolejnym podejściu ;)

środa, 25 lutego 2009

Pliki w formie binarnej w bazie

Wcześniej pisałem o tym problemie, gdy pliki trzymamy fizycznie na dysku a w bazie tylko informację np. nazwę pliku (Co się dzieje, gdy dane są nie tylko w bazie).

Jako, że nowe projekty także służą do eksperymentowania z nowymi rozwiązaniami, dlatego próbujemyz nowymi rozwiązaniami:
  1. Po pierwsze komponent: ma dostawać tablicę po uploadzie (chodzi o pola "file"), zwracać nazwę zapisanego pliku (dla standardowego podejścia) lub zawartość gotową do wrzucenia do BLOBa
  2. Po drugie Helper, który będzie się zajmował wyświetlaniem takich zdjęć (a w przyszłości także zajmie się cache'owaniem ze wspomnianą w powyższym artykule metodą z nagłówkami expire)

W takim rozwiązaniu niestety pojawia się problem (strzałki oznaczają przepływ danych, uwaga: diagram nie przedstawia dosłownie przebiegu jednego zapytania, ale dwóch zapisu (do kontrolera) i wyświetlenia obrazka (od kontrolera)) :


Chodzi o fragment zaznaczony na czerwono. To jest nadmiarowe zachowanie. Lepiej byłoby, gdyby komponent (poprzez kontroler) przekazał nazwę pliku, Helper sprawdził, czy plik istnieje, a w razie gdyby nie istniał - musiałby 'poprosić' komponent o aktualizację pliku. A to jest w konflikcie z tym co napisałem przy okazji Dobrych praktyk #2 - Helper nie może używać kontrolera, ani komponentu.

I tu się wydarzyła rzecz ciekawa, w tym poście chciałem napisac o problemie, na który nie widzę dobrego sposobu. Chciałem przedstawić ideę pierwszego podejścia.
Dla bardziej przejrzystego przekazania idei postanowiłem zbudowac diagram i uświadomiłem sobie jak bardzo wizualizacja pomaga w zrozumieniu problemu.
A od zrozumienia problemu do jego rozwiązania jest raczej niedaleko.

No i w tym momencie wpadłem na pomysł bez komponentu i bez Helpera, ale za to sam Behavior. Myślę sobie, że Behaviors są rozszerzeniem Modeli, które odpowiadają za warstwę przechowywania danych.
Pomysł jest taki, że -pomijając kwestię uploadu i sapisu - behavior w afterRead() sprawdził, czy plik fizycznie jest aktualny, jeśli jest - zwróci jego nazwę. Jeśli nie jest - utworzy go na podstawie danych w bazie (koniecznie z inną nazwą) i zwróci jego nazwę (relatywną do img/) ścieżką.

Sprawdzę to empirycznie i napiszę Wam jak poszło.

czwartek, 19 lutego 2009

Odpowiedzialność społeczna przedsiębiorcy, a motywacja pracownicza.

Dziś wielu przedsiębiorców ma świadomość tego, że motywowanie
pracowników jest ważnym aspektem działalności menadżerskiej. Odnoszę
jednak wrażenie, że wydaje się im to istotne jedynie dlatego, że wierzą
w to iż jest to opłacalne. Głównie dlatego, że wdrożenie nowego
pracownika jest kosztowne. Chciałbym jednak przedstawić aspekt etyczny
motywowania i systemu motywacji pracowników.

Czym jest motywacja, a czym odpowiedzialność
społeczna?




Odpowiedzmy sobie najpierw na to
ważne pytanie. Philip Zimbardzo pisze: „Motywacja (motivizion) to ogólny
termin na określenie wszystkich procesów zaangażowanych w rozpoczęcie,
kierowanie i podtrzymanie aktywności fizjologicznych i psychicznych”
1. Motywowaniem nazwiemy zatem
wywoływanie (w sobie lub w innych) tych procesów.

Odpowiedzialnością społeczną
możemy nazwać dobrowolne zobowiązanie do uwzględniania interesów
społecznych, ochrony środowiska przez przedsiębiorcę (a w efekcie przez
przedsiębiorstwo) w trakcie budowania strategii, oraz przestrzeganie
tego zobowiązania w działalności przedsiębiorstwa
2. Ta odpowiedzialność przejawiać
się może w finansowaniu publicznego transportu, budownictwa, rzetelnej
informacji na temat produktów, trosce o ekologię3
i wielu innych.

Jest jednak jeszcze jeden aspekt,
w którym odpowiedzialność społeczna przedsiębiorstwa może zostać
wyrażona- umożliwienie rozwoju pracownikom, którzy przecież również są
częścią tego społeczeństwa.


Motywowanie pracowników- w czym się przejawia?



Motywowanie zakłada, że można
wpłynąć na wewnętrzny stan napięć pracownika w celu skłonienia go do
pracy, lub zwiększenia jej wydajności. Dlatego musi wykorzystywać metody
i narzędzia wpływu społecznego, które nierzadko stawiają osobę stosującą
je świadomie na pograniczu manipulacji. Wykorzystują one często
naturalne potrzeby drzemiące w ludziach. Dodatkowo Abraham Maslow
różnicuje motywację na wynikającą z niedoboru i wynikającą z potrzeby
wyjścia poza stan aktualny
4.
Dlatego w dwojaki sposób motywować pracowników: albo zabierając im, lub
utrudniając dostęp do pewnych nagród, lub strasząc odcięciem tego
dostępu (obcinanie premii, grożenie zwolnieniem); albo roztaczając przed
nimi wizję zaspokojenia potrzeb wyższego rzędu takich jak rozwój i
uznanie. Nawet gdyby oba podejścia były tak samo efektywne, to warto
zastanowić się jakie efekty, oprócz zwiększenia wydajności pracownika,
przynosi nasze działanie.


W pierwszym przypadku wywołany
stan jest bliski niewolnictwu. Praca wynika z zagrożenia (życia,
stabilizacji) pracownika, zatem wydaje się pracownika degenerować. Nie
ma co liczyć na inwencję tak motywowanego pracownika. Nie ma też co
liczyć na jego lojalność. Widać ten efekt aktualnie w Polsce: wcześniej
zatrzymanie pracownika było proste, gdyż panował rynek pracodawcy,
wysokie bezrobocie oznaczało wysoką konkurencję wśród pracowników.
Dodatkowo w wypadku ludzi starszych strata pracy na pół roku mogła
oznaczać całkowite wypadnięcie z „obiegu”. Aktualnie przy zwiększonym
wzroście gospodarczym i otwarciu granic w swobodnym przepływie między
innymi pracowników, polskie firmy nie wytrzymują niejako konkurencji z
zachodnimi przedsiębiorcami.


Zastanawiające jest, że ludzie
gotowi są jechać tysiące kilometrów i rozstawać się z rodzinami; moim
zdaniem nie jest to tylko wynikiem nadziei na zwiększenie zarobków5.


Odkładając jednak na chwilę
sytuację aktualną w Polsce, zastanówmy się co się stanie, jeżeli
większość pracodawców zacznie straszyć swoich pracowników. W takim
czarnym scenariuszu łatwo dojść do wniosku, że prędzej czy później praca
sama w sobie stanie przestanie być źródłem dumy, czy zadowolenia. Raczej
będzie przykrym obowiązkiem, lub nawet przedmiotem żartów. Ktoś, kto
wkłada wiele energii w pracę stanie się w oczach społeczeństwa
„frajerem”6. Etos pracy przestanie istnieć.
Bumelanctwo i kombinowanie będzie wartościową i cenną umiejętnością. Do
tego spadek zaufania społecznego i mamy idealną mieszankę, która zwolni
skutecznie wzrost dobrobytu całej społeczności.

Wracając jeszcze na moment do
problemu emigracji zarobkowej. Sam, pracując w wakacje, jeszcze w
liceum, w pewnej z firm po usłyszeniu nowych wymogów co do wydajności
pracy, gdy ktoś wyraził niezadowolenie usłyszałem, że przed bramą czeka
stu takich, co będą za połowę tego co my pracować. Osobiście nie
przejąłem się tym za bardzo – w końcu chciałem mieć tylko trochę
oszczędności na rozpoczęcie studiów, jednak pracowali tam ludzie, dla
których ta praca oznaczała byt! Nie trudno mi wyobrazić sobie, jak
właśni Ci ludzie pierwsi szturmowali przysłowiowe zmywaki w Londynie
również po to, żeby wreszcie pracodawca potraktował ich po ludzku.


Weźmy teraz pod lupę pozytywny
sposób motywacji. Oprócz oczywistych profitów dla organizacji, takich
jak wzrost wydajności, inwencji i zadowolenia pracowników, degeneracja
społeczna opisana wcześniej na pewno nie wystąpi z tego powodu.
Wyobraźmy sobie dziecko, przysłuchujące się rozmowie rodziców, którzy
wymieniają się osiągnięciami w pracy, chwalą rozwojem i wręcz
promieniują zadowoleniem. Niemal pewne jest, że w swoim dorosłym życiu
będzie szukało ono pracy, która w co najmniej takim samym stopniu będzie
źródłem zadowolenia. Zdaję sobie sprawę, że przykłady te nie są poparte
żadnymi badaniami, jednak wydaje mi się, że brzmią po prostu rozsądnie.
Dlatego pokuszę się o stwierdzenie, że przedsiębiorca etyczny jest w
stanie, oprócz towarów wymienionych w cenniku, produkować szczęście. W
sytuacji dominowania takiego postępowania wśród menadżerów – skutki są
odwrotne do przedstawionych powyżej. Budowane jest społeczeństwo
uczciwej pracy, które w całości pracuje na swój dobrobyt.


Warto jeszcze dodać, że
dostarczając pracownikom możliwości rozwoju, sprawiamy po prostu, że są
nieco szczęśliwsi.


Dlaczego zatem etyka czasem się nam gubi?



„Wymiar etyczny bywa często
sztucznie eliminowany z rozważań o zarządzaniu i dlatego bez wzajemnego
powiązanie mówi się często o władzy jako o samodzielnym zjawisku - i o
trosce o innych, byciu w emocjonalnej relacji do innych jako o osobnym
od władzy. Tymczasem te stany są dwoma ekstremami wymiaru etycznego
organizowania. Dopiero wzięte razem przestają mieć charakter typów
idealnych i zawierają całe spektrum realnych możliwości odniesienia
ludzi do siebie.”7


Mamy skłonność do upraszczania.
Czasem, żeby coś zrozumieć, musimy zbudować zubożony model, gdyż inaczej
nie bylibyśmy w stanie zrozumieć całości. Nie ma w tym nic nagannego,
pod jednym warunkiem: nie możemy zapomnieć, że mamy do czynienia z
uproszczeniem! Nie można zapomnieć, że jest tam coś jeszcze. Coś co może
nie przynosić bezpośrednich dochodów:

„Władza formalna - struktura,
jest uważana za aktywny potencjał i kojarzona z przywództwem. Troska o
innych, często przybierająca aktywną postać podporządkowania władzy,
jest w rzeczywistości drugą stroną tej samej monety. Jedna relacja nie
występuje bez drugiej;”8


To jest właśnie powód, dla
którego warto przypominać o etyce biznesu i etyce w ogóle. Działalność
biznesowa, jak każda inna działalność nigdy nie jest oderwana od
otoczenia, zatem na nie wpływa. Skoro tak, to na tych, którzy podejmują
działalność, ciąży jednocześnie odpowiedzialność, żeby przyniosła jak
najwięcej pożytku, przy jak najmniejszej ilości strat.


Związek społeczeństwa z przedsiębiorstwem.



W pracy „Etyka biznesu” została
podana odpowiedzialność przedsiębiorcy:

"[Menadżer odpowiada – gp] Za
wypracowanie zysku. Biznes produkuje zyski. Firma, a dokładnie zarząd
firmy, musi powiększać zdolność zasobów rzeczowych i osobowych do
tworzenia i produkowania bogactwa. Odpowiedzialność ta - pisze P.
Druckner - jest nie do odrzucenia i absolutna. [...]

Ale społeczeństwo nie może wziąć
z przedsiębiorstwem rozwodu. Musi ponieść straty, jeśli przedsiębiorstwo
nie produkuje koniecznych zysków, musi ubożeć, jeśli nie odnosi
sukcesów. Nakłada to na firmę obowiązek zarządzania w pancerzu etyki"9.
Rozmyślania na temat nieetycznego zarządzania zdają się być potwierdzane
tym cytatem. Chcę tylko dodać jeszcze jedno przemyślenie: jak
społeczeństwo może się bogacić kosztem zubożenia społeczeństwa? To
wyraźna sprzeczność potwierdzająca rację bytu etyki w naukach
traktujących o gromadzeniu bogactw.


Jak promować etykę?


J. Dietl i W. Gasparski proponują10
następujące metody promowania etyki:



  1. "Propagowanie dobrych wzorów,
    "budujących" przykładów. Powszechnie znane jest postępowanie znanego
    menadżera, który w najgorszym dla Chryslera roku, jako szef koncernu
    zredukował wysokość swojej pensji do jednego dolara rocznie” - moim
    zdaniem najlepsza i najtańsza. Ci którzy mają szczęście doświadczać
    potrzeby etycznego postępowania muszą dawać (i niejako „siłą rzeczy”
    dają) dobry przykład.



  2. "Stanowcze potępianie
    nieetycznych zachowań. " Co oznacza zdefiniowanie kodeksu etycznego i
    definitywne jego przestrzeganie.



  3. "Powołanie sądownictwa
    polubownego", które jest świetnym sposobem na budowanie więzi
    społecznych. Do tego społecznie akceptowana instytucja arbitra będzie
    idealna do realizowania punktu poprzedniego.



  4. "Treningi wrażliwości etycznej.
    " dla tych, którzy nie mieli szczęścia tej wrażliwości się wyuczyć11.
    Miałaby ona polegać na przedstawianiu pozytywnych przykładów etycznego
    postępowania, które odniosły pożądany skutek. Dietl i Gasparski
    proponują również nawyk stawiania się po drugiej stronie. Można to
    nazwać nauką „menadżerskiej empatii”.




Czym tak w ogóle jest etyka?



Na zakończenie, aby domknąć
rozmyślania, chciałbym zamieścić dwa wybrane cytaty z pracy „Etyka
biznesu”, które odpowiadają na te pytanie.


Pierwszy Tadeusza Kotarbińskiego:

"Tadeusz Kotarbiński, w pracy
'Medytacja o życiu godziwym' podkreśla znaczenie takich wartości, jak:
miłość i przyjaźń, sprawiedliwość, hierarchiczność celów oraz kierowanie
się głosem sumienia, które domaga się od nas postawy znamiennej dla
opiekuna, na którym można polegać, spolegliwego opiekuna12"


Drugi Jana Pawła II:

„... Człowiek najpełniej afirmuje
siebie, dając siebie ... nie przyjmując perspektywy daru z siebie samego
, zawsze istnieje ryzyko wolności egoistycznej”


Powyższy tekst to praca zaliczeniowa na
przedmiot "Etyka w biznesie", napisana w połowie 2008 roku. Bardzo
ciekawie i inspirująco wykładany przez dra Leopolda Zgodę. Jego wykłady
otworzyły mi oczy na to, czego do tej pory nie widziałem (choć może
gdzieś pod skórą czułem). Postanowiłem się tym tekstem podzielić (tym
bardziej, że został on dobrze przez doktora oceniony :))




1Cyt. P.G.Zimbardzo,
Psychologia i życie, Warszawa: Wydaw. Naukowe PWN, 1999, s.436



2Zob. Wikipedia,
Wolna encyklopedia, http://pl.wikipedia.org/wiki/CSR,
Dostęp 24/05/2008



3Zob. Anna
Lewicka-Strzałecka, Etyczny wymiar przekształceń gospodarczych w Polsce,
pod red. Adama Węgrzeckiego ; Akademia Ekonomiczna w Krakowie, s 147



4Zob. P.G. Zimbardo,
F.L. Ruch Psychologia i życie, Warszawa 1997, s. 404.



5Można dopatrywać się
tutaj nadziei na lepsze traktowanie, o czym za chwilę.



6Warto porównać to z
nast. cytatem: „Najprawdopodobniej określenie "etyczny menadżer"
wywołałoby u licznej grupy co najmniej zdziewienie, komentarze i znaki
zapytania. Bo "etyczny" obecnie w Polsce to właściwie jaki: "naiwny",
"nieprzedsiębiorczy", a może wręcz "głupi"?”, Etyka biznesu, red. nauk.
Jerzy Dietl, Wojciech Gasparski, s.210



7Cyt. Etyka Biznesu,
J. Dietl, W. Gasparski, s.192



8Tamże.



9Cyt. Etyka biznesu,
s. 209



10Zob. Tamże, s.
224 - 225



11Również dla tych,
którzy to szczęście mieli – zawsze warto tę umiejętność pogłębiać.



12Por. z pojęciem
aktywne współczucie i z ideą Bodhisattwy w buddyzmie, http://www.diamentowadroga.pl/dd20/wspolczucie_jezyk_bodhisattwy,
http://www.diamentowadroga.pl/dd25/dzialania_bodhisattwy

wtorek, 17 lutego 2009

Dobre praktyki programowania w CakePHP #2

Przepływ przez architekturę MVC jest i powinien być: Model->Controller->View.

Niestety ze studiów wynieśliśmy to beznadziejne przyzwyczajenie, że funkcja to funkcja i tam się zrobi wszystko co da się zrobić. Dlatego są tacy, którzy nie starają się wpasować w MVC samodzielnie, za to MVC jako tako trzyma ich w koleinach w miarę dobrej architektury. Ci i ich kod jakoś tak naturalnie ciąży w kierunku kontrolerów.
To znaczy: model jest właściwie półprzeźroczystą wartswą dostępu do bazy danych ( findAll() + okazyjnie query() ), w kontrolerze jest wszystko, widoki na razie pominę.

Przykład: Mamy User hasMany Photos. Kluczem obcym jest oczywiście Photo.user_id. Photo.filename to plika w katalogu app/webroot/img. Prosta sprawa, User wrzuca sobie fotki i gdzieś tam one wszystkie są wyświetlane dla danego User(a). Do tej pory załatwił wszystko cake - wygenerował nam definicję tej relacji i nie zaprzątamy sobie tym głowy.
Przychodzi jednak czas, że gdzieś tam na stronie jest lista Userów i przy każdym przydałaby się jedna fotka wśród tych, które dodał. Załóżmy przypadek pierwszy, że zakładamy iż reprezentującą fotką jest pierwsza w kolejności (Photo.id - najmniejsze).

Opisany wyżej programista zrobi coś takiego (w widoku):
<?php image($User["Photo"][0]["filename"]; ?>(1),
jeśli pojawia się sprawdzenia isset($User['Photo'][0] to jest to spory sukces.

Jednak przepływ danych M->C->V sugeruje, że nasze myślenie też powinno płynąć w ten sposób. Czyli powiniśmy stosować zadać sobie trzy pytania:
if(to się nadaje do modelu?) {
wrzućToDoModelu($to);
}elseif(to się nadaje do kontrolera?) {
wrzućtoDoKontrolera($to);
else {
pewnie widok, ale może helper, komponent itd.
}
W (1) poleciało do widoku, a nie wiadomo dlaczego! Przecież to, które ze zdjęć z jakiegoś zestawu jest tym szczególnym zależy od modelu danych. Dlaczego ten wybór dokonywany jest w warstwie reprezentacji?

Bardziej prawidłowym podejściem jest dodanie relacji
$hasOne = array( 'RepresentativePhoto' =>
array('className' => 'Photo',
'order' => 'Photo.id ASC'
);
(2)

I wyświetlenie go w widoku:
<?php echo $html->image($User['RepresentativePhoto']['filename']; ) ?>

Jeśli zadałeś sobie pytanie w stylu: ale po co, przecież efekt jest ten sam? To znaczy, że powoli zaczynasz łapać. Masz rację, że efekt jest teraz taki sam. Jednak, jeśli uświadomisz sobie, że program komputerowy się ciągle zmienia, bo taka jest prawda, to powoli zaczniesz rozumieć po co w ogóle powstało coś co się nazywa MVC.

MVC istnieje dlatego, że każdy użytkownik ma pomysły jak ulepszyć dany program. Na nieszczęście najlepsze pomysły pojawiają się, gdy program już isnieje, a nie wtedy gdy próbujesz wysmarzyć specyfikację. Jednym z tych użytkowników jest Twój klient, a klient to ten który ma kasę, a Ty to ten, który chce ją zdobyć.

Dlatego po pół roku okazuje się, że trzeba by zrobić coś fajniejszego. Załóżmy, że User wśród swoich zdjęć może wybrać to jedno jedyne, które ma być reprezentacyjne. Oczywiście dodajemy pole Photo.representative (bool, 0 - nie, 1 - tak). Co teraz zrobisz?
Jeśli pojawiła się w Tobie ochota na wzięcie (1) i wstawienie tam uroczej pętelki, w środku której będzie słodki if sprawdzający to pole - proszę zacznij czytać od początku.

Cwaniak-leń, który nie jest masochistą i nie lubi się przepracowywać, a jednocześnie lubi zadowolonych klientów - weźmie 2 i ją zmodyfikuje:

$hasOne = array( 'RepresentativePhoto' =>
array('className' => 'Photo',
'conditions' => 'Photo.representative = 1',
'order' => 'Photo.id ASC'
);
Jednak linijka vs. foreach z ifem. Wygrałem. Do tego mam bonus taki, że sprawdzanie warunku odwala za mnie serwer sql (do tego został stworzony) co jest niemal zawsze bardziej wydajne od tych samych operacji w php.

Na zakończenie kolejna zmiana - użytkownik może wybrać dowolną ilość Photo jako reprezentacyjne, a przy jego imieniu ma się wyświetlić losowy. Teraz już naprawdę wyrażnie widać, że gdy zmodyfikujemy widok (1) dodając tam jakiś array_randm czy coś, to nie bardzo będzie to wyglądać na warstwę prezentacji danych (bo daną jest już wylosowane zdjęcie, reszta to zaledwie półprodukty).
Wyobraźmy sobie, że mogłoby to wyglądać jakoś tak:
foreach($user["Photo"] as $key => $val) {
if($val['representative'] != 1) {
unset($user['Photo'][$key];
}
}
$representativePhoto = array_rand($User['Photo'], 1);
echo $html->image($representativePhoto['filename']);
Tragedia! Serio chcesz, żeby Twoje widoki wyglądały w ten sposób? A jak w pięciu różnych widokach musisz wyświetlić tak wybrane zdjęcie?

Prawidłowa odpowiedź:

$hasOne = array( 'RepresentativePhoto' =>
array('className' => 'Photo',
'conditions' => 'Photo.representative = 1',

'order' => 'RAND'
);
Tyle.

Kolejna zasada, którą pozwolę sobie oznajmić światu brzmi:
Poznaj Modele i zacznij ich używać. Po coś w końcu ktoś je wymyślił!
Krócej: skinny controller, fat model

sobota, 14 lutego 2009

Czy pamiętasz ze szkoły trójkąt czas-koszt-jakość?

Jeśli nie, to z przyjemnością Ci przypomnę.

Wyobraż sobie trójkąt równoramienny. Jego wierzchołki oznaczają osiągalny (fizycznie, technologicznie) maksymalny danej z trzech cech. Jeden jakości, drugi czasu, trzeci kosztu (chodzi o pieniądze, nie abstrakcyjne pojęcie kosztu, bo wtedy oznaczałoby to również czas).

Twój produkt to jeden punkt wewnątrz tego trójkąta. Im mniejsza odległość tego punktu od jakiejś cechy, tym większa jej wartość. Może dokładniej: im bliżej punktu jakość - tym ta jakość większa. Im bliżej punktu koszt - tym taniej. Im bliżej punktu jakość - tym lepszy jakościowo jest produkt.

Jednocześnie maksymalna osiągalna odległość od danego punktu oznacza minimalną (najgorszą) wartość danej cechy...
Dla przykładu wyobraź sobie, że stawiasz mur (metr wysoki, 20metrów długi) na swojej posesji. Chcesz to zrobić jak najtaniej (przesuwasz się do wierzchołka koszt) więc zatrudniasz studenta pierwszego roku socjologii. Nie dajesz mu cegieł, ale każesz znosić kamienie i cegły z ruin, starych fabryk itd. Nie dostanie też cementu z sugestią, że może użyć błota z rzeki wymieszanego ze słomą. Nie płacisz mu pensji, ale pozwalasz rozbić namiot w pobliżu muru, który buduje. Może zjeść co upoluje i ogrzać się na masce Twojego samochodu, gdy wrócisz z pracy... ok zapędziłem się, ale wydaje mi się, że masz już w głowie TEN obraz. Myślę, że efektu końcowego nie muszę już opisywać. Widać, że wyżyłowaliśmy kwestię kosztów, jednak oddaliliśmy się znacznie od punktu jakość i czas (prawdopodobnie będzie liczony w latach, lub pokoleniach potomków owego studenta ;))
Niby sprawa oczywista, ale konsekwencje nie są oczywiste dla każdego. Szczególnie jeśli chodzi o projekt informatyczny. W tym wypadku produkt jest na tyle wirtualny, że występuje pokusa wirtualnego myślenia. Że jak w grze po wpisaniu kodu jesteśmy nieśmiertelni i potrafimy latać, tak tutaj punkt może się rozciągnąć i zetknąć z każdym wierzchołkiem trójkąta. Czy jeszcze muszę pisac, że tak niestety nie jest?

Trochę mnie bajkopisarstwo poniosło, więc na koniec napiszę o korzyściach wynikających z tego, że model ten będziesz miał w głowie.

Jeśli jesteś menadżerem projektu. To jest twój koronny argument w negocjacjach terminów. Jeśli ktoś Ci powie, że ten trójkąt to bujda, że Toyota i w ogóle - nie jesteś jeszcze stracony - czytaj dalej.

Jeśli jesteś klientem, to dobre dla całego projektu będzie, jeśli będziesz o nim myślał realistycznie a nie w kategoriach fantasy. Pamiętasz, że udany projekt leży również w Twoim interesie?

Jeśli jesteś programistą... cóż, możesz sobie ten trójkąt rysować kiedy wiesz, że projekt w którym uczestniczysz oddalił się znacznie od punktu jakość i czas, i nie mieć z tego powodu wyrzutów sumienia ;)


Oczywiście w myśl zasady "zawsze trafisz na większą górę, wyższe drzewo i mądrzejszego człowieka" nie możesz spocząć na larach. Prędzej czy później trafisz na mądralę, który odbije piłeczkę a na niej będzie logo Toyota.
O co chodzi? Otóż są tacy, którzy twierdzą, że model trójkąta jest bujdą, gdyż toyocie udaje się ciągła redukcja kosztów z ustawicznym wzrostem jakości. I nie jest to kłamstwem. Jednak są dwie rzeczy, których przytoczony mądrala sobie nie uświadomił:
  1. Trójkąt w modelu jest taki, nie inny w danej chwili.
  2. Toyota stosuje podejście procesowe w zarządzaniu powiązane z filozofią kaizen 
  3. Dlatego Toyota zmniejszyła swój trójkąt koszt-czas-jakość, a nie wpisała odpowiedni kod w grze ;)
  4. To trwało lata!
Dlatego mądrala nie ma racji, bo dany projekt:
  1. Ma do dyspozycji taką technologię jaką ma, taki know-how jaki ma i na potrzeby danego projektu tego kapitału nie powiększy (po zakończeniu - owszem)
  2. Prawdopodobnie nie wiesz na czym polega zarządzanie procesowe, a nawet jeśli wiesz, to pewnie nie jest wdrożone, a jeśli jest - to znaczy, że w tej kwestii na razie nie wiele możesz poprawić.
  3. Z dwóch powyższych wynika, że nie zmniejszysz swojego Trójkąta na potrzeby najbliższego projektu (chyba, że zmienisz wykonawce, ale wtedy trójkąt może okazać się nie równoramienny i punkt kosztu niknie za horyzontem)
  4. Projekt startuje już, nie za kilka lat jak już sobie "popróbujemy"

poniedziałek, 9 lutego 2009

Wreszcie ruszył!

Po kilku miesiącach ciężkiej pracy, zamknięciu zgłoszeń liczonych w tysiącach, uruchomiliśmy serwis dobrzemieszkaj.pl.

Zbudowany od podstaw system oferujacy m.in. zarządzanie wersjami artykułów (funkcjonalność zaliczana do kategorii "tricky ones"), zintegrowany z forum, adserwerem. Ogólnie to taka spora kobyła, katalog produktów i firm, część społecznościowa oprócz wspomnianego forum to komentarze, zakładki, oceny (zdjęć/galerii), no i jeszcze sekcja architektów i dizajnerów. Przy okazji takich przemyśleń przychodzi refleksja - w czasach prehistorycznych, gdy nie było jeszcze frameworków taki projekt musiałby być z 50 razy droższy!

Jest on dla mnie również źródłem osobistej satysfakcji. Projekt systemu i bazy danych, częściowo kod i zarządzanie zespołem, który odwalił kawał dobrej roboty, aby dotarł do szczęśliwego happy-endu było na mojej głowie.

Do tego było to całkiem niezwykłe doświadczenie. Można było obserwować jak ścierają się różne siły (klient, szef, programiści) i wpływają na ostateczny kształt systemu. A najfajniesze w tym wszystkim jest to, że (o ile mi wiadomo) wszyscy są przynajmniej "w miarę zadowoleni z efektów".

Jeśli ktoś z Was, szanownych czytających, chciałby takie cuś, to zgłoście się do firmy Netarch.

Uwaga! blog przeniesiony

Posty na tym blogu już nie będą się pojawiać. Zapraszam gorąco pod nowy adres: blog.grzegorzpawlik.com
Komentowanie artykułów możliwe jest pod nowym adresem.