RSS

Stałość parametrów istotna zawsze dla podprogramu, tylko czasem w interfejsie

Liczba odsłon: 1687

W wie­lu ję­zy­kach pro­gra­mo­wa­nia ist­nie­ją sło­wa klu­czo­we umoż­li­wia­ją­ce na­da­nie zmien­nym, po­lom i pa­ra­met­rom atry­bu­tu sta­ło­ści. Definio­wa­nie moż­li­wie naj­więk­szej – w gra­ni­cach roz­sąd­ku, oczy­wiś­cie – licz­by sym­bo­li ja­ko war­toś­ci sta­łych jest do­brą prak­ty­ką pro­gra­mi­stycz­ną. Przede wszyst­kim, trud­niej jest po­peł­nić błąd po­le­ga­ją­cy na nad­pi­sa­niu zmien­nej lub pa­ra­me­tru czy po­wtór­nym wy­ko­rzys­ta­niu jesz­cze uży­wa­ne­go sym­bo­lu. Dodatkowo, trans­la­tor ję­zy­ka częs­to wy­ko­rzys­tu­je in­for­mac­ję o sta­ło­ści sym­bo­lu przy opty­ma­li­zac­ji ge­ne­ro­wa­ne­go ko­du.

Szczególnie nie­do­ce­nia­na jest moż­li­wość usta­le­nia pa­ra­met­rów pod­pro­gra­mów ja­ko sta­łych. Zwykłe pa­ra­met­ry, prze­ka­zy­wa­ne przez war­tość, są lo­kal­ne dla pod­pro­gra­mu, lecz moż­na zmie­niać ich war­tość. Jeżeli ta­kiej po­trze­by nie ma, przy­pad­ko­wa zmia­na mo­że za­gro­zić po­praw­ne­mu dzia­ła­niu pro­gra­mu. Na przy­kład, w ję­zy­ku C++ ty­po­wy błąd po­cząt­ku­ją­ce­go pro­gra­mis­ty:

void podprogram(unsigned int ile_elementow)
{
   if (ile_elementow = 0) {
      // .....
   }
}

mo­że zo­stać łat­wo wy­kry­ty i zneu­tra­li­zo­wa­ny, je­że­li pa­ra­metr ile_elementow zo­sta­nie za­de­kla­ro­wa­ny ja­ko const:

void podprogram(const unsigned int ile_elementow)
...

Atrybut const ma jesz­cze więk­sze zna­cze­nie w przy­pad­ku pa­ra­met­rów prze­ka­zy­wa­nych przez wskaź­nik lub re­fe­ren­cję, co jest ty­po­we dla ar­gu­men­tów obiek­to­wych. Brak const mo­że w ta­kim przy­pad­ku spo­wo­do­wać po­ja­wie­nie się nie­po­żą­da­nych skut­ków ubocz­nych wy­wo­ła­nia pod­pro­gra­mu. O ile bo­wiem przy­pad­ko­wa zmia­na ar­gu­men­tu prze­ka­za­ne­go przez war­tość jest lo­kal­na dla pod­pro­gra­mu i zdez­or­ga­ni­zu­je głów­nie je­go dzia­ła­nie, przy­pad­ko­wa zmia­na ar­gu­men­tu prze­ka­za­ne­go przez wskaź­nik lub re­fe­ren­cję do­pro­wa­dzi do za­bu­rze­nia sta­nu pod­pro­gra­mu nad­rzęd­ne­go.

W ję­zy­ku Java wszyst­kie pa­ra­met­ry pod­pro­gra­mów są prze­ka­zy­wa­ne przez war­tość. Oczywiś­cie, pod­pro­gram mo­że zmie­nić war­tość pa­ra­me­tru:

void podprogram(int ileElementow) {
   ileElementow = 0; // Ups!
   ....
}

ale ist­nie­je sło­wo klu­czo­we final, któ­re czy­ni prze­ka­za­ny ar­gu­ment nie­zmien­nym:

void podprogram(final int ileElementow) {
   ileElementow = 0; // Błąd kompilacji!
   ....
}

Ponadto, ozna­cze­nie pa­ra­me­tru przez final mo­że być wręcz nie­zbęd­ne, je­że­li ar­gu­ment ma być uży­ty w kla­sie we­wnętrz­nej de­fi­nio­wa­nej we­wnątrz pod­pro­gra­mu.

Wspomnia­łem, że wszyst­kie pa­ra­met­ry pod­pro­gra­mów w ję­zy­ku Java są prze­ka­zy­wa­ne przez war­tość. Niestety, o ile re­fe­ren­cje do obiek­tów są rów­nież prze­ka­zy­wa­ne przez war­tość, to za po­śred­nic­twem re­fe­ren­cji moż­na do­wol­nie zmie­niać obiekt prze­ka­za­ny ja­ko ar­gu­ment. Niewiele tu zmie­nia sło­wo klu­czo­we final: choć sa­ma re­fe­ren­cja sta­nie się nie­zmien­na i ar­gu­men­tem bę­dzie na pew­no wy­łącz­nie prze­ka­za­ny orygi­nal­nie obiekt, sam obiekt – je­że­li jest mu­to­wal­ny – bę­dzie mógł być zmie­nia­ny.

Najważniej­sze jest jed­nak to, że oby­dwa wspom­nia­ne ję­zy­ki pro­gra­mo­wa­nia nie uzna­ją atry­bu­tów sta­ło­ści po­wią­za­nych z pa­ra­me­tra­mi za in­te­gral­ny ele­ment de­kla­ra­cji pod­pro­gra­mu.

C++

W ję­zy­ku C++ de­kla­ra­cja pod­pro­gra­mu mo­że róż­nić się od na­głów­ka de­fi­ni­cji pod­pro­gra­mu w za­kre­sie sta­ło­ści ar­gu­men­tów prze­ka­zy­wa­nych przez war­tość. Na przy­kład, me­to­da za­de­kla­ro­wa­na ja­ko:

struct Klasa
{
   void Metoda(int Parametr);
};

mo­że zo­stać na­stęp­nie zde­fi­nio­wa­na ja­ko:

void Klasa::Metoda(const int Parametr)
{
   // .....
}

Mimo do­da­nia sło­wa klu­czo­we­go const, zo­sta­nie ona pra­wid­ło­wo roz­poz­na­na i pod­da­na kon­so­li­da­cji. Nie wol­no jed­nak za­po­mi­nać, że do­ty­czy to wy­łącz­nie te­go kwa­li­fi­ka­to­ra uży­te­go w sto­sun­ku do ele­men­tu prze­ka­zy­wa­ne­go przez war­tość. O ile za­tem na­głów­ki

void podprogram(int parametr);
void podprogram(const int parametr);

są z punk­tu wi­dze­nia ję­zy­ka C++ rów­no­znacz­ne, po­niż­sze dwie pa­ry nie są i nie zo­sta­ną pra­wid­ło­wo sko­ja­rzo­ne, gdyż zmie­nia­ją sens ko­du i spo­so­bu prze­ka­zy­wa­nia pa­ra­met­rów:

void podprogram(klasa &parametr);
void podprogram(const klasa &parametr);
void klasa::metoda();
void klasa::metoda() const;

W pierw­szym przy­pad­ku kwa­li­fi­ka­tor do­ty­czy wska­zy­wa­ne­go obiek­tu, a więc sta­łość jest na­rzu­ca­na nie we­wnętrz­nie na ar­gu­ment wi­dzia­ny z po­zio­mu pod­pro­gra­mu, lecz na obiekt prze­ka­zy­wa­ny z zew­nątrz. W dru­gim przy­pad­ku sta­łość do­ty­czy ca­łej me­to­dy i wpły­wa na to, ja­kie pra­wa mo­dy­fi­ko­wa­nia obiek­tu bę­dzie mia­ła ta me­to­da.

Sytuacja jest nie­co bar­dziej skom­pli­ko­wa­na, je­że­li pa­ra­met­rem pod­pro­gra­mu jest wskaź­nik. Sam wskaź­nik jest prze­ka­zy­wa­ny przez war­tość, za­tem na­rzu­ce­nie mu atry­bu­tu sta­ło­ści jest we­wnętrz­ną kwe­stią pod­pro­gra­mu i po­niż­sze dwa na­głów­ki są rów­no­waż­ne:

void podprogram(klasa *parametr);
void podprogram(klasa * const parametr);

jed­nak sta­łość ele­men­tu wska­zy­wa­ne­go – po­dob­nie jak w przy­pad­ku re­fe­ren­cji – jest już częś­cią kon­trak­tu pod­pro­gra­mu i po­niż­sze dwa na­głów­ki nie są rów­no­waż­ne:

void podprogram(klasa *parametr);
void podprogram(const klasa *parametr);

Java

W ję­zy­ku Java de­kla­ra­cja me­to­dy wpro­wa­dzo­na w ra­mach inter­fej­su mo­że róż­nić się od na­głów­ka me­to­dy w im­ple­men­tac­ji te­go inter­fej­su w za­kre­sie sta­ło­ści ar­gu­men­tów. Na przy­kład, me­to­da za­de­kla­ro­wa­na ja­ko:

interface Interfejs {
   void metoda(int liczba);
}

mo­że zo­stać za­im­ple­men­to­wa­na ja­ko:

class Implementacja implements Interfejs {
   public void metoda(final int liczba) {
      // ...
   }
}

Ponie­waż w ję­zy­ku Java nie ma moż­li­woś­ci na­rzu­ce­nia sta­ło­ści na ar­gu­men­ty prze­ka­zy­wa­ne po­przez re­fe­ren­cję, kwa­li­fi­ka­tor final moż­na sto­so­wać do wszyst­kich pa­ra­met­rów pod­pro­gra­mów.


Przedsta­wio­ne po­wy­żej roz­wią­za­nia mo­gą być trak­to­wa­ne na gra­ni­cy sztucz­ki wy­ko­rzy­stu­ją­cej pew­ne ce­chy trans­la­to­rów wspom­nia­nych ję­zy­ków pro­gra­mo­wa­nia. Mają one jed­nak istot­ny wpływ na opro­gra­mo­wa­nie i je­go ja­kość.

Z jed­nej stro­ny, sto­so­wa­nie atry­bu­tów sta­ło­ści w sto­sun­ku do prze­ka­zy­wa­nych przez war­tość pa­ra­met­rów pod­pro­gra­mów zmniej­sza ry­zy­ko po­peł­nie­nia błę­du, ułat­wia wy­kry­cie przy­pad­ko­wej mo­dy­fi­ka­cji ar­gu­men­tów i po­sze­rza po­le dzia­ła­nia op­ty­ma­li­za­to­ra. Z tych po­wo­dów tech­ni­ka ta jest god­na po­le­ce­nia i jest opi­sy­wa­na w li­te­ra­tu­rze. Jej prze­ciw­ni­cy twier­dzą jed­nak – i nie bez rac­ji – że do­dat­ko­we kwa­li­fi­ka­to­ry za­ciem­nia­ją tekst źród­ło­wy pro­gra­mu, cze­go nie re­kom­pen­su­ją w peł­ni za­le­ty wy­ni­ka­ją­ce z ich sto­so­wa­nia.

Przedsta­wio­na w ar­ty­ku­le tech­ni­ka, w któ­rej tyl­ko de­fi­ni­cje pod­pro­gra­mów za­wie­ra­ją kwa­li­fi­ka­to­ry sta­ło­ści, łą­czy za­le­ty oby­dwu po­dejść. Interfejs funkcjo­nal­ny klas mo­że być zwar­ty i bar­dziej czy­tel­ny, a im­ple­men­tac­je mo­gą być za­bez­pie­czo­ne przed przy­pad­ko­wą mo­dy­fi­ka­cją ar­gu­men­tów.

Dodatkowo, ukry­wa się w ten spo­sób szcze­gó­ły im­ple­men­ta­cyj­ne po­szcze­gól­nych pod­pro­gra­mów. Użytkow­ni­ka pod­pro­gra­mu lub kla­sy nie in­te­re­su­je, w ja­ki spo­sób ich autor za­bez­pie­czył swój kod przed błę­da­mi. Istotne jest, by uży­te kwa­li­fi­ka­to­ry wska­zy­wa­ły kon­trakt funkcjo­nal­ny, któ­re­go użyt­kow­nik mu­si prze­strze­gać.

Oczywiś­cie, ta­kie łą­czo­ne po­dejś­cie wciąż wy­ma­ga od pro­gra­mis­tów więk­sze­go sku­pie­nia się na szcze­gó­łach tech­nicz­nych. W więk­szoś­ci przy­pad­ków moż­na przy­jąć za­sa­dę, że wszyst­kie pa­ra­met­ry prze­ka­zy­wa­ne przez war­tość po­win­ny być sta­łe, a zmie­nia­nie war­toś­ci ar­gu­men­tów po­win­no być świa­do­mym od­stęp­stwem od tej re­gu­ły. W pew­nym sen­sie, po­twier­dze­niem tej te­zy jest ist­nie­nie w ję­zy­ku C++ po­ję­cia re­fe­ren­cji, bę­dą­cej w prak­ty­ce sta­łym co do war­toś­ci, po­praw­nie za­ini­cja­li­zo­wa­nym wskaź­ni­kiem (*const).