A piramisokat négyezer éve építették.

Az elsők szétrepedtek, jónéhányan összedöltek.

Aztán néhány sikerült.

Majd mások megint nem.

De előbb-utóbb megtanultuk, mit kell tenni ahhoz, hogy megállják a helyüket.

Aztán a végére már elméletünk is volt mögötte.

A szép az, amikor már rutinból nem dőlnek be az épületeid, és azt is tudod, miért nem.

(Egy infomérnök vallomásai: régi draft az adamnemeth.hu -n)

Mostanában a munkámban egyre többet foglalkozom nagyléptékű tervezéssel ismét. Ennek minden szempontból örülök: beosztott kódernek sajnos soha nem voltam jó, és most se vagyok az, kényelmetlenül érzem magam benne, mint holmi szűk kabátban.

Az Architect dolgok ebbe kíván kis betekintést nyújtani. Szigorúan leendő és jelenlegi programozóknak.

I. rész: Logika és Struktúra, avagy Algoritmusok + Adatstruktúrák = Programok [1]

A legutóbbi feladat egy ősi - van már 15 éves - PHP-kóddal kapcsolatos. “De hisz az PHP4, jó esetben, de inkább 3″ kiáltanak fel sokan. Na és? Az informatika alapelvei nem változnak, bármely turing-teljes nyelven is kelljen programoznunk, márpedig a PHP4 (azért nem 3…) szerencsére az.

Elnézegetve a mellette lévő modern java kódot, mely bizonyos funkcionalitásokat hivatott lecserélni, cseppet se érzem azt, hogy hátrányban lennénk - vegyük csak a következőt:

void SomeFunction(SomeTransferObj params){
  aFewLinesOfMuslingsWithLogic();
  aFewLinesOfMuslingsWithLogic();
  aFewLinesOfMuslingsWithLogic();

  SomeDataObj someDataObj = new SomeDataObj();
  someDataObj.setSomeProperty(params.getSomeProperty());
  someDataObj.setSomeOtherProperty(params.getSomeOtherProperty());
  someDataObj.setSomeAnotherProperty(params.getSomeAnotherProperty());
  someDataObj.setSomeDifferentProperty(AConstant.ForTheSakeOfDifference);
  someDataObj.setAProperty(Some.Singleton.Or.Such.getAProperty());
  someDataObj.setSomeMoreProperty(params.getSomeMoreProperty()); 

  someClosingLogicLines(someDataObj);
  aFewLinesOfMuslingsWithLogic();

}

Enterprise minőségű kód, azt mondod? Az, természetesen. Kevés rosszabb minőségű kódhalmazt láttam a nagyvállalati programoknál, de ez még tényleg a szebbik változata, felületes kódelemző még csak kivetnivalót se találhat rajt.

Pedig alapvetően - hibás.

Nem választja szét a struktúrális fogalmakat az imperatív kódtól. Így persze - túl azon, hogy a kódról nem lehet megmondani ránézésre, mit is csinál, hisz eltömíti a struktúra sok eleméből következő rengeteg sor - ha változtatni kell, akkor jó eséllyel ebbe a függvénybe is bele kell nyúlni, és még sok más párjába, holott lehet, semmi lényeges nem történt, csak egy mezővel több vagy kevesebb. Ki tudja még, hány függvény használja?

A Java 1.4 egyik legnagyobb hibája, hogy nincs benne reflexiótámogatás. Természetesen a java 1.4 már vagy 10 éve volt, és már akkor se lehettek túl merészek elterjedtségük miatt - de ha a magam módján akarom megfogalmazni: már születésekor is elavult volt. A legtöbb programozó sajnos azóta se szakadt el szintaxisától. Nézzük meg ugyanezt a kódot PHP-ben!

Ha PHP5-ben lennénk, nagy hasznát vehetnénk a virtuális property-knek (__set, __get), és leginkább az objektumiterációnak, de ez PHP4. Semmi gond. Először is vegyük észre: Ez a tömött kódrészlet 2-3 objektum egymásra való leképzése, asszociációja. Semmi keresnivalója logikai kódban!

PHP4-ben általában egy külső leírót használok arra, hogy az objektumoknak milyen tulajdonságaik vannak, ez lehet asszociatív tömb, vagy épp YAML fájl. (XML-t ne! Az XML átláthatatlan.) Gyakran nem is kell igazi objektum, bolond, aki osztályt definiál puszta struktúráért! De most az OOP-sek miatt hagyjuk meg.

Egyszerűség kedvéért most vegyünk egy asszociatív tömböt leírónak:

$descriptor = array(
  "someDataObj" => array(
    "SomeProperty",
    "SomeOtherProperty",
    "SomeAnotherProperty",
    "SomeDifferentProperty",
    "SomeAProperty",
    "SomeMoreProperty"),
  "someTransferObj" => array(
    "SomeProperty",
    "SomeOtherProperty",
    "SomeAnotherProperty",
    "SomeAProperty",
    "SomeMoreProperty"
   )
);

(Indentálással jól javítható ennek átláthatósága, persze, sokkal általánosabb rendszereink szoktak lenni a végére…)

Nincs más dolgunk, mint azon elemeket, amik mindkettőben megvannak, beállítani:

foreach($descriptor["someDataObj"] as $propname)  //minden propertyre:
      if(isset($descriptor["someTransferObj"][$propname]) // ha kell ilyennek lenni a transferben
               $someDataObj->$propname=$options->$propname;

Természetesen még ez is elég csúnya. Eleve, mit keres ez imperatív kódban?

Ennek valami általánosabb helyen van a helye… Mit is csinálunk? Két adattípust leképezünk egymásra:

  map_object($someTransferObj, $someDataObj); // from => to, src => dest

És hozzá a kód:

function map_object(&$from, &$to, $descriptor=null){
  global $globalDescriptor;
  $fromClass = get_class($from);
  $toClass = get_class($to);
  if ($descriptor == null) $descriptor = &$globalDescriptor; // PHP4: mindent érték alapján adna át, felesleges.
    foreach($descriptor[$toClass] as $propname)  //minden propertyre:
          if(isset($descriptor[$fromClass][$propname]) // ha kell ilyennek lenni
                   $to->$propname=$from->$propname;
}

Persze mindig vannak a speciális esetek. Ezeket hogy kezeljük? Természetesen valakinek foglalkoznia kell velük. A legtöbb esetre jó a sima, de kell a speciális leképezés is. Ezeket egyszerűen meghívjuk - reflexíven!

function map_object(&$from, &$to, $descriptor=null, $specialmapperobj = null ){
  global $globalDescriptor;
  $fromClass = get_class($from);
  $toClass = get_class($to);
  if ($descriptor == null) $descriptor = &$globalDescriptor; // PHP4: mindent érték alapján adna át, felesleges.
    foreach($descriptor[$toClass] as $propname)  //minden propertyre:
         if (is_callable(array($specialmapperobj, "map_".$toClass."_".$propname)) call_user_func(array($specialmapperobj, "map_".$toClass."_".$propname), array($from, $to, $propname, $descriptor));
          else if(isset($descriptor[$fromClass][$propname]) // ha kell ilyennek lenni
                   $to->$propname=$from->$propname;
}

Hogy kinek a felelőssége a speciális mappelés kezelése? Ezzel mi nem foglalkozunk. Lehet, hogy a célobjektumé, lehet, hogy a forrásobjektumé, lehet, hogy a controlleré, szituációfüggő.

Kódunk tehát most áll

  • egy leírófájlból, amely leírja a két objektumot,
  • egy általános függvényből, ami elintézi a leképzést,
  • és tetszőlegesen rekonfigurálható.

A teljes PHP kód esetünkben:

require_once("object_mapper.php"); // map_object

function map_someDataObj_ SomeDifferentProperty(&$source, &$to, &$descriptor = null) {
  return $ACONSTANT_FOR_THE_SAKE_OF_DIFFERENCE;
}

function someFunction($params){
  aFewLinesOfMuslingsWithLogic();
  aFewLinesOfMuslingsWithLogic();
  aFewLinesOfMuslingsWithLogic();

  $someDataObj = new SomeDataObj();

  require_once("mydescriptor.php"); // sets $descriptor
  map_object($params, $someDataObj, $descriptor, $this);

  someClosingLogicLines($someDataObj);
  aFewLinesOfMuslingsWithLogic();
}

Átláthatóbb? Attól függ. Az említett java “kódtestvérben” sok BL függvény teli volt ilyenekkel, és sajnos amikor a java-n nevelkedett programozók elkezdték felújítani a PHP kódot, ugyanezt a stílust vitték át, pedig nagyon nem kéne. Nem igazán segített az sem, hogy absztrakt osztályokból öröklődés miatt az ilyen függvények neve gyakran generate(), execute() vagy más hasonló általános dolog.

Nyilván egyetlen hely miatt nem írnék én se ilyet: de amikor a tizedik ilyen fájl került a szemem elé, kezdett elegem lenni a 100-soros függvényekből, amikben a gondolkozás nincs 50, márpedig ez lenne a függvény lényege, hogy gondolkozzon!

Ez azonban még így is egy keményebb példa, és nem is feltétlenül jó környezetben: a business logic függvények többsége leképzéssel foglalkozik, ezek legegyszerűbb esete a tulajdonságonkénti asszociáció. Remélem azért illusztrálja: az általános algoritmusoknak általános helyen, a struktúrának struktúrában a helye.

Ha ritmust fedezel fel egy kódban, jó esélyed van arra, hogy valami olyasmit csináltak meg a programozók, amit gépre kellett volna inkább bízniuk.

[1] Wright (a Pascal megalkotójának) egyik leghíresebb könyvének címe.