FONDAMENTI LOGICI E METODOLOGICI INTRODUZIONE I seguenti appunti, più che una vera e propria dispensa, sono delucidazioni ed approfondimenti riguardanti le numerose slide con cui vengono spiegati gli argomenti trattati in questa parte del Corso di Informatica, concernente i FONDAMENTI LOGICI E METODOLOGICI (per brevità FLM) della materia. Le slide sono tendenzialmente autoesplicative ma presuppongono almeno un minimo di spiegazione, cosa che viene riservata per le lezioni in aula e/o in laboratorio ove, tra l’altro, saranno svolti parecchi esempi ed esercitazioni. Alcuni concetti di base, qui necessariamente solo introdotti, sono ripresi ed approfonditi in apposite dispense, in particolare: per un’introduzione all’informatica (nozione di Bit, Codifica Binaria dell’Informazione, Memoria e altre nozioni di base) si veda la dispensa Informazioni e Macchine; per un’introduzione agli elaboratori (schemi logicofunzionali, componenti interni e periferici, principi di funzionamento e architetture base) si veda la dispensa Unità di Governo Programmabili(1) ed eventualmente Sistemi di Numerazione. per i concetti di Informazione, Dato, Tipo di Dato, Struttura Informativa e le questioni concernenti la visione astratta e concreta dei dati, si veda la dispensa Dati(1). Inoltre, nell’apposita dispensa FLM-Esercitazioni sono chiaramente esemplificati diversi concetti per fissare bene le nozioni di base riguardanti la programmazione. La sezione Complementi riporta, in breve, alcune informazioni utili riguardanti il variegato mondo ICT (Information and Communication Technology). Sono altresì disponibili corsi, esempi ed esercitazioni per i linguaggi C/C++ e Visual Basic. Iniziare con le slide da FLM01 a FLM12a. prof. Felice Zampini Fondamenti Logici e Metodologici 1/34 LINGUAGGI DI PROGRAMMAZIONE Slide FLM13 e FLM14. Si premettono alcune definizioni circa i linguaggi. LINGUAGGIO: insieme finito dei simboli (alfabeto) e delle regole per associarli (grammatica o sintassi: regole di produzione che consentono di ottenere tutte e sole le frasi del linguaggio) in modo significativo (semantica: regole di assegnazione dei significati alle frasi del linguaggio) ai fini di una qualsiasi comunicazione. LINGUAGGIO scopi. ARTIFICIALE: linguaggio definito per determinati LINGUAGGIO FORMALE O SIMBOLICO: insieme di stringhe, costituite da caratteri o simboli di un dato alfabeto, definite mediante regole formali di generazione (grammatica) o algoritmi di analisi e riconoscimento. LINGUAGGIO DI PROGRAMMAZIONE: linguaggio artificiale codificare un algoritmo in una sequenza di proposizioni. per LINGUAGGIO PROCEDURALE: linguaggio programmativo in cui le istruzioni corrispondenti alla risoluzione di un problema devono essere eseguite in modo ordinato (linguaggio algoritmico). LINGUAGGIO PROCEDURALE STRUTTURATO: linguaggio programmativo che impone i costrutti della programmazione strutturata (sequenzaalternativa-selezione), di norma escludendo i trasferimenti di controllo all'interno di un modulo di programma. LINGUAGGIO NON PROCEDURALE: linguaggio in cui gli obiettivi da conseguire tramite il programma sono descritti senza specificare il relativo algoritmo risolvente (linguaggio non algoritmico: è richiesta solo la specifica del "cosa" fare senza richiedere il "come" farlo). Linguaggio Macchina (linguaggi di 1a generazione) Il linguaggio macchina (chiamato anche Linguaggio Assoluto) è quello direttamente usato dalla macchina (CPU) e costituisce il linguaggio al più basso livello (in quanto legato all'hardware, quindi alla specificità del tipo di elaboratore); tale linguaggio va annoverato come linguaggio di progettazione degli elaboratori elettronici. Il linguaggio macchina consiste in una CODIFICA BINARIA in corrispondenza della quale è prevista l'esecuzione di determinate funzioni (funzioni macchina) da parte della CPU; tale codifica binaria concerne: - le istruzioni macchina (codici operativi detti opcode); - gli operandi (indirizzi rispettive lunghezze. prof. Felice Zampini fisici di memoria Fondamenti Logici e Metodologici o dati) nonchè le 2/34 Linguaggio Assembler (linguaggi di 2a generazione) Il linguaggio codici operativi e si rappresentano codifica assembler elementi: assembler è un linguaggio di programmazione in cui i gli indirizzi degli operandi delle istruzioni macchina in modo simbolico (di norma codici mnemonici); la implica dunque (almeno) la specificazione dei seguenti - Codici Operativi simbolici; - Operandi (indirizzi di memoria o dati) simbolici. Nell'assembler, ogni proposizione del linguaggio corrisponde ad una singola istruzione macchina, percui tale linguaggio rappresenta il linguaggio simbolico al più basso livello (linguaggio machine-oriented). Data questa CORRISPONDENZA UNO-UNO TRA ISTRUZIONI ASSEMBLER ED ISTRUZIONI il linguaggio assemblativo resta strettamente vincolato all'hardware, consentendo di sfruttarne le prestazioni in modo ottimizzato ma implicando, da parte del programmatore, notevoli conoscenze circa l'elaboratore utilizzato; a tale vantaggio si contrappone lo svantaggio di una difficile "trasportabilità" dei programmi, motivo percui la programmazione assembler è spesso utilizzata per la realizzazione di software di base da parte di specialisti del settore. MACCHINA, Linguaggio ad alto livello (HLL-High Level Language) Un linguaggio HLL è un linguaggio programmativo in cui LA SINTASSI È INDIPENDENTE DALLA MACCHINA; in tale linguaggio le istruzioni vengono espresse in forma simbolica e corrispondono in genere a più operazioni macchina, mentre gli operandi vengono indicati non come indirizzi di memoria ma mediante nomi simbolici (linguaggi problem-oriented). Lo sviluppo di tali linguaggi (linguaggi di 3a generazione), svincolando la programmazione dagli aspetti hardware (tramite i relativi programmi traduttori) e facilitando lo sviluppo di software da parte di utenti non specialisti (di hardware), ha anche notevolmente contribuito a rimuovere il non trascurabile vincolo della difficile trasportabilità dei programmi assembler. Linguaggi di 4a generazione (4GL) Sono linguaggi di tipo non procedurale in cui la grammatica e la sintassi tendono ad avvicinarsi a quelle del linguaggio naturale, in modo da facilitarne il loro impiego; si possono genericamente considerare tali, p.es., i: - linguaggi conversazionali (interattività); - linguaggi di interrogazione e manipolazione di data bases. Linguaggi di 5a generazione (5GL) Sono linguaggi che ricorrono alle metodologie dell'intelligenza artificiale, con l'obiettivo di raggiungere una interazione globale uomomacchina (istruzioni comunicate direttamente alla macchina tramite la voce, il movimento, il tatto, ecc.). La seguente lista elenca alcuni (fra i più comuni) degli oltre 300 linguaggi di tipo evoluto (HLL) sviluppati a livello internazionale. prof. Felice Zampini Fondamenti Logici e Metodologici 3/34 LINGUAGGI SIGLA DI TIPO HLL S I G N I F I C A T O TIPO -------------------------------------------------------------------------------ADA A Defense Algorithm (1980) General Purpose ALGOL ALGOrithmic Language (1958) Scientif./Universale APL A Programming Language (1968) Algebrico BASIC Beginner's All-purp. Symb. Instr. Code (1964) Conversazionale C C (1972) - ANSI C (1989) General Purpose C++ C++ (1979) - ANSI C++ (1990) OOP CLOS Common Lisp Object Oriented OOP COBOL COmmon Business Oriented Language (1960) Gestion./Commerciale CSMP Continuous System Modelling Program Simulazione continua DL/1 Data Language/1 DBMS FORTH Forth (1968) 4GL FORTRAN FORmula TRANslation (1957) Scientifico GOLD Gold CAD/CAM GROOVE Groove (1970) Musicale/Interattivo HTML HyperText Markup Language (1995) Grafico (Internet) JAVA Java (1996) Animazione (Internet) LIFER Lifer AI/ES LISP LISt Processor (1958) Scientifico/Robotico LOGO Logo (1967) Education MODULA 2 Modula 2 Scientif./Gestionale MUSIC V Music V (1969) Musicale NATURAL Natural DBMS PASCAL Pascal (1968) - ANSI Pascal (1973) Scientif./Gestionale PL/1 Programming Language/1 (1965) General Purpose POP-2 Pop-2 HLL/AI PROLOG PROgramming in LOGic (1977) Logica/AI RPG Report Program Generator (1962) Gestionale SEQUEL Sructured English Query Language DBMS SIMULA Simula Simulazione discreta SMALLTALK Smalltalk OOP SNOBOL StriNg Oriented symBOlic Language (1963) WP VIS. BASIC Visual Basic Visuale VISUAL C++ Visual C++ Visuale VRML Virtual Reality Modeling Language (1995) Realtà Virtuale prof. Felice Zampini Fondamenti Logici e Metodologici 4/34 COMUNICAZIONE CON LA MACCHINA MACCHINE E LINGUAGGI Per programmare un elaboratore occorre disporre di un linguaggio di programmazione: un linguaggio di programmazione è un linguaggio formale o simbolico in cui sono definiti: ALFABETO, GRAMMATICA LESSICALE, GRAMMATICA STRUTTURALE (slide FLM15). Dato l’alfabeto di un linguaggio di programmazione, dal punto di vista lessicale gli elementi primitivi (TOKEN) che intervengono nella stesura di un programma (codifica) sono in genere: - Parole riservate (keywords) - Identificatori - Segni di punteggiatura e caratteri speciali - Operatori - Costanti numeriche e letterali - Commenti. Tramite questi elementi si possono formare, rispettando la sintassi e la semantica del linguaggio, ESPRESSIONI più o meno complesse per definire le elaborazioni da svolgere, scrivendo il PROGRAMMA SORGENTE. Come visto (slide FLM06, FLM07, FLM08), l’attività di programmazione coinvolge 2 soggetti che concorrono per la Risoluzione del problema (ben formulato) e per la Computazione della soluzione: RISOLUTORE ed ESECUTORE AUTOMATICO; nell’ambito di tale attività si possono distinguere fasi diverse, quindi individuare diversi AMBIENTI: Ambiente del Problema, Ambiente dell’Analisi, Ambiente di Elaborazione. Siccome un programma ha lo scopo di risolvere un qualche problema, affidandone la esecuzione ad un elaboratore, esso deve prevedere: la corretta gestione delle azioni da svolgere (FLUSSI); la specificazione delle azioni da svolgere (ISTRUZIONI); la specificazione degli oggetti su cui operare (DATI). La gestione dei flussi si realizza tramite le STRUTTURE DI CONTROLLO della programmazione (di cui le basilari sono: sequenza, alternativa, iterazione). Le istruzioni costituiscono le PAROLE RISERVATE del linguaggio di programmazione (keywords, parole cui sono associati precisi significati operativi). Slide da FLM16 a FLM20. prof. Felice Zampini Fondamenti Logici e Metodologici 5/34 I dati devono essere specificati nel programma, assegnando loro dei nomi (IDENTIFICATORI) e degli attributi (SPECIFICATORI DI TIPO), rispettando le regole del linguaggio, tramite apposite espressioni chiamate DICHIARAZIONI; i dati devono inoltre poter essere definiti ed elaborati ed a ciò si provvede con l’operazione di ASSEGNAZIONE. Dal punto di vista logico un Dato è un’informazione codificata, significativa, interpretabile e comunicabile cui possiamo associare un nome per identificarlo; introducendo una distinzione dei dati in base al loro contenuto (ciò che essi rappresentano) ed alle operazioni su di essi definibili (algebra associata) si perviene al concetto di TIPO DI DATO. Dal punto di vista concreto, fisico (implementazioni EDP) un Dato (più in generale un Oggetto) viene riguardato come una AREA DI MEMORIA in cui si possono memorizzare dei valori; ad un dato restano associati NOME - TIPO - VALORE dal punto di vista logico (programma), cui corrispondono INDIRIZZO - SPAZIO E CODIFICA - CONTENUTO BINARIO dal punto di vista fisico (memoria). COMPILE - LINK – GO Slide FLM21 e FLM22. Per comunicare con un elaboratore, al fine di fargli eseguire un algoritmo (più in generale un programma) in forma di PROGRAMMA ESEGUIBILE, è necessario che il programma sia portato nella sua MEMORIA CENTRALE, infatti l’elaboratore deve farsi una RAPPRESENTAZIONE INTERNA delle informazioni, pertanto occorre che il programma risieda in memoria in FORMATO MACCHINA. Un Programma Sorgente, creato con un editor ed espresso in un linguaggio simbolico secondo le regole sintattiche e semantiche di un dato linguaggio di programmazione, per essere trasformato in linguaggio macchina, in modo che sia fruibile dall’elaboratore per poter essere eseguito, deve essere trattato secondo le seguenti fasi (cosiddetta PROCEDURA COMPILE-LINK-GO): Compilazione (compile): fase in cui, tramite un apposito programma compilatore, si trasforma il programma sorgente generando il PROGRAMMA OGGETTO o Codice Rilocabile, cioè il programma in linguaggio macchina ma ancora in una forma intermedia in genere non effettivamente eseguibile; Correlazione (link): fase in cui, tramite un apposito programma correlatore (linker), si trasforma il codice oggetto in PROGRAMMA ESEGUIBILE, risolvendo i Collegamenti Esterni del programma (moduli di librerie utente o di sistema, variabili esterne, ecc.); Caricamento (load): fase in cui, tramite un apposito programma caricatore (loader), si trasforma il programma eseguibile, i cui indirizzi sono logici e non fisici, in PROGRAMMA CARICABILE (Codice Assoluto) o comunque si effettua il caricamento del codice eseguibile in memoria centrale (rilocazione dinamica) per l’effettiva esecuzione (Go o Run). prof. Felice Zampini Fondamenti Logici e Metodologici 6/34 IL PROGRAMMA E L’AREA DATI La considerazione che in un programma sono presenti istruzioni e dati porta a fare una distinzione, logica ma anche fisica, tra due specifiche aree di memoria concernenti il programma: area istruzioni: area riservata alle istruzioni del programma (linguaggio macchina) in quanto tali; area dati: area riservata ai dati dichiarati nel programma ed allocati in memoria, area che sarà fissa o variabile, di durata statica o dinamica in funzione delle dichiarazioni e delle proprietà attribuite agli identificatori (tipo, classe di memorizzazione, durata, visibilità, ecc.). IL PROGRAMMA E LA CPU Un elaboratore, visto come sistema, è inquadrabile come un AUTOMA PROGRAMMABILE SEQUENZIALE DISCRETO, ovvero come una macchina automatica (cioè che una volta avviata è in grado di eseguire compiti automaticamente) programmabile (cioè che funziona in base ad un programma fornito dall’esterno) che produce un’uscita in funzione degli ingressi (dati), dello stato e delle istruzioni di un programma attivo in memoria (assimilabile comunque a stato). La macchina funziona eseguendo nel tempo operazioni in modo sequenziale (cioè una alla volta) facendo agire un hardware se, in dipendenza di certe cadenze temporali (tempo discreto, determinato dagli impulsi di clock), arriva una istruzione (che determina l’avanzamento di stato); in prima approssimazione, si può pertanto considerare che la CPU esegue in modo sequenziale le istruzioni del programma. Giova comunque osservare che esistono sistemi di elaborazione dati (SED) in grado di eseguire più istruzioni simultaneamente e su più dati (macchine MIMD - Multiple Instruction Stream Multiple Data Stream). prof. Felice Zampini Fondamenti Logici e Metodologici 7/34 PARADIGMI DI PROGRAMMAZIONE PROGRAMMAZIONE Slide FLM23. Un PROGRAMMA è uno Schema Logico, un Modello, uno Schema di Comportamento, un Algoritmo per risolvere un problema. Un programma non è dunque, come comunemente si può ritenere, un "insieme di istruzioni" di un dato linguaggio di programmazione, istruzioni che opportunamente trasferite ad un Esecutore Automatico (elaboratore) determineranno le Azioni da questo effettuate per calcolare la soluzione di un problema: l'espressione di un programma tramite un linguaggio di programmazione (linguaggio formalizzato) non è che la traduzione all'esecutore del programma, cioè la CODIFICA, la quale costituisce un aspetto tecnico del programma, in quanto attività successiva e determinata dal programma stesso. PROGRAMMA Schema logico di costruzione di regole per la risoluzione di un problema. La PROGRAMMAZIONE, in generale, è un'attività di Risoluzione di Problemi, la cui esecuzione è poi affidata ad un esecutore (automatico); la programmazione, in un contesto EDP, individua quale esecutore un automa digitale (elaboratore) che garantisca la effettiva Computabilità, quindi il calcolo della soluzione, di un problema. La programmazione, di per sè, può esprimersi tramite un linguaggio o un meta-linguaggio (linguaggio di progetto, pseudocodifica, DaB, NLS, GNS, ecc.); in un secondo momento, a seconda della determinazione delle risorse e delle funzioni attribuite all'esecutore, si individua l'effettivo Linguaggio di Programmazione per tradurre all'esecutore la risoluzione del problema. PROGRAMMAZIONE Attività di risoluzione di problemi la cui computazione è affidata ad un esecutore automatico. prof. Felice Zampini Fondamenti Logici e Metodologici 8/34 Un programma è un prodotto industriale e come tale ha un suo Ciclo di Vita ed un mercato; fare un buon programma significa pertanto disporre di METODOLOGIE di progettazione, di analisi, di sviluppo e codifica dei programmi che nel loro insieme garantiscano la produzione di un prodotto rispondente a requisiti di qualità ed economicità, in particolare un prodotto cui corrisponda un buon rapporto prezzo/prestazioni. Due fattori fondamentali, tra di loro in correlazione, hanno posto in modo pressante l'esigenza di fare dei buoni programmi: il costante ABBATTIMENTO DEI COSTI HARDWARE ed il conseguente SVILUPPO DEI LINGUAGGI HLL (High Level Language), quindi del software in generale, come esemplificato nei seguenti semplici diagrammi. Una specifica branca della moderna informatica, che va sotto il nome di INGEGNERIA DEL SOFTWARE, si occupa più da vicino degli aspetti inerenti le tecnologie e le metodologie di produzione del software, riguardondoli e valutandoli sia sotto il profilo tecnico che economico e sociale. Dalla primitiva programmazione cosiddetta a "piatto di spaghetti" (uso indiscriminato del GOTO e di Switches, destrutturazione ed illeggibilità dei programmi, crescente impiego di risorse nella manutenzione anzichè nella produzione, ecc.) si è passati a nuovi modi di progettare e scrivere programmi, globalmente più razionali ed efficienti, cioè a vere e proprie Metodologie di Progettazione (metodologie di scelta degli algoritmi, delle strutture dati e della codifica per fare un programma) e Metodologie di Analisi (metodologie di analisi del livello di qualità di un programma). Nell’ambito dei Paradigmi di Programmazione, ove si registrano innovativi progressi (programmazione logica e funzionale, OOP, intelligenza artificiale, ecc.), approfondiremo inizialmente la PROGRAMMAZIONE IMPERATIVA, la quale ha sancito la nascita della programmazione ed è a tuttoggi affermata ed interessante. Nell'ambito delle Metodologie di Progettazione del Software ci occuperemo, in particolare, della PROGRAMMAZIONE STRUTTURATA, visto il ruolo fondamentale ed unificante che essa riveste per la logica e la programmazione. prof. Felice Zampini Fondamenti Logici e Metodologici 9/34 PROGRAMMAZIONE IMPERATIVA La Programmazione o pure operazionale) in modo completo istruendolo, ai fini dal programma, sul: Imperativa (detta anche PROCEDURALE, ALGORITMICA è una metodologia in cui occorre descrivere ed univoco un processo all’esecutore, del raggiungimento degli obiettivi previsti "cosa" deve fare (cosa si vuole); "come" farlo (strategia risolutiva). Nella programmazione imperativa pertanto il PROBLEMA DEL CONTROLLO (il "come") è a carico del programmatore, il quale, prima di invocare l’esecuzione del programma da parte dell’esecutore, deve aver completamente risolto il problema dal punto di vista logico, conoscendo a priori tutti i passi che l’esecutore dovrà compiere per fornire la soluzione. Un programma imperativo è dunque una sequenza di istruzioni (algoritmo risolutivo) che trasferite all’esecutore lo portano a trasformare i dati in ingresso nei risultati voluti. Si noti che il lavoro svolto dal risolutore dedicato alla conoscenza della realtà del problema resta, in un certo senso, "imprigionato" nella strategia risolutiva ed è trasferito alla macchina solo per la semplice esecuzione (l’esecutore non acquisisce conoscenze). Una volta identificato l’esecutore(1) un linguaggio imperativo deve fornire gli strumenti per poter agire sullo stato degli oggetti, cioè: Operazioni (Comandi) e relativi parametri per determinarne le condizioni di applicabilità; Funzioni di osservazione. (1) Assimilandolo ad una macchina virtuale (insieme di Risorse o Oggetti e relativo Linguaggio per gestirli) per astrazione ad un certo livello di visibilità rispetto alle risorse reali (si identifica una scatola nera, interessandosi più al suo comportamento che alla sua costituzione). prof. Felice Zampini Fondamenti Logici e Metodologici 10/34 In definitiva, i concetti alla base della programmazione imperativa sono quelli di COMANDO e di PROCEDURA ed in tale metodologia occorre: Specificare l’insieme degli Oggetti chiamati in causa, sui quali si agisce, coi loro nomi e attributi; DICHIARAZIONI - IDENTIFICATORI - TIPI Specificare l’insieme oggetti ("cosa" fare); delle DI Azioni DATI da compiere sugli ISTRUZIONI, cioè KEYWORDS del linguaggio Gestire i Flussi, cioè l’ordine di esecuzione delle azioni, funzionalmente al risultato da conseguire, nonchè le condizioni da verificare affinchè esse si succedano nel modo voluto ("come" arrivare al risultato); STRUTTURE DI CONTROLLO della programmazione. Uno dei vantaggi della programmazione imperativa, che ne compensa in parte la scarsa flessibilità, è attualmente quello della efficienza dei programmi: un programma imperativo infatti, una volta memorizzato, fornisce all’esecutore tutte le informazioni di controllo, consentendogli di raggiungere elevate velocità nell’eseguire le operazioni richieste e nel fornire i risultati voluti. PROGRAMMAZIONE DICHIARATIVA La programmazione dichiarativa si basa sull’idea di delegare il problema del controllo all’esecutore, facendo concentrare il risolutore sugli aspetti che concernono la conoscenza e la comprensione ("cosa" si vuole) del problema. Tale approccio alla risoluzione dei problemi implica che gli OGGETTI della programmazione dichiarativa non siano semplici dati, dovendo essi in qualche modo contenere una base di conoscenza della realtà tale da consentire all’esecutore di trovare una risoluzione del problema. In un certo senso, si può dire che nella programmazione dichiarativa la ricerca di una strategia risolutiva di un problema viene "programmata" nelle fasi, precedenti, relative alla sua conoscenza e comprensione, dotando un linguaggio dichiarativo di un MECCANISMO DI RAGIONAMENTO (motore inferenziale): occorre definire una BASE DI DATI, corretta e sufficientemente ricca di informazioni (descrizioni di oggetti, fatti e relazioni), tale da specificare adeguatamente la realtà e consentire di costruire una BASE DI FATTI tramite la quale il meccanismo di ragionamento, quindi l’esecutore, sia in grado di trovare la risoluzione del problema. prof. Felice Zampini Fondamenti Logici e Metodologici 11/34 La programmazione dichiarativa, salvo casi specifici, non fornisce le prestazioni di quella imperativa in termini di efficienza ma ha il prezioso vantaggio della flessibilità, svincolando notevolmente il programmatore dalle poco flessibili logiche procedurali e consentendo una separazione tra i meccanismi di risoluzione e le basi di conoscenza. *** Considerati i due tipi fondamentali di programmazione, programmazione imperativa e programmazione dichiarativa, si ha che, in effetti, gli attuali linguaggi di programmazione ad alto livello sono difficilmente classificabili come puramente imperativi o dichiarativi, ciascuno tendendo all’una o all’altra categoria in base al suo scopo ed al grado di libertà fornito al programmatore nel controllare il flusso delle azioni svolto dall’esecutore (grado di proceduralità del linguaggio). Tale fatto ha determinato lo sviluppo programmazione, riassumibili nei seguenti: PROGRAMMAZIONE IMPERATIVA PROGRAMMAZIONE ORIENTATA PROGRAMMAZIONE VISUALE PROGRAMMAZIONE FUNZIONALE PROGRAMMAZIONE LOGICA prof. Felice Zampini AGLI di diversi tipi di OGGETTI (OOP-OBJECT ORIENTED PROGRAMMING) Fondamenti Logici e Metodologici 12/34 PROGRAMMAZIONE STRUTTURATA La Programmazione Strutturata (Structured Design) consiste in un insieme di tecniche per progettare, sviluppare e codificare programmi. Le Metodologie di Progettazione Strutturata del Software attualmente disponibili sono diverse, ciascuna con proprie caratteristiche ed orientata verso determinati obiettivi, nè mancano, per talune, varianti e contributi per successivi perfezionamenti o affinamenti; tali metodologie sono comunque riconducibili a tre filoni logici principali: La Decomposizione funzionale; La Progettazione basata sui flussi di dati; La Progettazione basata sulla struttura dei dati; ed hanno come denominatore comune la SCOMPOSIZIONE MODULARE DEL PROBLEMA, al fine di ottenere un design migliore, più razionale e standardizzato del programma. Concetti base della programmazione strutturata sono quelli di: MODULO RELAZIONE TRA INDIPENDENZA MODULI TRA MODULI Modulo Insieme (funzionale) di istruzioni chiamato con un nome; la scomposizione modulare deve realizzare l'indipendenza tra moduli, massimizzando le relazioni tra componenti interne e minimizzando le relazioni tra moduli, basandosi sulle caratteristiche del modulo: Connessione: - per contenuto (peggiore) - comune - esterna - per controllo - di struttura - di dati (migliore) Coesione: - casuale (debole) - logica - temporale - comunicativa - sequenziale - funzionale (forte) Altre Caratteristiche: - semplicità - correttezza - aderenza - dimensioni - interfacciamento - generalizzabilità - duplicazione delle funzioni prof. Felice Zampini Fondamenti Logici e Metodologici 13/34 PRINCIPALI METODOLOGIE DI ANALISI/PROGETTAZIONE STRUTTURATA DEL SOFTWARE: GRAFI DI FLUSSO (DAB, GNS) DIAGRAMMI SINTATTICI NOTAZIONE LINEARE STRUTTURATA (NLS) BOTTOM-UP TOP-DOWN (MILLS) JACKSON WARNIER WIRTH DIJKSTRA CONSTANTINE-YOURDON-MYERS SSA (STRUCTURED SYSTEM ANALYSIS) SADT (STRUCTURED ANALYSIS AND DESIGN TECHNIQUE) HIPO (HIERARCHICAL-INPUT PROCESSING-OUTPUT) PSL (PROGRAM STATEMENT LANGUAGE) 4GL, 5GL, DBMS TECNICHE NON PROCEDURALI Siccome in un programma intervengono istruzioni, dati e flussi, si possono distinguere due tipologie fondamentali di Strutture: Strutture relative all'elaborazione; Strutture di controllo; le prime determinano le elaborazioni, mentre le seconde governano i flussi. La programmazione strutturata si basa sull'uso razionalizzato delle strutture o costrutti di controllo per realizzare programmi strutturati rispondenti alle caratteristiche precedentemente descritte. Poniamo le seguenti definizioni: Un programma si dice proprio se ha un solo punto di inizio ed un solo punto di fine; Due programmi si dicono equivalenti (più precisamente fortemente equivalenti) se sottoposti agli stessi dati di ingresso o non terminano o terminano producendo gli stessi effetti; Un insieme di strutture di controllo si dice completo se esse sono sufficienti a rappresentare qualunque algoritmo ammesso (di conseguenza, ogni programma potrà essere rappresentato con un programma equivalente che utilizzi i soli costrutti appartenenti a tale insieme). A questo punto, il problema fondamentale della programmazione strutturata può formularsi come segue: prof. Felice Zampini Fondamenti Logici e Metodologici 14/34 esiste un insieme completo di strutture di controllo che non limiti il potere espressivo degli algoritmi ammessi? La risposta a tale interrogativo è positiva ed è data dal seguente teorema. TEOREMA DELLE STRUTTURE (BÖHM-JACOPINI 1966) Le Strutture di Controllo SEQUENZA ALTERNATIVA ITERAZIONE costituiscono un Insieme Completo Il teorema afferma che i costrutti di controllo Sequenza, Alternativa, Iterazione sono sufficienti a rappresentare un programma equivalente a qualsiasi altro, dal punto di vista dei processi generati, per qualsiasi valore dei dati in ingresso, cioè che, in particolare, dato un programma esiste sempre un programma strutturato equivalente, ovvero che dà luogo alla stessa sequenza di azioni, per ogni combinazione dei dati in ingresso. In altri termini ancora, un programma proprio può essere risolto con i soli tre tipi di strutture di controllo Sequenza, Alternativa e Iterazione (quindi - vedi pure COMUNICAZIONE DI DIJKSTRA - non indispensabilità, inutilità o addirittura dannosità, di altre strutture), percui la programmazione strutturata non limita il potere espressivo degli algoritmi. Le strutture di controllo SEQUENZA - ALTERNATIVA - ITERAZIONE vengono dunque a costituire le tre STRUTTURE BASE DI CONTROLLO della programmazione strutturata, tuttavia, per la migliore leggibilità del programma e per una maggiore naturalezza delle rappresentazioni, è spesso conveniente affiancare tali costrutti con altri costrutti ausiliari (generalmente implementati nei moderni linguaggi di programmazione ad alto livello). Vediamo ora di descrivere queste strutture di controllo e la metodologia della programmazione strutturata; a tale scopo, definite alcune regole o convenzioni, utilizzeremo le seguenti rappresentazioni, basate su pseudo-linguaggi o meta-linguaggi più formalizzati e strutturati dei Linguaggi di Progetto (LP) e della Pseudocodifica generica: Diagrammi a blocchi strutturati (DaB); Notazione Lineare Strutturata (NLS); Grafi Nassi-Schneiderman (GNS). Vedasi slide da FLM24 a FLM35. prof. Felice Zampini Fondamenti Logici e Metodologici 15/34 SOTTOPROGRAMMI Slide FLM36. Molte metodologie di analisi e progettazione del software, in particolare la programmazione strutturata, si basano sulla SCOMPOSIZIONE MODULARE DEI PROBLEMI, un programma può dunque essere concepito come un modulo principale (main program) ed un insieme di sottoprogrammi che, opportunamente progettati e gestiti, facilitino e rendano più efficiente la progettazione e la codifica del software. Un MODULO O SOTTOPROGRAMMA consiste in una serie di istruzioni caratterizzato dai seguenti elementi generali: Nome: identificatore del sottoprogramma; Funzione: sottoproblema che il modulo deve risolvere; Interfaccia: relazioni che il modulo deve avere col main program e con gli altri moduli coi quali avviene uno scambio di informazioni (insieme dei dati di ingresso e di uscita del sottoprogramma); un buon interfacciamento software deve soddisfare a criteri di indipendenza tra moduli. La gestione di un sottoprogramma deve prevedere in generale: la possibilità di attivare o richiamare il sottoprogramma da un punto qualunque di un programma o sottoprogramma (ISTRUZIONI DI SALTO A SOTTOPROGRAMMA: si specifica il nome del sottoprogramma chiamato o istruzioni del tipo CALL, GOSUB e simili ed eventuali parametri); la sospensione del programma chiamante ed il salvataggio del suo stato; il PASSAGGIO chiamato; DI PARAMETRI, se previsto, al sottoprogramma la cessione del controllo al sottoprogramma chiamato; la RESTITUZIONE chiamante; DEI PARAMETRI, se prevista, dal chiamato al la restituzione del controllo al chiamante ed il ripristino del suo stato, in modo che l’elaborazione possa riprendere correttamente (ISTRUZIONI DI RITORNO DA SOTTOPROGRAMMA: si specificano istruzioni del tipo EXIT, RETURN e simili). Classificazione generale dei sottoprogrammi Procedurali (procedure o subroutine) Chiusi o esterni; Aperti o macroistruzioni; Funzionali (function). prof. Felice Zampini Fondamenti Logici e Metodologici 16/34 L’uso vantaggi: di sottoprogrammi consente eliminazione di riscritture analoghe di istruzioni; di di ottenere sequenze i seguenti sostanzialmente migliore modellizzazione dei problemi e possibilità di strutturare moduli generalizzati riutilizzabili in diversi contesti; virtuale estensione dell’insieme dei comandi (tramite procedure) e dell’insieme delle operazioni (tramite function) del linguaggio di programmazione; migliore protezione delle entità che intervengono nella programmazione (variabili locali e globali, visibilità degli identificatori, ecc.); possibilità di definire librerie-utente librerie di sistema (header file). e di utilizzare PASSAGGIO DEI PARAMETRI Slide FLM37. L’interfaccia di comunicazione tra moduli deve consentire la comunicazione tra di essi, cioè il passaggio dei parametri. Un sottoprogramma può essere senza parametri (stampa di un’immagine d’intestazione a video, pulizia dello schermo, produzione di un segnale acustico, ecc.) o con parametri. In PARAMETRI un sottoprogramma con parametri la corrispondenza tra ATTUALI e PARAMETRI FORMALI deve in generale essere tale che: - il numero dei parametri sia uguale; - l’ordine dei parametri sia uguale; - il tipo dei parametri sia uguale (o compatibile); - la direzionalità dei parametri (in/out/inout) sia congrua. Il passaggio dei parametri può avvenire secondo 2 modalità: - passaggio per valore (o per copia); - passaggio per indirizzo (o per riferimento). Passaggio per Valore I parametri attuali del programma chiamante e formali del programma chiamato fanno riferimento a 2 ambienti (aree di memoria) distinti ed indipendenti, ovvero le corrispondenti variabili sono allocate in appositi spazi di memoria. prof. Felice Zampini Fondamenti Logici e Metodologici 17/34 Il passaggio dei parametri avviene trasferendo (copiando) il valore contenuto nel parametro attuale del programma chiamante nel corrispondente parametro formale del programma chiamato (variabile del sottoprogramma), in tal caso i valori dei parametri attuali non vengono modificati dalle operazioni compiute sui corrispondenti parametri formali, attuando così una forma di protezione dei dati del programma chiamante. Passaggio per Indirizzo I parametri attuali del programma chiamante e formali del programma chiamato fanno riferimento allo stesso ambiente, ovvero all’area di memoria in cui sono allocate le variabili del programma chiamante. Il passaggio dei parametri avviene passando al programma chiamato gli indirizzi dei parametri attuali del programma chiamante (previa valutazione ed allocazione del risultato qualora il parametro attuale non sia una variabile semplice). In altri termini, i riferimenti di read/write sui parametri formali si traducono in riferimenti indiretti agli indirizzi di essi; si noti che nel passaggio per indirizzo le modifiche compiute dal programma chiamato sui parametri formali si riflettono sui corrispondenti parametri del programma chiamante. Passaggio degli Array Il problema del passaggio degli array ai sottoprogrammi, implicando per i parametri attuali e formali considerazioni riguardanti le dimensioni, gli indici, le lunghezze ed i tipi di dati, viene risolto in ciascun linguaggio di programmazione in modo opportuno. In via generale, si possono indicare due soluzioni ai limiti, entro le quali si potrebbe adottare il criterio migliore rispetto al contesto: 1. tra parametri attuali e formali si richiede una esatta corrispondenza; questa soluzione presenta il vantaggio di offrire la massima protezione dell’ambiente ma presenta la restrizione di non poter generalizzare i sottoprogrammi, in quanto gli array devono essere specificati con esattezza in tutti i loro parametri (p.es. non sarebbero gestibili array che differiscano anche per la sola lunghezza); 2. si passa soltanto l’indirizzo di memoria del primo elemento dell’array, indipendentemente dai suoi altri attributi; questa soluzione offre la massima flessibilità nella gestione degli array, consentendo l’utilizzazione di array di tipo dinamico e di conseguenza la generalizzazione dei sottoprogrammi, così facendo viene meno però la protezione dell’ambiente e l’esposizione al rischio di violarne l’integrità aumenta. prof. Felice Zampini Fondamenti Logici e Metodologici 18/34 Osservazioni Un SOTTOPROGRAMMA APERTO è assimilabile ad una macrodefinizione o macroistruzione; quando si parla di espansione locale di una macro si intende dire che per ogni richiamo della macro (macrorichiamo) il relativo gruppo di istruzioni va a sostituire il macrorichiamo nel punto in cui esso compare nel programma chiamante; siccome un sottoprogramma aperto è incluso nel codice del main program il suo ambiente ed i suoi simboli sono noti al compilatore e non si hanno problemi di risoluzione dei collegamenti da parte del linker. Un SOTTOPROGRAMMA CHIUSO non comporta nessuna espansione nel main ma implica la necessità della risoluzione dei links, in quanto un richiamo ad esso nel main appare al compilatore come un simbolo esterno; la risoluzione dei links si realizza tramite apposite direttive, definendo opportunamente l’etichetta della procedura come simbolo esterno nel main ed un entry point nella procedura. Per quanto detto, in via approssimativa (le considerazioni da fare dovrebbero riferirsi al contesto) si può dire che i sottoprogrammi aperti implicano in genere la richiesta di maggiore memoria ma potrebbero rivelarsi più veloci mentre i sottoprogrammi chiusi richiedono meno memoria ma necessitano di tempi maggiori per il fatto di essere procedure esterne (maggiori tempi di chiamata e ritorno da sottoprogramma). In genere il ritorno da sottoprogramma causa la deallocazione dell’ambiente riservato al sottoprogramma. Circa gli ambienti, gli oggetti, le dichiarazioni e gli identificatori i diversi linguaggi consentono, a vari livelli, diverse possibilità gestionali: ambienti (globale, locale) e tipi di oggetti (tipi di dati) variegati, dichiarazioni di riferimento in avanti, classi di memorizzazione, campo d’azione, durata e visibilità degli identificatori, allocazioni dinamiche, definizioni di prototipi, ecc. prof. Felice Zampini Fondamenti Logici e Metodologici 19/34 RICORSIONE Slide FLM38. Una definizione si dice ricorsiva se viene definito qualcosa in termini di se stesso. Un classico esempio di definizione ricorsiva fattoriale di un numero n (intero non negativo), n!: è dato dal n! = 1 se n=0 n*(n-1)! se n>0 In base a tale definizione, il calcolo del fattoriale di un numero si sviluppa in 2 fasi: 1. applicazione del procedimento ricorsivo cui da luogo la formula: n=n*(n-1)! fino ad arrivare alla condizione fissata per definizione: 0!=1 (condizioni iniziali); 2. valutazione delle espressioni ottenute per ricorsione, le quali devono poter essere rimaste sospese, fino ad arrivare al risultato finale. Inizio procedimento 4!=4*3! 4*6=24 Fine valutazione 3!=3*2! 3*2=6 2!=2*1! 2*1=2 1!=1*0! 1*1=1 Condizioni iniziali 0! = 1 Inizio valutazione BASE RICORSIVA prof. Felice Zampini Fondamenti Logici e Metodologici 20/34 Una definizione ricorsiva implica di stabilire: a) la BASE DI RICORSIONE, cioè viene definito l’inizio (nell’esempio: 0!=1); le o condizioni iniziali, percui chiusura della ricorsione b) la REGOLA DI RICORRENZA, cioè la definizione del risultato (per valori delle variabili diversi dalle condizioni iniziali) tramite espressioni in cui si richiede il risultato stesso ricorrendo a relazioni che richiamano se stesse, in modo da arrivare alla chiusura della ricorsione (riduzione del problema a problemi via via più semplici fino ad arrivare alle condizioni iniziali; nell’esempio: n!=n(n-1)!). Dal punto di vista algoritmo, o meglio dei sottoprogrammi (procedurali o funzionali), un sottoprogramma è ricorsivo quando al suo interno compare una chiamata a se stesso, cioè una istruzione del sottoprogramma richiede una nuova esecuzione del sottoprogramma medesimo, in tal caso si parla di ricorsione semplice. Si parla invece di ricorsione indiretta quando il sottoprogramma richiama se stesso attraverso chiamate ad altri sottoprogrammi che a loro volta lo richiamano. La considerazione che durante il procedimento ricorsivo, che deve terminare chiudendosi sulle condizioni iniziali (base di ricorsione), le varie chiamate originano espressioni che devono rimanere sospese per essere poi valutate a ritroso a partire dalle condizioni iniziali (valutazione del problema dai suoi aspetti più semplici fino al raggiungimento del risultato) induce a fare le seguenti osservazioni: occorre mantenere in memoria tutte le espressioni sospese, in altri termini il sistema di elaborazione deve consentire la gestione dinamica degli ambienti; in pratica, possiamo ritenere che la ricorsione equivalga ad una ITERAZIONE associata all’uso di uno STACK; la soluzione ricorsiva di un problema è senz’altro elegante ai fini della modellizzazione del problema e della scrittura del programma, l’esecuzione di quest’ultimo risulta però, in genere, piuttosto onerosa (in termini di chiamate procedurali, spazi di memoria richiesti, tempo di calcolo); una soluzione ricorsiva potrebbe rivelarsi dunque, a seconda dei contesti, globalmente non altrettanto efficiente quanto una soluzione iterativa; la ricorsione poggia le sue basi sul principio di induzione matematica. prof. Felice Zampini Fondamenti Logici e Metodologici 21/34 Riprendendo l’esempio del fattoriale dal punto di vista dei sottoprogrammi, si può schematizzare una procedura ricorsiva come nella slide FLM38, ove il sottoprogramma ricorsivo è la funzione FATT (vedi pure gli esempi di programmazione). Si noti che loops nidificati di costrutti iterativi simili (while nidificati, repeat until nidificati) equivalgono a costruzioni ricorsive. Il concetto di ricorsione viene generalizzato da quello di RICORSIONE STRUTTURALE. Esempi di definizioni ricorsive 1) a*n = 0 se n=0 a*(n-1)+a se n>0 5*3 = 5*(2)+5 = 5*(1)+5+5 = 5*(0)+5+5+5 = 0+5+5+5 = 15 2) an = 1 se n=0 a*(an-1) se n>0 53 = 5*(52) = 5*5*(51) = 5*5*5*(50) = 5*5*5*1 = 125 3) MCD(a,b) = a se b=0 MCD(b,amodb) se b>0 MCD(20,12) = MCD(12,8) = MCD(8,4) = MCD(4,0) = 4 prof. Felice Zampini Fondamenti Logici e Metodologici 22/34 RICERCA Introduzione La ricerca (search) è quell’operazione consistente nel trovare una certa informazione all’interno di un dato insieme di informazioni; la ricerca costituisce uno dei problemi fondamentali dell’informatica, sia perchè tale operazione è in genere preliminare ad altre (inserimenti, cancellazioni, modifiche, stampe, ecc.) sia perchè il grado di efficienza di una ricerca garantisce una generale economia in termini di tempi, spazi e costi nella gestione dei sistemi informativi. Una ricerca dipende dai seguenti fattori principali: * organizzazione dei dati; * supporto di registrazione dei dati; * complessità o costo della ricerca. Per quanto concerne la organizzazione dei dati le tecniche di ricerca si differenziano a seconda che l’insieme di informazioni sul quale effettuare una ricerca sia: ordinato/non ordinato; strutturato/non strutturato; dotato/non dotato di elementi informazioni (campi chiave); atti ad identificare le dotato/non dotato di elementi atti a far identificare le informazioni (indici, pointer). Tra i casi più interessanti che coinvolgono le applicazioni di ricerca, con riguardo all’organizzazione dei dati, possiamo menzionare due situazioni in un certo senso agli estremi: quella in cui l’insieme di informazioni abbia la struttura di un FILE DATI (insieme strutturato, dotabile anche di tutti gli attributi suddetti), ove l’informazione da individuare sarà un record, e quella in cui l’insieme di informazioni sia un file testuale (insieme non strutturato, scarsamente dotabile degli attributi suddetti), ove l’informazione da individuare sarà una parola (si pensi, p.es., alla ricerca di un vocabolo in un testo, per effettuare il controllo ortografico). A livello esemplificativo, si potrebbe pensare come situazione intermedia quella relativa all’organizzazione di un dizionario (struttura semplice e sostanzialmente testuale, necessità di un ordinamento, buona norma utilizzare degli indici. prof. Felice Zampini Fondamenti Logici e Metodologici 23/34 Per quanto concerne il supporto di memorizzazione dei dati le tecniche di ricerca si differenziano a seconda che esso sia: la memoria centrale, in tal caso si parla di RICERCA INTERNA; una memoria ausiliaria (nastro, disco), in tal caso si parla di RICERCA ESTERNA. Nella ricerca in memoria centrale un insieme di record può essere visto come una tabella, mentre nella ricerca esterna un insieme di record può essere visto come un file dati. Per quanto concerne la complessità (vedi seguito), le tecniche di ricerca si differenziano a seconda di una serie di considerazioni, sostanzialmente legate agli aspetti riguardanti l’organizzazione dei dati, il loro volume, la loro disposizione iniziale, le strategie di ricerca e relative implementazioni ed il tipo di contesto o applicazione in cui la ricerca stessa avviene. Circa la complessità, generalmente si parla di: complessità degli algoritmi; complessità dei programmi; complessità in spazio; complessità in tempo. Metodi Base di Ricerca Interna Confronto per chiavi La ricerca è condotta confrontando una data chiave (dato che si desidera ricercare) con un’informazione (strutturalmente data, di norma un campo chiave) contenuta in ciascun record del file, in modo da individuare nel file la posizione relativa al record cercato (se la ricerca è con successo). Metodi: - Lineare; - Lineare su insiemi ordinati; - Con sentinella; - Binaria o dicotomica; - Alberi binari di ricerca (B-tree). Trasformazione della chiave La ricerca è condotta introducendo una qualche elaborazione sulla chiave, in modo da individuare nel file la posizione relativa al record cercato (se la ricerca è con successo). Metodi: - Tecniche Hash. prof. Felice Zampini Fondamenti Logici e Metodologici 24/34 Metodi Base di Ricerca Esterna La ricerca esterna implica una serie di considerazioni che, in via introduttiva, riguardano sia i metodi d’accesso che le problematiche di allocazione degli spazi sui supporti di registrazione dei dati. La scelta di tecniche di ricerca esterna appropriate ed efficienti, nei vari contesti, diventa un fattore cruciale pressochè in tutte le applicazioni, essendo le memorie ausiliarie tecnologicamente assai più lente di quelle centrali. Diversi metodi di ricerca interna possono essere opportunamente applicati anche per la ricerca esterna, la quale può svolgersi secondo due metodi di base: - Ricerca per posizione; - Ricerca per chiave. Ricerca per posizione La ricerca è condotta specificando il numero d’ordine del record da ricercare, cioè la sua posizione all’interno del file (ciò implica la necessità di dover conoscere tale numero, rendendo tale tipo di ricerca piuttosto impraticabile da parte dell’utente finale). Ricerca per chiave La ricerca è condotta specificando il valore della chiave corrispondente alla chiave del record da trovare; tale ricerca si rivela più idonea nei confronti dell’utente finale, in quanto più vicina alla visione logica dei dati e non vincolata alla preventiva conoscenza della posizione del record nel file (posizione che dovrà essere determinata automaticamente dal sistema o da apposite procedure predisposte allo scopo). Data l’importanza della ricerca esterna e lo stretto legame esistente tra tale ricerca ed i metodi d’accesso, per ora si svilupperà la ricerca interna, tra l’altro più semplice e propedeutica a quella esterna, rimandando ad apposita sede (nell’ambito dello studio dei File Dati e del File System) l’approfondimento delle tecniche di ricerca esterna. Esempi di algoritmi e programmi di ricerca saranno svolti in aula ed in laboratorio. prof. Felice Zampini Fondamenti Logici e Metodologici 25/34 ORDINAMENTO Introduzione L’ordinamento (sort) è quell’operazione consistente nel disporre un dato insieme di informazioni secondo una data legge di ordinamento definita su di esso. Per l’ordinamento possono svolgersi considerazioni simili a quelle viste per la ricerca, in particolare si parlerà di ordinamento esterno ed interno. Tali considerazioni saranno meglio precisate di seguito nella trattazione specifica dei metodi di ordinamento. Per ora giova osservare che un metodo di ordinamento esplica, su ciascun elemento esaminato, le seguenti operazioni : determinazione della posizione finale dell’elemento funzione del criterio di ordinamento considerato; in spostamento dell’elemento (dato o riferimento al dato) nella sua posizione finale. Metodi Base di Ordinamento Per Inserzione - Base; - Inserzione Diretta; Per Selezione - Base; - Selezione Diretta; Per Scambio - Per Affioramento (bubble-sort); - Veloce (quick-sort); Per Fusione (merge) - Base; - Sort-merge binario. Le slide FLM39 e FLM40 illustrano tramite grafiche alcuni metodi di ordinamento. esemplificazioni Esempi di algoritmi e programmi di ordinamento saranno svolti in aula ed in laboratorio. prof. Felice Zampini Fondamenti Logici e Metodologici 26/34 COMPLESSITÀ Introduzione Gran parte del lavoro degli informatici riguarda soprattutto l’analisi e la risoluzione di problemi, in forma computabile ed automatizzata. In tale lavoro rivestono fondamentale importanza, in prima istanza, le scelte che si fanno in relazione a: - Algoritmi; - Programmi (in codice sorgente o eseguibili); - Strutture Dati. Per poter esprimere una qualche valutazione in merito alle scelte suddette, quantitativa o almeno qualitativa, occorre introdurre qualche criterio che consenta di decidere, in via generale ed eventualmente anche in base al contesto, quali siano le scelte migliori. In altri termini, occorre avere almeno un orientamento per decidere se e quando un algoritmo, un programma (sottintenderemo in codice sorgente o eseguibile) o una struttura dati siano un buon algoritmo, un buon programma o una buona struttura dati. Tali scelte non sono sempre semplici e spesso conducono a dover mediare tra elementi antitetici. In via generale, si possono elencare una serie di requisiti idonei a descrivere talune caratteristiche desiderabili per gli algoritmi, i programmi e le strutture dati (per gli algoritmi e le strutture dati si rivedano le loro definizioni e caratterizzazioni). Requisiti generali per un buon programma: Efficienza: grado di impiego delle risorse(1) privilegiate del sistema (memoria e CPU, cioè spazio e tempo); Flessibilità: gamma di applicazioni diverse soddisfatte dal programma; Modularità: architettura della decomposizione logicofunzionale del programma in moduli indipendenti e interconnessi (ottimizza la progettazione, la codifica, la messa a punto, la manutenzione ed in generale tutto il ciclo di vita del software); (1) Risorsa: entità Hw o Sw (CPU, memoria, periferica, istruzione, dato, messaggio, ecc.) nell’EDP, componente Hw o Sw necessaria per creare o far avanzare un processo. prof. Felice Zampini Fondamenti Logici e Metodologici 27/34 Portabilità: capacità del programma di essere eseguito su SED diversi (a costi di programmazione nulli o perlomeno esigui e senza alterarne significativamente le prestazioni); Interfaccia Utente : grado di accuratezza con cui è gestita la comunicazione utente-finale/macchina (consistenza, intuitività, interattività, GUI, data-entry controllato, aiuti in linea, ecc.); Leggibilità: grado di leggibilità del codice sorgente (uso di indentazioni o rientri per evidenziare i costrutti, di commenti per esplicitare lo scopo delle azioni o delle variabili, ecc.); Documentazione: grado di accuratezza della documentazione inerente il programma (manuale utente, documentazione online, ecc.). Tenendo conto della differenza tra algoritmo (schema logico) e programma (codificato o eseguibile = insieme di istruzioni), i requisiti sudddetti di efficienza, flessibilità, modularità, leggibilità e documentazione possono adeguatamente essere applicati anche agli algoritmi. Una FUNZIONE DI COSTO (in EDP) è una funzione che determina il costo, espresso secondo apposite unità di misura (non necessariamente monetarie), per la gestione/elaborazione di una risorsa (p.es. un archivio) o di un processo (p.es. un ordinamento). Così, per esempio, si può parlare di costo di un ordinamento in termini di numero di operazioni (quali scansioni di elementi e scambi di posto) necessarie per eseguire l’ordinamento di un dato numero di elementi, oppure in termini di tempo impiegato (in un dato contesto) per eseguire tale ordinamento. Nel primo caso la valutazione è più pertinente per un algoritmo mentre nel secondo caso è più pertinente per un programma in esecuzione. Analogamente, si può parlare di costo-uomo per la scrittura di un programma e di costo-macchina per eseguire tale programma, in questo caso utilizzando unità monetarie (di norma il costo-uomo è considerevolmente superiore al costo-macchina). Per valutare le caratteristiche e le prestazioni di algoritmi e programmi (i quali sono comunque in stretta correlazione, essendo un programma l’implementazione di un algoritmo) possiamo introdurre il concetto di Complessità (complessità computazionale), intesa come misura della quantità di risorse impiegate da un algoritmo o da un programma per la sua esecuzione. Consegue da ciò che è lecito relazionare la Complessità di un Problema con la complessità degli algoritmi che risolvono quel problema. prof. Felice Zampini Fondamenti Logici e Metodologici 28/34 L’analisi della complessità deve essere svolta in modo da rendere indipendenti gli algoritmi ma anche i programmi dal particolare elaboratore (reale o virtuale) sul quale essi potrebbbero essere implementati. A tale scopo, occorrerà prescindere (in particolare per gli algoritmi) da valutazioni coinvolgenti le risorse fisiche (tempi di CPU, spazi di memoria) e cercare espressioni in grado di esprimere la complessità basandosi sul tipo e numero di operazioni ed eventualmente sul tipo e numero di dati chiamati in causa negli algoritmi e nei programmi. In altri termini, la complessità deve essere valutata in modo generale, tramite funzioni matematiche che ne esprimano non tanto il valore quanto l’ordine, la legge di variazione (studio di tipo qualitativo, ed in tal senso saranno usati termini quali Tempo di Esecuzione e simili). Per determinare una funzione di complessità occorre individuare quelle grandezze dalle quali questa può dipendere, tali grandezze potrebbero essere anche molte ed influire in modo diverso, più o meno significativamente, nel determinare la complessità. Per esempio, si potrebbero considerare grandezze quali: il tipo ed il numero di operazioni, la dimensione del problema (numero di elementi da trattare), il tipo di dati, la disposizione iniziale dei dati e via dicendo. Si dice Parametro Rilevante per un algoritmo A o un programma P una grandezza (opportunamente scelta) dalla quale dipende la complessità C(A) di A o C(P) di P: C(A) e C(P) sono dunque funzioni dei parametri rilevanti per A e per P. Lo studio della complessità che segue è in relazione agli Esecutori Sequenziali; gli algoritmi orientati a modelli di esecutori paralleli saranno trattati a parte. Complessità dei Programmi La complessità fattori principali: C(P) di un programma dipende dai seguenti Struttura dell’algoritmo utilizzato Dimensione del problema (numerosità dei dati) Caratteristiche dei dati di input Disposizione (ordinamento) Attributi (tipo di dati) Qualità del codice generato dal traduttore Natura e velocità delle istruzioni macchina utilizzate. Le funzioni di costo più interessanti per riguardano le risorse privilegiate: memoria e CPU. prof. Felice Zampini Fondamenti Logici e Metodologici valutare C(P) 29/34 Si può dunque parlare di due tipi di complessità. Complessità in Spazio Esprime una misura quantitativa della memoria centrale effettivamente occupata dal programma e dai dati su cui esso opera durante l’esecuzione (si tenga presente che alcuni linguaggi consentono la allocazione dinamica della memoria). Tale misura riguarda: l’area istruzioni, determinata dal tipo di traduttore; l’area dati, determinata dai dati. Complessità in Tempo Esprime la legge di variazione del tempo di esecuzione del programma in funzione dei parametri rilevanti considerati. Essendo la complessità in tempo la funzione più interessante da determinare, parleremo di complessità sottintendendo complessità in tempo. Sono da ritenersi parametri rilevanti, ai fini della determinazione della funzione di complessità in modo indipendente dal contesto (ambiente di elaborazione: CPU, linguaggio, codifica, ecc.), i seguenti elementi: Dimensione N del problema; Numero e tipo di operazioni rilevanti svolte dall’algoritmo risolutore per risolvere il problema. Possono considerarsi rilevanti, subordinatamente al contesto, i seguenti elementi: Disposizione iniziale dei dati; Ambiente di elaborazione. Una funzione completa per esprimere C(P) in forma generalizzata potrebbe assumere la forma seguente: C(N) = k*f(operazioni rilevanti algoritmo) ove k indica una costante globalmente il contesto. di proporzionalità che rappresenta La stretta dipendenza della complessità dall’algoritmo rinvia dunque all’analisi della complessità computazionale degli algoritmi, sostanzialmente in grado di fornire le funzioni di complessità dei programmi a meno di fattori costanti (dipendenti dal contesto e dalla complessità in spazio). prof. Felice Zampini Fondamenti Logici e Metodologici 30/34 La dimensione del problema (numerosità N dei dati) è, come visto, un parametro rilevante per la complessità; allo scopo di far rientrare nell’analisi anche valutazioni concernenti aspetti più generali dei dati (disposizione, tipo) tale analisi viene condotta considerando le seguenti situazioni. Caso Peggiore: è la complessità determinata in funzione della disposizione dei dati percui l’algoritmo ha comportamento peggiore. Tale caso è particolarmente interessante ai fini della determinazione del limite superiore di complessità entro il quale l’algoritmo opera. Caso Medio: è la complessità determinata come media sulla base di tutte le possibili occorrenze dei dati. Tale caso può fornire indicazioni utili sul comportamento dell’algoritmo in generale, ma spesso è difficile da analizzare e non sempre può avere significato applicativo. Caso Migliore: è la complessità determinata in funzione della disposizione dei dati percui l’algoritmo ha comportamento migliore. Tale caso riveste importanza ai fini della migliore applicazione dell’algoritmo. Studio Asintotico: è lo studio della funzione di complessità al crescere della dimensione N del problema. Tale studio fornisce indicazioni utili ai fini del raggruppamento degli algoritmi suddividendoli in base a ordini o classi di funzioni di complessità (si noti che può capitare anche che per piccole dimensioni del problema l’algoritmo abbia un comportamento anomalo rispetto alle grandi dimensioni). Nello studio asintotico interessa in sostanza sapere se esiste una funzione f(N) e 2 costanti intere positive N0 e c tali che per NN0 si abbia C(N)c*f(N), se f(N) esiste allora si dice che C(N) cresce come o è proporzionale o dello stesso ordine di f(N) - in termini grafici questo significa che da N0 in poi C(N) è al di sotto di o delimitata da c*f(N) - ciò che si indica con la cosiddetta notazione O-grande (big-o notation): C(N)=O(f(N)). In tal modo si possono definire scale o ordini di complessità e confrontare classi di algoritmi in base alle relative funzioni di complessità (vedi slide FLM41). Complessità Asintotiche (esempi) C(N) = O(1) Costante (caso migliore) C(N) = O(LogbN) Logaritmica (caso buono) C(N) = O(Nk) Polinomiale (k>1) (Lineare per K=1, Sottolineare per k<1) C(N) = O(aN) prof. Felice Zampini Esponenziale (a>1; caso peggiore). Fondamenti Logici e Metodologici 31/34 Complessità degli Algoritmi Per quanto visto, la complessità C(A) di un algoritmo A è una funzione (valutabile nei 4 casi considerati) dei seguenti elementi rilevanti dell’algoritmo: Dimensione N del problema; Tipi di operazioni da fare per eseguire A; Numero di operazioni eseguite da A. Due algoritmi A e B si dicono confrontabili se: Risolvono la stessa classe di problemi; Operano sullo stesso insieme di dati di dimensione N; C(A) e C(B) sono funzioni degli stessi parametri rilevanti. Se A e B sono confrontabili allora si dice che A è migliore di B se per ogni valore dei parametri rilevanti risulta C(A)<C(B), in altri termini, indicate con O(f(N)) e O(g(N) le funzioni di complessità di A e B allora A è migliore di B se per ogni valore dei parametri rilevanti si ha f(N)<g(N). Valori di Taglio Il confronto tra algoritmi tramite le relative funzioni di complessità potrebbe dar luogo a situazioni in cui risulti A migliore di B solo limitatamente a certi intervalli di N, in tal caso è importante determinare tali intervalli. Dati 2 algoritmi confrontabili A e B si dice Valore di Taglio tra A e B di un parametro rilevante p quel valore t tale che t è il massimo dei valori di p percui si ha C(B)<C(A). Se per ogni pt risulta C(B)<C(A) allora per tali valori B è migliore di A mentre per tutti i valori p>t risulta A migliore di B. In modo analogo, le considerazioni appena svolte si possono applicare ai programmi, introducendo delle costanti di proporzionalità nelle funzioni di complessità allo scopo di rappresentare, come già osservato, gli effetti di complessità dovuti al contesto: se C(A)=a*f(N) e C(B)=b*g(N) e f(N)<g(N) si dice valore di taglio quel valore Nt tale che per ogni NNt si ha b*g(N)a*f(N). La slide FLM41 illustra quanto detto. prof. Felice Zampini Fondamenti Logici e Metodologici 32/34 Computabilità L’analisi della complessità asintotica consente di dedurre una serie di considerazioni che esulano dagli scopi di questa trattazione introduttiva. In particolare, con riferimento alla slide FLM41 ed a quanto detto, giova comunque fare qualche osservazione. Nella classe degli algoritmi (o dei programmi) computabili quelli con complessità esponenziale rappresentano casi critici, infatti i tempi di esecuzione di tali algoritmi diventano enormi anche per piccoli valori del parametro N e sono praticamente indipendenti dalla velocità dell’elaboratore utilizzato (anche se ipoteticamente supposto velocissimo). Tali algoritmi sono da ritenersi pertanto non effettivamente computabili, cioè rappresentano problemi intrattabili (almeno al livello delle attuali conoscenze). Di norma, si considerano come algoritmi effettivamente computabili quelli di complessità al più polinomiale, inoltre, se la complessità può essere espressa da un polinomio, si ha che il suo ordine è rappresentato dal grado del polinomio. prof. Felice Zampini Fondamenti Logici e Metodologici 33/34 CICLO DI VITA DEL SOFTWARE Per Ciclo di Vita del Software s’intende una scomposizione del processo di produzione del software basata in genere su una sequenza ordinata di fasi temporali ben definite e controllabili. Si hanno vari modelli di cicli di vita del software, differenti a seconda del livello di dettaglio e di specializzazione; visto il carattere (necessariamente) introduttivo di questo paragrafo, saranno quì fornite solo delle schematizzazioni orientative di carattere generale. La slide FLM42 illustra un MODELLO A CASCATA (Waterfall Model) del ciclo di vita del software, fondamentalmente caratterizzato dalla proprietà che ogni fase è dotata di un’interfaccia verso la successiva e di un processo di verifica e controllo. La slide FLM43 illustra un diversificazione e delucidazione. modello analogo con qualche Oltre a quanto già visto fino a questo punto, in particolare nel paragrafo Complessità, giova almeno elencare talune altre questioni che intervengono nell’ambito del ciclo di vita del software (questioni concernenti l’INGEGNERIA DEL SOFTWARE). MISURA DELLE PRESTAZIONI DEI CALCOLATORI: IER – INSTRUCTION EXECUTION RATE (MIPS) ETR – EXTERNAL THROUGHPUT RATE (N. TASK/TEMPO) ITR – INTERNAL THROUGHPUT RATE (N. TASK/TEMPO - DI CPU ATTIVA) RESPONSE TIME (SEC) BENCHMARK (SEC) TECNICHE PER LA PREVENZIONE DEGLI ERRORI: INSPECTION WALKTHROUGH Approfondimenti lezioni. prof. Felice Zampini saranno svolti nel Fondamenti Logici e Metodologici seguito e durante le 34/34