RSS

Zawsze grupuj powiązane dane w strukturach

Liczba odsłon: 247

Mija sześć­dzie­siąt lat od chwi­li, gdy w ję­zy­kach pro­gra­mo­wa­nia wy­so­kie­go po­zio­mu po­ja­wi­ły się struk­tu­ry: zło­żo­ne ty­py da­nych wią­żą­ce kil­ka od­ręb­nych pól. Powiąza­nie to dzia­ła na po­zio­mie lo­gicz­nym, gdyż po­la do­ty­czą wspól­nie jed­ne­go obiek­tu, oraz fi­zycz­nym, gdyż wy­mu­sza, by zaw­sze wy­stę­po­wa­ły ra­zem i by­ły trak­to­wa­ne ja­ko jed­ność. Jednak mi­mo dłu­gie­go sta­żu struk­tur w in­ży­nierii opro­gra­mo­wa­nia, a tak­że awan­su do mia­na kla­sy i obiek­tu, pro­gra­miś­ci – na­wet ci bar­dziej do­świad­cze­ni – nie ma­ją wy­ro­bio­ne­go na­wy­ku two­rze­nia zło­żo­nych ty­pów da­nych. Mści się to w mo­men­cie pierw­szej roz­bu­do­wy, gdy pros­ta prze­rób­ka zmie­nia się w ob­szer­ną re­fak­to­ry­za­cję.

Podczas pi­sa­nia apli­kac­ji wy­ma­ga­ją­cej po­da­nia da­nych kon­tak­to­wych oso­by, ta­kich jak imię, naz­wis­ko i adres, mo­że kor­cić, by za­pi­sać je w na­stę­pu­ją­cej struk­tu­rze:

struct Person
{
   std::string first_name;
   std::string last_name;
   std::string street_name;
   std::string building_no;
   std::string locum_no;
};

Takie po­dejś­cie mo­że wy­da­wać się sen­sow­ne w ro­zu­mie­niu idei YAGNI: stwo­rzy­liś­my naj­prost­szy kod speł­nia­ją­cy wy­ma­ga­nia. Ta im­ple­men­tac­ja po­win­na jed­nak wzbu­dzać nie­po­kój pro­gra­mis­ty: z lo­gicz­ne­go punk­tu wi­dze­nia adres oso­by jest od­dziel­ną kla­są da­nych. I fak­tycz­nie, nie­dłu­go po­tem mo­że po­ja­wić się wy­móg, by oso­ba – oprócz adre­su za­miesz­ka­nia – mia­ła rów­nież za­pi­sa­ny op­cjo­nal­ny adres ko­res­pon­den­cyj­ny.

Oczywiś­cie, prob­lem moż­na „roz­wią­zać” do­da­jąc ko­lej­ne po­la do kla­sy Person. Takie po­dejś­cie bar­dzo szyb­ko się jed­nak ze­mści, a je­że­li wy­mo­gi zacz­ną obej­mo­wać przy­pi­sy­wa­nie do­wol­nej licz­by adre­sów do jed­nej oso­by, prze­sta­nie być moż­li­we. Właści­wym spo­so­bem wy­brnię­cia z prob­le­mu, któ­ry po­wi­nien był zo­stać wy­bra­ny już na eta­pie pi­sa­nia kla­sy Person, jest wy­dzie­le­nie kla­sy adre­su:

struct Address
{
   std::string street_name;
   std::string building_no;
   std::string locum_no;
};

struct Person
{
   std::string first_name;
   std::string last_name;
   Address address;
};

Wprowadze­nie adre­su ko­res­pon­den­cyj­ne­go ogra­ni­cza się w ta­kim przy­pad­ku do do­da­nia jed­ne­go po­la do kla­sy Person:

struct Person
{
   std::string first_name;
   std::string last_name;
   Address address;
   std::optional<Address> correspondence_address;
};

Czasem kon­cep­cję wy­dzie­la­nia od­ręb­nych ty­pów zło­żo­nych dla lo­gicz­nie po­wią­za­nych da­nych opła­ca się rów­nież sto­so­wać dla po­je­dyn­czych war­toś­ci. W pierw­szym mo­men­cie mo­że się to wy­da­wać nie­lo­gicz­ne i nie­po­trzeb­nie skom­pli­ko­wa­ne. Jeżeli jed­nak ma­my uza­sad­nio­ne po­dej­rze­nie, że nie­dłu­go war­tość ta uleg­nie uzu­peł­nie­niu o dal­sze ele­men­ty skła­do­we, do­dat­ko­wy na­kład pra­cy mo­że się zwró­cić.

Jako przy­kład po­słu­ży opis mar­gi­ne­sów kart­ki pa­pie­ru:

struct Page
{
   float margin_left;
   float margin_right;
   float margin_top;
   float margin_bottom;
};

Wydawa­ło­by się, cał­kiem nor­mal­na struk­tu­ra, a ewen­tu­al­ne zmia­ny mog­ły­by do­ty­czyć je­dy­nie za­stą­pie­nia czte­rech od­ręb­nych pól ja­kąś ko­lek­cją. Przewidu­ją­cy pro­gra­mis­ta mo­że jed­nak zmie­nić po­wyż­szy za­pis na na­stę­pu­ją­cy:

struct Distance
{
   float value;
};

struct Page
{
   Distance margin_left;
   Distance margin_right;
   Distance margin_top;
   Distance margin_bottom;
};

Wbrew po­zo­rom ma to sens. Między licz­bą a od­leg­łoś­cią jest sub­tel­na róż­ni­ca: pierw­sza jest bez­wy­mia­ro­wa, dru­ga mia­no­wa­na. Dopóki umó­wi­my się, że w ca­łej apli­kac­ji sto­so­wa­na jest jed­na wspól­na jed­nost­ka mia­ry, mo­że­my przed­sta­wiać od­leg­ło­ści ja­ko licz­by ty­pu float. Jeżeli jed­nak każ­da mo­że mieć in­ną jed­nost­kę, ko­niecz­ne sta­je się roz­bu­do­wa­nie kla­sy Distance o do­dat­ko­we po­le:

class Unit
{
   ………
};

struct Distance
{
   float value;
   Unit unit;
};

Zmiana ni­by nie­wiel­ka, ale je­że­li wcześ­niej nie po­myś­la­ło się o wy­dzie­le­niu za­pi­su od­leg­ło­ści w po­sta­ci od­dziel­nej kla­sy, bę­dzie po­wią­za­na z po­waż­ną re­fak­to­ry­zac­ją. Ponadto, za­pi­sy­wa­nie od­leg­ło­ści nie ja­ko licz­by float lecz obiek­tu Distance ma do­dat­ko­wą, mi­łą ce­chę: kom­pi­la­tor spraw­dza za nas zgod­ność ty­pów i za­pro­tes­tu­je, je­że­li do po­la od­leg­ło­ści spró­bu­je­my wpi­sać cię­żar lub licz­ność ja­kie­goś przed­mio­tu.

Oczywiś­cie, nie moż­na po­paść w pa­ra­no­ję i dla każ­de­go po­la two­rzyć no­we­go ty­pu zło­żo­ne­go. Warto jed­nak za­sta­no­wić się nad każ­dym przy­pad­kiem i sto­so­wać ty­py pros­te tyl­ko wte­dy, gdy ma się pew­ność, że nic nie prze­ma­wia za bar­dziej skom­pli­ko­wa­ną op­cją.