Modulaarisuus

Moduulaarisuus on ohjelman suunnittelu- ja toteutusvaiheen mekanismi, jolla suuri ohjelma saadaan jaettua pienempiin ja helpommin hallittaviin osiin. Jos ohjelma on modulaarinen, se on jaettu selkeisiin osakokonaisuuksiin, joiden yhteistyön tuloksena saadaan koko ohjelma.

Moduuli on ohjelman osakokonaisuus, joka koostuu yhteenkuuluvista ohjelmarakenteista. Useimmissa ohjelmointikielissä jokainen moduuli toteutetaan erillisenä lähdekooditiedostona tai lähdekooditiedostoparina.

Moduulit ovat jossain määrin analogisia luokkien kanssa: Jokaisella moduulilla on julkinen ja yksityinen rajapinta. Luokat ja moduulit eivät kuitenkaan missään tapauksessa tarkoita samaa asiaa. Ainoat yhtäläisyydet liittyvät siihen, että kumpiakin käytetään julkisen rajapinnan avulla. Lisäksi, jos luokka muodostaa selkeän osakokonaisuuden ohjelmasta, se on usein järkevää toteuttaa moduulina, kuten myöhemmissä esimerkeissä käy ilmi.

Miniesimerkki: geometrialaskut

Tutkitaan esimerkkiä, josta moduulien toteuttamisen mekaaniset perusyksityiskohdat käyvät ilmi. Tämän esimerkin ohjelma on yksinkertainen prototyyppi geometrialaskin-ohjelmalle. Se koostuu kahdesta moduulista: pääohjelmasta ja geometriset laskutoimitukset sisältävästä moduulista.

Pääohjelmamoduuli tiedostossa calculator.cpp näyttää seuraavalta:

// Module: calculator / file: calculator.cpp
// Provides the main function for a geometry calculator.
#include "geometry.hh"
#include <iostream>

using namespace std;

int main() {
    double dimension = 0.0;

    cout << "Input the length of the side of a square: ";
    cin >> dimension;
    cout << "Perimeter: " << square_perimeter(dimension) << endl
         << "Area:      " << square_area(dimension) << endl;

    cout << "Input the radius of a circle: ";
    cin >> dimension;
    cout << "Perimeter: " << circle_perimeter(dimension) << endl
         << "Area:      " << circle_area(dimension) << endl;
}

C++:ssa jokainen moduuli, joka tarjoaa julkisessa rajapinnassaan palveluita muille moduuleille, tarvitsee esittelytiedoston (otsikkotiedosto, headerfile), jossa kuvataan kaikki ne palvelut, jotka kuuluvat moduulin julkiseen rajapintaan.

Esimerkin ohjelmassa moduuli geometry tarjoaa pääohjelmalle valmiit funktiot ympärysmittojen ja pinta-alojen laskemiseksi tarvittaville geometrisille kuvioille. Moduulin esittelytiedosto koostuu siis pääohjelman tarvitsemien funktioiden esittelyistä:

// Module: geometry / file: geometry.hh
// Header file of module geometry: provides the declarations for functions
// needed in calculations concerning geometric shapes
#ifndef GEOMETRY_HH
#define GEOMETRY_HH

double square_perimeter(double side);
double square_area(double side);

double circle_perimeter(double radius);
double circle_area(double radius);

#endif // GEOMETRY_HH

Viimeistään tässä vaiheessa on syytä panna merkille, kuinka pääohjelmamoduulissa (calculator.cpp) on rivi

#include "geometry.hh"

Tämä on esiprosessorin direktiivi, jolla C++-kääntäjälle kerrotaan moduulin käyttävän palveluita toisen moduulin julkisesta rajapinnasta. Kun edellä mainittu #include-rivi on lisätty calculator.cpp-tiedoston alkuun, voidaan main-funktiossa kutsua geometry.hh:ssa esiteltyjä funktioita, vaikka niiden määrittely onkin jossakin muualla.

Esiprosessori on kääntäjän osa, joka valmistelee tiedostot ennen varsinaista käännöstä. Esiprosessori tekee oikeastaan vain tekstikorvausta, eli rivin #include "geometry.hh" tilalle esiprosessori korvaa tiedoston geometry.hh sisällön.

Koska #include ainoastaan liittää tiedostot osaksi käännettävää tiedostoa, on mahdollista, että jokin tiedosto tulee mukaan useampaan kertaan ja päällekkäiset määrittelyt estävät kääntämisen. Ongelma ratkaistaan kirjoittamalla otsikkotiedostoon direktiivit #ifndef, #define ja #endif yllä olevan koodin mukaisesti. Ne kertovat esiprosessorille, että tiedoston sisältö liitetään ainoastaan kerran käännettävään tiedostoon riippumatta siitä, kuinka monta kertaa sitä yritetään ottaa mukaan.

Esimerkin mukaisesti  #ifndef-  ja  #define-rivien perässä on kyseessä olevan esittelytiedoston nimi isoilla kirjaimilla ja piste alaviivaksi muutettuna. Tämä on yleinen ohjelmointityylillinen menettely, jota kannattaa noudattaa.

Pääohjelmamoduulille ei yleensä ole tarpeen kirjoittaa omaa esittelytiedostoa, koska se ei tarjoa palveluita muille moduuleille.

Toteutustiedosto geometry.cpp sisältää moduulin julkisessa rajapinnassa esiteltyjen funktioiden määrittelyt sekä niiden tarvitsemat yksityisen rajapinnan apupalvelut:

// Module: geometry / file: geometry.cpp
// Implementation file of module geometry: provides the implementations
// for functions needed in calculations concerning geometric shapes
const double PI = 3.141593;

double square_perimeter(double side) {
    return 4 * side;
}

double square_area(double side) {
    return side * side;
}

double circle_perimeter(double radius) {
    return 2 * PI * radius;
}

double circle_area(double radius) {
    return PI * radius * radius;
}

Geometrialaskimen käännösvaiheet

Qt Creator huolehtii siitä, että ohjelmat käännetään oikealla tavalla. Tutkitaan nyt tarkemmin, mitä vaiheita ohjelman kääntämisessä on. Käännetään edellä esitelty geometrialaskin komentorivillä, jolloin käännöksen kaikki vaiheet tulevat paremmin esiin.

Kopioi hakemisto examples/11/geometry kääntämistä varten student-hakemiston puolelle. Huomaat, että kyseisessä hakemistossa ei ole Qt Creatorin .pro-tiedostoa.

Kaikkein yksinkertaisimmalla tavalla saat ohjelman käännettyä siirtymällä hakemistoon, jossa tiedostot ovat ja kirjoittamalla komennon:

g++ calculator.cpp geometry.cpp

Huomaa, että käännöskomentoon ei kirjoiteta ollenkaan tiedostoa geometry.hh. Miksi ei? Kyseinen tiedosto tulee mukaan käännökseen siten, että esiprosessori sisällyttää sen sisällön tiedostoon calculator.cpp.

Jos otat komennon suorittamisen jälkeen tiedostolistauksen, näet, että hakemistosta löytyy tiedosto nimeltä a.out. Tämä on suoritettava ohjelma (binääri). Saat ohjelman suoritettua komentorivillä kirjoittamalla komennon:

./a.out

Tämä yksinkertaisin käännöstapa ei kuitenkaan paljasta käännöksen vaiheista paljon sen enempää kuin Qt Creatorissa kääntäminenkään. Poista binääritiedosto (komento, jota tarvitset on rm), ja aloitetaan kääntäminen alusta vaiheittain.

../../_images/kaannos_fi.png

Geometrialaskimen käännöksen vaiheet kuvallisesti esitettynä

Ensimmäiseksi käännetään vain tiedosto geometry.cpp. Tämä tapahtuu komennolla:

g++ -c geometry.cpp

Tässä komentoriviparametri -c (compile) kertoo kääntäjälle, että tehdään vain käännös, ei linkitetä. Kun otat tämän jälkeen tiedostolistauksen, huomaat, että hakemistoon on ilmestynyt tiedosto geometry.o. Tämä tietosto sisältää konekoodia, mutta sitä ei voi yksinään suorittaa, koska se ei sisällä kokonaista ohjelmaa. Kuten tiedät, tiedosto geometry.cpp ei sisällä esimerkiksi main-funktiota ollenkaan.

Seuraavaksi käännetään vain tiedosto calculator.cpp samalla tavoin komennolla:

g++ -c calculator.cpp

Tämän jälkeen hakemistosta löytyy kaksi .o-päätteistä objektitiedostoa.

Käännöksen viimeinen vaihe on objektitiedostojen yhdistäminen suoritettavaksi tiedostoksi. Tämän saa tehtyä komennolla:

g++ calculator.o geometry.o

Jos binäärin haluaa nimetä joksikin muuksi kuin a.out, voi käännöskomennolle antaa parametrin -o nimi, esimerkiksi:

g++ -o calculator calculator.o geometry.o

Isoja ohjelmia käännettäessä voi olla selkeämpää erottaa käännös- ja linkitysvaiheet toisistaan. Näin ohjelmoija tietää koko ajan käännöstä suorittaessaan, kummasta vaiheesta virheilmoitukset tulevat.

Qt Creator käyttää ohjelmaa nimeltä make käännöksen automatisoimisessa. Sitä voi käyttää myös komentoriviltä. Jos päädyt joskus tekemään projektia, joka käännetään ja suoritetaan komentoriviltä, kannattaa make-ohjelman toimintaan perehtyä huolella.

Monimutkaisempi esimerkki: bussiaikataulut

Tutkitaan hakemistosta examples/11/bus_timetables löytyvää ohjelmaa, joka osaa kellonajan ja bussinumeron kysyttyään tulostaa kolmen seuraavan kyseisen numeroisen bussin lähtöajat. (Tässäkään hakemistossa ei ole valmiina Qt Creatorin .pro-tiedostoa, eli saat harjoitella ohjelman kääntämistä komentoriviltä.)

Ohjelma koostuu kolmesta moduulista:

timetable.cpp

Pääohjelmamoduuli sisältää aikataulutietorakenteen alustuksen, hyvin yksinkertaisen käyttöliittymän ja käyttäjän syöttämien kellonajan ja bussivuoron perusteella suoritettavan bussivuorojen haun tietorakenteesta.

Se käyttää hyväkseen sekä moduulin time että moduulin utilities julkisen rajapinnan tarjoamia palveluita.

time.hh + time.cpp

Moduuli määrittelee luokan Time, joka mahdollistaa kellonaikojen käsittelyn: alustuksen, asettamisen, näppäimistöltä lukemisen, tulostamisen ja pienempi–yhtäsuuri -vertailun suorittamisen kahdelle Time-tyyppiselle oliolle.

Se käyttää hyväkseen moduulin utilities julkisen rajapinnan palveluita.

utilities.hh + utilities.cpp

Moduuli tarjoaa funktiot merkkijonon muuttamiseksi kokonaisluvuksi ja kokonaisluvun lukemiseksi näppäimistöltä. Moduulin tarjoamat palvelut ovat jossain määrin sekalaisia funktioita, joille ei ollut muuta sopivampaa moduulia.

Se ei hyödynnä muiden moduulien tarjoamia palveluita.

Ohjelma saattaa vaikuttaa aluksi monimutkaiselta, mutta se ei muutamaa kohtaa lukuunottamatta sisällä mitään uutta. Uutta asiaa sisältävät kohdat on merkitty kommentilla //***  ja ne käydään läpi tässä:

Tiedoston timetable.cpp rivi 52

Kannattaa muistaa, että helpoin tulkinta viitteelle on lisänimen antaminen olemassa olevalle muuttujalla. Tästä näkökulmasta ajateltuna määrittely

const vector<Time>& timevec = iter->second;

tarkoittaa vain sitä, että jatkossa nimeä timevec voidaan käyttää tarkoittamaan samaa kuin iter->second. Määrittelyn avulla koodia on saatu selkeytettyä ja kirjoittamisen vaivaa vähennettyä.

Sana const on mukana siksi, että iter osoittaa const-säiliöön timetable, jota ei voi käsitellä kuin vakiona (const).

Tiedoston utilities.cpp rivi 7

Rivillä on varattu sana namespace, jonka avulla moduulille saadaan muodostettua yksityinen rajapinta. Sen palveluihin päästään käsiksi vain saman moduulin (lähdekooditiedoston) sisältä.

Kaikkia esittelyjä ja määrittelyjä, jotka ovat varattua sanaa namespace seuraavien aaltosulkeiden sisällä, voidaan käyttää vain utilities.cpp-tiedostossa.

Nimetön nimiavaruus on moduulimekanismin vastike luokkien private-osalle.

Tiedoston time.cpp rivi 11

Tämä on ensimmäinen esimerkki luokan rakentajasta, jossa olion alustus ei tapahdu alustuslistassa, vaan se tehdään rakentajafunktion rungossa olevien käskyjen avulla. Tässä ei sinällään ole mitään kummallista, sillä rakentaja on funktio, jonka rungossa voi tarvittaessa olla käskyjä.

Kuitenkin kurssin tässä vaiheessa saavutetulla osaamistasolla Time-luokan rakentajan toteutukseen liittyy ongelma: Mitä tulisi tehdä, jos parametri time on virheellinen? Funktio set_value palauttaa kyllä siinä tilanteessa arvon false, mutta rakentaja ei voi tehdä arvolla mitään, koska sillä itsellään ei ole paluuarvoa.

Oikea ratkaisu olisi poikkeuksen aiheuttaminen, kuten Pythonissa samankaltaisissa tilanteissa tehtiin. Sitä vain ei osata C++:lla vielä tehdä (vaan asia opitaan vasta seuraavalla ohjelmointikurssilla).

Muuta täysin uutta asiaa esimerkissä ei pitäisi olla. Olkoonkin, että aiemmin opittuja mekanismeja on saatettu käyttää luovilla tavoilla, joten esimerkkiin kannattaa tutustua ajatuksen kanssa.

Moduulin julkinen rajapinta (.hh-tiedosto)

Moduulin julkinen rajapinta (siis moduulin toisille moduuleille tarjoamat palvelut) kirjataan moduulin esittelytiedostoon, jonka nimi perinteisesti päättyy C++:ssa .hh-kirjainyhdistelmään.

Julkinen rajapinta voi sisältää:

  • funktioiden esittelyjä
  • const-vakioiden määrittelyjä
  • uusien tietotyyppien (myös luokkien) määrittelyjä
  • mitä tahansa yhdistelmiä edellisistä.

Julkinen rajapinta ei saa sisältää:

  • muuttujien määrittelyjä
  • funktioiden tai luokan metodien määrittelyjä.

Jokaiseen lähdekooditiedostoon, joka tarvitsee jotain palvelua toisen moduulin julkisesta rajapinnasta, lisätään rivi

#include "palvelun_tarjoavan_moduulin_nimi.hh"

Vaikka esimerkkikoodissa ei näin ollut tarvetta tehdä, edellä mainittu #include-rivi saattaa olla tarpeen laittaa myös jonkun moduulin .hh-tiedostoon. Millaisessa tilanteessa näin voisi käydä?

Samoin on yleistä, että moduulin .cpp-tiedosto joutuu tekemään include-operaation omalle .hh-tiedostolleen (vrt. esimerkin tiedosto time.cpp). Miksi näin on pitänyt tehdä?

Direktiiviä #include ei saa käyttää siihen, että sen avulla otettaisiin käyttöön .cpp-tiedostoja. Vaikka tämä joissain tilainteissa saattaakin toimia, se kuvaa ohjelmoijan syvää ymmärtämättömyyttä moduulimekanismin toiminnasta.

Ohjelmointityylillisesti pidetään hyvänä käytäntönä sitä, että otettaessa käyttöön sekä ohjelmoijan omia moduuleja että systeemin standardikirjastoja, luetellaan omat moduulit ensin:

#include "omamoduuli-1.hh"
...
#include "omamoduuli-n.hh"
#include <standardikirjasto-1>
...
#include <standardikirjasto-m>

Tällä tavalla kääntäjä saadaan tarkistamaan, että itse kirjoitetut moduulit sisältävät kaikki tarvittavat include-tiedostot. Toisin sanoen voidaan varmistua siitä, omat moduulit muodostavat itsenäisesti kääntyvän käännösyksikön.

Monimutkaisten ohjelmien kanssa eteen tulee usein vastaan tilanne, jossa yksi ja sama .hh-tiedosto saattaa tulla otetuksi käyttöön useita kertoja, kun eri lähdekooditiedostot suorittavat sille #include-käskyn omien tarpeidensa täyttämiseksi. Ongelman ratkaisu esitettiin tämän materiaaliosion alkupäässä, kun käsiteltiin miniesimerkkiä geometrialaskuista.

Moduulin yksityinen rajapinta (.cpp-tiedosto)

Toteutus- eli .cpp-tiedostossa määritellään kaikki ne funktiot ja metodit, jotka esittelytiedostossa on kuvattu moduulin julkiseksi rajapinnaksi. Toteutustiedosto voi toki sisältää mitä tahansa koodia, jota julkisen rajapinnan funktioiden toteuttamisen avuksi tarvitaan.

Jos .cpp-tiedostossa halutaan toteuttaa moduulin omia apufunktioita, joita ei voida edes kikkailemalla kutsua muista moduuleista, ne on esiteltävä ja määriteltävä nimettömän nimiavaruuden sisällä:

namespace {  // Esittelyosa
    void moduulin_yksityinen_funktio();
    ...
}

...
// julkisen rajapinnan funktiomäärittelyt tässä
...

namespace {  // Määrittelyosa
    void moduulin_yksityinen_funktio() {
        ...
    }
    ...
}

Esittelyosa voi tuttuun tapaan puuttua, jos funktioiden määrittelyt järjestetään niin, että kääntäjä on nähnyt funktioiden määrittelyt ennen kuin niitä yritetään kutsua.

Käytännössä siis määrittelyosa kirjoitetaan tiedoston alkuun, kuten esimerkkiohjelman utilities.cpp-tiedostossa oli tehty riviltä 7 alkaen.

Edellä esitetty nimetön nimiavaruus -mekanismi pätee vain normaaleihin funktioihin. Jos taas .cpp-tiedostossa määritellään luokan metodeita, namespace-mekanismilla ei ole merkitystä, koska luokka huolehtii omista rajapinta/näkyvyysaluejärjestelyistään public- ja private-mekanismin avulla.

Jos moduulin julkisessa rajapinnassa (.hh-tiedosto) on määritelty const-vakioita tai tietotyyppejä, joita tarvitaan myös moduulin .cpp-tiedostossa, joudutaan tekemään #include moduulin omalle .hh-tiedostolle.

Ohjelmoija voi halutessaan määritellä myös nimettyjä nimiavaruuksia:

namespace mun_avaruus {
    void mun_funktio() {
        ...
    }
}

Tällaisia funktioita voidaan kutsua kahdella tavalla joko

mun_avaruus::mun_funktio();

tai lisäämällä kooditiedostoon using namespace-käskyn

using namespace mun_avaruus;
...
mun_funktio();

Nimetyn nimiavaruuden toteuttamiseen kurssilla tuskin tulee tarvetta, mutta kyseessä on mukava yleissivistävä selitys sille, miksi toisinaan ohjelmakooditiedostoihin on kirjoitettu

using namespace std;

Nimettyjen nimiavaruuksien avulla voidaan välttää nimikonflikteja. Esimerkiksi tunniste nimi voisi opintorekisteriohjelmassa olla käytössä sekä opiskelijan tiedoissa että opintojakson tiedoissa. Kun molemmat näistä olisi toteutettu omissa nimiavaruuksissaan, tunnisteisiin päästäisiin käsiksi kirjoittamalla Opiskelija::nimi tai Opintojakso::nimi.

Moduulien suunnittelusta

Oikeassa ohjelmointiprojektissa ohjelman jako moduuleihin tehdään ohjelman suunnitteluvaiheessa. Periaatteessa moduulit löytää miettimällä, mistä loogisista osakokonaisuuksista toteutettava ohjelma koostuu.

Esimerkkinä käytetty bussiaikatauluohjelma on lähes lapsellisen yksinkertainen ja pieni, jotta moduulijaolla varsinaisesti saavutettiin mitään hyötyä, mutta silti asiaa miettimällä (jo ennen koodauksen aloittamista) päällimäisiksi ajatuksiksi kohosi:

  • Ohjelman pitää pystyä käsittelemään ja vertailemaan kellonaikoja.
  • Koska eri muodossa esitettyjä numeroita pitää pystyä lukemaan näppäimistöltä ja suorittaa muutoksia merkkijonojen ja numeroiden välillä, tuntui aika selvältä, että tarvitaan jonkinlainen joukko funktioita käyttäjän syöttämien numeroiden käsittelyyn.
  • Myös hyvin yksinkertainen käyttöliittymä tarvittaisiin.
  • Lisäksi tarvitaan algoritmi, joka osaa hakea sopivat bussivuorot ohjelman käyttämästä tietorakenteesta.

Lopulta, lähinnä esitysteknisistä syistä, käyttöliittymä ja hakualgoritmi päätettiin toteuttaa pääohjelmamoduulina ja kaksi muuta kokonaisuutta omina moduuleinaan, jolloin lopputuloksena ohjelman moduulijaoksi saatiin:

  • pääohjelmamoduuli (timetable)
  • ajankäsittelymoduuli (time)
  • apufunktiomoduuli numeroiden käsittelyyn (utilities).

Moduulien etsinnässä siis on päämääränä jakaa ohjelma osiin, joista kukin:

  • toteuttaa selkeästi rajatun osan kokonaisuudesta ja
  • on riittävän yksinkertainen (mitä jos ei ole?).

Jos ongelma on isohko, kannattaa moduuleita yrittää etsiä pilkkomalla ongelmaa ja saatuja osaongelmia toistuvasti pienempiin osiin, kunnes lopulta päädytään niin pieniin osaongelmiin, että ne ovat helposti hallittavissa. Tällaista lähestymistapaa kutsutaan top-down-menetelmäksi.

Seuraavassa listassa luetellaan yleisiä suuntaviivoja kokonaisuuksista, jotka tyypillisesti kannattaa toteuttaa moduuleina sen kokoluokan ohjelmissa, joihin kurssilla törmätään:

  • luokka
  • pääohjelma
  • käyttöliittymä/monimutkaisen syötteen käsittely
  • tiedoston lukeminen ja jäsentely
  • yleiskäyttöiset algoritmit (haku, lajittelu).

Oma mielikuvitus auttaa myös paljon.

Huomaa, että bussiaikatauluohjelman moduulit olisi voitu toteuttaa myös luokkina, joilla on omat julkiset ja yksityiset rajapinnat. Luokat ovat tyypillisin esimerkki moduuleista C++-ohjelmoinnissa.

Moduulien julkisten rajapintojen suunnittelu on haastavampaa kuin moduulijaon suunnittelu.

Varsinkin kokemuksen karttuessa ainakin suurpiirteisen moduulijaon sen kokoisissa ohjelmissa, joihin peruskursseilla törmätään, näkee lähes suoraan.

Julkisen rajapinnan suunnittelussa täytyy ottaa jo huomattavasti syvällisemmin kantaa siihen, mitä palveluita ja miten moduulin on tarkoitus tarjota muille. Jotta tämän voi tehdä edes jollain tasolla menestyksellisesti, ohjelman rakennetta ja toteutusperiaatteita on tarpeen miettiä melkoisen tarkasti.

Jos rajapinta on suunniteltu ideaalisen onnistuneesti, niin lopputuloksena kukin moduuli tarjoaa palveluita muille moduuleille julkisessa rajapinnassaan, jota ei periaatteessa ole tarpeen muokata projektin edetessä. Näin onnistuneeseen lopputulokseen päästään harvoin. Käytännössä projektin edetessä tulee aina vastaan tilanteita, joissa ymmärretään, että rajapinnassa on päädytty tekemään jotain huonosti tai siitä on unohtunut jotain aivan kokonaan. Näissä tilanteissa rajapintoja joudutaan muuttamaan, mikä saattaa olla kallista, jos puutteellisen rajapinnan pohjalta on ehditty jo kirjoittamaan paljon koodia. Muutokset rajapintaan heijastuvat muutoksina kaikkialle, missä sitä käytetään.

Moduulaarisuudella saavutettavat hyödyt

Modulaarisuuden hyödyt ovat pitkälti samoja kuin luokkien hyödyt, koska ne juontavat juurensa pääosin rajapintojen käytöstä:

  • Moduulin toteutusta (siis .cpp-tiedostoa eli yksityistä rajapintaa) voidaan muuttaa julkisen rajapinnan säilyessä ennallaan.
  • Ohjelmiston loogisesti yhteenkuuluvat osat voidaan koota samaan pakettiin, mikä selkeyttää ohjelmaa ja helpottaa sen testaamista ja ylläpitoa.
  • Moduuleita voidaan kehittää projektissa rinnakkain sen jälkeen, kun julkinen rajapinta on sovittu.
  • Modulaarisuus on hyvä työkalu suurien ohjelmointiprojektien hallintaan.
  • Usein moduuleita voidaan uudelleenkäyttää joko kokonaan tai ainakin osittain.
  • Useimmat modulaarisuutta tukevat ohjelmointikielet mahdollistavat moduulien kääntämisen erikseen. Tämä nopeuttaa kehitystyötä ja kuluttaa vähemmän resursseja, sillä muutosten jälkeen vain muuttuneet moduulit tarvitsee kääntää uudelleen.