Aula 3
Modularização: funções e procedimentos
Modularizar
Arte de identificar claramente que módulos
devem existir num sistema
Sistema
Modular
Sistema Integrado
Vantagens da modularização
Facilita detecção de erros:
Permite testar módulos individualmente:
Reduz probabilidade de haver consequências imprevistas
Permite desenvolvimento independente dos módulos:
Reduz complexidade do teste
Permite testar antes de completado o sistema
Permite fazer manutenção do sistema módulo a módulo:
Simples identificar módulo responsável
Reduz tempo gasto na identificação
Simplifica trabalho em equipa
Permite reutilização dos módulos desenvolvidos
Boa modularização
Módulos têm função única e bem definida
Módulos são coesos
Componentes internos com fortes ligações entre si
Existem poucas ligações entre os componentes internos?
Talvez se deva dividir o módulo…
Ligações entre módulos mínimas:
Existem muitas ligações entre os módulos? Talvez
devam formar um único módulo…
Abstracção
In this connection it might be worthwhile to point out that
the purpose of abstraction is not to be vague, but to create
a new semantic level in which one can be absolutely precise.
Edsger W. Dijkstra, “The Humble Programmer”,
Communications of the ACM, 15(10), 1972.
Abstracção
Utilização de sistema olhando apenas para o
seu funcionamento externo
Limita quantidade de informação com que lidar
Maior produtividade
Menor taxa de erros
Encapsulamento
Única parte visível é a interface
Mecanismo interno dos módulos encerrado
numa cápsula (caixa preta)
Protege consumidor dos seus erros
Facilita abstracção
Papeis do programador
Programador assume papeis distintos:
Produtor
• Desenvolve mecanismo do módulo
• Preocupa-se com:
• o que faz
• como se usa
• como funciona
Consumidor
• Integra cada módulo em sistema complexo
• Preocupa-se com:
• o que faz
• como se usa
• como funciona
Contrato
Entre produtor e consumidor
Indica como se usa
Produtor garante resultados…
…se consumidor respeitar condições de
utilização
Módulos em C++
Rotinas
Funções
Conjunto de instruções, com interface bem
definida, que efectua um dado cálculo
Procedimentos
Conjunto de instruções, com interface bem
definida, que faz qualquer coisa
Resolução de
problemas
Abordagem descendente
(top down)
Análise global do problema
Identificação de sub-problemas
Resolução independentemente de cada
sub-problema usando a mesma abordagem
Problemas e sub-problemas correspondem a rotinas
Vantagens
Diminuição da informação a processar pelo programador
em cada instante
Redução da complexidade dos problemas até à
trivialidade
Problema
Escrever programa para somar duas fracções
positivas fornecidas pelo utilizador e mostrar
resultado no ecrã na forma de uma fracção
irredutível
6/9 + 7/3 = 3/1
Sub-problemas
1.
Ler fracções
2.
Calcular fracção soma reduzida
3.
Escrever resultado
Em C++
#include <iostream>
using namespace std;
int main()
{
// Ler fracções:
...
// Calcular fracção soma reduzida:
...
// Escrever resultado:
...
}
Ler fracções
// Ler fracções:
cout << "Introduza duas fracções (numerador denominador): ";
int n1, d1, n2, d2;
cin >> n1 >> d1 >> n2 >> d2;
Escrever resultado
// Escrever resultado:
cout << "A soma de ";
escreveFracção(n1, d1);
cout << " com ";
escreveFracção(n2, d2);
cout << " é ";
escreveFracção(?, ?);
cout << '.' << endl;
Calcular fracção soma reduzida
// Calcular fracção soma reduzida
int n;
int d;
Calcular fracção soma reduzida
// Calcular fracção soma reduzida
int n = d2 * n1 + d1 * n2;
int d = d1 * d2;
Calcular fracção soma reduzida
// Calcular fracção soma reduzida
int n = d2 * n1 + d1 * n2;
int d = d1 * d2;
int k = mdc(n, d);
n /= k;
d /= k;
Ler fracções
// Ler fracções:
cout << "Introduza duas fracções (numerador denominador): ";
int n1, d1, n2, d2;
cin >> n1 >> d1 >> n2 >> d2;
int k = mdc(n1, d1);
n1 /= k;
d1 /= k;
int k = mdc(n2, d2);
n2 /= k;
d2 /= k;
Ler fracções
// Ler fracções:
cout << "Introduza duas fracções (numerador denominador): ";
int n1, d1, n2, d2;
cin >> n1 >> d1 >> n2 >> d2;
int k = mdc(n1, d1);
n1 /= k;
d1 /= k;
int k = mdc(n2, d2);
n2 /= k;
d2 /= k;
Calcular fracção soma reduzida
// Calcular fracção soma reduzida
int n = d2 * n1 + d1 * n2;
int d = d1 * d2;
int k = mdc(n, d);
n /= k;
d /= k;
Máximo divisor comum (I)
int m;
int n;
// Inicializadas
// automagicamente!
m : int
int r;
if(m < n)
r = m;
else
r = n;
while(m % r != 0 or n % r != 0)
--r;
n : int
k : int
Máximo divisor comum (II)
int const m; // Inicializadas
int const n; // automagicamente!
m : int {frozen}
int r;
if(m < n)
r = m;
else
r = n;
while(m % r != 0 or n % r != 0)
--r;
n : int {frozen}
k : int
Máximo divisor comum (III)
{
int const m; // Inicializadas
int const n; // automagicamente!
int r;
if(m < n)
r = m;
else
r = n;
while(m % r != 0 or n % r != 0)
--r;
}
Encapsular
código numa
caixa
Máximo divisor comum (IV)
mdc
{
int const m; // Inicializadas
int const n; // automagicamente!
int r;
if(m < n)
r = m;
else
r = n;
while(m % r != 0 or n % r != 0)
--r;
}
Nome do
módulo
Máximo divisor comum (V)
mdc(int const m, int const n)
{
int r;
if(m < n)
r = m;
else
r = n;
while(m % r != 0 or n % r != 0)
--r;
}
Parâmetros:
variáveis ou
constantes
especiais de
entrada
Máximo divisor comum (VI)
mdc(int const m, int const n)
{
int r;
if(m < n)
r = m;
else
r = n;
while(m % r != 0 or n % r != 0)
--r;
return r;
}
Retorno e
devolução:
regressa ao ponto
onde foi invocada e
devolve valor
guardado em r
Máximo divisor comum (VII)
int mdc(int const m, int const n)
{
int r;
if(m < n)
r = m;
else
r = n;
while(m % r != 0 or n % r != 0)
--r;
return r;
}
Devolução de
valor com tipo
int
Máximo divisor comum (VIII)
/** Devolve o máximo divisor comum dos
inteiros positivos passados como argumento.
@pre 0 < m e 0 < n.
@post o valor r devolvido é o mdc de m e n. */
int mdc(int const m, int const n)
{
int r;
if(m < n)
r = m;
else
r = n;
Documentação:
o que faz
Cabeçalho:
como se usa
while(m % r != 0 or n % r != 0)
--r;
return r;
}
Corpo: como
funciona
Máximo divisor comum (IX)
/** Devolve o máximo divisor comum dos inteiros positivos passados como argumento.
@pre 0 < m e 0 < n.
@post o valor r devolvido é o mdc de m e n. */
int mdc(int const m, int const n)
{
assert(0 < m);
assert(0 < n);
int r;
if(m < n)
r = m;
else
r = n;
while(m % r != 0 or n % r != 0)
--r;
assert(0 < r);
assert(m % r == 0);
assert(n % r == 0);
return r;
}
Instruções de
asserção
(afirmação)
Escreve fracção
/** Escreve no ecrã uma fracção, no formato usual, que
lhe é passada na forma de dois argumentos inteiros positivos.
@pre nenhuma.
@post o ecrã contém n/d em que n e d são os valores de
n e d em base decimal. */
void escreveFracção(int const n, int const d)
{
}
Um procedimento
não devolve nada:
faz qualquer coisa
Escreve fracção
/** Escreve no ecrã uma fracção, no formato usual, que
lhe é passada na forma de dois argumentos inteiros positivos.
@pre nenhuma.
@post o ecrã contém n/d em que n e d são os valores de
n e d em base decimal. */
void escreveFracção(int const n, int const d)
{
cout << n << ‘/’ << d;
}
Somador de fracções
#include <iostream>
using namespace std;
/** … */
int mdc(int const m, int const n)
{
...
}
/** … */
void escreveFracção(int const n, int const d)
{
...
}
int main()
{
...
}
Aula 3: Sumário
Modularização, abstracção e encapsulamento.
Rotinas como unidades atómicas de modularização.
Abordagem descendente.
Sintaxe da definição de rotinas: cabeçalho vs. corpo.
Interface e implementação.
Contratos: pré-condições e condições objectivo.
Instruções de asserção e programação por contrato.
Parâmetros e argumentos.
Instrução return. Retorno e devolução.
Procedimentos: não há devolução (tipo de devolução void).
Invocação de rotinas.