risorse | btp
Ho assistito a questa lezione di progettazione object-oriented incrementale diversi anni fa (novembre 2008), e la considero un ottimo esempio di quanto possano essere sorprendenti, a posteriori, gli oggetti “emersi” dall’applicazione del TDD. Il merito ovviamente non è della metodologia in sè, ma delle scelte fatte durante la fase di refactoring.
Trattandosi di progettazione incrementale, i requisiti sono stati presentati in sequenza. Qui ho cercato di ricostruire le prime quattro iterazioni.
Il primo requisito richiedeva di determinare il prezzo di acquisto di un BTP – Buono del tesoro poliennale, noti il capitale, la cedola, l’interesse e il numero di anni alla scadenza. Si tratta, in sostanza, di implementare la formula del calcolo del prezzo del titolo riportata in[2]:
p = ∑t=1..T FF(t)/(1 + i)t
ove p è il prezzo, T il numero di anni alla scadenza del titolo, FF(t) i flussi finanziari futuri, i l’interesse. Per flussi finanziari futuri si intendono le cedole non ancora rimborsate, rivalutate dell’interesse composto per gli anni che mancano alla scadenza, più il capitale (c è il valore della cedola, C quello del capitale):
FF(t) = c (1 + i)t-1 + c (1 + i)t-2 + … + c (1 + i) + C
A fronte dell’impegno del capitale C per N anni (la durata del titolo), si riceve un pagamento annuale c (la cedola). Se a T anni dalla scadenza si vende il titolo, il prezzo di vendita corrisponde alle cedole non ancora ricevute, più il capitale. A ciò si aggiungono gli interessi che ogni singola cedola avrebbe maturato nel periodo trascorso dal suo pagamento fino alla scadenza del titolo. La somma di tutti questi termini corrisponde al termine FF(t). L’espressione (1 + i)t a denominatore è il fattore di attualizzazione, che tiene conto che l’intero ammontare è corrisposto con T anni di anticipo rispetto alla scadenza prevista.
Complicazioni di dominio a parte (che in retrospettiva hanno giocato comunque un ruolo determinante, distraendo i partecipanti dallo scopo principale della lezione, ovvero il design), vengono forniti i seguenti dati per validare la procedura di calcolo del prezzo:
capitale...........: 100 anno di scadenza...: 2011 anno di vendita....: 2008 cedola.............: 10% interesse..........: 15% -> prezzo: 85.73
L’implementazione è immediata:
#include <cassert> #include <cmath> class Btp { const double capitale_; const double cedola_; const int scadenza_; public: Btp(double capitale, double cedola, int scadenza) : capitale_(capitale), cedola_(cedola), scadenza_(scadenza) { } double prezzo(int anno, double interesse) const { const double pagamento = capitale_ * cedola_; const int anni = scadenza_ - anno + 1; double cumulo = capitale_; for (int i = 0; i < anni; i += 1) cumulo += pagamento * pow(1 + interesse, i); return cumulo / pow(1 + interesse, anni); } }; int main() { Btp btp(100, 0.1, 2011); assert(fabs(btp.prezzo(2008, 0.15) - 85.73) < 0.01); return 0; }
Il nuovo requisito richiede di determinare il prezzo di vendita di una diversa tipologia di titolo, il BTZ discreto, che utilizza la stessa logica di calcolo, che in più applica una correzione all’interesse per tener conto del tasso di inflazione μ e di un altro tasso finanziario, il ribor:
i ← i - (μ - ribor)
Anche in questo caso sono forniti dei dati per verificare la correttezza dell’implementazione:
capitale...........: 100 anno di scadenza...: 2011 anno di vendita....: 2008 cedola.............: 10% interesse..........: 15% inflazione.........: 10% ribor..............: 12% -> prezzo: 80.79
La tentazione di specializzare la classe Btp è forte:
// ... class Btz : public Btp { public: Btz(double capitale, double cedola, int scadenza) : Btp(capitale, cedola, scadenza) { } double prezzo(int anno, double interesse, double mu, double ribor) const { return Btp::prezzo(anno, interesse - (mu - ribor)); } }; int main() { Btp btp(100, 0.1, 2011); assert(fabs(btp.prezzo(2008, 0.15) - 85.73) < 0.01); Btz btz(100, 0.1, 2011); assert(fabs(btz.prezzo(2008, 0.15, 0.10, 0.12) - 80.79) < 0.01); return 0; }
Il terzo requisito richiede di determinare il prezzo per un BTZ continuo, basato cioè su un regime di capitalizzazione esponenziale:
(1 + i)t ← eit
I dati di validazione sono:
capitale...........: 100 anno di scadenza...: 2011 anno di vendita....: 2008 cedola.............: 10% interesse..........: 10% inflazione.........: 10% ribor..............: 12% -> prezzo: 91.78
Prima di procedere con l’implementazione della nuova funzionalità, conviene estrarre dalla classe Btp la responsabilità del calcolo dell’interesse composto:
#include <cassert> #include <cmath> class Interesse { const double valore_; public: explicit Interesse(double i) : valore_(i) { } Interesse(double i, double mu, double ribor) : valore_(i - (mu - ribor)) { } double attualizzazione(int anni) const { return pow(1 + valore_, anni); } }; classBtpTitolo { const double capitale_; const double cedola_; const int scadenza_; public:BtpTitolo(double capitale, double cedola, int scadenza) : capitale_(capitale), cedola_(cedola), scadenza_(scadenza) { } double prezzo(int anno,doubleInteresse interesse) const { const double pagamento = capitale_ * cedola_; const int anni = scadenza_ - anno + 1; double cumulo = capitale_; for (int i = 0; i < anni; i += 1) cumulo += pagamento *pow(1 + interesse, i)interesse.attualizzazione(i); return cumulo /pow(1 + interesse, anni)interesse.attualizzazione(anni); } };class Btz : public Btp { public: Btz(double capitale, double cedola, int scadenza) : Btp(capitale, cedola, scadenza) { } double prezzo(int anno, double interesse, double mu, double ribor) const { return Btp::prezzo(anno, interesse - (mu - ribor)); } };int main() {BtpTitolo btp(100, 0.1, 2011); assert(fabs(btp.prezzo(2008, Interesse(0.15)) - 85.73) < 0.01);BtzTitolo btz(100, 0.1, 2011); assert(fabs(btz.prezzo(2008, Interesse(0.15, 0.10, 0.12)) - 80.79) < 0.01); return 0; }
A questo punto si introduce la nuova forma di deteminazione dell’interesse composto:
// ... class Interesse { const double valore_; protected: double valore() const { return valore_; } public: // ... virtual double attualizzazione(int anni) const = 0;{ return pow(1 + valore_, anni); }}; struct InteresseDiscreto : public Interesse { explicit InteresseDiscreto(double i) : Interesse(i) { } InteresseDiscreto(double i, double mu, double ribor) : Interesse(i, mu, ribor) { } virtual double attualizzazione(int anni) const { return pow(1 + valore(), anni); } }; struct InteresseContinuo : public Interesse { InteresseContinuo(double i, double mu, double ribor) : Interesse(i, mu, ribor) { } virtual double attualizzazione(int anni) const { return exp(valore() * anni); } }; class Titolo { // ... double prezzo(int anno, const Interesse& interesse) const { // ... }; // ... int main() { Titolo btp(100, 0.1, 2011); assert(fabs(btp.prezzo(2008, InteresseDiscreto(0.15)) - 85.73) < 0.01); Titolo btz(100, 0.1, 2011); assert(fabs(btz.prezzo(2008, InteresseDiscreto(0.15, 0.10, 0.12)) - 80.79) < 0.01); Titolo btz_c(100, 0.1, 2011); assert(fabs(btz_c.prezzo(2008, InteresseContinuo(0.10, 0.10, 0.12)) - 91.78) < 0.01); return 0; }
Rispetto al BTP iniziale, questo tipo di titolo prevede due pagamenti all’anno, dall’importo pari alla metà del valore della cedola. I dati di validazione sono:
capitale...........: 100 anno di scadenza...: 2009 anno di vendita....: 2008 (prima cedola riscossa, seconda da riscuotere) cedola.............: 10% interesse..........: 10% -> prezzo: 100.42
Occorre introdurre un nuovo oggetto, Pagamenti, che modella il numero di cedole staccate nell’anno:
// ... struct Pagamento { const int rateAnnuali; public: Pagamento(int rateAnnuali_) : rateAnnuali(rateAnnuali_) { } }; struct PagamentoAnnuale : public Pagamento { PagamentoAnnuale() : Pagamento(1) { } }; struct PagamentoSemestrale : public Pagamento { PagamentoSemestrale() : Pagamento(2) { } }; class Interesse { // ... virtual double attualizzazione(intdouble anni) const = 0; }; struct InteresseDiscreto : public Interesse { // ... virtual double attualizzazione(intdouble anni) const { // ... }; struct InteresseContinuo : public Interesse { // ... virtual double attualizzazione(intdouble anni) const { // ... }; class Titolo { const double capitale_; const double cedola_; const int scadenza_; const Pagamento pagamento_; public: Titolo(double capitale, double cedola, int scadenza, Pagamento pagamento) : capitale_(capitale), cedola_(cedola), scadenza_(scadenza), pagamento_(pagamento) { } double prezzo(int anno, const Interesse& interesse) const { const double pagamento = capitale_ * cedola_ / pagamento_.rateAnnuali; constintdouble anni = scadenza_ - anno + 1; double cumulo = capitale_; for (intdouble i = 0; i < anni; i += 1. / pagamento_.rateAnnuali) cumulo += pagamento * interesse.attualizzazione(i); return cumulo / interesse.attualizzazione(anni); } }; int main() { Titolo btp(100, 0.1, 2011, PagamentoAnnuale()); assert(fabs(btp.prezzo(2008, InteresseDiscreto(0.15)) - 85.73) < 0.01); Titolo btz(100, 0.1, 2011, PagamentoAnnuale()); assert(fabs(btz.prezzo(2008, InteresseDiscreto(0.15, 0.10, 0.12)) - 80.79) < 0.01); Titolo btz_c(100, 0.1, 2011, PagamentoAnnuale()); assert(fabs(btz_c.prezzo(2008, InteresseContinuo(0.10, 0.10, 0.12)) - 91.78) < 0.01); Titolo btp_s(100, 0.1, 2009, PagamentoSemestrale()); assert(fabs(btp_s.prezzo(2008, InteresseDiscreto(0.10)) - 100.42) < 0.01); return 0; }
Tralasciando gli aspetti legati al dominio — ignoro quanto il modello proposto rispecchi la realtà, ma di fatto ai fini dell’esercizio ciò è del tutto ininfluente —, è interessante notare che le diverse tipologie di titolo sono state definite per composizione, anziché per specializzazione, come inizialmente poteva essere lecito attendersi. L’uso di una relazione più debole[3] (specializzazione ≫ composizione ≫ aggregazione ≫ associazione) riduce il grado di accoppiamento del sistema, aumentandone nel contempo la flessibilità; la separazione delle responsabilità lo rende più modulare e testabile.
Pagina modificata il 17/03/2015