Atribuam as responsabilidades às classes que as devem ter. Uma boa solução deste problema envolvia as seguintes classes:
RegistoDeBarcoCaisFilaRegistoDeBarcoPortoControladorDePortosendo a última a única responsável pela interface com o utilizador.
Cada classe deve ser responsável por ler o que lhe diz respeito. O Porto lê-se, pedindo aos seus cais e filas para se lerem, que
por sua vês pedem aos registos de barcos para se lerem.
Não se deve colocar directivas de utilização (using namespace)
nem nos ficheiros de interface (.H) nem nos auxiliares de implementação (_impl.H). Ver
Capítulo 8.
.H) devem-se incluir todos os ficheiros de interface correspondentes a ferramentas (classes, modelos, rotinas, etc.) usados
nesse ficheiro de interface. Não devem retirar inclusões só porque sabem que
elas já foram feitas indirectamente noutros ficheiros que já se incluíram
antes. Também não se devem colocar inclusões nos ficheiros de
interface só porque elas serão necessárias nos ficheiros que se calcula
virem a incluir o ficheiro de interface em causa. Exemplo:A.H:class A {
};B.H:#include "A.H"
class B { A a;};C.H:#include "B.H"
#include "A.H" // mesmo sabendo que B.H inclui já A.H.
class C {
B b;
A a;
};D.H#include "B.H"#include "C.H" // má ideia!
B função();D.C#include D.H#include "C.H"
static C outraFunção() { ...}Porquê? Porque se em B se deixar de usar directamente A (e portanto eliminar a inclusão de
A.H), tudo continua a funcionar na perfeição. Porque os
consumidores futuros de D.H podem não necessitar de C.H,
e por isso incluí-lo em D.H só serve para aumentar as
interdependências entre módulo físicos sem qualquer ganho prático.
Nos argumentos das rotinas e métodos usem sempre istream e ostream e nunca
ifstream ou ofstream. Ficam com código muito mais flexível.
istream e ostream O ficheiro de interface a incluir para usar istream e ostream
é iostream e não fstream .
Os comentários de documentação de rotinas e métodos devem incluir sempre a pré-condição e a condição objectivo.
O código das rotinas e métodos deve verificar a pré-condição e a condição objectivo sempre que possível. Essa verificação faz-se com asserções.
É fundamental indicá-la claramente e verificá-la em todos os métodos:
Relembra-se que as verificações com asserções podem ser
"desligadas" pré-processando o código com a opção -DNDEBUG.
Pelo que o argumento da eficiência não pode ser usado para justificar a não
colocação de asserções.
Há três tipos de comentários:
//
/* */
///
/** */
Os comentários propriamente ditos servem para o leitor compreender melhor o código. Dirigem-se tipicamente aos programadores produtores (ou às equipas de manutenção).
Comentários de documentação destinam-se à produção automática de documentação, que é suposto ser lida pelos programadores consumidores. Fazem parte da interface dos módulos. I.e., não se colocam nunca na definição de métodos e rotinas e sempre na sua declaração!
Os comentários errados servem para obscurecer o significado do programa, dificultando a trabalho de toda a gente. Normalmente ocorrem quando são colocados no código depois da obra feita e só porque "as regras mandam" (leia-se "o professor gosta"). Comentários inexistentes descontarão sempre na nota. Comentários errados descontarão sempre mais. Mais vale comentário nenhum que comentários errados, portanto. Dizer que uma rotina ou um método não têm pré-condições quando isso não é verdade é pior que não dizer nada. É assinar um contrato que não se vai cumprir. Exemplo:
class Barco {
public:
/** Construtor a partir de um canal de entrada.
@pre V. */
Barco(istream& canal_de_entrada);
};
É evidente que há uma pré-condição:
Se a. ou b. não forem verdadeiras a construção não pode ter sucesso.
Comentários de documentação devem ter um alto nível de abstracção. Por exemplo:
class Cais {função
public:
/*intcomprimento que devolve o comprimento da lista*/...
int comprimento() const;
};
numeroDeBarcosAtracados().
class Cais {
public:
/// Devolve o número de barcos atracados.
int numeroDeBarcosAtracados() const;
...
};
Os nomes dos métodos não devem em geral incluir referências à própria classe:
class Cais {
void guardaEstadoDoCais(ostream& panama);
};
deveria ser:
class Cais {
void guardaEstado(ostream& canal);
};
ou melhor:
class Cais {
void guardaEm(ostream& canal_de_saida);
};
pois a intenção é tornar legível o código:
Cais cais_10;
ofstream canal_de_saída("nome");
cais_10.guardaEm(canal_de_saida);
Usem sempre um tipo de letra não-proporcional, i.e., em que a largura de um 'i' e de um 'm' seja a mesma. O tipo mais comum nessas circunstâncias é o Courier (New). Nunca usem um tipo de letra proporcional, como o Times (New Roman).
Partam à mão todas as linhas de código que excedam cerca de 78 caracteres.
Na versão impressa de um trabalho listem primeiro todos os ficheiros de
interface (.H), desde o mais genérico ao mais específico e só
depois listem os ficheiros auxiliares de implementação (_impl.H)
e de implementação (.C), pela ordem inversa, do mais específico
para o mais genérico.
class Cais {...
...
bool contemBarcoAtracadoComMatricula(string const& matricula) const;
list<RegistoDeBarco> barcos_atracados;
};
É muito má ideia escrever:
void Cais::zarpaBarcoComMatricula(string const& matricula)
{
for(list<RegistoDeBarco>::iterator i = barcos_atracados.begin();
i != barcos_atracados.end(); ++i)
if(i->matricula() == matricula) {
barcos_atracados.erase(i);
break;
}
}
}
É preferível escrever:
/**Faz zarpar o barco com a matrícula dada.@pre
contemBarcoAtracadoComMatricula(matricula).@post
¬contemBarcoAtracadoComMatricula(matricula) e contém todos os outros
atracados anteriormente.*/
void Cais::zarpaBarcoComMatricula(string const& matricula) {
assert(cumpreInvariante());
assert(contemBarcoAtracadoComMatricula(matricula));
list<RegistoDeBarco>::iterator i = barcos_atracados.begin();
while(i->matricula() != matricula)
++i;
barcos_atracados.erase(i);
}
class Porto {...
vector<Cais> cais;
FilaRegistoDeBarco fila_de_espera_para_carga;
FilaRegistoDeBarco fila_de_espera_para_descarga;
};
O código:
void Porto::zarpaBarcoComMatricula(string const& matricula)
{
vector<Cais>::size_type i = 0;
bool barco_foi_retirado = false;
while(i != cais.size() and not barco_foi_retirado) {
if(cais[i].contemBarcoAtracadoComMatricula(matricula)) {
cais[i].zarpaBarcoComMatricula(matricula);
barco_foi_retirado = true;
cout << "Bla bla" << endl;
if(cais[i].operacao() == carga and
not fila_de_espera_para_carga.estaVazia()) {
cais[i].atraca(fila_de_espera_para_carga.frente());
cout << "Bla bla." << endl;
fila_de_espera_para_carga.tira();
} else if(cais[i].operacao() == descarga and
not fila_de_espera_para_descarga.estaVazia()) {
cais[i].atraca(fila_de_espera_para_descarga.frente());
cout << "Bla bla." << endl;
fila_de_espera_para_descarga.tira();
}
}
++i;
}
if(not barco_foi_retirado)
cout << "bla bla" << endl;
}
deveria ser:
/**Faz zarpar o barco com a matrícula dada do respectivo cais, procedendo depois à entrada
de um novo barco se estiver algum à espera.@pre
contemBarcoAtracadoComMatricula(matricula).@post
¬contemBarcoAtracadoComMatricula(matricula) e contém todos os outros
atracados anteriormente e, se a fila da operação a ser realizada pelo barco que zarpou
não estava vazia, então foi dada autorização ao seu primeiro barco para atracar.*/Isto deveria estar noutro método, invocado à parte!
void Porto::zarpaBarcoComMatricula(string const& matricula) {
assert(cumpreInvariante());
assert(contemBarcoAtracadoComMatricula(matricula));
vector<Cais>::size_type i = 0;
while(not cais[i].contemBarcoAtracadoComMatricula(matricula))
++i;
cais[i].zarpaBarcoComMatricula(matricula);
if(cais[i].operacao() == carga) {
if(not fila_de_espera_para_carga.estaVazia()) {
cais[i].atraca(fila_de_espera_para_carga.frente());
fila_de_espera_para_carga.tira();
}
} else if(cais[i].operacao() == descarga)
if(not fila_de_espera_para_descarga.estaVazia()) {
cais[i].atraca(fila_de_espera_para_descarga.frente());
fila_de_espera_para_descarga.tira();
}
}
Notas:
Porto não deve escrever mensagens no ecrã. Por isso foram retirados os
cout << ....Continua a haver muita gente a escrever código como
bool f(xxxx)
{
if(condição)}
return true;
else
return false;
Escrevam antes
bool f(xxxx)
{
return condição;
}
pois é muito mais simples e claro!
O mesmo para:
if(condição == true)...
e
if(condição == true)...
que devem ser escritas antes como
if(condição)...
e
if(not condição)...
Um construtor serve para construir um objecto, deixando-o em "bom estado", deixando-o cumprindo a sua condição invariante de instância. Muita gente escreveu:
class Cais {...
public:
Cais(istream& canal_de_entrada);
int numeroDeBarcosAtracados() const;
void atraca(RegistoDeBarco const& barco);
void atracaDeFicheiro(RegistoDeBarco const& barco);
private:
Operacao operacao_permitida;
int capacidade_de_atracagem;
int numero_de_barcos_atracados;
list<RegistoDeBarco> barcos_atracados;
};
Porque diabo é que há duas versões de atraca()? Já vão perceber:
Cais::Cais(istream& canal_de_entrada) {inútil, ignorar.
assert(canal_de_entrada);
int numero_do_cais; //
canal_de_entrada >> numero_do_cais;
canal_de_entrada >> operacao_permitida;
canal_de_entrada >> capacidade_de_atracagem;
canal_de_entrada >> numero_de_barcos_atracados;
assert(canal_de_entrada);
}
Oops... Faltou aqui alguma coisa, não foi?
inline int Cais::numeroDeBarcosAtracados() const {
return numero_de_barcos_atracados;
}
inline void Cais::atraca(RegistoDeBarco const& barco) {
barcos_atracados.push_back(barco);
++numero_de_barcos_atracados;
}
Como o construtor lê o número de barcos mas não lê os barcos a solução escolhida por muitos foi fazer a segunda versão da função:
inline void Cais::atracaDeFicheiro(RegistoDeBarco const& barco) {
barcos_atracados.push_back(barco);
}
A subtileza é que não incrementa o número de barcos. Assim, ao usar a classe pode-se fazer:
ifstream canal_de_entrada("cais_de_santos.txt");Leu tudo menos os barcos? Não há problema...
Cais cais_de_santos(canal_de_entrada);
//
for(int i = 0; i != cais_de_santos.numeroDeBarcosAtracados(); ++i)
cais_de_santos.atracaDeFicheiro(RegistoDeBarco(canal_de_entrada));
É claro porque é que se tem de usar a segunda versão de atraca? É claro porque é que os autores do código acima terão muita dificuldade em indicar uma condição invariante de instância para a classe?
(Em muitos outros casos existia um construtor do cais que recebia toda a informação, incluindo o número de barcos acostados, mas... não recebia os barcos... O erro é equivalente.)
Isto é muito má ideia! Se o cais diz que tem x barcos atracados tem de ter a sua informação já lida! É exactamente este tipo de erros que os construtores pretendem evitar. A versão correcta é:
class Cais {...
public:
Cais(istream& canal_de_entrada);
list<RegistoDeBarco>::size_type numeroDeBarcosAtracados() const;
void atraca(RegistoDeBarco const& barco);
private:
Operacao operacao_permitida;
int capacidade_de_atracagem;
list<RegistoDeBarco> barcos_atracados;
};
Cais::Cais(istream& canal_de_entrada)
{
assert(canal_de_entrada);
int numero_do_cais; // inútil, ignorar.
canal_de_entrada >> numero_do_cais;
canal_de_entrada >> operacao_permitida;
canal_de_entrada >> capacidade_de_atracagem;
int numero_de_barcos_atracados;
canal_de_entrada >> numero_de_barcos_atracados;
assert(canal_de_entrada);
for(; numero_de_barcos_atracados > 0; --i)
barcos_atracados.push_back(RegistoDeBarco(canal_de_entrada));
}
inline list<RegistoDeBarco>::size_type
Cais::numeroDeBarcosAtracados() const {
return barcos_atracados.size();
}
inline void Cais::atraca(RegistoDeBarco const& barco) {
barcos_atracados.push_back(barco);
}
Agora ao usar a classe pode-se fazer:
ifstream canal_de_entrada("cais_de_santos.txt");Leu tudo incluindo os barcos.
Cais cais_de_santos(canal_de_entrada);
//
Os cais podem ser de carga ou descarga. Porque se hão de usar strings para representar isso? Para que servem afinal os tipos enumerados?
enum Operacao {carga, descarga};
É possível depois fazer operadores << e >> para os
inserir e extraír de canais onde podem ter representação por extenso.
Usar recursividade para substituir ciclos simples já é má ideia. Pior ainda é quando se usa erradamente. E uma verdadeira desgraça é quando se usa erradamente e... parece funcionar impecavelmente!
As duas funções menu() e menu2() abaixo estão erradas.
Geram mesmo um aviso compreensível do compilador (long long é uma extensão do C++,
é um tipo aritmético com 64 bits, compilar sem -pedantic).
Mas a primeira funciona em Linux com a versão actual do CGG! Alguém sabe explicar porquê?
E explicar porque é que, apesar de o programa funcionar bem, ele está errado na mesma?
E porque é que na segunda o erro é bem explícito ao executar?
#include <iostream>
using namespace std;
int menu()
{
cout << "Introduza inteiro de 1 a 10: ";
int opcao;
cin >> opcao;
if(opcao < 1 or 10 < opcao) {
cout << "Opção inválida!" << endl;
menu();
} else
return opcao;
}
long long menu2()
{
cout << "Introduza inteiro de 1 a 10: ";
long long opcao;
cin >> opcao;
if(opcao < 1 or 10 < opcao) {
cout << "Opção inválida!" << endl;
menu();
} else
return opcao;
}
int main()
{
int opcao = menu();
cout << "Opção foi << " << opcao << '.' << endl;
long long opcao2 = menu2();
cout << "Opção foi << " << opcao2 << '.' << endl;
}
Continua a haver muitos métodos ou rotinas "dois em um", i.e., são procedimentos, porque fazem alguma coisa, e são funções porque devolvem qualquer coisa também. Ou então são procedimentos, fazem qualquer coisa, mas também fazem mais coisas "às escondidas". Dar nomes apropriados a rotinas e métodos evita o problema.
Rotinas e métodos "dois em um" serão sempre fortemente penalizados.
Pegando no exemplo mais acima:
/**Faz zarpar o barco com a matrícula dada do respectivo cais, procedendo depois à entrada
de um novo barco se estiver algum à espera.@pre
contemBarcoAtracadoComMatricula(matricula).@post
¬contemBarcoAtracadoComMatricula(matricula) e contém todos os outros
atracados anteriormente e, se a fila da operação a ser realizada pelo barco que zarpou
não estava vazia, então foi dada autorização ao seu primeiro barco para atracar.*/Isto deveria estar noutro método, invocado à parte!
void Porto::zarpaBarcoComMatricula(string const& matricula) {
assert(cumpreInvariante());
assert(contemBarcoAtracadoComMatricula(matricula));
vector<Cais>::size_type i = 0;
while(not cais[i].contemBarcoAtracadoComMatricula(matricula))
++i;
cais[i].zarpaBarcoComMatricula(matricula);
if(cais[i].operacao() == carga) {
if(not fila_de_espera_para_carga.estaVazia()) {
cais[i].atraca(fila_de_espera_para_carga.frente());
fila_de_espera_para_carga.tira();
}
} else if(cais[i].operacao() == descarga)
if(not fila_de_espera_para_descarga.estaVazia()) {
cais[i].atraca(fila_de_espera_para_descarga.frente());
fila_de_espera_para_descarga.tira();
}
}
Este método é dois em um. Não se limita a zarpar um barco. Dá entrada a barcos em espera. Pode-se resolver de duas formas:
zarpaBarcoComMatriculaEAtracaBarcoCorrespondenteEmEspera.
Sempre que um nome de rotina ou método envolver a conjunção "e"
já sabem que faz coisas demais.
/**Faz zarpar o barco com a matrícula dada do respectivo cais.@pre
contemBarcoAtracadoComMatricula(matricula).@post
¬contemBarcoAtracadoComMatricula(matricula) e contém todos os outros
atracados anteriormente.*/Faz atracar todos os barcos em espera que puder.
void Porto::zarpaBarcoComMatricula(string const& matricula) {
assert(cumpreInvariante());
assert(contemBarcoAtracadoComMatricula(matricula));
vector<Cais>::size_type i = 0;
while(not cais[i].contemBarcoAtracadoComMatricula(matricula))
++i;
cais[i].zarpaBarcoComMatricula(matricula);
}
/**@pre V.
@post todos os barcos em espera para os quais existiam cais livres estão atracados.
*/
void Porto::autorizaEntradaDeBarcosEmEspera()
{
while(not fila_de_espera_para_carga.estaVazia() and
haCaisDisponiveisPara(carga)) {
vector<Cais>::size_type i = primeiroCaisDisponivelPara(carga);
cais[i].atraca(fila_de_espera_para_carga.frente());
fila_de_espera_para_carga.tira();
}
while(not fila_de_espera_para_descarga.estaVazia() and
haCaisDisponiveisPara(descarga)) {
vector<Cais>::size_type i = primeiroCaisDisponivelPara(descarga);
cais[i].atraca(fila_de_espera_para_descarga.frente());
fila_de_espera_para_descarga.tira();
}
}
Convém que haja coerência nos nomes. Se se usa guardaEm(),
como nome do método para guardar num canal toda a informação de uma
instância de uma classe, então deve-se usar esse nome em todas as classes!
Nos trabalhos encontrava-se guarda() aqui, grava() ali,
salva() acolá, preservaEstado() mais abaixo,
etc. Este mau hábito só contribui para dificultar a leitura do código.
Os construtores que tenham um só parâmetro ou que possam ser invocados com um só argumento devem ser
explicit a não ser que haja excelentes e justificadas razões para se admitirem conversões implícitas. Por exemplo:
class Cais {...
public:
Cais(istream% canal_de_entrada);
...
};
vector<Cais> cais;
ifstream canal_de_entrada("ola");
cais.push_back(canal_de_entrada);
Perceberam? Eu também não... Nem à primeira nem à segunda.
Pareceu-me que estavam a pôr canais num vector de Cais. Só
depois percebi: é que o construtor de Cais define uma conversão implícita de
istream para Cais! Por isso o código acima é equivalente a:
cais.push_back(Cais(canal_de_entrada));
Para evitar a possibilidade de escrever a primeira e absurda versão, usem:
class Cais {...
public:
explicit Cais(istream% canal_de_entrada);
};
(P.S. Eu próprio me esqueci deste pormenor na classe Data fornecida para o
Problema 2).
const
Coloquem sempre const depois do tipo. I.e.,
int const a = 10;
e não
const int a = 10;